diff --git a/.classpath b/.classpath index d57ec0251..51769745b 100755 --- a/.classpath +++ b/.classpath @@ -2,8 +2,8 @@ + - diff --git a/.gitignore b/.gitignore index bc43e6efd..1b0fc7663 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,15 @@ +# auto-generated files from Android builds bin/ gen/ +build.xml +ant.properties +default.properties local.properties proguard.cfg -assets/gibberbot.properties +proguard-project.txt +# +assets/chatsecure.properties releases docs doc +.directory diff --git a/.gitmodules b/.gitmodules index 4f9df2f78..5be359c04 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,9 @@ -[submodule "external/ActionBarSherlock"] - path = external/ActionBarSherlock - url = https://github.com/JakeWharton/ActionBarSherlock.git [submodule "external/asmack"] path = external/asmack url = https://github.com/guardianproject/asmack.git [submodule "external/MemorizingTrustManager"] path = external/MemorizingTrustManager - url = https://github.com/devrandom/MemorizingTrustManager.git -[submodule "external/OnionKit"] - path = external/OnionKit - url = https://github.com/guardianproject/OnionKit.git + url = https://github.com/ge0rg/MemorizingTrustManager.git [submodule "external/SlidingMenu"] path = external/SlidingMenu url = https://github.com/jfeinstein10/SlidingMenu.git @@ -19,18 +13,12 @@ [submodule "external/AndroidPinning"] path = external/AndroidPinning url = https://github.com/moxie0/AndroidPinning.git -[submodule "external/SlideListView"] - path = external/SlideListView - url = https://github.com/guardianproject/Android-SlideExpandableListView.git -[submodule "external/MessageBar"] - path = external/MessageBar - url = https://github.com/SimonVT/MessageBar.git -[submodule "external/ShowcaseView"] - path = external/ShowcaseView - url = https://github.com/Espiandev/ShowcaseView.git -[submodule "external/NineOldAndroids"] - path = external/NineOldAndroids - url = https://github.com/JakeWharton/NineOldAndroids.git [submodule "external/AndroidEmojiInput"] path = external/AndroidEmojiInput url = https://github.com/n8fr8/AndroidEmojiInput.git +[submodule "external/ViewPagerIndicator"] + path = external/ViewPagerIndicator + url = https://github.com/JakeWharton/Android-ViewPagerIndicator.git +[submodule "external/bho"] + path = external/bho + url = https://github.com/iamironrabbit/bho diff --git a/.tx/config b/.tx/config new file mode 100644 index 000000000..20977894c --- /dev/null +++ b/.tx/config @@ -0,0 +1,10 @@ +[main] +host = https://www.transifex.com +lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, he: iw, he_IL: iw-rIL, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id: in, id_ID: in-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr: sr, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA + +[chatsecureandroid.stringsxml] +file_filter = res/values-/strings.xml +host = https://www.transifex.com +source_file = res/values/strings.xml +source_lang = en + diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3ec105fe5..35efa65ad 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,137 +1,191 @@ + android:versionCode="1423001" + android:versionName="14.2.3" > + - + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" /> + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + - - + - - - - - + android:permissionGroup="android.permission-group.MESSAGES" + android:protectionLevel="dangerous" /> + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - + + + + - - - - - + + + + + + android:permission="info.guardianproject.otr.app.im.permission.IM_SERVICE" > + - + android:readPermission="info.guardianproject.otr.app.providers.imps.permission.READ_ONLY" + android:writePermission="info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY" /> - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - > + android:windowSoftInputMode="stateUnchanged" > + + + + - - - + + - - - - - - - - - - - - - - - + android:label="@string/add_contact_title" /> + + + + + - - - + + + + + - - + android:label="@string/contacts_picker_title" > + + + + + + - - + android:label="@string/blocked_list_title" > + + - - + + + - - + + + - - + + + + + - - - - - - - - - - + - + - + + - - + android:label="@string/title_activity_lock_screen" > - - - + + + + + - - + + + + diff --git a/BUILD b/BUILD deleted file mode 100644 index 60a392998..000000000 --- a/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -== Preparation - - git submodule update --init - ./update-ant-build.sh - * setup external/asmack/local.properties to point to your android sdk * - (cd external/asmack && ./build.bash && cp build/asmack-android-4.jar ../../libs) - -== Building with ant - -Follow the steps from the prep section, then: - - ant debug - -== Eclipse - -First, follow the above instructions in the Preparation section. Then, import -the following as existing Android projects - -1. File --> Import... --> Android --> Existing Android Code Into Workspace - * external/OnionKit/library - * external/MemorizingTrustManager - * external/ActionBarSherlock/library - -2. Right-click on the project called 'library' --> Refactor --> Rename... and - rename it to 'ActionBarSherlock' - -3. Import Gibberbot itself like #1 above - - -== Old Stuff - -Patching Smack library for Android [1] - -$ svn co -r 10869 \ - http://svn.igniterealtime.org/svn/repos/smack/trunk smack-android -$ cd smack-android/source -$ patch -p0 -i patches/smack/smack.diff -$ cd ../build -$ ant -$ cd ../target - - - -[1] Thanks to: http://bjdodson.blogspot.com/2009/07/xmpp-on-android-using-smack.html) -l diff --git a/CHANGELOG b/CHANGELOG index 5f60694d8..6bfdbace1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,732 @@ Track the source commits here: -https://github.com/guardianproject/Gibberbot/commits/master# +https://github.com/guardianproject/ChatSecureAndroid/commits/master# -0.0.7 / 2012-01-17 +/** 14.2.3 / 02-Feb-2016 / 37c501bce15c3f884958c2c230dce5b3acdd6668 **/ + +95e63a9 fixes #728 and #718 by disabling unsupport cipher suites +9b31ce3 Merge pull request #735 from uxname/patch-1 +05991a4 Added missing translation strings +765fe92 update to version name 14.2.2 code 1422001 +940599b Merge pull request #733 from eighthave/master +63fe37d get keysync working again! +790f16c OtrFileConverter is called KeySync! +86eb67b update translations: ar bg bo ca cs da el es fa fi fr hu it ja nb pt ru sr tr zh +6415200 add new translation: mk Macedonian +7c16015 add more translation fixes and unified into a single script +441cddb fix NPE crash when setting background +b13a88d stop crashing if external storage is not available +28a8914 do not claim .ofcaes files, there is no handler hooked up +c66fc94 if import keystore fails, always show Toast +2c4a5ba Merge pull request #731 from eighthave/master +13c37e7 make "Shutdown & Lock" also clear from "Recent Apps" +9ae2dc7 after receiving panic trigger, quit and remove from history +c8bf278 fix crash on shutdown/lock with progress dialog +172c10b use string resource for "Chat Encryption" preference +4792376 Merge pull request #712 from kden/github-672-negative-xmpp-set +a2ec464 Merge pull request #730 from eighthave/master +66bfcbe Fixed bad comparisons for 'checkGroupElem' and 'checkExpon' basic verification methods for parameters in SMP. +758bbd7 use symlinks to provide alternate folders for Hebrew and Indonesian +20ce719 GitHub #672 Allowing setting negative XMPP priority +bdd8bca Merge pull request #706 from n8fr8/master +1cd4be5 Merge branch 'master' of github.com:n8fr8/AwesomeApp +482daca fixes github issue #699 for multiple resend of postponed messages +1990815 ensure the locale is properly set for the app +03f5f89 handle errors thrown lower in the stack here +78c66f1 handle connection being null +f6be4d9 this is not used anymore +5606e08 no need to clear this +c1c18fe handle possible NPE +c21ce30 Merge branch 'master' of github.com:guardianproject/ChatSecureAndroid +5ca08b5 Merge pull request #698 from eighthave/master +b094091 make-release-build: disable faketime since it is problematic +4a83e97 update changelog for v14.2 +6761575 Merge pull request #695 from eighthave/master +0fed76e improve regexps for linkifying and add bitcoin: for payments +1f4bb8e too many views in the account wizard + +14.2.0 / 2015-07-13 / 67615753e0f87e09fa0aa3df1f6f8e0fc347c825 + +6761575 Merge pull request #695 from eighthave/master +0fed76e improve regexps for linkifying and add bitcoin: for payments +1f4bb8e too many views in the account wizard +b587ef6 this is now 14.2.0 +9af9115 add support for auto lookup of proper MUC conference server host for user +167432b remove bam.yt +dca3aab fixes #4679 making sure user set port is always used +f2a33aa fixes #4652 makes sure there are no NPE +47781b4 don't pull arrays from tx anymore, as we don't need it +b3dddb5 otr_options_values should not be localized as they are keys all strings for arrays moved to strings.xml +c2b2ef0 Merge pull request #693 from n8fr8/v14.2-more-bug-fixes +7a31686 Merge branch 'v14.2-more-bug-fixes' of github.com:n8fr8/Gibberbot into v14.2-more-bug-fixes +b32639e remove switch support +e51728f cleanup account view layout +334cc5b Merge pull request #684 from eighthave/master +d1e9bdc Merge pull request #683 from n8fr8/master +58abae2 make-release-build: use version from `git describe` in original name format +f31b57e include app description in the app manifest +d47ef09 fixes #677 on github: update asmack to address "sessionSupported" bug https://github.com/guardianproject/ChatSecureAndroid/is +d72d79b fixes #677 on github: update asmack to address "sessionSupported" bug https://github.com/guardianproject/ChatSecureAndroid/is +9f42ad9 Merge pull request #682 from n8fr8/v14.2-more-bug-fixes +0920091 simplify the refreshSessionPresence code by merging into login logic +c08ff61 merge updateNotification() into networkStateChange for simplicity +5484f43 final fixes to message postpone/re-send logic +75100c2 Merge pull request #681 from eighthave/some-small-fixes +ea91042 partial fix #2147 if OTR is set to require/force then don't send messages without OTR Also attempts to set messages to POSTPO +3df88f3 use java.security.SecureRandom to be sure +f5ce7fb Fix SMP for non-ASCII chars. +2ae6b02 fixes #5132 handles cases where there is no display name for some reason +93c4de0 feature: add support for image preview in conversation view +acb9bec fixes bug #2146 messages sent while offline stay in right place! previously, the sent time was being reset to -1 for offline/ +ac1ae6f fixes #3256 removes debug code that caused crash +084f51e add new base art images +66f4918 multiple fixes to import from content:// URIs and image resizing Apps like CameraV share images and video direclty using cont +214e2ab reduce the size of the thumbnail cache +860d769 make it easy to get active sessions +467f628 only reconnect if suspended and then refresh presence of all open sessions +9ac0b37 fix NPE if notifyBuilder is not yet instanced +3121196 add new offline/disconnected icon +26e4974 update manifest types supported for picker +1b9aacd initial fixes to handle content:// URI files shared into ChatSecure +a7c72d9 Merge pull request #678 from n8fr8/bug-fix-network-state +767cb2a update notificaiton with proper intent +448a7b2 simplify, clean-up handling of network connectivity state and improve notifications +a8d8cf4 Merge pull request #676 from n8fr8/dev-14.2 +250a689 make the message view properly load form a stream instead of just a file object +326fefe remove use of ugly background +635f0ca remove ugly background +790b164 update layout of avatars to make them slightly bigger 48dp +6fc9001 make round avatars a little bigger with a smaller border +fa01457 begin handling low memory states better +c5bf709 only load the full roster once (don't reload it on reconnect) this saves bandwidth, battery and memory +d696c98 never disconnect unless the user explicitly tells us to (always retry!) +9027951 reduce memory usage in half by reducing avatars in half! +55c5a4d resize images for sharing +4ac1c90 Merge pull request #652 from eighthave/release-build-tweaks +b858857 do not include BUILD.MF in debug APK, it breaks signature verification +aa36a32 make versionName match the signed release git tag +46444e1 include build metadata in release APK to help reproducibility +ce48ce2 use `git describe` to name APK file +575c684 make-release-build: include 'v' in release APK name, following convention +0407072 remove references to non-existant Activites + +14.1.1-RC-5 / 2015-03-09 / abea2282eacc1975738bd643efa9e1e972eda149 +83a06fe fix presence and add contact handling logic to smooth new user experience + +14.1.1-RC-4 / 2015-03-08 / 126ba01d3e1966998d184e39d0c1bef7d549f11a + +9c1ee62 Merge pull request #643 from n8fr8/bug_4650_remoteservice_crash +8d7cf8e fixes #4682 where presence from can somtimes be null +5f7086d tune Google OAuth setup and presence/avatar refresh +0cf3b1f don't use an avatar in the action/title bar +b6a314b empty space +a62c2d3 make sure there is an icon for the remove account menu +9e1c954 make status warning text smaller +6fbffb3 improve message status display +6520c27 allow all proper jabberid strings in account setup +e7be7cd Merge pull request #640 from n8fr8/bug_avatars_not_displayed +29a0f87 fix view profile menu option +9ec1b3b refresh presence when new session is created +e3a43e0 fixes #4653 chatmanager can be null, so make sure to check +479187c fixes #4652 roster might be null +3d964d7 fixes #4651 handle exception throwing from problems with ChatFileStore/VFS +daf181e fixes #4650 cacheword might be null +461e85e make sure avatars show up in chatview as well The nickname string was being used instead of the address/JID to lookup avatars +54af765 re-enables proper storage and retrieval of avatars + +14.1.1-RC-3 / 2015-03-01 / 34deffd103b9a2dfcec32f5f4b934dbe049dd921 + +27ac3ba fixes bug with incorrect display of media transfer progress +39f01ea small final layout cleanups, menu tuning and related fixes +9c7ff10 handle NoSuchElement error happening due to poll(); peek() first! +2df2d14 fix audio playback, media rendering and remove errant tap logic + +14.1.1-RC-1 / 2015-02-27 / 757c50144bf04975063031204250c0e755c2fef6 + +* MultiUser Chat / Group fixes +23454b2 fix creation of new ChatGroup/MUC sessions Previously, new chat instance was being inserted into IMPS when we created a new MU +6e66920 UI logic for handling group vs normal contact +a366cfd set provider default sort to unambigious column (_ID instead of name) +672becb read the contact type to properly determine if it is a groupchat +fe1c585 Fix handling of groupchat/MUC nickname storage and display + +* Usability +b27841c make Tor bridge: links clickable always, whether on Tor or not +cee8a3a auto-resize images taken with embedded camera for quick transfer +3b762b4 make sure all desired accounts sign in on startup +9c99e50 create prompt screen if chat media store or SD card is missing +be2c6e2 Trigger message requery when file transfer is done Sometimes the message display doesn't update after you ahve received in an +d263292 stop delete prompt after taking audio/pictures, just delete automatically + +* OTR sessoon init fixes +2062b77 OtrEngine doesn't need to set the from - the XMPP Provider will do it + +* Stability Improvements +bfaa660 improved logic for handling service restart/reinit +de92973 if VFS not mounted, redirect to WelcomeActivity for logic This can happen if user opens app from Notification and not from Lau +693bc29 an extra check to make sure mounted() before getting image +7a684a2 reconnect to cacheword on restart of Service after OS kills it +f9c606f Enable Service to response to CacheWord in case of restart/kills In some cases, on devices with low memory like my Moto E, the +11aa581 prevent NPE after ending a chat session +18508b3 purge old code structure that launches NewChatActivity for each account +a4ed775 prefer internal storage for ChatFileStore, unless there isn't enough space +424f931 rename IocVfs to ChatFileStore following what it does +db9c873 rename chat_screen_menu to new_chat_menu, its only used in NewChatActivity +a02f05e provide single method for "Shutdown & Lock" +26aca9b AccountWizardActivity: purge unused code to clarify "Shutdown & Exit" usage +3885625 there is only one VFS, so if its already mounted, nothing to do +69609ff rename IocVfs.dbFile to dbFilePath to distinguish it from the File instances +d011614 store the location of IOCipher media store so if its missing, prompt user +3d4c8c4 move all encrypted store password transformations to single method +d1d357f create IocVfs.initWithoutPassword(Context) for when no password is set +f092b41 unmount IOCipher container on "complete shutdown" to zero out stored keys +8520b14 support new IOCipher v0.3 API +db2405f switch to IOCipher v0.3 + +14.1.1-BETA-1 / 2015-02-24 / b938c5b0e7c424bc0be907b78bc38d3e1eb97125 + +* UI tweaks +415070e re-instate chat OTR state/drop-down menu +ad3367b make button panel scroll horizontally +bd0068f Revert "Only linkify when it is a text message and not media" +302904d last cleanup of groupchat dialog box +72fdb80 small clean-up of dropdown dialog for groupchat +cd82821 add new dark attachment icon for dark theme +b9ba9f4 use a ScrollView for Account Wizard screens to accomodate long translations +f57083f add new dark attachment icon for dark theme +53227fe move share/attach to bottom of the screen popup menu This declutters the actionbar, and matches more what other popular apps +9cfcfa3 last cleanup of groupchat dialog box + +* Feature fixes +02044de fix message address targeting for OTR setup Messages were being sent to wrong resource, and OTR init never occured +853af9e OTR code cleanup to remove still existing issues with encryption start + +* Stability +5fb1ac4 fix crash if loader is working, then ChatSecure quits +1a3b695 launch all clicked links into a new Task + +* Misc +7d6d558 explain why the SQLCipher libs are loaded at app start +3b2530c do not translate SharedPreferences key strings +6f10871 make-release-build: generate APK file name based on `git describe` +df3cc88 use correct classes and casts in LinkifyHelper +2391f76 small clean-up of dropdown dialog for groupchat +6f70426 make MUC chat work better and persist across app instances +0bbb908 Only linkify when it is a text message and not media Linkify code expects a Spannable which only exists if the message contai +8d5d2c3 purge dead code from MessageView +eddb171 add new translations + +14.1.0-RC1 / 2015-01-06 / 863bfec7b0881395dbaec8937f999e0c9b0b9dd7 + +863bfec update translations +21d18af remove duplicate packet subscribe code +bd0c743 update to latest cacheword +f172551 reinstate contact object instance clearing on logout +7880bc0 this just clears objects from memory; it should be done +bc4bbaa query or insert new contact to ensure you don't create duplicates +cc5f8d8 don't add a contact if it already exists +5ecf862 don't load chats until connections are init'd also re-impl subscription dialog prompts properly +815a6ed simplify subscription prompt text +6bb7deb just show normal contact for now in contact picker - still looking into a way to show pending/subscribe notices here +c0ab354 clean up menu options for account and chat views +eddfd38 Merge branch 'master' of github.com:guardianproject/ChatSecureAndroid +6625a91 additional fixes for contact add, subscribe, presence interaction +d476883 multiple fixes for handling subscription request in UI +71d1c1d don't re-init timers if they already have been created +51eb8ea Merge pull request #621 from n8fr8/bug4461-normalizeusernames +74ccfdd fixes #4473 makes sure the roster is not null +bf6bae2 fixes #4472 makes sure connection, view are not null +6ee25fe fixes #4461 (and others) related to invalid username/jids This should solve cases where OTR does not init when a user creates +d8ce28d include the 'v' before the version number in the release file name +063fa4b make-release-build: configure ant build after all conf files are in place +089aaa8 update cacheword to get build fixes +5c33aed make-release-build: always build the git tag that matches versionName +b9dcf37 automatically name ant release builds using versionName from AndroidManifest.xml +55be894 make-release-build: temp workaround for old cacheword that signs the jar +de8a9be update translations: ar bg bo ca cs de es_ES fa fi it nl pl sr tr + +14.1.0-beta-2 / 2015-01-24 / b2fb94fa8c98b29ea922d864dff88968e57af7c1 + +6a69d43 fixes #4448 makes sure cursor is closed +975f1c3 fixes #4447 sometimes cursor is null if not init'd yet +b962e20 fixes #4437 - flag should be added, not set (which overrides NEW flag) the CLEAR flag must be used in conjunction with the NEW +63841f8 typo fix +6d96780 fixes #4441 - networkinfo or other network info could be null +68c532c fixes #4438 - presence might be null + +14.1.0-beta-1 / 2015-01-21 / 424927dfa7778bc0a9a64a9202029f15bcb4255c + +* Network +3992ac2 if remote connect is offline when you send message, refresh presence +2575942 ignore stale XMPP Connection instance, and re-init/login +f4dc7b4 Improves the connectivity monitoring listener accuracy + +* Usability +9b9412f ensuring message notifications are cleared when chat is displayed +905da53 let's not show a background in the account setup +5f78377 only single background image file needed, its dynamically resized +09e5efe don't automatically linkify in chat windows of accounts running on Tor +9256651 add custom URI scheme linkifying to handle geo:, xmpp:, etc. +41dfdd3 Ensure presence is updated all the way to the UI/View Also fix mismatches in int values for presence status types Small improv +01b3562 go back to main conversation list when chat is ended this fixes the issue with chat name/titles not properly updating on some +9909501 remove profile menu if OTR is not init'd we need an active session/fingerprint for this to work +8db20d9 add horizontal progress bar for otr status +5a06cdd make sure avatars show up on medium screens +1cbe65d Merge pull request #598 from n8fr8/master + +* Core Stability +a38da81 Don't clear list/contacts data from memory on logout +20e262c Clearing data causes issues on network disconnect/reconnects We don't need to so aggressively clear data anymore, now that we +31db61f remove unneeded duplicate contact cleanup code +6570d85 closes the settings cursor +c3d8532 cursor needs to be closed manually +eb76c46 Fix verify method : the 2 arrays compared were the same -> use this.xEncryptedMAC instead of xEncryptedMAC +d9e40c4 move Orbot market: link to donottranslate.xml +cf596a2 Merge pull request #607 from raphaelm/fix-build-scripts +baf7842 Adjust pom.xml version of asmack to the one actually included in the project +762651e Update update-ant-build.sh to work correctly on current build tool versions +0b2926e simplify presence updating to make it more reliable +92f3ba1 don't clear presence table ever, and don't load offline contacts these were causing problems with presence table being incorre +5439b75 add ability to request an async presence refresh from roster +3ba2a8f avoid concurrent mode problem by using idx +c0cfa8d fix code for detecting OTR capable resource +f6f1a91 nothing changed +0a040b2 add new low-res versions of default background +a44e1ad Merge branch 'eighthave-fix-crashes-related-to-large-background-bitmap' +43e213a load background bitmaps in a custom AsyncTask +7806e7a remove drawables targeted at android < 9 since minSdk is 9 + +14.0.9 / 2014-12-15 / ac8027fe74409cf2afb2ad22e3a0e76118017ced + +* Design and usability +2d811fd make lock, wizard screens user user background fix lookup of file path from returned URI of images +c6c1e4b add new default material images original images generated with Tapet app +831c7cc fixes #4277 and issue with blank frag after rotate +f899c96 update background image to material style +caba6fa sync up file layout with portrait version for easy comparing +98e7533 include languageButton in landscape layout +c0536d2 make Orbot install link a direct link to app in Google Play and F-Droid +a8a79ab update LetterAvatar to better match Gmail material style +a607d49 add round letter avatar for contact w/o image +5d86664 make start OTR menu the green/verified one +3f651c8 don't throw unhandled Exception if audio message is unplayable +6134d31 handle "opaque" xmpp: URIs from QR scan +51d7d3d in "New Account", only show Google option if there are Google accounts + +* Core fixes +4536135 test whether pinned domains are working properly with the pins +e397110 remove defunct XMPP server from recommended list: rkquery.de +a2f7511 fix crash when setting presence to "not subscribed" +e5f42c1 fix crash when timer runs XmppConnection.handlePresenceChanged() +4942329 fix crash if logout during contacts list sync +d875409 fix NPE +e37adab purge defunct XMPP cert pin for dukgo.com, the main COMODO one covers this +1c6f772 fix crash if mProviders is reset while loading +22d0a8a fix crash when selecting "verify" from a chat +6ae9dfc CLOSE ALL THE THINGS! aka "Resource leak: 'foo' is never closed" +12b1545 when user selects "End chat", also send OTR FINISHED message +b79ffbc after creating a new account, automatically sign in +47bac15 make MessageView also guess the MIME type to display uploaded media +113a59d strip down inline Audio Player to make it more responsive +a62a32b send contacts that are shared to ChatSecure +a1ab729 standardize on IocVfs.isVfsUri(Uri) for testing VFS URLs +3c9702d show error message if shared URL is not understood +9fc3be5 make ChatSecure claim .ofcaes files that are opened in file managers +0227fe2 show error message if sharing data when all accounts are offline + +* Tests! +6110556 port basic tests from robo-tests/ to an Android Test Project + + +14.0.8 / 2014-12-05 / c5e5c38d9ffda8a19addd5ee10ff88e980a62b7f + +* Misc fixes +d9c1b2d only auto-start OTR if the other person is also using chatsecure +a2fcd17 added beautiful new background from trey ratcliff https://www.flickr.com/photos/stuckincustoms/6960953088/ https://creativeco +aa5658c re-implemented keysync +af4b270 improve account setup screen with left/right swipe hint +6c6ad63 simplify connection creation code + +* Improve support for locale/language switching +21e741c Merge branch 'xmpp-urls-and-languages' of https://github.com/eighthave/ChatSecureAndroid into eighthave-xmpp-urls-and-languag +2a2e0e0 purge unused menu from WelcomeActivity +05ff8cb add button to first launch lock screen to change language +5893eb0 don't use android.R.string, it might not have Tibetan and others +a012cf4 ImApp.checkLocale() removed unused return value +75bd214 use system names for Languages preferences menu +bb130cb check status of pref.commit() otherwise just use pref.apply() +3111d91 remove empty translations so they don't show up in the languages list +16f1dbe ImApp.setNewLocale uses new setLocale() method on >= android-17 +e2b3536 move all strings to resources so they can be translated + +* Improve presence/contact list loading +7d8e2bf always load lists +383bd85 always reload lists even if previously loaded +da31c36 improve presence handling +00d84de clean up UI lists and contact views + +* Standardize handler URI logic +40b50d0 receive clicked im: URLs (syntax defined in RFC3860) +a78ecc3 tweak displayed xmpp: URL to work on more apps and follow RFC5122 +dd16cb3 handle all incoming xmpp: URIs using XmppUriHandler.parse() +0c566ec XmppUriHelper.parse() parses xmpp:, xmpp://, and xmpp:/// URLs + + +14.0.7 / 2014-11-13 / 9914be3aad11880bbeb7616934d6056e35957b03 + +* Connection management improvements +366d895 ensure logout happens fast, and reload contacts on new add +87d016d check for state (disconnected) to ensure shutdown is complete +551394a removing these is causing dups and confusion just because it is disconnected doesn't mean we should remove +64aefd5 don't keep local instances of anything as they can get stale +d6abb8f new connection instances should override the old/stale ones +05c6985 InexactRepeating seems to be one source of delays and oddities Let's go back to "exact"! +2a86288 Fix Presence/Subscribe handling; Improve Message sending - presence subscribe(d) requests were being lost due to trying to rel +b2446dd always reload lists on logging to refresh presence state +905adaa add new state to Presence so we can support retry subscribe + +* OTR encryption fixes +397ee80 send OTR "FINISHED" message to all OTR sessions on logout +0b9b591 OTRKeyMgr might be null if the user has not yet entered a passphrase + +* User interface updates (Material!) +58de542 simplify lockscreen UI - we don't need a button +14c63c0 show 2 checks now for sent and delivered like WhatsApp +46ca027 fixing theme colors and other material tweaks +69b445c updates for new Material design and color schemes +9d13e05 target 21 now to ensure Material support on Android L +a9de17a updates to support appcompat v21 and new drawerlayout/toolbar removed external SlidingMenu dependency - only appcompat now +8ac9802 update to target android-21 to support Material +59a5ef5 update appcompat for latest SDK/Android-L +c343510 for lower-res devices hide the avatar icon in messages as it takes up too much space + +14.0.6 / 2014-11-04 / 8f8771c16513b21c03387ccc835011fe755158ef + +* Message sending and OTR init reliability +5bfaa90 set a date to the internal Message instance +3912b95 setFrom() to full JID to address bug #3948 +f604949 guarantee a unique XMPP Resource +89f0661 some optimizations for sha1 hashing on OTRData +cb30014 getAddress() should return the base address with resource The Presence is updated with the latest resource value, but the addr +80dfb94 remove SessionID.getSessionID(), it duplicates toString() and is misnamed +0a5b47d make sure a SessionID is created, even if LocalUser is null or empty +ef718b6 When SessionID object is used as key, make sure to be specific toString() doesn't always seem to produce the same results this +e8dd2ff set unique resource before initConnection() +02f71aa add a random tag to our default XMPP resource value if the user is still using our default "ChatSecure" resource, then we shou +2ac3187 fix for #3879 NPE in some otr init cases +e7854c7 SessionID instances must NOT be held anywhere, as they change a new SessionID can be created if the remote user /resource chan +45e1180 don't update remote sessions; if jabberid changes, create new session + +* OTR keystore management +7c3897d make the save/persist code a bit more reliable + +* Presence updating +944a47a make sure roster is not null when you tried to load presence +ce89fd2 load latest presence for all that request +18b2278 improve logic for presence handling and parsing +bd25922 fixes contact loading by using proper cursormanager/loadermanager fixes #3909 +79b56b0 when new roster entries are added, make sure they are on the local list + +* User interface +0220d72 ensures chat title is reset on end chat. fixes #555 on github. +1620509 add new alpha+mode sort as default for contact picking +0a31a5f on Sign In, put indeterminite, spinning progress meter in ActionBar +57a7f4f fixes #3909 by using proper CursorLoader/LoaderManager for contact list +17de5a6 update to support Tibetan font if Tibetan Keyboard is installed +a7c1b13 update to latest tibetantextlibrary +03852ec had two tap twice to open chat the first time (fixed!) +a5abb79 throttle and batch contact presence updates +7fbff76 improve groupchat user interface/setup + +* Network management and security +7247581 include pins for self-signed/cacert.org for people who install those certs +0b6ce0a update pins to use the CA's certificates +0ccdbf2 fix reconnect try from heartbeat and tune retry/relogin code +a8cf348 slight fix for ciphers and method for setting ssl context +d7ad0d6 Merge branch 'crypto3' of https://github.com/knoy/ChatSecureAndroid into knoy-crypto3 +b9ec7e3 Add TLSv1.1 and TLSv1.2 support +01d6fb6 New TLS ciphersuites + +14.0.4 / 2014-09-24 / 0eccf0213403c74243056730d432b8d0b217b432 + update to 14.0.4 + +* Multiple fixes and improvements to OTR encryption session management + commit a9053a91e1f751aab4a70a3131e8d618bf58d1c9 fixes for OTR sessionID handling + commit 7b038a6fd3e1ab84c40989b2c280cacb256a2880 addt'l fixes for OTR error handling + commit 40826a7c0ff86c52410c9e9af8e0743c7f38bff0 fix issues with OTR session error messages and refresh + as reported here: https://github.com/guardianproject/ChatSecureAndroid/issues/531 + commit cea0b82b3c473a661780ff2cc806e0aa0860d771 more tuning of OTR session management and instantiating + commit 3783b4ecf26b49ef992827eb8b08beea4ea020af allow updating of remote user id, if resource changes modify OTR session handling logging + +* Solved an issue with using the wrong jabberid for local user (when servers appends random ID to it) + commit cb87b14660e738f2ccd92febbca95207a38deed4 get the actual full JID from the server some servers append info to the resource + +* Worked on problem related to resuming and maintaining network connections + commit 66c1fb67ad6a3d8fa1aba97aedaaa806f9ee208b improving connectivity management and performance + +14.0.1-14.0.3 / 2014-09-19 / 3fb5a43336f7fcc040de8157c070320381d731e0 + +* Ensured when you add a new contact it is updated in local list state + e743f4a avoid NPE for uninit'd state and make sure to insert new contact + cd2454e fix management of contactlist instances and adding new + 3e2d604 streamline add contact and add account user interface workflow + +* Fixes for XMPP SASL Authentication + dda8937 fixes in password management and Google OAuth Logic also improvements in reconnect logic to make it actually work + 4b544d9 fix a timeout issue on first login that was happening with Google Talk servers also if ping() fails, we should only maybe() re + 2a6b6c0 fix NPE bug with checking null password value ensure Google OAuth refresh all persists correctly + +* Improvements to OTR session management + ff0b645 another fix for otrdata handling + 364bb7e if a user accepts all transfers for session, make that work + 60fcae2 use the JID in the SessionID and don't append session resouce here it was causing addressing problems, and OTR was not init'in + 194d31f use the SessionID variable here since we have it + 8dd6b93 make the chunks smaller and the tolerance larger + acb7b43 update the OTR policy setting + dab9804 sessions are not tied to full jid, only username + +* Fixed inability to login to app due to CacheWord library bug + dc19140 update to cacheword with fix for parse() NPE + +* Updated localizations / translation strings + 54568c7 big update to localizations from transifex + +* Update SQLCipher and added back in x86 support + 2f74372 update binaries from SQLCipher v3.1 release + 9418d19 updated iocipher and added back in x86 support + +14.0.0 +* small changes to lockscreen layout and graphic; small layout tweaks for wizard slider +* add nickname support to groupchat/MUC dialog and methods +* fix bug with OAuth prioritization over PLAIN/MD5 Auth if Googel OAuth is not used, it should be marked unsupported + +14.0.0-beta-2 / 2014-09-09 / d22207bae8ddc3f94ede1b9b9568592641cf7306 +* OTR data sharing improved with progress display and export capability +* Add contacts can now be done via QR code and new xmpp://foo@bar.com?otr-fingerprint=blat "auto verify" URI +* Account info screen enables display of xmpp:// URI +* auto-start OTR chat when opened if auto/force is set +* fix for handling expiration of Google OAuth token +* update new tls 1.2 ciphers for API 20+ (4.4.4) only +* Update XMPPCertPins.java +* Updates to latet SQLCipher, IOCipher and Cacheword libraries +* 20% reducation in appsize, removal of unneeded dependencies (ActionBarSherlock) + +14.0.0-alpha-1 / 2014-08-27 / 12c39dab38fb5458f01829bce2f9b659f7395647 +* fixes for network suspend / resume +* fixes for sharing of files/media from external apps +* efficiency improvements for presence loading + +13.2.0-beta-2 / 2014-08-13 / dd8d4f6fa091439f3e55217ee85a7f24e6145c6b + +13.1.3-beta-4 / 2014-02-26 / 24a447d0ee0807b75a45a385b870f6f532328480 + +13.1.3-beta-3 / 2014-02-20 / 8bdd6cec55dad332c2e9a0e7458c56ad7893a770 +* small fix for getting connection + +13.1.2 / 2014-01-16 / 2b75704b68a7a36acaa438d7b2276bcffb34c116 +* network improvements and UI tweaks + +13.1.1 / 2014-01-15 / ff9bd3175768b25cfceb1b6aa474c7e24466a44f +* a few more small testing fixes + +13.1.0 / 2014-01-15 / 81ba4c78013e98df3967bc7121e70a99e929a0d2 +* contact nickname change +* major bug fixes + +13.0.9 / 2014-01-10 / dabbdb08496f016d0081ad797fb83a901762f7df +* worked on issue #571 (nicknames) +* fixed fixes order of ops errors for exit/shutdown +* worked on account setup + +13.0.8 / 2014-01-07 / 4364d6a9abb7ddf421335845e32a39f344fa21bd +* worked on contact zombies + proper exit / stop services + +13.0.7 / 2013-12-20 / d9d6b839393d89e1d038cf61fc2c17309e11d34a +* worked on on media sharing viewing + +13.0.6 / 2013-12-14 / cfc0ca1c73c57a86f98f7f73b8ac3c170bfe819a + +13.0.5 / 2013-12-14 / eacb08daa4ec61a4bb9390c56e27a9d8c8ba5153 +* small tweaks for inline layout, and a bitmap cache +* Feature: inline media +* Service telemetry - record timestamps +* Feature: remove emoji + +13.0.3 / 2013-11-17 / 4b333c8d65a0a7aeb2fd7be74b7b2e480cf6accc + +13.0.2 / 2013-11-07 / 6053fa6f825da7c50aaa5949f599099a872893ab +* Design tweaks +* Jump to new chat + +13.0.1 / 2013-11-04 / 27462c74ae7fd2ddd3d54fb5c28ef5b60a231f58 +* Feature: audio messaging +* Account list tuning +* Fix bug that forced SSL when TLS is optional +* Ensure that connection list is in sync in ImApp and RemoteImService +* Fix issues with OTR whitespace tag +* Feature: menu tuning + +12.7.1 / 2013-10-24 / f2fd6f19cc99db31cc67d87b3ca6a23012f2c5cf +* hotfix - updating pinned certificate list and code for pinning support + +12.7.0 / 2013-10-24 / 1277b61f53d9c3cbd4093f402dae5e518a532300 +* Feature: action bar + +12.6.5 / 2013-10-21 / 73fd3af75acf2077816eb811cf564dc05e23d1ab +* many bug fixes + +12.6.4 / 2013-10-18 / a8102da0c9bf3c2f13a1e680212fd2b3d95426df +* fixed bug: gingerbread fixes +* fixed bug: bonjour broke +* fixed bug: properly reorg fragment saved state on dataset change + +12.6.3 / 2013-10-16 / 5d3c8dfc3aa8ddfb4f45ce158ba01b2e441c168d +* fixed bug #2095 (OTR session setup) +* apply cacheword cleanup to ImUrlActivity +* fixed bug: presence not updating +* added feature: custom notify sound +* fixed bugs: minor ui +* fixed issue: lockscreen layout to match portrait + +12.6.2 / 2013-10-11 / d0eb3476e0d2427c2063906b804ca6ce43185a90 +* bug fixes + +12.6.1 / 2013-10-11 / af8f00f430f15d092dfb0c466e68526b38d4e3a8 +* bug fixes +* fixes #1920 improved manual verification and OTR verify dialog +* cacheword: simplify logic and handle unencrypted properly +* Bug 2024 bonjour broken +* Bug contact list cleanup +* fixes #1916 - ensures service is shutdown/killed after provider logout + +12.5 / 2013-10-03 / d165ed322540055f5272464a156c962d3d1d3b2b +* bug fixes +** Create account fixes +** Presence fixes +* db upgrade (fixes #1931) +* Improve memory behavior + +12.4.2 / 2013-09-25 / c6af331e3681726ca397a5aac1c2f283e30ff04e +* fixes for contactlist view +** re-enable filter input for large contact lists +** ensure we can easily switch between grid avatar and text list view + +12.4.1 / 2013-09-23 / 3dce0f145185ddc16b06c8022347afeed2c029e3 +* Misc UI fixes +* Improved OTR decryption error reporting +* Jump to correct fragment, prevent NPE +* Backwards compat with V11 DB + +12.4 / 2013-09-20 / 65c3147d8d998931973586a29a7be7b9d41d8c4d +* added auto OTR refresh attempt if there is an error decrypting + +12.3 / 2013-09-18 / f2ae50bcba13f55d1c482ed4f22a85cc375bdb4f +* show "ChatSecure" in the otr plaintext message. Fixes #1742. + +12.2.5 / 2013-09-16 / d0d2f1646f9183d0428b9f905ea2f219f9f4e628 + +12.2.3 / 2013-09-13 / 4813f8bbc6719eaaf4a0249f7dd63160f9bba444 + +12.2.2 / 2013-09-12 / 9d6bfc0962bdf56a0c500e1e1030ba52566ed730 +* full implementation of OTR data user interface + +12.2.1 / 2013-09-11 / b4149ba9bf7ee806cdb559d7b38bfd1259631042 +* bugfixes + +12.2.0 / 2013-09-08 / c8ec5801b7a54a6297441092b99de1a24c03db54 +* many bugfixes +* added file transfer +* Feature account setup +* MUC fixes + +12.1.0 / 2013-08-16 / 62575a31df7bcd75d6e9709ed20e0c8262c75620 + +12.0.7 / 2013-08-14 / aa1dfb3378419a763fda3bc7f22c15b117670538 +* bugfix release + +12.0.6 / 2013-08-12 / d78b2bb37bc73b7cfd0a9732d2e07f27ee2c783c +* updated translations +* added Feature intent launching +* added Emoji + +12.0.5 / 2013-07-31 / 55099b8e134d759229f48b0c02c0c4ed58f19904 +* Feature small ui fixes + +12.0.4 / 2013-07-31 / 7a6abbd844f143814a4c3c7e4997242b4a8bdb25 +* changed version number shema +* small UI fixes + +0.0.12-alpha-3-UI / 2013-07-19 / 47d68447b4a745be3ce3155db3740e984c3f0db5 +* Feature UI pager +* fix loss of verification upon refresh of OTR +* Feature MUC +* changes on OTR + +0.0.12-alpha-2 / 2013-07-01 / 6bef7e6a160ba29367eac7ff1c0bc1de529f6c38 +* improved support for XEP-0198 Stream Management +* improved OTR +* many important UI improvements +* the project has been renamed to ChatSecure + +0.0.12-alpha-1 / 2013-06-14 / 2deff3ebcf54bf8cf0fdf041b5cefb6cb4140c53 + +0.0.11-RC3 / 2013-03-08 / 7eab09c050ab1ae7f99ca887064de4ba9c82cbab + +0.0.11-RC2 / 2013-03-04 / e3aeba272616193477bb67a32f9699f31d38016b +* multiple small fixes for NPE and other crashes + +0.0.11-RC1 / 2013-03-04 / 24ea980f6c500d772420a43bf3155d2f3a28e1e2 +* fixes for Veracode test response on CRLF injection log issues +* moves close chat back to menu +* reloads contact list on roster change + +0.0.11-beta-4 / 2013-02-26 / 2c1c76935c2300340043bcc8a4a72aac386732da + +0.0.11-beta-3 / 2013-02-19 / 87619388ada1de0189fe91d72be4ffbf04223ae3 + +0.0.11-beta-2 / 2013-02-19 / 8b028f2a42b890249ef11cd75cdda01dcd3253c9 + +0.0.11-beta-1 / 2013-02-17 / 48c207e23bbc813dc994225d65f25d67d707e22d + +0.0.11-alpha-3 / 2013-02-06 / f59112f2367af59d67ff9929f72d856c384daf28 +* multiple UI improvements +* Google Auth +* added global setting to control start on boot + +0.0.11-alpha-1 / 2013-01-02 / 6b570686db3a03d63e5480ca18937b87f0c12ee8 +* changes on OTR +* Use notification lights + +0.0.10-RC6 / 2012-11-05 / 8e6d80edf05ca5501a9e6201e448bf39ccf78516 +* bugfix release + +0.0.10-RC5 / 2012-11-02 / 5df3fcef45066c2575e5a601548d8e74856edc72 + +0.0.10-RC3 / 2012-10-26 / bee08544505f538153cf172560339c822161d523 + +0.0.10-RC1 / 2012-09-25 / ce71048ec0bd8045dd817750e937eef61775793a +* Issue #232 - remove signing in page +* Finish outgoing XEP-0198 and offline queuing + +0.0.9-RC5 / 2012-06-15 / b3a7433be145f7f7beff8cb8f0225b6db4addfa3 +* added support for multiple accounts +* added support for XEP-0174 (Serverless messaging) +* added option to context menu to resend messages +* added XMPP resource priority to account settings +* fixed multi-account OTR support +* fixed contact removal and presence conversion +* fixed issue with SSL wildcard matching to server/domain +* many more fixes and changes + +0.0.8 / 2012-04-20 / e4b49ca9508477564c2cba4cfe2821b270281218 +* adding es and zh translations +* improved german translation +* ported SMP from javaotr to otr4j +* added support for XEP-0184 (Message Delivery Receipts) +* added support for XEP-0198 (Stream Management) +* store fingerprints as hex in keystore file +* many more fixes and changes + +0.0.7 / 2012-01-17 / e17dbbcf338af00466cabc5178634cd0d5ffded8 - updated translation strings - major fixes to connection stability by devrandom - user interface clean-ness aka "turn to the light side" by n8fr8 diff --git a/README.md b/README.md index 0328f4fee..26068375f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ -Gibberbot, an Android app to support XMPP Jabber chat using OTR encryption -https://guardianproject.info/apps/gibberbot +## This Repository is Retired + +ChatSecure for Android has been renamed and is continuing under the name Zom. + +Learn more about Zom here: https://zom.im + +Head to the new repo here: https://github.com/zom/Zom-Android + +--- +Everything below is considered archived. +--- + +ChatSecure for Android (previously known as Gibberbot) is a secure messaging +app built on open standards like XMPP/Jabber and OTR encryption: +https://guardianproject.info/apps/chatsecure It includes OTR4J: -http://code.google.com/p/otr4j/ +https://github.com/otr4j/otr4j and BouncyCastle for Java: http://www.bouncycastle.org/java.html @@ -10,20 +23,81 @@ http://www.bouncycastle.org/java.html and SQLCipher for Android: https://guardianproject.info/code/sqlcipher/ -## Get the source -1. Clone this repository -2. Initialize the submodules `git submodule init` -3. Update the submodules `git submodule update` +Original wallpaper generated using Tapet app and Gimp: +https://play.google.com/store/apps/details?id=com.sharpregion.tapet + +and previously included some CC0 public domain beautiful images: +Ry Van +https://unsplash.com/ryvanveluwen +https://unsplash.com/license + +## Bug reports + +Please report any and all bugs or problems that you find. This is essential +for us to be able to improve this software! + +https://dev.guardianproject.info/projects/chatsecure/issues + ## Build Instructions -1. For these instructions, you'll need the Android SDK and Eclipse installed. Follow instructions here: http://developer.android.com/sdk/index.html and here: https://developer.android.com/sdk/installing.html -2. Gibberbot is currently configured to run on the version 4+ of the Android SDK. This corresponds to Platform 1.6 - make sure to install it. -3. From the main Gibberbot GitHub project page (https://github.com/guardianproject/Gibberbot) grab the Gibberbot source through your method of choice. -4. Open up Eclipse and select File > Import > Existing Projects into Workspace. Follow the prompts and select the root directory of the Gibberbot source. -5. Depending on how willing to cooperate Eclipse is, you may need to Clean the project manually. -6. In Eclipse, right-click on the project root and select Run As > Android Application. Run on your favorite debug-enabled Android device or emulator! -That's it! Generally speaking, this should be an easy project to build locally for anyone who's used Eclipse and/or ADT before. If you have any questions, don't be afraid to jump into IRC for real-time help at #guardianproject on freenode or OFTC. +First make sure you have the Android SDK and Eclipse installed. Follow +instructions here: + +* https://developer.android.com/sdk/index.html +* https://developer.android.com/sdk/installing.html + +Please help us keep this process easy by letting us know if you have problems. +If you have any questions, don't be afraid to email us at +support@guardianproject.info or jump into our IRC chatrooms for real-time help +at #guardianproject on freenode or OFTC (https://guardianproject.info/contact/chat/). + + +### Get the source + +The source code is all in the main git repos, with sub-projects setup as git +submodules: + + git clone https://github.com/guardianproject/ChatSecureAndroid.git + cd ChatSecureAndroid + git submodule update --init + + +### ant setup + +We use `ant` to make our official releases and automated test builds. If you +are not familiar with Eclipse, then it is easier to start with the `ant` +build: + + export ANDROID_HOME=/path/to/android-sdk + ./update-ant-build.sh + ant clean debug + +Then the installable APK will be in **bin/ChatSecure-debug.apk**. + + +### Eclipse setup + +1. Start by adding ChatSecureAndroid to Eclipse by going to _File_ -> _New_ -> +_Project..._ -> _Android project from existing code_. + +2. Open the ChatSecureAndroid folder that was just cloned from git. + +3. Eclipse will next show a list of subprojects to import, all of the +libraries with _New Project Name_ of **library** must be renamed after the +project name, i.e. SlidingMenu, AndroidEmojiInput, ViewPagerIndicator. + +4. Click *Deselect All*. The sample and example projects are not needed, and +can cause conflicts. + +5. Select __ChatSecure__ again by clicking the top item in the list. + +6. Outside of Eclipse, open up the text file _project.properties_. Then back +in Eclipse, for each line that starts with `android.library.reference`, select +that path from the list of included sub-projects in Eclipse. + +Now you should be ready to build ChatSecure! + ## Test Instructions @@ -33,10 +107,12 @@ See robo-tests/README.md for eclipse instructions. Currently the instrumented target tests (to be run on a device) in the directory `tests` are empty. + ## Logging `adb shell setprop log.tag.GB.XmppConnection DEBUG` + ## Building for a Locale ant -Dgibberbot.locale=fa release diff --git a/assets/emoji/phantom.json b/assets/emoji/phantom.json deleted file mode 100644 index 17992095f..000000000 --- a/assets/emoji/phantom.json +++ /dev/null @@ -1,1488 +0,0 @@ -[ -{ - "moji": "🌀", - "name": "cyclone", - "name-ja": "台風", - "category": "nature", - "unicode": "1f300", - "attribution": "Sébastien Hut" -}, -{ - "moji": "🌈", - "name": "rainbow", - "name-ja": "虹", - "category": "nature", - "unicode": "1f308" -}, -{ - "moji": "🌌", - "name": "milky_way", - "name-ja": "天の川", - "category": "cosmos", - "unicode": "1f30c", - "attribution": "Jonah Libster" -}, -{ - "moji": "🌑", - "name": "new_moon", - "name-ja": "新月", - "category": "cosmos", - "unicode": "1f311" -}, -{ - "moji": "🌓", - "name": "first_quarter_moon", - "name-ja": "上弦の月", - "category": "cosmos", - "unicode": "1f313" -}, -{ - "moji": "🌔", - "name": "waxing_gibbous_moon", - "name-ja": "やや欠け月", - "category": "cosmos", - "unicode": "1f314" -}, -{ - "moji": "🌕", - "name": "full_moon", - "name-ja": "満月", - "category": "cosmos", - "unicode": "1f315" -}, -{ - "moji": "🌗", - "name": "last_quarter_moon", - "name-ja": "下弦の月", - "category": "cosmos", - "unicode": "1f317" -}, -{ - "moji": "🌙", - "name": "crescent_moon", - "name-ja": "三日月", - "category": "cosmos", - "unicode": "1f319" -}, -{ - "moji": "🌠", - "name": "shooting_star", - "name-ja": "流れ星", - "category": "cosmos", - "unicode": "1f320" -}, -{ - "moji": "🌛", - "name": "moon_with_face", - "name-ja": "顔月", - "category": "cosmos", - "unicode": "1f31b" -}, -{ - "moji": "🌟", - "name": "glowing_star", - "name-ja": "輝く星", - "category": "cosmos", - "unicode": "1f31f" -}, -{ - "moji": "🌵", - "name": "cactus", - "name-ja": "サボテン", - "category": "nature", - "unicode": "1f335", - "attribution": "xfer" -}, -{ - "moji": "🍊", - "name": "tangerine", - "name-ja": "みかん", - "category": "food", - "unicode": "1f34a", - "attribution": "Lunasea Studios" -}, -{ - "moji": "🍔", - "name": "hamburger", - "name-ja": "ハンバーガー", - "category": "food", - "unicode": "1f354" -}, -{ - "moji": "🍕", - "name": "pizza", - "name-ja": "ピザ", - "category": "food", - "unicode": "1f355" -}, -{ - "moji": "🍙", - "name": "rice_ball", - "name-ja": "おにぎり", - "category": "food", - "unicode": "1f359" -}, -{ - "moji": "🍥", - "alts": ["naruto"], - "name": "fish_cake_with_swirl_design", - "name-ja": "なると", - "category": "food", - "unicode": "1f365", - "attribution": "anonymous" -}, -{ - "moji": "🍰", - "name": "cake", - "name-ja": "ケーキ", - "category": "food", - "unicode": "1f370" -}, -{ - "moji": "🍱", - "name": "bento_box", - "name-ja": "お弁当", - "category": "food", - "unicode": "1f371", - "attribution": "Matthew Ender" -}, -{ - "moji": "🍹", - "name": "tropical_drink", - "name-ja": "トロピカルドリンク", - "category": "food", - "unicode": "1f379", - "attribution": "Jorge Lugo" -}, -{ - "moji": "🍺", - "name": "beer_mug", - "name-ja": "ビールジョッキ", - "category": "food", - "unicode": "1f37a", - "attribution": "Evereq" -}, -{ - "moji": "🎃", - "name": "jack_o_lantern", - "name-ja": "ジャック・オ・ランタン", - "category": "object", - "unicode": "1f383", - "attribution": "Noah Greenstein" -}, -{ - "moji": "🎤", - "name": "microphone", - "name-ja": "マイク", - "category": "object", - "unicode": "1f3a4", - "attribution": "Letia Nichols" -}, -{ - "moji": "🎮", - "name": "video_game", - "name-ja": "ゲームコントローラー", - "category": "object", - "unicode": "1f3ae", - "attribution": "Joel Thoms" -}, -{ - "moji": "🎸", - "name": "guitar", - "name-ja": "ギター", - "category": "object", - "unicode": "1f3b8", - "attribution": "barakmich" -}, -{ - "moji": "🎾", - "name": "tennis_racquet_and_ball", - "name-ja": "テニスラケットとボール", - "category": "object", - "unicode": "1f3be", - "attribution": "Pat May", - "url": "http://www.maycoworld.net" -}, -{ - "moji": "🏂", - "name": "snowboarder", - "name-ja": "スノーボード", - "category": "people", - "unicode": "1f3c2", - "attribution": "Bas Kok" -}, -{ - "moji": "🐌", - "name": "snail", - "name-ja": "カタツムリ", - "category": "nature", - "unicode": "1f40c" -}, -{ - "moji": "🐍", - "name": "snake", - "name-ja": "ヘビ", - "category": "nature", - "unicode": "1f40d" -}, -{ - "moji": "🐔", - "name": "chicken", - "name-ja": "ニワトリ", - "category": "nature", - "unicode": "1f414", - "attribution": "Daniel Devine" -}, -{ - "moji": "🐗", - "name": "boar", - "name-ja": "イノシシ", - "category": "nature", - "unicode": "1f417" -}, -{ - "moji": "🐘", - "name": "elephant", - "name-ja": "ゾウ", - "category": "nature", - "unicode": "1f418" -}, -{ - "moji": "🐙", - "name": "octopus", - "name-ja": "タコ", - "category": "nature", - "unicode": "1f419", - "attribution": "Antón Campos" -}, -{ - "moji": "🐡", - "alto": ["pufferfish"], - "name": "blowfish", - "name-ja": "フグ", - "category": "nature", - "unicode": "1f421", - "attribution": "Rompcat" -}, -{ - "moji": "🐣", - "name": "hatching_chick", - "name-ja": "ヒヨコ", - "category": "nature", - "unicode": "1f423", - "attribution": "Dmitry Pashkevich" -}, -{ - "moji": "🐧", - "name": "penguin", - "name-ja": "ペンギン", - "category": "nature", - "unicode": "1f427", - "attribution": "Sebastian Schauenburg" -}, -{ - "moji": "🐨", - "name": "koala", - "name-ja": "コアラ", - "category": "nature", - "unicode": "1f428" -}, -{ - "moji": "🐫", - "name": "bactrian_camel", - "name-ja": "フタコブラクダ", - "category": "nature", - "unicode": "1f42b" -}, -{ - "moji": "🐱", - "name": "cat_face", - "name-ja": "ネコ", - "category": "nature", - "unicode": "1f431", - "attribution": "Peether" -}, -{ - "moji": "🐳", - "name": "spouting_whale", - "name-ja": "クジラ", - "category": "nature", - "unicode": "1f433", - "attribution": "Fred Benenson" -}, -{ - "moji": "🐴", - "name": "horse", - "name-ja": "ウマ", - "category": "nature", - "unicode": "1f434" -}, -{ - "moji": "🐶", - "name": "dog_face", - "name-ja": "イヌ", - "category": "nature", - "unicode": "1f436", - "attribution": "Unspecial Ando" -}, -{ - "moji": "🐸", - "name": "frog_face", - "name-ja": "カエル", - "category": "nature", - "unicode": "1f438", - "attribution": "wispfrog" -}, -{ - "moji": "🐺", - "name": "wolf_face", - "name-ja": "オオカミ", - "category": "nature", - "unicode": "1f43a", - "attribution": "Hans de Wolf" -}, -{ - "moji": "🐾", - "name": "paw_prints", - "name-ja": "足跡(犬)", - "category": "nature", - "unicode": "1f43e", - "attribution": "patter-app.net" -}, -{ - "moji": "👊", - "name": "fisted_hand_sign", - "name-ja": "パンチ", - "category": "gesture", - "unicode": "1f44a", - "attribution": "Jy Yaworski" -}, -{ - "moji": "👍", - "name": "thumbs_up_sign", - "name-ja": "おやゆびサイン", - "category": "gesture", - "unicode": "1f44d", - "attribution": "unki2aut" -}, -{ - "moji": "👪", - "name": "family", - "name-ja": "家族", - "category": "people", - "unicode": "1f46a" -}, -{ - "moji": "👫", - "name": "couple_holding_hands", - "name-ja": "手をつないだカップル", - "category": "people", - "unicode": "1f46b" -}, -{ - "moji": "👬", - "name": "two_men_holding_hands", - "name-ja": "手をつないだ二人の男性", - "category": "people", - "unicode": "1f46c" -}, -{ - "moji": "👭", - "name": "two_women_holding_hands", - "name-ja": "手をつないだ二人の女性", - "category": "people", - "unicode": "1f46d" -}, -{ - "moji": "👸", - "name": "princess", - "name-ja": "お姫様", - "category": "people", - "unicode": "1f478", - "attribution": "Kim Baumann Larsen" -}, -{ - "moji": "👻", - "name": "ghost", - "name-ja": "お化け", - "category": "people", - "unicode": "1f47b", - "attribution": "Seto Konowa" -}, -{ - "moji": "👽", - "name": "extraterrestrial_alien", - "name-ja": "宇宙人", - "category": "people", - "unicode": "1f47d", - "attribution": "Lloyd Smart" -}, -{ - "moji": "💀", - "name": "skull", - "name-ja": "ドクロ", - "category": "people", - "unicode": "1f480", - "attribution": "Josh Freeman" -}, -{ - "moji": "💃", - "name": "dancer", - "name-ja": "ダンサー", - "category": "people", - "unicode": "1f483", - "attribution": "Daniel 'Crusher' Nersveen" -}, -{ - "moji": "💊", - "name": "pill", - "name-ja": "薬", - "category": "object", - "unicode": "1f48a", - "attribution": "Cynthia McCoy" -}, -{ - "moji": "💏", - "alts": ["kiss", "couple_kissing"], - "name": "couple_kiss", - "name-ja": "カップルのキス", - "category": "people", - "unicode": "1f48f" -}, -{ - "moji": "💑", - "name": "couple_with_heart", - "name-ja": "カップルとハート", - "category": "people", - "unicode": "1f491" -}, -{ - "moji": "💓", - "alts": ["beating_heart"], - "name": "heartbeat", - "name-ja": "ドキドキしているハート", - "category": "zabstract", - "unicode": "1f493" -}, -{ - "moji": "💩", - "name": "poop", - "name-ja": "うんち(顔あり)", - "category": "zabstract", - "unicode": "1f4a9" -}, -{ - "moji": "💬", - "name": "speech_balloon", - "name-ja": "フキダシ", - "category": "zabstract", - "unicode": "1f4ac", - "attribution": "Mike Boers" -}, -{ - "moji": "🔞", - "name": "no_one_under_eighteen_symbol", - "name-ja": "18禁", - "category": "zabstract", - "unicode": "1f51e", - "attribution": "Świstak" -}, -{ - "moji": "🗻", - "name": "mount_fuji", - "name-ja": "富士山", - "category": "nature", - "unicode": "1f5fb", - "attribution": "Yuki Kodama" -}, -{ - "moji": "😀", - "name": "grinning", - "name-ja": "にんまり", - "category": "faces", - "unicode": "1f600" -}, -{ - "moji": "😁", - "alts": ["grinning_face_with_smiling_eyes"], - "name": "grin", - "name-ja": "うっしっし", - "category": "faces", - "unicode": "1f601", - "attribution": "Joonas Pihlajamaa" -}, -{ - "moji": "😂", - "name": "face_with_tear_of_joy", - "name-ja": "泣き笑い", - "emoticon": ";D", - "category": "faces", - "unicode": "1f602" -}, -{ - "moji": "😃", - "alts": ["smiling_face_with_open_mouth"], - "name": "smiley", - "name-ja": "スマイル", - "emoticon": ":)", - "category": "faces", - "unicode": "1f603", - "attribution": "Eric Nikolaisen" -}, -{ - "moji": "😄", - "alts": ["smiling_face_with_open_mouth_and_smiling_eyes"], - "name": "smile", - "name-ja": "嬉しい顔", - "category": "faces", - "unicode": "1f604", - "attribution": "rastilin@gmail.com" -}, -{ - "moji": "😅", - "alts": ["smiling_face_with_open_mouth_and_cold_sweat"], - "name": "sweat_smile", - "name-ja": "冷や汗", - "category": "faces", - "unicode": "1f605", - "attribution": "Damien Goujard" -}, -{ - "moji": "😆", - "alts": ["smiling_face_with_open_mouth_and_tightly_closed_eyes"], - "name": "laughing", - "name-ja": "笑い", - "emoticon": ":D", - "category": "faces", - "unicode": "1f606" -}, -{ - "moji": "😉", - "alts": ["winking_face"], - "name": "wink", - "name-ja": "ウインク", - "emoticon": ";)", - "category": "faces", - "unicode": "1f609" -}, -{ - "moji": "😊", - "alts": ["smiling_face_with_smiling_eyes"], - "name": "blush", - "name-ja": "にこにこ", - "emoticon": ":$", - "category": "faces", - "unicode": "1f60a" -}, -{ - "moji": "😋", - "name": "face_savouring_delicious_food", - "name-ja": "うまい", - "category": "faces", - "unicode": "1f60b", - "attribution": "Merleawe | Foodie For My Tastebuds" -}, -{ - "moji": "😌", - "alts": ["relieved_face"], - "name": "relieved", - "name-ja": "ほっとした顔", - "category": "faces", - "unicode": "1f60c" -}, -{ - "moji": "😍", - "alts": ["smiling_face_with_heart_shaped_eyes"], - "name": "heart_eyes", - "name-ja": "目がハート", - "category": "faces", - "unicode": "1f60d", - "attribution": "Michael Day" -}, -{ - "moji": "😏", - "alts": ["smirk_face"], - "name": "smirk", - "name-ja": "ふっ", - "category": "faces", - "unicode": "1f60f" -}, -{ - "moji": "😑", - "alts": ["expressionless_face"], - "name": "expressionless", - "name-ja": "ぼけーっとした顔", - "category": "faces", - "unicode": "1f611" -}, -{ - "moji": "😒", - "alts": ["unamused_face"], - "name": "unamused", - "name-ja": "横目", - "category": "faces", - "unicode": "1f612" -}, -{ - "moji": "😓", - "alts": ["face_with_cold_sweat"], - "name": "sweat", - "name-ja": "困り顔", - "emoticon": "(:|", - "category": "faces", - "unicode": "1f613" -}, -{ - "moji": "😔", - "name": "pensive_face", - "name-ja": "しょんぼり", - "category": "faces", - "unicode": "1f614", - "attribution": "geekahedron" -}, -{ - "moji": "😕", - "alts": ["confused_face"], - "name": "confused", - "name-ja": "困る", - "emoticon": ":^)", - "category": "faces", - "unicode": "1f615" -}, -{ - "moji": "😗", - "alts": ["kissing_face"], - "name": "kissing", - "name-ja": "チュー", - "emoticon": ":*", - "category": "faces", - "unicode": "1f617" -}, -{ - "moji": "😘", - "alts": ["face_throwing_a_kiss"], - "name": "kissing_heart", - "name-ja": "投げキッス", - "category": "faces", - "unicode": "1f618" -}, -{ - "moji": "😙", - "alts": ["kissing_face_with_smiling_eyes"], - "name": "kissing_smiling_eyes", - "name-ja": "チューしよ", - "category": "faces", - "unicode": "1f619" -}, -{ - "moji": "😚", - "alts": ["kissing_face_with_closed_eyes"], - "name": "kissing_closed_eyes", - "name-ja": "チュッ", - "category": "faces", - "unicode": "1f61a", - "attribution": "Elena Maureen Martinez" -}, -{ - "moji": "😛", - "alts": ["face_with_stuck_out_tongue"], - "name": "stuck_out_tongue", - "name-ja": "べー", - "emoticon": ":P", - "category": "faces", - "unicode": "1f61b", - "attribution": "Victor Westmann" -}, -{ - "moji": "😜", - "alts": ["face_with_stuck_out_tongue_winking_eye"], - "name": "stuck_out_tongue_winking_eye", - "name-ja": "あっかんべー", - "emoticon": ";P", - "category": "faces", - "unicode": "1f61c" -}, -{ - "moji": "😝", - "alts": ["face_with_stuck_out_tongue_and_tightly_closed_eye"], - "name": "stuck_out_tongue_closed_eyes", - "name-ja": "べーっ", - "emoticon": "XP", - "category": "faces", - "unicode": "1f61d" -}, -{ - "moji": "😞", - "name": "disappointed_face", - "name-ja": "がっかり", - "category": "faces", - "unicode": "1f61e" -}, -{ - "moji": "😟", - "alts": ["worried_face"], - "name": "worried", - "name-ja": "心配", - "category": "faces", - "unicode": "1f61f" -}, -{ - "moji": "😠", - "name": "angry_face", - "name-ja": "怒った顔", - "category": "faces", - "unicode": "1f620", - "attribution": "Krysanto" -}, -{ - "moji": "😡", - "name": "pouting_face", - "name-ja": "ふくれっ面", - "category": "faces", - "unicode": "1f621" -}, -{ - "moji": "😢", - "name": "crying_face", - "name-ja": "涙", - "emoticon": ":'(", - "category": "faces", - "unicode": "1f622", - "attribution": "mike mg" -}, -{ - "moji": "😣", - "name": "persevering_face", - "name-ja": "がまん顔", - "category": "faces", - "unicode": "1f623" -}, -{ - "moji": "😤", - "name": "face_with_look_of_triumph", - "name-ja": "勝ち誇り", - "category": "faces", - "unicode": "1f624" -}, -{ - "moji": "😥", - "name": "disappointed_but_relieved_face", - "name-ja": "やれやれ", - "category": "faces", - "unicode": "1f625" -}, -{ - "moji": "😦", - "alts": ["frowning_face_with_open_mouth"], - "name": "frowning", - "name-ja": "しかめっ面", - "emoticon": ":(", - "category": "faces", - "unicode": "1f626" -}, -{ - "moji": "😧", - "alts": ["anguished_face"], - "name": "anguished", - "name-ja": "悲しい顔", - "category": "faces", - "unicode": "1f627" -}, -{ - "moji": "😨", - "name": "fearful_face", - "name-ja": "青ざめ", - "category": "faces", - "unicode": "1f628" -}, -{ - "moji": "😩", - "name": "weary_face", - "name-ja": "うんざり", - "emoticon": "X(", - "category": "faces", - "unicode": "1f629" -}, -{ - "moji": "😬", - "alts": ["grimacing_face"], - "name": "grimacing", - "name-ja": "いーっ", - "category": "faces", - "unicode": "1f62c" -}, -{ - "moji": "😭", - "name": "loudly_crying_face", - "name-ja": "大泣き", - "category": "faces", - "unicode": "1f62d" -}, -{ - "moji": "😮", - "alts": ["face_with_open_mouth"], - "name": "open_mouth", - "name-ja": "ぽかんと", - "emoticon": ":O", - "category": "faces", - "unicode": "1f62e" -}, -{ - "moji": "😯", - "alts": ["hushed_face"], - "name": "hushed", - "name-ja": "しーん", - "emoticon": ":x", - "category": "faces", - "unicode": "1f62f" -}, -{ - "moji": "😰", - "name": "face_with_open_mouth_and_cold_sweat", - "name-ja": "冷や汗2", - "category": "faces", - "unicode": "1f630" -}, -{ - "moji": "😲", - "name": "astonished_face", - "name-ja": "びっくり", - "category": "faces", - "unicode": "1f632" -}, -{ - "moji": "😳", - "alts": ["flushed_fase"], - "name": "flushed", - "name-ja": "ぽっ", - "category": "faces", - "unicode": "1f633" -}, -{ - "moji": "😴", - "alts": ["sleeping_face"], - "name": "sleeping", - "name-ja": "眠い", - "category": "faces", - "unicode": "1f634" -}, -{ - "moji": "😵", - "name": "dizzy_face", - "name-ja": "ふらふら", - "emoticon": "Xo", - "category": "faces", - "unicode": "1f635" -}, -{ - "moji": "😷", - "name": "face_with_medical_mask", - "name-ja": "風邪ひき", - "category": "faces", - "unicode": "1f637" -}, -{ - "moji": "😸", - "name": "grinning_cat_face_with_smiling_eyes", - "name-ja": "うっしっし(ネコ)", - "category": "faces", - "unicode": "1f638", - "attribution": "Robert Adam, II" -}, -{ - "moji": "😹", - "name": "cat_face_with_tears_of_joy", - "name-ja": "泣き笑い(ネコ)", - "category": "faces", - "unicode": "1f639" -}, -{ - "moji": "😺", - "name": "smiling_cat_face_with_open_mouth", - "name-ja": "にこ(ネコ)", - "category": "faces", - "unicode": "1f63a", - "attribution": "Laura47" -}, -{ - "moji": "😻", - "name": "smiling_cat_face_with_heart_shaped_eyes", - "name-ja": "目がハート(ネコ)", - "category": "faces", - "unicode": "1f63b", - "attribution": "Arvid Andersson" -}, -{ - "moji": "😼", - "name": "cat_face_with_wry_smile", - "name-ja": "きりり(ネコ)", - "category": "faces", - "unicode": "1f63c", - "attribution": "Nick Richards" -}, -{ - "moji": "😽", - "name": "kissing_cat_face_with_closed_eyes", - "name-ja": "チュー(ネコ)", - "category": "faces", - "unicode": "1f63d", - "attribution": "ErickaJo" -}, -{ - "moji": "😾", - "name": "pouting_cat_face", - "name-ja": "ぷー(ネコ)", - "category": "faces", - "unicode": "1f63e" -}, -{ - "moji": "😿", - "name": "crying_cat_face", - "name-ja": "涙ぽろり(ネコ)", - "category": "faces", - "unicode": "1f63f" -}, -{ - "moji": "🙀", - "name": "weary_cat_face", - "name-ja": "ほえー(ネコ)", - "category": "faces", - "unicode": "1f640", - "attribution": "Lars Ivar Igesund" -}, -{ - "moji": "🙅", - "name": "face_with_no_good_gesture", - "name-ja": "NG", - "category": "gesture", - "unicode": "1f645" -}, -{ - "moji": "🙆", - "name": "face_with_ok_gesture", - "name-ja": "OK!", - "category": "gesture", - "unicode": "1f646" -}, -{ - "moji": "🙇", - "name": "person_bowing_deeply", - "name-ja": "平謝り", - "category": "gesture", - "unicode": "1f647", - "attribution": "Michael B" -}, -{ - "moji": "🙈", - "name": "see_no_evil_monkey", - "name-ja": "見ざる", - "category": "gesture", - "unicode": "1f648", - "attribution": "Markj" -}, -{ - "moji": "🙉", - "name": "hear_no_evil_monkey", - "name-ja": "聞かざる", - "category": "gesture", - "unicode": "1f649" -}, -{ - "moji": "🙊", - "name": "speak_no_evil_monkey", - "name-ja": "言わざる", - "category": "gesture", - "unicode": "1f64a" -}, -{ - "moji": "🙋", - "name": "happy_person_raising_one_hand", - "name-ja": "キャラクター(挙手)", - "category": "gesture", - "unicode": "1f64b" -}, -{ - "moji": "🙌", - "name": "person_raising_both_hands_in_celebration", - "name-ja": "バンザイ", - "category": "gesture", - "unicode": "1f64c", - "attribution": "Anh Vu" -}, -{ - "moji": "🙍", - "name": "person_frowning", - "name-ja": "キャラクター(しょんぼり)", - "category": "gesture", - "unicode": "1f64d" -}, -{ - "moji": "🙎", - "name": "person_with_pouting_face", - "name-ja": "キャラクター(怒る)", - "category": "gesture", - "unicode": "1f64e" -}, -{ - "moji": "🙏", - "name": "person_with_folded_hands", - "name-ja": "お願い", - "category": "gesture", - "unicode": "1f64f" -}, -{ - "moji": "🚏", - "name": "bus_stop", - "name-ja": "バス停", - "category": "transportation", - "unicode": "1f68f", - "attribution": "Albert L Perrien II" -}, -{ - "moji": "🚗", - "name": "car", - "name-ja": "車", - "category": "transportation", - "unicode": "1f697" -}, -{ - "moji": "🚙", - "name": "RV", - "name-ja": "RV車", - "category": "transportation", - "unicode": "1f699" -}, -{ - "moji": "🚢", - "name": "ship", - "name-ja": "船", - "category": "transportation", - "unicode": "1f6a2", - "attribution": "Mike Canfield" -}, -{ - "moji": "🚹", - "name": "mens_symbol", - "name-ja": "男性マーク", - "category": "zabstract", - "unicode": "1f6b9", - "attribution": "Ken Gander" -}, -{ - "moji": "☀", - "alts": ["black_sun_with_rays"], - "name": "sun", - "name-ja": "晴れ", - "category": "zabstract", - "unicode": "2600", - "attribution": "Aleksandr Panzin" -}, -{ - "moji": "☁", - "name": "cloud", - "name-ja": "曇り", - "category": "zabstract", - "unicode": "2601", - "attribution": "Oliver Salzburg" -}, -{ - "moji": "☕", - "name": "hot_beverage", - "name-ja": "コーヒー", - "category": "object", - "unicode": "2615", - "attribution": "Jory Felice" -}, -{ - "moji": "☺", - "name": "white_smiling_face", - "name-ja": "スマイルフェイス", - "category": "faces", - "unicode": "263a" -}, -{ - "moji": "♈", - "name": "Aries", - "name-ja": "牡羊座", - "category": "zabstract", - "unicode": "2648" -}, -{ - "moji": "♉", - "name": "Taurus", - "name-ja": "牡牛座", - "category": "zabstract", - "unicode": "2649" -}, -{ - "moji": "♊", - "name": "Gemini", - "name-ja": "双子座", - "category": "zabstract", - "unicode": "264a" -}, -{ - "moji": "♋", - "name": "Cancer", - "name-ja": "蟹座", - "category": "zabstract", - "unicode": "264b", - "attribution": "Glenn Møller" -}, -{ - "moji": "♌", - "name": "Leo", - "name-ja": "獅子座", - "category": "zabstract", - "unicode": "264c" -}, -{ - "moji": "♍", - "name": "Virgo", - "name-ja": "乙女座", - "category": "zabstract", - "unicode": "264d" -}, -{ - "moji": "♎", - "name": "Libra", - "name-ja": "天秤座", - "category": "zabstract", - "unicode": "264e" -}, -{ - "moji": "♏", - "name": "Scorpius", - "name-ja": "蠍座", - "category": "zabstract", - "unicode": "264f" -}, -{ - "moji": "♐", - "name": "Sagittarius", - "name-ja": "射手座", - "category": "zabstract", - "unicode": "2650" -}, -{ - "moji": "♑", - "name": "Capricorn", - "name-ja": "山羊座", - "category": "zabstract", - "unicode": "2651" -}, -{ - "moji": "♒", - "name": "Aquarius", - "name-ja": "水瓶座", - "category": "zabstract", - "unicode": "2652" -}, -{ - "moji": "♓", - "name": "Pisces", - "name-ja": "魚座", - "category": "zabstract", - "unicode": "2653" -}, -{ - "moji": "♿", - "name": "wheelchair", - "name-ja": "車椅子マーク", - "category": "zabstract", - "unicode": "267f", - "attribution": "Adam Baxter" -}, -{ - "moji": "⚓", - "name": "anchor", - "name-ja": "いかり", - "category": "transportation", - "unicode": "2693", - "attribution": "Hein-Pieter van Braam" -}, -{ - "moji": "⚡", - "name": "high_voltage_sign", - "name-ja": "イナズマ", - "category": "object", - "unicode": "26a1", - "attribution": "Finallyanime!" -}, -{ - "moji": "✌", - "name": "victory_hand", - "name-ja": "チョキ", - "category": "gesture", - "unicode": "270c", - "attribution": "Rudy Dreamingrudes Valenta" -}, -{ - "moji": "❤", - "name": "heart", - "name-ja": "ハート", - "category": "zabstract", - "unicode": "2764" -}, -{ - "moji": "㊙", - "name": "circled_ideograph_secret", - "name-ja": "マル秘", - "category": "zabstract", - "unicode": "3299", - "attribution": "Benjamin Bangsberg" -}, -{ - "name": "smiling_face", - "name-ja": "笑顔", - "category": "faces" -}, -{ - "name": "relaxed", - "name-ja": "リラックス", - "category": "faces" -}, -{ - "name": "bowtie", - "name-ja": "ちょうネクタイ", - "category": "faces" -}, -{ - "name": "satisfied", - "name-ja": "満足", - "category": "faces" -}, -{ - "name": "wink2", - "name-ja": "ウインク2", - "category": "faces" -}, -{ - "name": "drunk", - "name-ja": "酔っ払い", - "category": "faces" -}, -{ - "name": "sun_with_face", - "name-ja": "顔太陽", - "category": "nature" -}, -{ - "name": "rainbow_solid", - "name-ja": "虹[不透明]", - "category": "nature" -}, -{ - "name": "rainbow_sky", - "name-ja": "虹空", - "category": "nature" -}, -{ - "name": "jumping_spider", - "name-ja": "ハエトリグモ", - "category": "nature" -}, -{ - "name": "jumping_spider_red", - "name-ja": "ハエトリグモ(赤)", - "category": "nature" -}, -{ - "name": "family_daughters", - "name-ja": "家族2", - "category": "people" -}, -{ - "name": "couple_in_love", - "name-ja": "手をつないだカップルとハート", - "category": "people" -}, -{ - "name": "two_men_in_love", - "name-ja": "手をつないだ二人の男性とハート", - "category": "people", - "attribution": "Michael Dandy" -}, -{ - "name": "two_men_with_heart", - "name-ja": "二人の男性とハート", - "category": "people" -}, -{ - "name": "two_women_in_love", - "name-ja": "手をつないだ二人の女性とハート", - "category": "people" -}, -{ - "name": "two_women_with_heart", - "name-ja": "二人の女性とハート", - "category": "people" -}, -{ - "name": "ninja", - "name-ja": "忍者", - "category": "people" -}, -{ - "name": "quoll", - "name-ja": "フクロネコ", - "category": "nature", - "attribution": "Peter Halasz" -}, -{ - "name": "palm_pre3", - "name-ja": "HP Palm Pre 3", - "category": "object", - "attribution": "Donald C. Kirker and webOS-Ports" -}, -{ - "name": "cutting_lines", - "name-ja": "カミソリ", - "category": "object", - "attribution": "Key Bump Apparel" -}, -{ - "name": "scuba_diver", - "name-ja": "スキューバダイバー", - "category": "people", - "attribution": "fidelinho" -}, -{ - "name": "penguin_chick", - "name-ja": "子どもペンギン", - "category": "nature" -}, -{ - "name": "pegasus_black", - "name-ja": "ペガサス(黒)", - "category": "nature", - "attribution": "Mark Atwood, the FallenPegasusbin" -}, -{ - "name": "puke_finger", - "name-ja": "嘔吐", - "category": "gesture", - "attribution": "Naya J." -}, -{ - "name": "shit", - "name-ja": "うんち", - "category": "zabstract", - "attribution": "Adam Neuwirth" -}, -{ - "name": "Happy_FMC", - "name-ja": "Happy FMC", - "category": "transportation", - "attribution": "The Grape" -}, -{ - "name": "lambda_chi_alpha", - "name-ja": "Lambda Chi Alpha", - "category": "food", - "attribution": "Vander Vander Gamma Iota Zeta" -}, -{ - "name": "apple_of_discord", - "name-ja": "不和の林檎", - "category": "object", - "attribution": "Jo-Herman Haughol" -}, -{ - "name": "gentleman_octopus", - "name-ja": "紳士蛸", - "category": "nature", - "attribution": "nickythegreek" -}, -{ - "name": "monster", - "name-ja": "怪物", - "category": "nature", - "attribution": "Themonsterfactory.com" -}, -{ - "name": "sunrise_over_mountains", - "name-ja": "Pacific Crest Trailからの日の出", - "category": "nature", - "attribution": "Justin Kazmark" -}, -{ - "name": "bgok", - "name-ja": "ニワトリ(鳴き)", - "category": "nature", - "attribution": "https://github.com/bgok" -}, -{ - "name": "phone_book", - "name-ja": "電話帳", - "category": "object", - "attribution": "Rod Dorman" -}, -{ - "name": "oars", - "name-ja": "櫂", - "category": "transportation", - "attribution": "Bridge" -}, -{ - "name": "siamese_kitten", - "name-ja": "シャム", - "category": "nature", - "attribution": "njx" -}, -{ - "name": "one_eyed_robot", - "name-ja": "ロボット", - "category": "object", - "attribution": "Mr. Daniel Scott Fowler" -}, -{ - "name": "octocat", - "name-ja": "オクトキャット", - "category": "object", - "attribution": "Josh Vera" -}, -{ - "name": "DSLR_click", - "name-ja": "一眼レフ", - "category": "object", - "attribution": "A.Tabisz" -}, -{ - "name": "high_hopes", - "name-ja": "大きな望み", - "category": "nature", - "attribution": "Meniou" -}, -{ - "name": "cookie_fairy", - "name-ja": "クッキーの妖精", - "category": "people", - "attribution": "Onery" -}, -{ - "name": "smiley_confused", - "name-ja": "スマイリー(困る)", - "category": "faces" -}, -{ - "name": "smiley_kissing_heart", - "name-ja": "スマイリー(投げキッス)", - "category": "faces" -}, -{ - "name": "smiley_sleeping", - "name-ja": "スマイリー(眠い)", - "category": "faces" -}, -{ - "name": "smiley_smile", - "name-ja": "スマイリー(嬉しい顔)", - "category": "faces" -}, -{ - "name": "smiley_stuck_out_tongue_winking_eye", - "name-ja": "スマイリー(あっかんべー)", - "category": "faces" -} -] diff --git a/assets/emoji/phantom/Aquarius.png b/assets/emoji/phantom/Aquarius.png deleted file mode 100644 index fe6458b0b..000000000 Binary files a/assets/emoji/phantom/Aquarius.png and /dev/null differ diff --git a/assets/emoji/phantom/Aries.png b/assets/emoji/phantom/Aries.png deleted file mode 100644 index 9cfcc45aa..000000000 Binary files a/assets/emoji/phantom/Aries.png and /dev/null differ diff --git a/assets/emoji/phantom/Cancer.png b/assets/emoji/phantom/Cancer.png deleted file mode 100644 index b5878258c..000000000 Binary files a/assets/emoji/phantom/Cancer.png and /dev/null differ diff --git a/assets/emoji/phantom/Capricorn.png b/assets/emoji/phantom/Capricorn.png deleted file mode 100644 index 37bef78ad..000000000 Binary files a/assets/emoji/phantom/Capricorn.png and /dev/null differ diff --git a/assets/emoji/phantom/Gemini.png b/assets/emoji/phantom/Gemini.png deleted file mode 100644 index 937d0e633..000000000 Binary files a/assets/emoji/phantom/Gemini.png and /dev/null differ diff --git a/assets/emoji/phantom/Giorgio.png b/assets/emoji/phantom/Giorgio.png deleted file mode 100644 index 26711c0e1..000000000 Binary files a/assets/emoji/phantom/Giorgio.png and /dev/null differ diff --git a/assets/emoji/phantom/Happy_FMC.png b/assets/emoji/phantom/Happy_FMC.png deleted file mode 100644 index 4392d10a9..000000000 Binary files a/assets/emoji/phantom/Happy_FMC.png and /dev/null differ diff --git a/assets/emoji/phantom/Kagetsuki.png b/assets/emoji/phantom/Kagetsuki.png deleted file mode 100644 index 977572bb1..000000000 Binary files a/assets/emoji/phantom/Kagetsuki.png and /dev/null differ diff --git a/assets/emoji/phantom/Leo.png b/assets/emoji/phantom/Leo.png deleted file mode 100644 index 3c3ebfbe6..000000000 Binary files a/assets/emoji/phantom/Leo.png and /dev/null differ diff --git a/assets/emoji/phantom/Libra.png b/assets/emoji/phantom/Libra.png deleted file mode 100644 index 9380e1d64..000000000 Binary files a/assets/emoji/phantom/Libra.png and /dev/null differ diff --git a/assets/emoji/phantom/Pisces.png b/assets/emoji/phantom/Pisces.png deleted file mode 100644 index 9b542368e..000000000 Binary files a/assets/emoji/phantom/Pisces.png and /dev/null differ diff --git a/assets/emoji/phantom/RV.png b/assets/emoji/phantom/RV.png deleted file mode 100644 index 3e22ec69f..000000000 Binary files a/assets/emoji/phantom/RV.png and /dev/null differ diff --git a/assets/emoji/phantom/Sagittarius.png b/assets/emoji/phantom/Sagittarius.png deleted file mode 100644 index 97bba38c5..000000000 Binary files a/assets/emoji/phantom/Sagittarius.png and /dev/null differ diff --git a/assets/emoji/phantom/Scorpius.png b/assets/emoji/phantom/Scorpius.png deleted file mode 100644 index 2e3928abf..000000000 Binary files a/assets/emoji/phantom/Scorpius.png and /dev/null differ diff --git a/assets/emoji/phantom/Taurus.png b/assets/emoji/phantom/Taurus.png deleted file mode 100644 index 2af6f0283..000000000 Binary files a/assets/emoji/phantom/Taurus.png and /dev/null differ diff --git a/assets/emoji/phantom/Virgo.png b/assets/emoji/phantom/Virgo.png deleted file mode 100644 index 4e2de2497..000000000 Binary files a/assets/emoji/phantom/Virgo.png and /dev/null differ diff --git a/assets/emoji/phantom/anchor.png b/assets/emoji/phantom/anchor.png deleted file mode 100644 index 2fd8b4c6e..000000000 Binary files a/assets/emoji/phantom/anchor.png and /dev/null differ diff --git a/assets/emoji/phantom/angry_face.png b/assets/emoji/phantom/angry_face.png deleted file mode 100644 index d6e2cfccf..000000000 Binary files a/assets/emoji/phantom/angry_face.png and /dev/null differ diff --git a/assets/emoji/phantom/anguished.png b/assets/emoji/phantom/anguished.png deleted file mode 100644 index 85addcd6f..000000000 Binary files a/assets/emoji/phantom/anguished.png and /dev/null differ diff --git a/assets/emoji/phantom/apple_of_discord.png b/assets/emoji/phantom/apple_of_discord.png deleted file mode 100644 index 6ed31f306..000000000 Binary files a/assets/emoji/phantom/apple_of_discord.png and /dev/null differ diff --git a/assets/emoji/phantom/assault_rifle.png b/assets/emoji/phantom/assault_rifle.png deleted file mode 100644 index b600f2414..000000000 Binary files a/assets/emoji/phantom/assault_rifle.png and /dev/null differ diff --git a/assets/emoji/phantom/astonished_face.png b/assets/emoji/phantom/astonished_face.png deleted file mode 100644 index 86915be21..000000000 Binary files a/assets/emoji/phantom/astonished_face.png and /dev/null differ diff --git a/assets/emoji/phantom/bactrian_camel.png b/assets/emoji/phantom/bactrian_camel.png deleted file mode 100644 index d67e9532f..000000000 Binary files a/assets/emoji/phantom/bactrian_camel.png and /dev/null differ diff --git a/assets/emoji/phantom/beer_mug.png b/assets/emoji/phantom/beer_mug.png deleted file mode 100644 index e5386a9a5..000000000 Binary files a/assets/emoji/phantom/beer_mug.png and /dev/null differ diff --git a/assets/emoji/phantom/bento_box.png b/assets/emoji/phantom/bento_box.png deleted file mode 100644 index fae5e31ef..000000000 Binary files a/assets/emoji/phantom/bento_box.png and /dev/null differ diff --git a/assets/emoji/phantom/blowfish.png b/assets/emoji/phantom/blowfish.png deleted file mode 100644 index 359185df8..000000000 Binary files a/assets/emoji/phantom/blowfish.png and /dev/null differ diff --git a/assets/emoji/phantom/blush.png b/assets/emoji/phantom/blush.png deleted file mode 100644 index 86b70853b..000000000 Binary files a/assets/emoji/phantom/blush.png and /dev/null differ diff --git a/assets/emoji/phantom/boar.png b/assets/emoji/phantom/boar.png deleted file mode 100644 index 89212455a..000000000 Binary files a/assets/emoji/phantom/boar.png and /dev/null differ diff --git a/assets/emoji/phantom/bowtie.png b/assets/emoji/phantom/bowtie.png deleted file mode 100644 index 6661961c4..000000000 Binary files a/assets/emoji/phantom/bowtie.png and /dev/null differ diff --git a/assets/emoji/phantom/bus_stop.png b/assets/emoji/phantom/bus_stop.png deleted file mode 100644 index b6af70077..000000000 Binary files a/assets/emoji/phantom/bus_stop.png and /dev/null differ diff --git a/assets/emoji/phantom/cactus.png b/assets/emoji/phantom/cactus.png deleted file mode 100644 index 182b4aa3a..000000000 Binary files a/assets/emoji/phantom/cactus.png and /dev/null differ diff --git a/assets/emoji/phantom/cake.png b/assets/emoji/phantom/cake.png deleted file mode 100644 index b80d66376..000000000 Binary files a/assets/emoji/phantom/cake.png and /dev/null differ diff --git a/assets/emoji/phantom/car.png b/assets/emoji/phantom/car.png deleted file mode 100644 index 8b68cf606..000000000 Binary files a/assets/emoji/phantom/car.png and /dev/null differ diff --git a/assets/emoji/phantom/cat_face.png b/assets/emoji/phantom/cat_face.png deleted file mode 100644 index aadfc985d..000000000 Binary files a/assets/emoji/phantom/cat_face.png and /dev/null differ diff --git a/assets/emoji/phantom/cat_face_with_tears_of_joy.png b/assets/emoji/phantom/cat_face_with_tears_of_joy.png deleted file mode 100644 index 06838e921..000000000 Binary files a/assets/emoji/phantom/cat_face_with_tears_of_joy.png and /dev/null differ diff --git a/assets/emoji/phantom/cat_face_with_wry_smile.png b/assets/emoji/phantom/cat_face_with_wry_smile.png deleted file mode 100644 index 455e8c1b2..000000000 Binary files a/assets/emoji/phantom/cat_face_with_wry_smile.png and /dev/null differ diff --git a/assets/emoji/phantom/chicken.png b/assets/emoji/phantom/chicken.png deleted file mode 100644 index f21763b12..000000000 Binary files a/assets/emoji/phantom/chicken.png and /dev/null differ diff --git a/assets/emoji/phantom/circled_ideograph_secret.png b/assets/emoji/phantom/circled_ideograph_secret.png deleted file mode 100644 index c94939529..000000000 Binary files a/assets/emoji/phantom/circled_ideograph_secret.png and /dev/null differ diff --git a/assets/emoji/phantom/cloud.png b/assets/emoji/phantom/cloud.png deleted file mode 100644 index 4ca91e5de..000000000 Binary files a/assets/emoji/phantom/cloud.png and /dev/null differ diff --git a/assets/emoji/phantom/confused.png b/assets/emoji/phantom/confused.png deleted file mode 100644 index 2b956741f..000000000 Binary files a/assets/emoji/phantom/confused.png and /dev/null differ diff --git a/assets/emoji/phantom/couple_holding_hands.png b/assets/emoji/phantom/couple_holding_hands.png deleted file mode 100644 index fcb1030f5..000000000 Binary files a/assets/emoji/phantom/couple_holding_hands.png and /dev/null differ diff --git a/assets/emoji/phantom/couple_in_love.png b/assets/emoji/phantom/couple_in_love.png deleted file mode 100644 index e46b6fbdb..000000000 Binary files a/assets/emoji/phantom/couple_in_love.png and /dev/null differ diff --git a/assets/emoji/phantom/couple_with_heart.png b/assets/emoji/phantom/couple_with_heart.png deleted file mode 100644 index 529ad4808..000000000 Binary files a/assets/emoji/phantom/couple_with_heart.png and /dev/null differ diff --git a/assets/emoji/phantom/crescent_moon.png b/assets/emoji/phantom/crescent_moon.png deleted file mode 100644 index 301ae8d8a..000000000 Binary files a/assets/emoji/phantom/crescent_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/crying_cat_face.png b/assets/emoji/phantom/crying_cat_face.png deleted file mode 100644 index 9795e3ed1..000000000 Binary files a/assets/emoji/phantom/crying_cat_face.png and /dev/null differ diff --git a/assets/emoji/phantom/crying_face.png b/assets/emoji/phantom/crying_face.png deleted file mode 100644 index 25bab631a..000000000 Binary files a/assets/emoji/phantom/crying_face.png and /dev/null differ diff --git a/assets/emoji/phantom/cutting_lines.png b/assets/emoji/phantom/cutting_lines.png deleted file mode 100644 index eea627853..000000000 Binary files a/assets/emoji/phantom/cutting_lines.png and /dev/null differ diff --git a/assets/emoji/phantom/cyclone.png b/assets/emoji/phantom/cyclone.png deleted file mode 100644 index 93048e2aa..000000000 Binary files a/assets/emoji/phantom/cyclone.png and /dev/null differ diff --git a/assets/emoji/phantom/disappointed_but_relieved_face.png b/assets/emoji/phantom/disappointed_but_relieved_face.png deleted file mode 100644 index 4de9b4c05..000000000 Binary files a/assets/emoji/phantom/disappointed_but_relieved_face.png and /dev/null differ diff --git a/assets/emoji/phantom/disappointed_face.png b/assets/emoji/phantom/disappointed_face.png deleted file mode 100644 index 7614670ed..000000000 Binary files a/assets/emoji/phantom/disappointed_face.png and /dev/null differ diff --git a/assets/emoji/phantom/dizzy_face.png b/assets/emoji/phantom/dizzy_face.png deleted file mode 100644 index 0e1f68015..000000000 Binary files a/assets/emoji/phantom/dizzy_face.png and /dev/null differ diff --git a/assets/emoji/phantom/dog_face.png b/assets/emoji/phantom/dog_face.png deleted file mode 100644 index cf71992b5..000000000 Binary files a/assets/emoji/phantom/dog_face.png and /dev/null differ diff --git a/assets/emoji/phantom/drunk.png b/assets/emoji/phantom/drunk.png deleted file mode 100644 index b29343871..000000000 Binary files a/assets/emoji/phantom/drunk.png and /dev/null differ diff --git a/assets/emoji/phantom/elephant.png b/assets/emoji/phantom/elephant.png deleted file mode 100644 index 0a7f4b049..000000000 Binary files a/assets/emoji/phantom/elephant.png and /dev/null differ diff --git a/assets/emoji/phantom/expressionless.png b/assets/emoji/phantom/expressionless.png deleted file mode 100644 index 1236a4c18..000000000 Binary files a/assets/emoji/phantom/expressionless.png and /dev/null differ diff --git a/assets/emoji/phantom/extraterrestrial_alien.png b/assets/emoji/phantom/extraterrestrial_alien.png deleted file mode 100644 index ee0abfe7b..000000000 Binary files a/assets/emoji/phantom/extraterrestrial_alien.png and /dev/null differ diff --git a/assets/emoji/phantom/face_savouring_delicious_food.png b/assets/emoji/phantom/face_savouring_delicious_food.png deleted file mode 100644 index 24e0efa90..000000000 Binary files a/assets/emoji/phantom/face_savouring_delicious_food.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_look_of_triumph.png b/assets/emoji/phantom/face_with_look_of_triumph.png deleted file mode 100644 index 13467b5d3..000000000 Binary files a/assets/emoji/phantom/face_with_look_of_triumph.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_medical_mask.png b/assets/emoji/phantom/face_with_medical_mask.png deleted file mode 100644 index 87775fef0..000000000 Binary files a/assets/emoji/phantom/face_with_medical_mask.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_no_good_gesture.png b/assets/emoji/phantom/face_with_no_good_gesture.png deleted file mode 100644 index 95bd1b414..000000000 Binary files a/assets/emoji/phantom/face_with_no_good_gesture.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_ok_gesture.png b/assets/emoji/phantom/face_with_ok_gesture.png deleted file mode 100644 index 078123b03..000000000 Binary files a/assets/emoji/phantom/face_with_ok_gesture.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_open_mouth_and_cold_sweat.png b/assets/emoji/phantom/face_with_open_mouth_and_cold_sweat.png deleted file mode 100644 index d5ef52ac5..000000000 Binary files a/assets/emoji/phantom/face_with_open_mouth_and_cold_sweat.png and /dev/null differ diff --git a/assets/emoji/phantom/face_with_tear_of_joy.png b/assets/emoji/phantom/face_with_tear_of_joy.png deleted file mode 100644 index 6e6f091f8..000000000 Binary files a/assets/emoji/phantom/face_with_tear_of_joy.png and /dev/null differ diff --git a/assets/emoji/phantom/family.png b/assets/emoji/phantom/family.png deleted file mode 100644 index 1658a85b6..000000000 Binary files a/assets/emoji/phantom/family.png and /dev/null differ diff --git a/assets/emoji/phantom/family_daughters.png b/assets/emoji/phantom/family_daughters.png deleted file mode 100644 index 5906b0989..000000000 Binary files a/assets/emoji/phantom/family_daughters.png and /dev/null differ diff --git a/assets/emoji/phantom/fearful_face.png b/assets/emoji/phantom/fearful_face.png deleted file mode 100644 index b512cc475..000000000 Binary files a/assets/emoji/phantom/fearful_face.png and /dev/null differ diff --git a/assets/emoji/phantom/first_quarter_moon.png b/assets/emoji/phantom/first_quarter_moon.png deleted file mode 100644 index 2316a5200..000000000 Binary files a/assets/emoji/phantom/first_quarter_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/fish_cake_with_swirl_design.png b/assets/emoji/phantom/fish_cake_with_swirl_design.png deleted file mode 100644 index 1ceb46a69..000000000 Binary files a/assets/emoji/phantom/fish_cake_with_swirl_design.png and /dev/null differ diff --git a/assets/emoji/phantom/fisted_hand_sign.png b/assets/emoji/phantom/fisted_hand_sign.png deleted file mode 100644 index 60fb84142..000000000 Binary files a/assets/emoji/phantom/fisted_hand_sign.png and /dev/null differ diff --git a/assets/emoji/phantom/flushed.png b/assets/emoji/phantom/flushed.png deleted file mode 100644 index d57aca324..000000000 Binary files a/assets/emoji/phantom/flushed.png and /dev/null differ diff --git a/assets/emoji/phantom/frog_face.png b/assets/emoji/phantom/frog_face.png deleted file mode 100644 index ca03c0132..000000000 Binary files a/assets/emoji/phantom/frog_face.png and /dev/null differ diff --git a/assets/emoji/phantom/frowning.png b/assets/emoji/phantom/frowning.png deleted file mode 100644 index d9289ad82..000000000 Binary files a/assets/emoji/phantom/frowning.png and /dev/null differ diff --git a/assets/emoji/phantom/full_moon.png b/assets/emoji/phantom/full_moon.png deleted file mode 100644 index 9223baf34..000000000 Binary files a/assets/emoji/phantom/full_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/genshin.png b/assets/emoji/phantom/genshin.png deleted file mode 100644 index 00b2510cd..000000000 Binary files a/assets/emoji/phantom/genshin.png and /dev/null differ diff --git a/assets/emoji/phantom/gentleman_octopus.png b/assets/emoji/phantom/gentleman_octopus.png deleted file mode 100644 index 520756d45..000000000 Binary files a/assets/emoji/phantom/gentleman_octopus.png and /dev/null differ diff --git a/assets/emoji/phantom/ghost.png b/assets/emoji/phantom/ghost.png deleted file mode 100644 index fb37c556b..000000000 Binary files a/assets/emoji/phantom/ghost.png and /dev/null differ diff --git a/assets/emoji/phantom/glowing_star.png b/assets/emoji/phantom/glowing_star.png deleted file mode 100644 index 88eea778f..000000000 Binary files a/assets/emoji/phantom/glowing_star.png and /dev/null differ diff --git a/assets/emoji/phantom/grimacing.png b/assets/emoji/phantom/grimacing.png deleted file mode 100644 index bffe2ad8c..000000000 Binary files a/assets/emoji/phantom/grimacing.png and /dev/null differ diff --git a/assets/emoji/phantom/grin.png b/assets/emoji/phantom/grin.png deleted file mode 100644 index 8032465b8..000000000 Binary files a/assets/emoji/phantom/grin.png and /dev/null differ diff --git a/assets/emoji/phantom/grinning.png b/assets/emoji/phantom/grinning.png deleted file mode 100644 index 057b21ba8..000000000 Binary files a/assets/emoji/phantom/grinning.png and /dev/null differ diff --git a/assets/emoji/phantom/grinning_cat_face_with_smiling_eyes.png b/assets/emoji/phantom/grinning_cat_face_with_smiling_eyes.png deleted file mode 100644 index cff6d9114..000000000 Binary files a/assets/emoji/phantom/grinning_cat_face_with_smiling_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/guitar.png b/assets/emoji/phantom/guitar.png deleted file mode 100644 index 8e24f5c90..000000000 Binary files a/assets/emoji/phantom/guitar.png and /dev/null differ diff --git a/assets/emoji/phantom/hamburger.png b/assets/emoji/phantom/hamburger.png deleted file mode 100644 index 273836404..000000000 Binary files a/assets/emoji/phantom/hamburger.png and /dev/null differ diff --git a/assets/emoji/phantom/happijar.png b/assets/emoji/phantom/happijar.png deleted file mode 100644 index 7617300a6..000000000 Binary files a/assets/emoji/phantom/happijar.png and /dev/null differ diff --git a/assets/emoji/phantom/happy_person_raising_one_hand.png b/assets/emoji/phantom/happy_person_raising_one_hand.png deleted file mode 100644 index 2eb3f3a58..000000000 Binary files a/assets/emoji/phantom/happy_person_raising_one_hand.png and /dev/null differ diff --git a/assets/emoji/phantom/hatching_chick.png b/assets/emoji/phantom/hatching_chick.png deleted file mode 100644 index 2e5e830fb..000000000 Binary files a/assets/emoji/phantom/hatching_chick.png and /dev/null differ diff --git a/assets/emoji/phantom/hear_no_evil_monkey.png b/assets/emoji/phantom/hear_no_evil_monkey.png deleted file mode 100644 index ea4751328..000000000 Binary files a/assets/emoji/phantom/hear_no_evil_monkey.png and /dev/null differ diff --git a/assets/emoji/phantom/heart.png b/assets/emoji/phantom/heart.png deleted file mode 100644 index ce4262296..000000000 Binary files a/assets/emoji/phantom/heart.png and /dev/null differ diff --git a/assets/emoji/phantom/heart_eyes.png b/assets/emoji/phantom/heart_eyes.png deleted file mode 100644 index df5f11586..000000000 Binary files a/assets/emoji/phantom/heart_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/high_voltage_sign.png b/assets/emoji/phantom/high_voltage_sign.png deleted file mode 100644 index 9b23994e8..000000000 Binary files a/assets/emoji/phantom/high_voltage_sign.png and /dev/null differ diff --git a/assets/emoji/phantom/horse.png b/assets/emoji/phantom/horse.png deleted file mode 100644 index e26cb7653..000000000 Binary files a/assets/emoji/phantom/horse.png and /dev/null differ diff --git a/assets/emoji/phantom/hot_beverage.png b/assets/emoji/phantom/hot_beverage.png deleted file mode 100644 index 8dee1d9bc..000000000 Binary files a/assets/emoji/phantom/hot_beverage.png and /dev/null differ diff --git a/assets/emoji/phantom/hushed.png b/assets/emoji/phantom/hushed.png deleted file mode 100644 index 1b30026e5..000000000 Binary files a/assets/emoji/phantom/hushed.png and /dev/null differ diff --git a/assets/emoji/phantom/jack_o_lantern.png b/assets/emoji/phantom/jack_o_lantern.png deleted file mode 100644 index fde63a8bd..000000000 Binary files a/assets/emoji/phantom/jack_o_lantern.png and /dev/null differ diff --git a/assets/emoji/phantom/jumping_spider.png b/assets/emoji/phantom/jumping_spider.png deleted file mode 100644 index bb7d63945..000000000 Binary files a/assets/emoji/phantom/jumping_spider.png and /dev/null differ diff --git a/assets/emoji/phantom/jumping_spider_red.png b/assets/emoji/phantom/jumping_spider_red.png deleted file mode 100644 index 877579cb6..000000000 Binary files a/assets/emoji/phantom/jumping_spider_red.png and /dev/null differ diff --git a/assets/emoji/phantom/kissing.png b/assets/emoji/phantom/kissing.png deleted file mode 100644 index c9a756023..000000000 Binary files a/assets/emoji/phantom/kissing.png and /dev/null differ diff --git a/assets/emoji/phantom/kissing_cat_face_with_closed_eyes.png b/assets/emoji/phantom/kissing_cat_face_with_closed_eyes.png deleted file mode 100644 index fb565c605..000000000 Binary files a/assets/emoji/phantom/kissing_cat_face_with_closed_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/kissing_closed_eyes.png b/assets/emoji/phantom/kissing_closed_eyes.png deleted file mode 100644 index dd33fbc5d..000000000 Binary files a/assets/emoji/phantom/kissing_closed_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/kissing_heart.png b/assets/emoji/phantom/kissing_heart.png deleted file mode 100644 index 83e01f0d4..000000000 Binary files a/assets/emoji/phantom/kissing_heart.png and /dev/null differ diff --git a/assets/emoji/phantom/kissing_smiling_eyes.png b/assets/emoji/phantom/kissing_smiling_eyes.png deleted file mode 100644 index 6651b9b21..000000000 Binary files a/assets/emoji/phantom/kissing_smiling_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/koala.png b/assets/emoji/phantom/koala.png deleted file mode 100644 index 93d2ed25b..000000000 Binary files a/assets/emoji/phantom/koala.png and /dev/null differ diff --git a/assets/emoji/phantom/ksroom.png b/assets/emoji/phantom/ksroom.png deleted file mode 100644 index 7ee3fe69c..000000000 Binary files a/assets/emoji/phantom/ksroom.png and /dev/null differ diff --git a/assets/emoji/phantom/lambda_chi_alpha.png b/assets/emoji/phantom/lambda_chi_alpha.png deleted file mode 100644 index ff6c772cd..000000000 Binary files a/assets/emoji/phantom/lambda_chi_alpha.png and /dev/null differ diff --git a/assets/emoji/phantom/last_quarter_moon.png b/assets/emoji/phantom/last_quarter_moon.png deleted file mode 100644 index 7d1eee16a..000000000 Binary files a/assets/emoji/phantom/last_quarter_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/laughing.png b/assets/emoji/phantom/laughing.png deleted file mode 100644 index 389405c14..000000000 Binary files a/assets/emoji/phantom/laughing.png and /dev/null differ diff --git a/assets/emoji/phantom/loudly_crying_face.png b/assets/emoji/phantom/loudly_crying_face.png deleted file mode 100644 index 942669078..000000000 Binary files a/assets/emoji/phantom/loudly_crying_face.png and /dev/null differ diff --git a/assets/emoji/phantom/love_hotel.png b/assets/emoji/phantom/love_hotel.png deleted file mode 100644 index 547867797..000000000 Binary files a/assets/emoji/phantom/love_hotel.png and /dev/null differ diff --git a/assets/emoji/phantom/mens_symbol.png b/assets/emoji/phantom/mens_symbol.png deleted file mode 100644 index bcf665326..000000000 Binary files a/assets/emoji/phantom/mens_symbol.png and /dev/null differ diff --git a/assets/emoji/phantom/microphone.png b/assets/emoji/phantom/microphone.png deleted file mode 100644 index 033f45734..000000000 Binary files a/assets/emoji/phantom/microphone.png and /dev/null differ diff --git a/assets/emoji/phantom/milky_way.png b/assets/emoji/phantom/milky_way.png deleted file mode 100644 index c03ff84ec..000000000 Binary files a/assets/emoji/phantom/milky_way.png and /dev/null differ diff --git a/assets/emoji/phantom/monster.png b/assets/emoji/phantom/monster.png deleted file mode 100644 index 0ade7611f..000000000 Binary files a/assets/emoji/phantom/monster.png and /dev/null differ diff --git a/assets/emoji/phantom/moon_with_face.png b/assets/emoji/phantom/moon_with_face.png deleted file mode 100644 index da84ddc64..000000000 Binary files a/assets/emoji/phantom/moon_with_face.png and /dev/null differ diff --git a/assets/emoji/phantom/mount_fuji.png b/assets/emoji/phantom/mount_fuji.png deleted file mode 100644 index e9b515f59..000000000 Binary files a/assets/emoji/phantom/mount_fuji.png and /dev/null differ diff --git a/assets/emoji/phantom/new_moon.png b/assets/emoji/phantom/new_moon.png deleted file mode 100644 index 903ab4875..000000000 Binary files a/assets/emoji/phantom/new_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/ninja.png b/assets/emoji/phantom/ninja.png deleted file mode 100644 index 0b8138ec5..000000000 Binary files a/assets/emoji/phantom/ninja.png and /dev/null differ diff --git a/assets/emoji/phantom/no_one_under_eighteen_symbol.png b/assets/emoji/phantom/no_one_under_eighteen_symbol.png deleted file mode 100644 index fc1e1bc63..000000000 Binary files a/assets/emoji/phantom/no_one_under_eighteen_symbol.png and /dev/null differ diff --git a/assets/emoji/phantom/oars.png b/assets/emoji/phantom/oars.png deleted file mode 100644 index 58cc84a8c..000000000 Binary files a/assets/emoji/phantom/oars.png and /dev/null differ diff --git a/assets/emoji/phantom/ocean_dive_view.png b/assets/emoji/phantom/ocean_dive_view.png deleted file mode 100644 index 8546e8617..000000000 Binary files a/assets/emoji/phantom/ocean_dive_view.png and /dev/null differ diff --git a/assets/emoji/phantom/octopus.png b/assets/emoji/phantom/octopus.png deleted file mode 100644 index 05d09f03c..000000000 Binary files a/assets/emoji/phantom/octopus.png and /dev/null differ diff --git a/assets/emoji/phantom/open_mouth.png b/assets/emoji/phantom/open_mouth.png deleted file mode 100644 index cacfc04e6..000000000 Binary files a/assets/emoji/phantom/open_mouth.png and /dev/null differ diff --git a/assets/emoji/phantom/palm_pre3.png b/assets/emoji/phantom/palm_pre3.png deleted file mode 100644 index 1adc74412..000000000 Binary files a/assets/emoji/phantom/palm_pre3.png and /dev/null differ diff --git a/assets/emoji/phantom/paw_prints.png b/assets/emoji/phantom/paw_prints.png deleted file mode 100644 index 07815e717..000000000 Binary files a/assets/emoji/phantom/paw_prints.png and /dev/null differ diff --git a/assets/emoji/phantom/pegasus_black.png b/assets/emoji/phantom/pegasus_black.png deleted file mode 100644 index e002f6d78..000000000 Binary files a/assets/emoji/phantom/pegasus_black.png and /dev/null differ diff --git a/assets/emoji/phantom/penguin.png b/assets/emoji/phantom/penguin.png deleted file mode 100644 index 8a96d4afe..000000000 Binary files a/assets/emoji/phantom/penguin.png and /dev/null differ diff --git a/assets/emoji/phantom/penguin_chick.png b/assets/emoji/phantom/penguin_chick.png deleted file mode 100644 index c976ab103..000000000 Binary files a/assets/emoji/phantom/penguin_chick.png and /dev/null differ diff --git a/assets/emoji/phantom/pensive_face.png b/assets/emoji/phantom/pensive_face.png deleted file mode 100644 index 27f40e675..000000000 Binary files a/assets/emoji/phantom/pensive_face.png and /dev/null differ diff --git a/assets/emoji/phantom/persevering_face.png b/assets/emoji/phantom/persevering_face.png deleted file mode 100644 index a7a848388..000000000 Binary files a/assets/emoji/phantom/persevering_face.png and /dev/null differ diff --git a/assets/emoji/phantom/person_bowing_deeply.png b/assets/emoji/phantom/person_bowing_deeply.png deleted file mode 100644 index c236b5c42..000000000 Binary files a/assets/emoji/phantom/person_bowing_deeply.png and /dev/null differ diff --git a/assets/emoji/phantom/person_frowning.png b/assets/emoji/phantom/person_frowning.png deleted file mode 100644 index 0db9a68e8..000000000 Binary files a/assets/emoji/phantom/person_frowning.png and /dev/null differ diff --git a/assets/emoji/phantom/person_raising_both_hands_in_celebration.png b/assets/emoji/phantom/person_raising_both_hands_in_celebration.png deleted file mode 100644 index 3e1f26f8e..000000000 Binary files a/assets/emoji/phantom/person_raising_both_hands_in_celebration.png and /dev/null differ diff --git a/assets/emoji/phantom/person_with_folded_hands.png b/assets/emoji/phantom/person_with_folded_hands.png deleted file mode 100644 index 7a9432bb8..000000000 Binary files a/assets/emoji/phantom/person_with_folded_hands.png and /dev/null differ diff --git a/assets/emoji/phantom/person_with_pouting_face.png b/assets/emoji/phantom/person_with_pouting_face.png deleted file mode 100644 index a54d44ac9..000000000 Binary files a/assets/emoji/phantom/person_with_pouting_face.png and /dev/null differ diff --git a/assets/emoji/phantom/phone_book.png b/assets/emoji/phantom/phone_book.png deleted file mode 100644 index 1512746ee..000000000 Binary files a/assets/emoji/phantom/phone_book.png and /dev/null differ diff --git a/assets/emoji/phantom/pill.png b/assets/emoji/phantom/pill.png deleted file mode 100644 index 2e0b83f73..000000000 Binary files a/assets/emoji/phantom/pill.png and /dev/null differ diff --git a/assets/emoji/phantom/pistol.png b/assets/emoji/phantom/pistol.png deleted file mode 100644 index 83bd6d442..000000000 Binary files a/assets/emoji/phantom/pistol.png and /dev/null differ diff --git a/assets/emoji/phantom/pizza.png b/assets/emoji/phantom/pizza.png deleted file mode 100644 index 55e494e08..000000000 Binary files a/assets/emoji/phantom/pizza.png and /dev/null differ diff --git a/assets/emoji/phantom/poop.png b/assets/emoji/phantom/poop.png deleted file mode 100644 index ca570b4b6..000000000 Binary files a/assets/emoji/phantom/poop.png and /dev/null differ diff --git a/assets/emoji/phantom/pouting_cat_face.png b/assets/emoji/phantom/pouting_cat_face.png deleted file mode 100644 index 28708bd7f..000000000 Binary files a/assets/emoji/phantom/pouting_cat_face.png and /dev/null differ diff --git a/assets/emoji/phantom/pouting_face.png b/assets/emoji/phantom/pouting_face.png deleted file mode 100644 index 5a90f6d7f..000000000 Binary files a/assets/emoji/phantom/pouting_face.png and /dev/null differ diff --git a/assets/emoji/phantom/princess.png b/assets/emoji/phantom/princess.png deleted file mode 100644 index 09fa8ddc7..000000000 Binary files a/assets/emoji/phantom/princess.png and /dev/null differ diff --git a/assets/emoji/phantom/puke_finger.png b/assets/emoji/phantom/puke_finger.png deleted file mode 100644 index 7e5e76551..000000000 Binary files a/assets/emoji/phantom/puke_finger.png and /dev/null differ diff --git a/assets/emoji/phantom/quoll.png b/assets/emoji/phantom/quoll.png deleted file mode 100644 index 3f052f095..000000000 Binary files a/assets/emoji/phantom/quoll.png and /dev/null differ diff --git a/assets/emoji/phantom/rainbow.png b/assets/emoji/phantom/rainbow.png deleted file mode 100644 index 0d5765bae..000000000 Binary files a/assets/emoji/phantom/rainbow.png and /dev/null differ diff --git a/assets/emoji/phantom/rainbow_sky.png b/assets/emoji/phantom/rainbow_sky.png deleted file mode 100644 index 445f86d15..000000000 Binary files a/assets/emoji/phantom/rainbow_sky.png and /dev/null differ diff --git a/assets/emoji/phantom/rainbow_solid.png b/assets/emoji/phantom/rainbow_solid.png deleted file mode 100644 index cd9a6b91d..000000000 Binary files a/assets/emoji/phantom/rainbow_solid.png and /dev/null differ diff --git a/assets/emoji/phantom/relaxed.png b/assets/emoji/phantom/relaxed.png deleted file mode 100644 index 1fef61d38..000000000 Binary files a/assets/emoji/phantom/relaxed.png and /dev/null differ diff --git a/assets/emoji/phantom/relieved.png b/assets/emoji/phantom/relieved.png deleted file mode 100644 index 370c902b4..000000000 Binary files a/assets/emoji/phantom/relieved.png and /dev/null differ diff --git a/assets/emoji/phantom/rice_ball.png b/assets/emoji/phantom/rice_ball.png deleted file mode 100644 index 344514f47..000000000 Binary files a/assets/emoji/phantom/rice_ball.png and /dev/null differ diff --git a/assets/emoji/phantom/ruby.png b/assets/emoji/phantom/ruby.png deleted file mode 100644 index 88cbad747..000000000 Binary files a/assets/emoji/phantom/ruby.png and /dev/null differ diff --git a/assets/emoji/phantom/satisfied.png b/assets/emoji/phantom/satisfied.png deleted file mode 100644 index 99e96c9f7..000000000 Binary files a/assets/emoji/phantom/satisfied.png and /dev/null differ diff --git a/assets/emoji/phantom/scuba_diver.png b/assets/emoji/phantom/scuba_diver.png deleted file mode 100644 index 8ecb260b5..000000000 Binary files a/assets/emoji/phantom/scuba_diver.png and /dev/null differ diff --git a/assets/emoji/phantom/see_no_evil_monkey.png b/assets/emoji/phantom/see_no_evil_monkey.png deleted file mode 100644 index f2426616c..000000000 Binary files a/assets/emoji/phantom/see_no_evil_monkey.png and /dev/null differ diff --git a/assets/emoji/phantom/ship.png b/assets/emoji/phantom/ship.png deleted file mode 100644 index 4b524dc93..000000000 Binary files a/assets/emoji/phantom/ship.png and /dev/null differ diff --git a/assets/emoji/phantom/shit.png b/assets/emoji/phantom/shit.png deleted file mode 100644 index f7b4e1e03..000000000 Binary files a/assets/emoji/phantom/shit.png and /dev/null differ diff --git a/assets/emoji/phantom/shooting_star.png b/assets/emoji/phantom/shooting_star.png deleted file mode 100644 index febf0f009..000000000 Binary files a/assets/emoji/phantom/shooting_star.png and /dev/null differ diff --git a/assets/emoji/phantom/skull.png b/assets/emoji/phantom/skull.png deleted file mode 100644 index 73c8ce169..000000000 Binary files a/assets/emoji/phantom/skull.png and /dev/null differ diff --git a/assets/emoji/phantom/sleeping.png b/assets/emoji/phantom/sleeping.png deleted file mode 100644 index a448d09bf..000000000 Binary files a/assets/emoji/phantom/sleeping.png and /dev/null differ diff --git a/assets/emoji/phantom/smile.png b/assets/emoji/phantom/smile.png deleted file mode 100644 index d49252735..000000000 Binary files a/assets/emoji/phantom/smile.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley.png b/assets/emoji/phantom/smiley.png deleted file mode 100644 index 094fab51f..000000000 Binary files a/assets/emoji/phantom/smiley.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley_confused.png b/assets/emoji/phantom/smiley_confused.png deleted file mode 100644 index fcd2649a3..000000000 Binary files a/assets/emoji/phantom/smiley_confused.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley_kissing_heart.png b/assets/emoji/phantom/smiley_kissing_heart.png deleted file mode 100644 index 492f5b0c8..000000000 Binary files a/assets/emoji/phantom/smiley_kissing_heart.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley_sleeping.png b/assets/emoji/phantom/smiley_sleeping.png deleted file mode 100644 index e0f164581..000000000 Binary files a/assets/emoji/phantom/smiley_sleeping.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley_smile.png b/assets/emoji/phantom/smiley_smile.png deleted file mode 100644 index 89e5a00bb..000000000 Binary files a/assets/emoji/phantom/smiley_smile.png and /dev/null differ diff --git a/assets/emoji/phantom/smiley_stuck_out_tongue_winking_eye.png b/assets/emoji/phantom/smiley_stuck_out_tongue_winking_eye.png deleted file mode 100644 index d5f1dedbe..000000000 Binary files a/assets/emoji/phantom/smiley_stuck_out_tongue_winking_eye.png and /dev/null differ diff --git a/assets/emoji/phantom/smiling_cat_face_with_open_mouth.png b/assets/emoji/phantom/smiling_cat_face_with_open_mouth.png deleted file mode 100644 index b8d38e8a4..000000000 Binary files a/assets/emoji/phantom/smiling_cat_face_with_open_mouth.png and /dev/null differ diff --git a/assets/emoji/phantom/smiling_face.png b/assets/emoji/phantom/smiling_face.png deleted file mode 100644 index 2371ea374..000000000 Binary files a/assets/emoji/phantom/smiling_face.png and /dev/null differ diff --git a/assets/emoji/phantom/smirk.png b/assets/emoji/phantom/smirk.png deleted file mode 100644 index e3f0153cb..000000000 Binary files a/assets/emoji/phantom/smirk.png and /dev/null differ diff --git a/assets/emoji/phantom/snail.png b/assets/emoji/phantom/snail.png deleted file mode 100644 index bd88e426b..000000000 Binary files a/assets/emoji/phantom/snail.png and /dev/null differ diff --git a/assets/emoji/phantom/snake.png b/assets/emoji/phantom/snake.png deleted file mode 100644 index c0e9d2845..000000000 Binary files a/assets/emoji/phantom/snake.png and /dev/null differ diff --git a/assets/emoji/phantom/snake_alt.png b/assets/emoji/phantom/snake_alt.png deleted file mode 100644 index 92eef9214..000000000 Binary files a/assets/emoji/phantom/snake_alt.png and /dev/null differ diff --git a/assets/emoji/phantom/snowboarder.png b/assets/emoji/phantom/snowboarder.png deleted file mode 100644 index b2d727b83..000000000 Binary files a/assets/emoji/phantom/snowboarder.png and /dev/null differ diff --git a/assets/emoji/phantom/speak_no_evil_monkey.png b/assets/emoji/phantom/speak_no_evil_monkey.png deleted file mode 100644 index 304213b65..000000000 Binary files a/assets/emoji/phantom/speak_no_evil_monkey.png and /dev/null differ diff --git a/assets/emoji/phantom/speech_balloon.png b/assets/emoji/phantom/speech_balloon.png deleted file mode 100644 index 615fbd458..000000000 Binary files a/assets/emoji/phantom/speech_balloon.png and /dev/null differ diff --git a/assets/emoji/phantom/spouting_whale.png b/assets/emoji/phantom/spouting_whale.png deleted file mode 100644 index 7631b0eb4..000000000 Binary files a/assets/emoji/phantom/spouting_whale.png and /dev/null differ diff --git a/assets/emoji/phantom/stuck_out_tongue_closed_eyes.png b/assets/emoji/phantom/stuck_out_tongue_closed_eyes.png deleted file mode 100644 index 43d931066..000000000 Binary files a/assets/emoji/phantom/stuck_out_tongue_closed_eyes.png and /dev/null differ diff --git a/assets/emoji/phantom/stuck_out_tongue_winking_eye.png b/assets/emoji/phantom/stuck_out_tongue_winking_eye.png deleted file mode 100644 index bbe660927..000000000 Binary files a/assets/emoji/phantom/stuck_out_tongue_winking_eye.png and /dev/null differ diff --git a/assets/emoji/phantom/sun.png b/assets/emoji/phantom/sun.png deleted file mode 100644 index 1a613c2b7..000000000 Binary files a/assets/emoji/phantom/sun.png and /dev/null differ diff --git a/assets/emoji/phantom/sun_with_face.png b/assets/emoji/phantom/sun_with_face.png deleted file mode 100644 index 6462d35d6..000000000 Binary files a/assets/emoji/phantom/sun_with_face.png and /dev/null differ diff --git a/assets/emoji/phantom/sunrise_over_mountains.png b/assets/emoji/phantom/sunrise_over_mountains.png deleted file mode 100644 index f906428c8..000000000 Binary files a/assets/emoji/phantom/sunrise_over_mountains.png and /dev/null differ diff --git a/assets/emoji/phantom/sweat.png b/assets/emoji/phantom/sweat.png deleted file mode 100644 index 05d4fff68..000000000 Binary files a/assets/emoji/phantom/sweat.png and /dev/null differ diff --git a/assets/emoji/phantom/sweat_smile.png b/assets/emoji/phantom/sweat_smile.png deleted file mode 100644 index cdde240d1..000000000 Binary files a/assets/emoji/phantom/sweat_smile.png and /dev/null differ diff --git a/assets/emoji/phantom/tangerine.png b/assets/emoji/phantom/tangerine.png deleted file mode 100644 index a03e1109f..000000000 Binary files a/assets/emoji/phantom/tangerine.png and /dev/null differ diff --git a/assets/emoji/phantom/tennis_racquet_and_ball.png b/assets/emoji/phantom/tennis_racquet_and_ball.png deleted file mode 100644 index 43d8aa37e..000000000 Binary files a/assets/emoji/phantom/tennis_racquet_and_ball.png and /dev/null differ diff --git a/assets/emoji/phantom/thumbs_up_sign.png b/assets/emoji/phantom/thumbs_up_sign.png deleted file mode 100644 index 68a67bf54..000000000 Binary files a/assets/emoji/phantom/thumbs_up_sign.png and /dev/null differ diff --git a/assets/emoji/phantom/tropical_drink.png b/assets/emoji/phantom/tropical_drink.png deleted file mode 100644 index 2602fc6be..000000000 Binary files a/assets/emoji/phantom/tropical_drink.png and /dev/null differ diff --git a/assets/emoji/phantom/two_men_holding_hands.png b/assets/emoji/phantom/two_men_holding_hands.png deleted file mode 100644 index 11459a19c..000000000 Binary files a/assets/emoji/phantom/two_men_holding_hands.png and /dev/null differ diff --git a/assets/emoji/phantom/two_men_with_heart.png b/assets/emoji/phantom/two_men_with_heart.png deleted file mode 100644 index 74397b06e..000000000 Binary files a/assets/emoji/phantom/two_men_with_heart.png and /dev/null differ diff --git a/assets/emoji/phantom/two_women_holding_hands.png b/assets/emoji/phantom/two_women_holding_hands.png deleted file mode 100644 index 898a09c05..000000000 Binary files a/assets/emoji/phantom/two_women_holding_hands.png and /dev/null differ diff --git a/assets/emoji/phantom/two_women_in_love.png b/assets/emoji/phantom/two_women_in_love.png deleted file mode 100644 index 8a340e229..000000000 Binary files a/assets/emoji/phantom/two_women_in_love.png and /dev/null differ diff --git a/assets/emoji/phantom/two_women_with_heart.png b/assets/emoji/phantom/two_women_with_heart.png deleted file mode 100644 index a003f80c2..000000000 Binary files a/assets/emoji/phantom/two_women_with_heart.png and /dev/null differ diff --git a/assets/emoji/phantom/unamused.png b/assets/emoji/phantom/unamused.png deleted file mode 100644 index 2258f6e5b..000000000 Binary files a/assets/emoji/phantom/unamused.png and /dev/null differ diff --git a/assets/emoji/phantom/victory_hand.png b/assets/emoji/phantom/victory_hand.png deleted file mode 100644 index dec3eeebd..000000000 Binary files a/assets/emoji/phantom/victory_hand.png and /dev/null differ diff --git a/assets/emoji/phantom/video_game.png b/assets/emoji/phantom/video_game.png deleted file mode 100644 index a3cd0db49..000000000 Binary files a/assets/emoji/phantom/video_game.png and /dev/null differ diff --git a/assets/emoji/phantom/waxing_gibbous_moon.png b/assets/emoji/phantom/waxing_gibbous_moon.png deleted file mode 100644 index 651311e08..000000000 Binary files a/assets/emoji/phantom/waxing_gibbous_moon.png and /dev/null differ diff --git a/assets/emoji/phantom/weary_cat_face.png b/assets/emoji/phantom/weary_cat_face.png deleted file mode 100644 index ad283e511..000000000 Binary files a/assets/emoji/phantom/weary_cat_face.png and /dev/null differ diff --git a/assets/emoji/phantom/weary_face.png b/assets/emoji/phantom/weary_face.png deleted file mode 100644 index 9dbe5b6b1..000000000 Binary files a/assets/emoji/phantom/weary_face.png and /dev/null differ diff --git a/assets/emoji/phantom/wheelchair.png b/assets/emoji/phantom/wheelchair.png deleted file mode 100644 index 8c58e79ff..000000000 Binary files a/assets/emoji/phantom/wheelchair.png and /dev/null differ diff --git a/assets/emoji/phantom/white_smiling_face.png b/assets/emoji/phantom/white_smiling_face.png deleted file mode 100644 index ee174a758..000000000 Binary files a/assets/emoji/phantom/white_smiling_face.png and /dev/null differ diff --git a/assets/emoji/phantom/wink.png b/assets/emoji/phantom/wink.png deleted file mode 100644 index ef290a0b1..000000000 Binary files a/assets/emoji/phantom/wink.png and /dev/null differ diff --git a/assets/emoji/phantom/wink2.png b/assets/emoji/phantom/wink2.png deleted file mode 100644 index 818715787..000000000 Binary files a/assets/emoji/phantom/wink2.png and /dev/null differ diff --git a/assets/emoji/phantom/wolf_face.png b/assets/emoji/phantom/wolf_face.png deleted file mode 100644 index 29f4fad8b..000000000 Binary files a/assets/emoji/phantom/wolf_face.png and /dev/null differ diff --git a/assets/emoji/phantom/worried.png b/assets/emoji/phantom/worried.png deleted file mode 100644 index 677550847..000000000 Binary files a/assets/emoji/phantom/worried.png and /dev/null differ diff --git a/build.xml b/build.xml index ae65d0857..4e101c93e 100644 --- a/build.xml +++ b/build.xml @@ -1,5 +1,5 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/FLOW.md b/doc/FLOW.md new file mode 100644 index 000000000..b1c086dd1 --- /dev/null +++ b/doc/FLOW.md @@ -0,0 +1,29 @@ +Incoming Message +================ + +Service process: + +* asmack +* XmppConnection (processPacket in PacketListener) +* ChatSession.onReceiveMessage (created if not existing) +* OtrChatListener.onIncomingMessage +* ChatSessionAdapter.ListenerAdapter.onIncomingMessage + * Insert in DB + * listeners - IChatListener.onIncomingMessage + * or if no listeners - fire notification + +UI process: + +* ChatView (IChatListener).onIncomingMessage + +Incoming Data +================ + +Service process: + +* asmack +* XmppConnection (processPacket in PacketListener) +* ChatSession.onReceiveMessage (created if not existing) +* OtrChatListener.onIncomingMessage +* ChatSessionAdapter.ListenerAdapter.onIncomingDataRequest/Response +* OtrDataHandler.onIncomingRequest/Response diff --git a/external/.tx/config b/external/.tx/config deleted file mode 100644 index 43081eb50..000000000 --- a/external/.tx/config +++ /dev/null @@ -1,16 +0,0 @@ -[main] -host = https://www.transifex.com -lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, he_IL: he-rIL, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA - -[gibberbot.stringsxml] -file_filter = ../res/values-/strings.xml -host = https://www.transifex.com -source_file = ../res/values/strings.xml -source_lang = en - -[gibberbot.arraysxml] -file_filter = ../res/values-/arrays.xml -host = https://www.transifex.com -source_file = ../res/values/arrays.xml -source_lang = en - diff --git a/external/ActionBarSherlock b/external/ActionBarSherlock deleted file mode 160000 index c0d437ce4..000000000 --- a/external/ActionBarSherlock +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c0d437ce4c47cb344e5d96414ffbf5f1a85c26d0 diff --git a/external/AndroidEmojiInput b/external/AndroidEmojiInput index 17ab51657..2b3d0960f 160000 --- a/external/AndroidEmojiInput +++ b/external/AndroidEmojiInput @@ -1 +1 @@ -Subproject commit 17ab516576c1281b23ed772ebeadca263e43a7e2 +Subproject commit 2b3d0960f47d1c1ce415b29bb4c3296eb792bdf6 diff --git a/external/AndroidPinning b/external/AndroidPinning index 9125a85b3..526654e1b 160000 --- a/external/AndroidPinning +++ b/external/AndroidPinning @@ -1 +1 @@ -Subproject commit 9125a85b3a7173a979000254d5d90c7fab23a3fb +Subproject commit 526654e1b9997b32e513d58d9094d4c1102a6cb3 diff --git a/external/MemorizingTrustManager b/external/MemorizingTrustManager index 3864ffa28..36f86bd15 160000 --- a/external/MemorizingTrustManager +++ b/external/MemorizingTrustManager @@ -1 +1 @@ -Subproject commit 3864ffa2870a455729ec09dbecb682e005f348e0 +Subproject commit 36f86bd1592671a1eb28264e1f6c81c7173d4b9b diff --git a/external/MessageBar b/external/MessageBar deleted file mode 160000 index 9ca4a87b8..000000000 --- a/external/MessageBar +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9ca4a87b8d44f8e16622745ba25c4ba30b84c0ca diff --git a/external/NineOldAndroids b/external/NineOldAndroids deleted file mode 160000 index 9f20fd77e..000000000 --- a/external/NineOldAndroids +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f20fd77e04942fd50b95aeb1c492a38e36c06dd diff --git a/external/OnionKit b/external/OnionKit deleted file mode 160000 index 7127602f1..000000000 --- a/external/OnionKit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7127602f1877c0b65a2a4c487d643700f9c34e9a diff --git a/external/ShowcaseView b/external/ShowcaseView deleted file mode 160000 index d4ec875a9..000000000 --- a/external/ShowcaseView +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d4ec875a92774b0b902953b5646beba1c35e18f4 diff --git a/external/SlideListView b/external/SlideListView deleted file mode 160000 index 2c98c7e30..000000000 --- a/external/SlideListView +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c98c7e304e93a8ec1b8bb8133d823db801cbf7a diff --git a/external/ViewPagerIndicator b/external/ViewPagerIndicator new file mode 160000 index 000000000..8cd549f23 --- /dev/null +++ b/external/ViewPagerIndicator @@ -0,0 +1 @@ +Subproject commit 8cd549f23f3d20ff920e19a2345c54983f65e26b diff --git a/external/appcompat/.classpath b/external/appcompat/.classpath new file mode 100644 index 000000000..51769745b --- /dev/null +++ b/external/appcompat/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/external/appcompat/.project b/external/appcompat/.project new file mode 100644 index 000000000..957d33da1 --- /dev/null +++ b/external/appcompat/.project @@ -0,0 +1,33 @@ + + + android-support-v7-appcompat + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/external/appcompat/AndroidManifest.xml b/external/appcompat/AndroidManifest.xml new file mode 100644 index 000000000..dac4cb27e --- /dev/null +++ b/external/appcompat/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/external/appcompat/README.txt b/external/appcompat/README.txt new file mode 100644 index 000000000..8e8de0568 --- /dev/null +++ b/external/appcompat/README.txt @@ -0,0 +1,10 @@ +Library Project including compatibility ActionBar. + +This can be used by an Android project to provide +access to ActionBar on applications running on API 7+. + +There is technically no source, but the src folder is necessary +to ensure that the build system works. The content is actually +located in libs/android-support-v7-appcompat.jar. +The accompanying resources must also be included in the application. + diff --git a/external/appcompat/libs/android-support-v4.jar b/external/appcompat/libs/android-support-v4.jar new file mode 100644 index 000000000..4ebdaa9ed Binary files /dev/null and b/external/appcompat/libs/android-support-v4.jar differ diff --git a/external/appcompat/libs/android-support-v7-appcompat.jar b/external/appcompat/libs/android-support-v7-appcompat.jar new file mode 100644 index 000000000..fdd6c5bf8 Binary files /dev/null and b/external/appcompat/libs/android-support-v7-appcompat.jar differ diff --git a/external/appcompat/project.properties b/external/appcompat/project.properties new file mode 100644 index 000000000..93c8c3c08 --- /dev/null +++ b/external/appcompat/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-21 +android.library=true diff --git a/external/appcompat/res/anim/abc_fade_in.xml b/external/appcompat/res/anim/abc_fade_in.xml new file mode 100644 index 000000000..da7ee295c --- /dev/null +++ b/external/appcompat/res/anim/abc_fade_in.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/external/appcompat/res/anim/abc_fade_out.xml b/external/appcompat/res/anim/abc_fade_out.xml new file mode 100644 index 000000000..c81b39a9b --- /dev/null +++ b/external/appcompat/res/anim/abc_fade_out.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/external/appcompat/res/anim/abc_slide_in_bottom.xml b/external/appcompat/res/anim/abc_slide_in_bottom.xml new file mode 100644 index 000000000..1afa8febc --- /dev/null +++ b/external/appcompat/res/anim/abc_slide_in_bottom.xml @@ -0,0 +1,19 @@ + + + diff --git a/external/appcompat/res/anim/abc_slide_in_top.xml b/external/appcompat/res/anim/abc_slide_in_top.xml new file mode 100644 index 000000000..ab824f2e4 --- /dev/null +++ b/external/appcompat/res/anim/abc_slide_in_top.xml @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/external/appcompat/res/anim/abc_slide_out_bottom.xml b/external/appcompat/res/anim/abc_slide_out_bottom.xml new file mode 100644 index 000000000..b309fe89c --- /dev/null +++ b/external/appcompat/res/anim/abc_slide_out_bottom.xml @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/external/appcompat/res/anim/abc_slide_out_top.xml b/external/appcompat/res/anim/abc_slide_out_top.xml new file mode 100644 index 000000000..e84d1c7fb --- /dev/null +++ b/external/appcompat/res/anim/abc_slide_out_top.xml @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/external/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml b/external/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml new file mode 100644 index 000000000..e0160766e --- /dev/null +++ b/external/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml b/external/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml new file mode 100644 index 000000000..290faf1a0 --- /dev/null +++ b/external/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml b/external/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml new file mode 100644 index 000000000..724c2557d --- /dev/null +++ b/external/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_primary_text_disable_only_material_light.xml b/external/appcompat/res/color/abc_primary_text_disable_only_material_light.xml new file mode 100644 index 000000000..7395e680c --- /dev/null +++ b/external/appcompat/res/color/abc_primary_text_disable_only_material_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_primary_text_material_dark.xml b/external/appcompat/res/color/abc_primary_text_material_dark.xml new file mode 100644 index 000000000..7d66d02d6 --- /dev/null +++ b/external/appcompat/res/color/abc_primary_text_material_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_primary_text_material_light.xml b/external/appcompat/res/color/abc_primary_text_material_light.xml new file mode 100644 index 000000000..105b643dd --- /dev/null +++ b/external/appcompat/res/color/abc_primary_text_material_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_search_url_text.xml b/external/appcompat/res/color/abc_search_url_text.xml new file mode 100644 index 000000000..0631d5d4c --- /dev/null +++ b/external/appcompat/res/color/abc_search_url_text.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/color/abc_secondary_text_material_dark.xml b/external/appcompat/res/color/abc_secondary_text_material_dark.xml new file mode 100644 index 000000000..6399b1d02 --- /dev/null +++ b/external/appcompat/res/color/abc_secondary_text_material_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/color/abc_secondary_text_material_light.xml b/external/appcompat/res/color/abc_secondary_text_material_light.xml new file mode 100644 index 000000000..87c015a4c --- /dev/null +++ b/external/appcompat/res/color/abc_secondary_text_material_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/drawable-hdpi-v5/ic_tab_contacts_unselected.png b/external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png old mode 100755 new mode 100644 similarity index 55% rename from res/drawable-hdpi-v5/ic_tab_contacts_unselected.png rename to external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png index 8c27fd9d0..6c1415772 Binary files a/res/drawable-hdpi-v5/ic_tab_contacts_unselected.png and b/external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png b/external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png new file mode 100644 index 000000000..f4ff16be7 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png b/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png new file mode 100644 index 000000000..7a9e9bd2b Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png b/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png new file mode 100644 index 000000000..874edbff6 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png b/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png new file mode 100644 index 000000000..0d3e1e7a1 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png b/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png new file mode 100644 index 000000000..a8c390efa Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 000000000..8e7b62f04 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 000000000..adcb9e96c Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png new file mode 100644 index 000000000..e51ef280d Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..6c36eae2f Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png new file mode 100644 index 000000000..82459ea94 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png new file mode 100644 index 000000000..47263ea74 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png new file mode 100644 index 000000000..aa23c591e Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..03b1aac4e Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..4c1754130 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png new file mode 100644 index 000000000..675f3ee92 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png new file mode 100644 index 000000000..a30dc0676 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png new file mode 100644 index 000000000..413b220fd Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 000000000..0eaceddf1 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png new file mode 100644 index 000000000..f7382d373 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png new file mode 100644 index 000000000..eefd59e52 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png new file mode 100644 index 000000000..2fa6d7e76 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png b/external/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png new file mode 100644 index 000000000..555270842 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png b/external/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png new file mode 100644 index 000000000..4ea7afa00 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png b/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png new file mode 100644 index 000000000..596accb8a Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png b/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png new file mode 100644 index 000000000..2054530ed Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png b/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png new file mode 100644 index 000000000..f6fd30dcd Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png b/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png new file mode 100644 index 000000000..ca8e9a277 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/external/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png new file mode 100644 index 000000000..76a5c53d7 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png b/external/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png new file mode 100644 index 000000000..385734ee4 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..de7ac29d6 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png new file mode 100644 index 000000000..0ebe65e79 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png new file mode 100644 index 000000000..21b213579 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..b9a81bec8 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png new file mode 100644 index 000000000..368262986 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..ce577e500 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png new file mode 100644 index 000000000..7c305ab71 Binary files /dev/null and b/external/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..dcdd03b7f Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..5338f02a4 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..fd27a0f1b Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..d6e0b9984 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..482e142d1 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..5aaad7eb5 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..c0246b3c1 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..74160c38c Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..753496a86 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..8a4e22efc Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..694426772 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..2d6333463 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..2b308bf9c Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..9b5be204b Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..07d0a5d30 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..bd1029d80 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..33f658798 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..a5015c682 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..2f12fc0d5 Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..b1641732e Binary files /dev/null and b/external/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png b/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png new file mode 100644 index 000000000..ed4ba34ec Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png b/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png new file mode 100644 index 000000000..8f10bd522 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png b/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png new file mode 100644 index 000000000..70793c474 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png b/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png new file mode 100644 index 000000000..8aa1be2b6 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png b/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png new file mode 100644 index 000000000..54ef48082 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png b/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png new file mode 100644 index 000000000..4f8a162a0 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 000000000..03d3dfb5c Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 000000000..66358308d Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png new file mode 100644 index 000000000..ae8cccdd6 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..667435189 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png new file mode 100644 index 000000000..bbc43b19a Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png new file mode 100644 index 000000000..42ac8ca68 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png new file mode 100644 index 000000000..b5f617658 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..6aa238c56 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..aa4f1c213 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png new file mode 100644 index 000000000..1d8ad18a0 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png new file mode 100644 index 000000000..d40353c51 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png new file mode 100644 index 000000000..488d1ab7d Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 000000000..e0d5ac4e5 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png new file mode 100644 index 000000000..0fb57b2ea Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png new file mode 100644 index 000000000..fca776fb9 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png new file mode 100644 index 000000000..070bdbfdb Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png b/external/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png new file mode 100644 index 000000000..00f05d8c9 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png b/external/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png new file mode 100644 index 000000000..3bf8e0362 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png b/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png new file mode 100644 index 000000000..fd0e8d7d7 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png b/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png new file mode 100644 index 000000000..061904c42 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png b/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png new file mode 100644 index 000000000..92da2f0dd Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png b/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png new file mode 100644 index 000000000..42cb6463e Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/external/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png new file mode 100644 index 000000000..02b25f09f Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png b/external/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png new file mode 100644 index 000000000..e9204993d Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..bbf59287f Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png new file mode 100644 index 000000000..4918d33fd Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png new file mode 100644 index 000000000..b69529cb7 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..f3d06fe0e Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png new file mode 100644 index 000000000..f0e7db873 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..d7faacf3e Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png new file mode 100644 index 000000000..0a3603991 Binary files /dev/null and b/external/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png b/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png new file mode 100644 index 000000000..55099d49d Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png b/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png new file mode 100644 index 000000000..3c4701fc2 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png b/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png new file mode 100644 index 000000000..9244174b9 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png b/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png new file mode 100644 index 000000000..5f40d737d Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png b/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png new file mode 100644 index 000000000..d068dbeb8 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png b/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png new file mode 100644 index 000000000..99244967e Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 000000000..8a648b8ba Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 000000000..435ce2150 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png new file mode 100644 index 000000000..ed8d34114 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..27bdcb79e Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png new file mode 100644 index 000000000..84968eedb Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png new file mode 100644 index 000000000..c10a1b723 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png new file mode 100644 index 000000000..bd80981c3 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..a9e6cc560 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..ce5d4a7ed Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png new file mode 100644 index 000000000..bb9d84d3a Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png new file mode 100644 index 000000000..9f9cb3bfd Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png new file mode 100644 index 000000000..53d08148b Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 000000000..7accf52ac Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png new file mode 100644 index 000000000..05cfab7ee Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png new file mode 100644 index 000000000..b7d8dc70a Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png new file mode 100644 index 000000000..0d2836d86 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png new file mode 100644 index 000000000..b545f8e57 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png new file mode 100644 index 000000000..eda10e612 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png new file mode 100644 index 000000000..29037a0d7 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png new file mode 100644 index 000000000..f4af92657 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png new file mode 100644 index 000000000..88726b691 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png b/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png new file mode 100644 index 000000000..c6a7d4d87 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/external/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png new file mode 100644 index 000000000..4fda86774 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png b/external/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png new file mode 100644 index 000000000..a081ceb95 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..d4bd169b9 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png new file mode 100644 index 000000000..fd47f15e4 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png new file mode 100644 index 000000000..5610d8c8d Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..7174b67fa Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png new file mode 100644 index 000000000..46dad22fb Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..33c103562 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png new file mode 100644 index 000000000..0226f8496 Binary files /dev/null and b/external/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png b/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png new file mode 100644 index 000000000..d8cdf1ac2 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png b/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png new file mode 100644 index 000000000..a49a20781 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png new file mode 100644 index 000000000..0d544d90b Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png new file mode 100644 index 000000000..810a02942 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png new file mode 100644 index 000000000..c9af24b3f Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png new file mode 100644 index 000000000..db1d93af6 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 000000000..b149e4758 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 000000000..00fb83ec9 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png new file mode 100644 index 000000000..1dd64b9ad Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..c2d6a542c Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png new file mode 100644 index 000000000..24a194fb8 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png new file mode 100644 index 000000000..fc1b8b442 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png new file mode 100644 index 000000000..8e1ab5bbf Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..5fc17a4d1 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..11a9f9787 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png new file mode 100644 index 000000000..cada2fb70 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png new file mode 100644 index 000000000..556c30df8 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png new file mode 100644 index 000000000..f0a0b7373 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 000000000..66f7d1627 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png new file mode 100644 index 000000000..6f60bd3c2 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png new file mode 100644 index 000000000..658c5a5a2 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png new file mode 100644 index 000000000..b8ac46d17 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png new file mode 100644 index 000000000..76cad1739 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png new file mode 100644 index 000000000..8f436eaf1 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png new file mode 100644 index 000000000..d4952eaf0 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png new file mode 100644 index 000000000..1352a1702 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png new file mode 100644 index 000000000..175b82ca6 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png b/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png new file mode 100644 index 000000000..aad8a4687 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/external/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png new file mode 100644 index 000000000..f5c18d088 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png b/external/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png new file mode 100644 index 000000000..fb7d715fa Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..2e7bc12c1 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png new file mode 100644 index 000000000..3e3174d08 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png new file mode 100644 index 000000000..248f4f860 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..661d5f0a8 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png new file mode 100644 index 000000000..d7696c314 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png new file mode 100644 index 000000000..b6efff309 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png new file mode 100644 index 000000000..2b253fb26 Binary files /dev/null and b/external/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png new file mode 100644 index 000000000..5dd0e5ba6 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png new file mode 100644 index 000000000..f0ff1a70f Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png new file mode 100644 index 000000000..adef87180 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png new file mode 100644 index 000000000..44028af07 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png new file mode 100644 index 000000000..d3f2a9a4d Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png new file mode 100644 index 000000000..a3caefb7f Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png new file mode 100644 index 000000000..70c204021 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png new file mode 100644 index 000000000..72522081d Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png new file mode 100644 index 000000000..2a6f6ba82 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png new file mode 100644 index 000000000..13cc0fd03 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png new file mode 100644 index 000000000..e232cf7c6 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png new file mode 100644 index 000000000..8e9041f3a Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png new file mode 100644 index 000000000..66fc42f5f Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png new file mode 100644 index 000000000..c873e9b0c Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/external/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png new file mode 100644 index 000000000..fe00ae5fe Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/external/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png new file mode 100644 index 000000000..1086e9d6d Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png new file mode 100644 index 000000000..1e4a74c8a Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/external/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png new file mode 100644 index 000000000..5813179d4 Binary files /dev/null and b/external/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png differ diff --git a/external/appcompat/res/drawable/abc_btn_check_material.xml b/external/appcompat/res/drawable/abc_btn_check_material.xml new file mode 100644 index 000000000..4934a929d --- /dev/null +++ b/external/appcompat/res/drawable/abc_btn_check_material.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/drawable/abc_btn_radio_material.xml b/external/appcompat/res/drawable/abc_btn_radio_material.xml new file mode 100644 index 000000000..6e9f9cf37 --- /dev/null +++ b/external/appcompat/res/drawable/abc_btn_radio_material.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/pressed_background_chatsecure.xml b/external/appcompat/res/drawable/abc_cab_background_internal_bg.xml similarity index 71% rename from res/drawable/pressed_background_chatsecure.xml rename to external/appcompat/res/drawable/abc_cab_background_internal_bg.xml index 84a428484..9faf60ac6 100644 --- a/res/drawable/pressed_background_chatsecure.xml +++ b/external/appcompat/res/drawable/abc_cab_background_internal_bg.xml @@ -1,15 +1,12 @@ - - - - + + + + \ No newline at end of file diff --git a/external/appcompat/res/drawable/abc_cab_background_top_material.xml b/external/appcompat/res/drawable/abc_cab_background_top_material.xml new file mode 100644 index 000000000..68b76343b --- /dev/null +++ b/external/appcompat/res/drawable/abc_cab_background_top_material.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/drawable/abc_edit_text_material.xml b/external/appcompat/res/drawable/abc_edit_text_material.xml new file mode 100644 index 000000000..754ab18d0 --- /dev/null +++ b/external/appcompat/res/drawable/abc_edit_text_material.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/external/appcompat/res/drawable/abc_item_background_holo_dark.xml b/external/appcompat/res/drawable/abc_item_background_holo_dark.xml new file mode 100644 index 000000000..72162c222 --- /dev/null +++ b/external/appcompat/res/drawable/abc_item_background_holo_dark.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/external/appcompat/res/drawable/abc_item_background_holo_light.xml b/external/appcompat/res/drawable/abc_item_background_holo_light.xml new file mode 100644 index 000000000..1c180b2ee --- /dev/null +++ b/external/appcompat/res/drawable/abc_item_background_holo_light.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_dark.xml b/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_dark.xml new file mode 100644 index 000000000..0add58c86 --- /dev/null +++ b/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_dark.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_light.xml b/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_light.xml new file mode 100644 index 000000000..0c1d3e678 --- /dev/null +++ b/external/appcompat/res/drawable/abc_list_selector_background_transition_holo_light.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/external/appcompat/res/drawable/abc_list_selector_holo_dark.xml b/external/appcompat/res/drawable/abc_list_selector_holo_dark.xml new file mode 100644 index 000000000..1fb5fc451 --- /dev/null +++ b/external/appcompat/res/drawable/abc_list_selector_holo_dark.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/external/appcompat/res/drawable/abc_list_selector_holo_light.xml b/external/appcompat/res/drawable/abc_list_selector_holo_light.xml new file mode 100644 index 000000000..8d2404722 --- /dev/null +++ b/external/appcompat/res/drawable/abc_list_selector_holo_light.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/drawable/abc_switch_thumb_material.xml b/external/appcompat/res/drawable/abc_switch_thumb_material.xml new file mode 100644 index 000000000..ee96ec2e7 --- /dev/null +++ b/external/appcompat/res/drawable/abc_switch_thumb_material.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/selectable_background_chatsecure.xml b/external/appcompat/res/drawable/abc_tab_indicator_material.xml similarity index 60% rename from res/drawable/selectable_background_chatsecure.xml rename to external/appcompat/res/drawable/abc_tab_indicator_material.xml index a4997c03c..1a8de1b69 100644 --- a/res/drawable/selectable_background_chatsecure.xml +++ b/external/appcompat/res/drawable/abc_tab_indicator_material.xml @@ -1,15 +1,12 @@ - - - - + + - \ No newline at end of file + diff --git a/external/appcompat/res/drawable/abc_textfield_search_material.xml b/external/appcompat/res/drawable/abc_textfield_search_material.xml new file mode 100644 index 000000000..08873966e --- /dev/null +++ b/external/appcompat/res/drawable/abc_textfield_search_material.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/external/appcompat/res/layout-v11/abc_screen_content_include.xml b/external/appcompat/res/layout-v11/abc_screen_content_include.xml new file mode 100644 index 000000000..757be1c1d --- /dev/null +++ b/external/appcompat/res/layout-v11/abc_screen_content_include.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/external/appcompat/res/layout/abc_action_bar_title_item.xml b/external/appcompat/res/layout/abc_action_bar_title_item.xml new file mode 100644 index 000000000..194afb74c --- /dev/null +++ b/external/appcompat/res/layout/abc_action_bar_title_item.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/external/appcompat/res/layout/abc_action_bar_up_container.xml b/external/appcompat/res/layout/abc_action_bar_up_container.xml new file mode 100644 index 000000000..f46550a55 --- /dev/null +++ b/external/appcompat/res/layout/abc_action_bar_up_container.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/external/appcompat/res/layout/abc_action_bar_view_list_nav_layout.xml b/external/appcompat/res/layout/abc_action_bar_view_list_nav_layout.xml new file mode 100644 index 000000000..5c105ab55 --- /dev/null +++ b/external/appcompat/res/layout/abc_action_bar_view_list_nav_layout.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_action_menu_item_layout.xml b/external/appcompat/res/layout/abc_action_menu_item_layout.xml new file mode 100644 index 000000000..150ea50dc --- /dev/null +++ b/external/appcompat/res/layout/abc_action_menu_item_layout.xml @@ -0,0 +1,30 @@ + + + + diff --git a/external/appcompat/res/layout/abc_action_menu_layout.xml b/external/appcompat/res/layout/abc_action_menu_layout.xml new file mode 100644 index 000000000..4918d2fba --- /dev/null +++ b/external/appcompat/res/layout/abc_action_menu_layout.xml @@ -0,0 +1,24 @@ + + + + diff --git a/external/appcompat/res/layout/abc_action_mode_bar.xml b/external/appcompat/res/layout/abc_action_mode_bar.xml new file mode 100644 index 000000000..6af12ea61 --- /dev/null +++ b/external/appcompat/res/layout/abc_action_mode_bar.xml @@ -0,0 +1,24 @@ + + + diff --git a/external/appcompat/res/layout/abc_action_mode_close_item_material.xml b/external/appcompat/res/layout/abc_action_mode_close_item_material.xml new file mode 100644 index 000000000..dfc4debec --- /dev/null +++ b/external/appcompat/res/layout/abc_action_mode_close_item_material.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_activity_chooser_view.xml b/external/appcompat/res/layout/abc_activity_chooser_view.xml new file mode 100644 index 000000000..99c2395bc --- /dev/null +++ b/external/appcompat/res/layout/abc_activity_chooser_view.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_activity_chooser_view_include.xml b/external/appcompat/res/layout/abc_activity_chooser_view_include.xml new file mode 100644 index 000000000..975b13e5e --- /dev/null +++ b/external/appcompat/res/layout/abc_activity_chooser_view_include.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_activity_chooser_view_list_item.xml b/external/appcompat/res/layout/abc_activity_chooser_view_list_item.xml new file mode 100644 index 000000000..887427d80 --- /dev/null +++ b/external/appcompat/res/layout/abc_activity_chooser_view_list_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_expanded_menu_layout.xml b/external/appcompat/res/layout/abc_expanded_menu_layout.xml new file mode 100644 index 000000000..20e8b19be --- /dev/null +++ b/external/appcompat/res/layout/abc_expanded_menu_layout.xml @@ -0,0 +1,22 @@ + + + + diff --git a/external/appcompat/res/layout/abc_list_menu_item_checkbox.xml b/external/appcompat/res/layout/abc_list_menu_item_checkbox.xml new file mode 100644 index 000000000..d9c3f0681 --- /dev/null +++ b/external/appcompat/res/layout/abc_list_menu_item_checkbox.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/external/appcompat/res/layout/abc_list_menu_item_icon.xml b/external/appcompat/res/layout/abc_list_menu_item_icon.xml new file mode 100644 index 000000000..acd005a13 --- /dev/null +++ b/external/appcompat/res/layout/abc_list_menu_item_icon.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/external/appcompat/res/layout/abc_list_menu_item_layout.xml b/external/appcompat/res/layout/abc_list_menu_item_layout.xml new file mode 100644 index 000000000..1cee43e70 --- /dev/null +++ b/external/appcompat/res/layout/abc_list_menu_item_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_list_menu_item_radio.xml b/external/appcompat/res/layout/abc_list_menu_item_radio.xml new file mode 100644 index 000000000..0ca8d7a2a --- /dev/null +++ b/external/appcompat/res/layout/abc_list_menu_item_radio.xml @@ -0,0 +1,24 @@ + + + + diff --git a/external/appcompat/res/layout/abc_popup_menu_item_layout.xml b/external/appcompat/res/layout/abc_popup_menu_item_layout.xml new file mode 100644 index 000000000..76820e078 --- /dev/null +++ b/external/appcompat/res/layout/abc_popup_menu_item_layout.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_screen_content_include.xml b/external/appcompat/res/layout/abc_screen_content_include.xml new file mode 100644 index 000000000..6e21f2e33 --- /dev/null +++ b/external/appcompat/res/layout/abc_screen_content_include.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/external/appcompat/res/layout/abc_screen_simple.xml b/external/appcompat/res/layout/abc_screen_simple.xml new file mode 100644 index 000000000..426851c20 --- /dev/null +++ b/external/appcompat/res/layout/abc_screen_simple.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml b/external/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml new file mode 100644 index 000000000..ac399c1d7 --- /dev/null +++ b/external/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/abc_screen_toolbar.xml b/external/appcompat/res/layout/abc_screen_toolbar.xml new file mode 100644 index 000000000..20e0c0e2a --- /dev/null +++ b/external/appcompat/res/layout/abc_screen_toolbar.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml b/external/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml new file mode 100644 index 000000000..7407498a5 --- /dev/null +++ b/external/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_search_view.xml b/external/appcompat/res/layout/abc_search_view.xml new file mode 100644 index 000000000..ff9361dca --- /dev/null +++ b/external/appcompat/res/layout/abc_search_view.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/layout/abc_simple_dropdown_hint.xml b/external/appcompat/res/layout/abc_simple_dropdown_hint.xml new file mode 100644 index 000000000..8326b5ccf --- /dev/null +++ b/external/appcompat/res/layout/abc_simple_dropdown_hint.xml @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/external/appcompat/res/layout/support_simple_spinner_dropdown_item.xml b/external/appcompat/res/layout/support_simple_spinner_dropdown_item.xml new file mode 100644 index 000000000..d2f177ac8 --- /dev/null +++ b/external/appcompat/res/layout/support_simple_spinner_dropdown_item.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/external/appcompat/res/values-af/strings.xml b/external/appcompat/res/values-af/strings.xml new file mode 100644 index 000000000..474f3aa5a --- /dev/null +++ b/external/appcompat/res/values-af/strings.xml @@ -0,0 +1,34 @@ + + + + + "Klaar" + "Navigeer tuis" + "Navigeer op" + "Nog opsies" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Soek" + "Soeknavraag" + "Vee navraag uit" + "Dien navraag in" + "Stemsoektog" + "Kies \'n program" + "Sien alles" + "Deel met %s" + "Deel met" + diff --git a/external/appcompat/res/values-am/strings.xml b/external/appcompat/res/values-am/strings.xml new file mode 100644 index 000000000..dbd53d6ec --- /dev/null +++ b/external/appcompat/res/values-am/strings.xml @@ -0,0 +1,34 @@ + + + + + "ተከናውኗል" + "ወደ መነሻ ይዳስሱ" + "ወደ ላይ ይዳስሱ" + "ተጨማሪ አማራጮች" + "%1$s፣ %2$s" + "%1$s፣ %2$s፣ %3$s" + "ፍለጋ" + "የፍለጋ ጥያቄ" + "መጠይቅ አጽዳ" + "መጠይቅ ያስረክቡ" + "የድምፅ ፍለጋ" + "መተግበሪያ ይምረጡ" + "ሁሉንም ይመልከቱ" + "ከ%s ጋር ያጋሩ" + "ከሚከተለው ጋር ያጋሩ" + diff --git a/external/appcompat/res/values-ar/strings.xml b/external/appcompat/res/values-ar/strings.xml new file mode 100644 index 000000000..84d6fbaa7 --- /dev/null +++ b/external/appcompat/res/values-ar/strings.xml @@ -0,0 +1,34 @@ + + + + + "تم" + "التنقل إلى الشاشة الرئيسية" + "التنقل إلى أعلى" + "خيارات إضافية" + "%1$s، %2$s" + "%1$s، %2$s، %3$s" + "بحث" + "طلب البحث" + "محو طلب البحث" + "إرسال طلب البحث" + "البحث الصوتي" + "اختيار تطبيق" + "عرض الكل" + "‏مشاركة مع %s" + "مشاركة مع" + diff --git a/external/appcompat/res/values-bg/strings.xml b/external/appcompat/res/values-bg/strings.xml new file mode 100644 index 000000000..9d87ef7b2 --- /dev/null +++ b/external/appcompat/res/values-bg/strings.xml @@ -0,0 +1,34 @@ + + + + + "Готово" + "Придвижване към „Начало“" + "Придвижване нагоре" + "Още опции" + "„%1$s“ – %2$s" + "„%1$s“, „%2$s“ – %3$s" + "Търсене" + "Заявка за търсене" + "Изчистване на заявката" + "Изпращане на заявката" + "Гласово търсене" + "Изберете приложение" + "Вижте всички" + "Споделяне със: %s" + "Споделяне със:" + diff --git a/external/appcompat/res/values-bn-rBD/strings.xml b/external/appcompat/res/values-bn-rBD/strings.xml new file mode 100644 index 000000000..ee522c679 --- /dev/null +++ b/external/appcompat/res/values-bn-rBD/strings.xml @@ -0,0 +1,34 @@ + + + + + "সম্পন্ন হয়েছে" + "হোম এ নেভিগেট করুন" + "উপরের দিকে নেভিগেট করুন" + "আরো বিকল্প" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "অনুসন্ধান করুন" + "ক্যোয়ারী অনুসন্ধান করুন" + "ক্যোয়ারী সাফ করুন" + "ক্যোয়ারী জমা দিন" + "ভয়েস অনুসন্ধান" + "একটি অ্যাপ্লিকেশান চয়ন করুন" + "সবগুলো দেখুন" + "%s এর সাথে ভাগ করুন" + "এর সাথে ভাগ করুন" + diff --git a/external/appcompat/res/values-ca/strings.xml b/external/appcompat/res/values-ca/strings.xml new file mode 100644 index 000000000..5fe4b0dd3 --- /dev/null +++ b/external/appcompat/res/values-ca/strings.xml @@ -0,0 +1,34 @@ + + + + + "Fet" + "Navega a la pàgina d\'inici" + "Navega cap a dalt" + "Més opcions" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Cerca" + "Consulta de cerca" + "Esborra la consulta" + "Envia la consulta" + "Cerca per veu" + "Selecciona una aplicació" + "Mostra\'ls tots" + "Comparteix amb %s" + "Comparteix amb" + diff --git a/external/appcompat/res/values-cs/strings.xml b/external/appcompat/res/values-cs/strings.xml new file mode 100644 index 000000000..13c9ba8eb --- /dev/null +++ b/external/appcompat/res/values-cs/strings.xml @@ -0,0 +1,34 @@ + + + + + "Hotovo" + "Přejít na plochu" + "Přejít nahoru" + "Více možností" + "%1$s – %2$s" + "%1$s, %2$s – %3$s" + "Hledat" + "Vyhledávací dotaz" + "Smazat dotaz" + "Odeslat dotaz" + "Hlasové vyhledávání" + "Vybrat aplikaci" + "Zobrazit vše" + "Sdílet pomocí %s" + "Sdílet pomocí" + diff --git a/external/appcompat/res/values-da/strings.xml b/external/appcompat/res/values-da/strings.xml new file mode 100644 index 000000000..03fec328c --- /dev/null +++ b/external/appcompat/res/values-da/strings.xml @@ -0,0 +1,34 @@ + + + + + "Luk" + "Naviger hjem" + "Naviger op" + "Flere muligheder" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Søg" + "Søgeforespørgsel" + "Ryd forespørgslen" + "Indsend forespørgslen" + "Talesøgning" + "Vælg en app" + "Se alle" + "Del med %s" + "Del med" + diff --git a/external/appcompat/res/values-de/strings.xml b/external/appcompat/res/values-de/strings.xml new file mode 100644 index 000000000..8a0224c47 --- /dev/null +++ b/external/appcompat/res/values-de/strings.xml @@ -0,0 +1,34 @@ + + + + + "Fertig" + "Zur Startseite" + "Nach oben" + "Weitere Optionen" + "%1$s: %2$s" + "%1$s, %2$s: %3$s" + "Suchen" + "Suchanfrage" + "Suchanfrage löschen" + "Suchanfrage senden" + "Sprachsuche" + "App auswählen" + "Alle ansehen" + "Freigeben für %s" + "Freigeben für" + diff --git a/external/appcompat/res/values-el/strings.xml b/external/appcompat/res/values-el/strings.xml new file mode 100644 index 000000000..52d1b81e7 --- /dev/null +++ b/external/appcompat/res/values-el/strings.xml @@ -0,0 +1,34 @@ + + + + + "Τέλος" + "Πλοήγηση στην αρχική σελίδα" + "Πλοήγηση προς τα επάνω" + "Περισσότερες επιλογές" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Αναζήτηση" + "Ερώτημα αναζήτησης" + "Διαγραφή ερωτήματος" + "Υποβολή ερωτήματος" + "Φωνητική αναζήτηση" + "Επιλέξτε κάποια εφαρμογή" + "Προβολή όλων" + "Κοινή χρήση με %s" + "Κοινή χρήση με" + diff --git a/external/appcompat/res/values-en-rGB/strings.xml b/external/appcompat/res/values-en-rGB/strings.xml new file mode 100644 index 000000000..8a8a1119e --- /dev/null +++ b/external/appcompat/res/values-en-rGB/strings.xml @@ -0,0 +1,34 @@ + + + + + "Done" + "Navigate home" + "Navigate up" + "More options" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Search" + "Search query" + "Clear query" + "Submit query" + "Voice search" + "Choose an app" + "See all" + "Share with %s" + "Share with" + diff --git a/external/appcompat/res/values-en-rIN/strings.xml b/external/appcompat/res/values-en-rIN/strings.xml new file mode 100644 index 000000000..8a8a1119e --- /dev/null +++ b/external/appcompat/res/values-en-rIN/strings.xml @@ -0,0 +1,34 @@ + + + + + "Done" + "Navigate home" + "Navigate up" + "More options" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Search" + "Search query" + "Clear query" + "Submit query" + "Voice search" + "Choose an app" + "See all" + "Share with %s" + "Share with" + diff --git a/external/appcompat/res/values-es-rUS/strings.xml b/external/appcompat/res/values-es-rUS/strings.xml new file mode 100644 index 000000000..ea5004cbb --- /dev/null +++ b/external/appcompat/res/values-es-rUS/strings.xml @@ -0,0 +1,34 @@ + + + + + "Listo" + "Navegar a la página principal" + "Navegar hacia arriba" + "Más opciones" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Búsqueda" + "Consulta de búsqueda" + "Eliminar la consulta" + "Enviar consulta" + "Búsqueda por voz" + "Elige una aplicación." + "Ver todo" + "Compartir con %s" + "Compartir con" + diff --git a/external/appcompat/res/values-es/strings.xml b/external/appcompat/res/values-es/strings.xml new file mode 100644 index 000000000..c50796ee8 --- /dev/null +++ b/external/appcompat/res/values-es/strings.xml @@ -0,0 +1,34 @@ + + + + + "Listo" + "Ir a la pantalla de inicio" + "Desplazarse hacia arriba" + "Más opciones" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Buscar" + "Consulta" + "Borrar consulta" + "Enviar consulta" + "Búsqueda por voz" + "Seleccionar una aplicación" + "Ver todo" + "Compartir con %s" + "Compartir con" + diff --git a/external/appcompat/res/values-et-rEE/strings.xml b/external/appcompat/res/values-et-rEE/strings.xml new file mode 100644 index 000000000..139fcf915 --- /dev/null +++ b/external/appcompat/res/values-et-rEE/strings.xml @@ -0,0 +1,34 @@ + + + + + "Valmis" + "Navigeerimine avaekraanile" + "Navigeerimine üles" + "Rohkem valikuid" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Otsing" + "Otsingupäring" + "Päringu tühistamine" + "Päringu esitamine" + "Häälotsing" + "Valige rakendus" + "Kuva kõik" + "Jagamine kasutajaga %s" + "Jagamine:" + diff --git a/external/appcompat/res/values-eu-rES/strings.xml b/external/appcompat/res/values-eu-rES/strings.xml new file mode 100644 index 000000000..541c2ed3c --- /dev/null +++ b/external/appcompat/res/values-eu-rES/strings.xml @@ -0,0 +1,34 @@ + + + + + "Eginda" + "Joan orri nagusira" + "Joan gora" + "Aukera gehiago" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Bilatu" + "Bilaketa-kontsulta" + "Garbitu kontsulta" + "Bidali kontsulta" + "Ahots bidezko bilaketa" + "Aukeratu aplikazio bat" + "Ikusi guztiak" + "Partekatu %s erabiltzailearekin" + "Partekatu hauekin" + diff --git a/external/appcompat/res/values-fa/strings.xml b/external/appcompat/res/values-fa/strings.xml new file mode 100644 index 000000000..c317bdaf3 --- /dev/null +++ b/external/appcompat/res/values-fa/strings.xml @@ -0,0 +1,34 @@ + + + + + "انجام شد" + "پیمایش به صفحه اصلی" + "پیمایش به بالا" + "گزینه‌های بیشتر" + "‏%1$s‏، %2$s" + "‏%1$s‏، %2$s‏، %3$s" + "جستجو" + "عبارت جستجو" + "پاک کردن عبارت جستجو" + "ارسال عبارت جستجو" + "جستجوی شفاهی" + "انتخاب برنامه" + "مشاهده همه" + "‏اشتراک‌گذاری با %s" + "اشتراک‌گذاری با" + diff --git a/external/appcompat/res/values-fi/strings.xml b/external/appcompat/res/values-fi/strings.xml new file mode 100644 index 000000000..218229b0f --- /dev/null +++ b/external/appcompat/res/values-fi/strings.xml @@ -0,0 +1,34 @@ + + + + + "Valmis" + "Siirry etusivulle" + "Siirry ylös" + "Lisää" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Haku" + "Hakulauseke" + "Tyhjennä kysely" + "Lähetä kysely" + "Puhehaku" + "Valitse sovellus" + "Näytä kaikki" + "Jakaminen: %s" + "Jakaminen:" + diff --git a/external/appcompat/res/values-fr-rCA/strings.xml b/external/appcompat/res/values-fr-rCA/strings.xml new file mode 100644 index 000000000..571ff9a33 --- /dev/null +++ b/external/appcompat/res/values-fr-rCA/strings.xml @@ -0,0 +1,34 @@ + + + + + "Terminé" + "Revenir à l\'accueil" + "Revenir en haut de la page" + "Plus d\'options" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Rechercher" + "Requête de recherche" + "Effacer la requête" + "Envoyer la requête" + "Recherche vocale" + "Sélectionnez une application" + "Voir toutes les chaînes" + "Partager avec %s" + "Partager avec" + diff --git a/external/appcompat/res/values-fr/strings.xml b/external/appcompat/res/values-fr/strings.xml new file mode 100644 index 000000000..353665a88 --- /dev/null +++ b/external/appcompat/res/values-fr/strings.xml @@ -0,0 +1,34 @@ + + + + + "OK" + "Revenir à l\'accueil" + "Revenir en haut de la page" + "Plus d\'options" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Rechercher" + "Requête de recherche" + "Effacer la requête" + "Envoyer la requête" + "Recherche vocale" + "Sélectionner une application" + "Tout afficher" + "Partager avec %s" + "Partager avec" + diff --git a/external/appcompat/res/values-gl-rES/strings.xml b/external/appcompat/res/values-gl-rES/strings.xml new file mode 100644 index 000000000..3f665ed6c --- /dev/null +++ b/external/appcompat/res/values-gl-rES/strings.xml @@ -0,0 +1,34 @@ + + + + + "Feito" + "Ir á páxina de inicio" + "Desprazarse cara arriba" + "Máis opcións" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Buscar" + "Consulta de busca" + "Borrar consulta" + "Enviar consulta" + "Busca de voz" + "Escoller unha aplicación" + "Ver todas" + "Compartir con %s" + "Compartir con" + diff --git a/external/appcompat/res/values-hi/strings.xml b/external/appcompat/res/values-hi/strings.xml new file mode 100644 index 000000000..23cfacaa3 --- /dev/null +++ b/external/appcompat/res/values-hi/strings.xml @@ -0,0 +1,34 @@ + + + + + "पूर्ण" + "मुखपृष्ठ पर नेविगेट करें" + "ऊपर नेविगेट करें" + "अधिक विकल्प" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "खोजें" + "खोज क्वेरी" + "क्‍वेरी साफ़ करें" + "क्वेरी सबमिट करें" + "ध्वनि खोज" + "कोई एप्‍लिकेशन चुनें" + "सभी देखें" + "%s के साथ साझा करें" + "इसके द्वारा साझा करें" + diff --git a/external/appcompat/res/values-hr/strings.xml b/external/appcompat/res/values-hr/strings.xml new file mode 100644 index 000000000..034859635 --- /dev/null +++ b/external/appcompat/res/values-hr/strings.xml @@ -0,0 +1,34 @@ + + + + + "Gotovo" + "Idi na početnu" + "Idi gore" + "Dodatne opcije" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Pretraživanje" + "Upit za pretraživanje" + "Izbriši upit" + "Pošalji upit" + "Glasovno pretraživanje" + "Odabir aplikacije" + "Prikaži sve" + "Dijeljenje sa: %s" + "Dijeljenje sa" + diff --git a/external/appcompat/res/values-hu/strings.xml b/external/appcompat/res/values-hu/strings.xml new file mode 100644 index 000000000..fc67f00ef --- /dev/null +++ b/external/appcompat/res/values-hu/strings.xml @@ -0,0 +1,34 @@ + + + + + "Kész" + "Ugrás a főoldalra" + "Felfelé mozgatás" + "További lehetőségek" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Keresés" + "Keresési lekérdezés" + "Lekérdezés törlése" + "Lekérdezés küldése" + "Hangalapú keresés" + "Válasszon ki egy alkalmazást" + "Összes megtekintése" + "Megosztás a következővel: %s" + "Megosztás a következővel:" + diff --git a/external/appcompat/res/values-hy-rAM/strings.xml b/external/appcompat/res/values-hy-rAM/strings.xml new file mode 100644 index 000000000..da67fe4c5 --- /dev/null +++ b/external/appcompat/res/values-hy-rAM/strings.xml @@ -0,0 +1,34 @@ + + + + + "Կատարված է" + "Ուղղվել տուն" + "Ուղղվել վերև" + "Այլ ընտրանքներ" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Որոնել" + "Որոնման հարցում" + "Մաքրել հարցումը" + "Ուղարկել հարցումը" + "Ձայնային որոնում" + "Ընտրել ծրագիր" + "Տեսնել բոլորը" + "Տարածել ըստ %s" + "Տարածել" + diff --git a/external/appcompat/res/values-in/strings.xml b/external/appcompat/res/values-in/strings.xml new file mode 100644 index 000000000..3c31755ad --- /dev/null +++ b/external/appcompat/res/values-in/strings.xml @@ -0,0 +1,34 @@ + + + + + "Selesai" + "Navigasi ke beranda" + "Navigasi naik" + "Opsi lain" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Telusuri" + "Kueri penelusuran" + "Hapus kueri" + "Kirim kueri" + "Penelusuran suara" + "Pilih aplikasi" + "Lihat semua" + "Bagikan dengan %s" + "Bagikan dengan" + diff --git a/external/appcompat/res/values-is-rIS/strings.xml b/external/appcompat/res/values-is-rIS/strings.xml new file mode 100644 index 000000000..7846b514f --- /dev/null +++ b/external/appcompat/res/values-is-rIS/strings.xml @@ -0,0 +1,34 @@ + + + + + "Lokið" + "Fara heim" + "Fara upp" + "Fleiri valkostir" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Leita" + "Leitarfyrirspurn" + "Hreinsa fyrirspurn" + "Senda fyrirspurn" + "Raddleit" + "Veldu forrit" + "Sjá allt" + "Deila með %s" + "Deila með" + diff --git a/external/appcompat/res/values-it/strings.xml b/external/appcompat/res/values-it/strings.xml new file mode 100644 index 000000000..6ed52be06 --- /dev/null +++ b/external/appcompat/res/values-it/strings.xml @@ -0,0 +1,34 @@ + + + + + "Fine" + "Vai alla home page" + "Vai in alto" + "Altre opzioni" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Cerca" + "Query di ricerca" + "Cancella query" + "Invia query" + "Ricerca vocale" + "Scegli un\'applicazione" + "Visualizza tutte" + "Condividi con %s" + "Condividi con" + diff --git a/external/appcompat/res/values-iw/strings.xml b/external/appcompat/res/values-iw/strings.xml new file mode 100644 index 000000000..fec0e6228 --- /dev/null +++ b/external/appcompat/res/values-iw/strings.xml @@ -0,0 +1,34 @@ + + + + + "בוצע" + "נווט לדף הבית" + "נווט למעלה" + "עוד אפשרויות" + "‏%1$s‏, %2$s" + "‏%1$s‏, %2$s‏, %3$s" + "חפש" + "שאילתת חיפוש" + "מחק שאילתה" + "שלח שאילתה" + "חיפוש קולי" + "בחר אפליקציה" + "ראה הכל" + "‏שתף עם %s" + "שתף עם" + diff --git a/external/appcompat/res/values-ja/strings.xml b/external/appcompat/res/values-ja/strings.xml new file mode 100644 index 000000000..181dd5e5f --- /dev/null +++ b/external/appcompat/res/values-ja/strings.xml @@ -0,0 +1,34 @@ + + + + + "完了" + "ホームへ移動" + "上へ移動" + "その他のオプション" + "%1$s、%2$s" + "%1$s、%2$s、%3$s" + "検索" + "検索キーワード" + "検索キーワードを削除" + "検索キーワードを送信" + "音声検索" + "アプリの選択" + "すべて表示" + "%sと共有" + "共有" + diff --git a/external/appcompat/res/values-ka-rGE/strings.xml b/external/appcompat/res/values-ka-rGE/strings.xml new file mode 100644 index 000000000..a96f26c3f --- /dev/null +++ b/external/appcompat/res/values-ka-rGE/strings.xml @@ -0,0 +1,34 @@ + + + + + "დასრულდა" + "მთავარზე ნავიგაცია" + "ზემოთ ნავიგაცია" + "მეტი ვარიანტები" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "ძიება" + "ძიების მოთხოვნა" + "მოთხოვნის გასუფთავება" + "მოთხოვნის გადაგზავნა" + "ხმოვანი ძიება" + "აპის არჩევა" + "ყველას ნახვა" + "%s-თან გაზიარება" + "გაზიარება:" + diff --git a/external/appcompat/res/values-kk-rKZ/strings.xml b/external/appcompat/res/values-kk-rKZ/strings.xml new file mode 100644 index 000000000..fb20a0049 --- /dev/null +++ b/external/appcompat/res/values-kk-rKZ/strings.xml @@ -0,0 +1,34 @@ + + + + + "Орындалды" + "Негізгі бетте қозғалу" + "Жоғары қозғалу" + "Басқа опциялар" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Іздеу" + "Сұрақты іздеу" + "Сұрақты жою" + "Сұрақты жіберу" + "Дауыс арқылы іздеу" + "Қолданбаны таңдау" + "Барлығын көру" + "%s бөлісу" + "Бөлісу" + diff --git a/external/appcompat/res/values-km-rKH/strings.xml b/external/appcompat/res/values-km-rKH/strings.xml new file mode 100644 index 000000000..704f4ee48 --- /dev/null +++ b/external/appcompat/res/values-km-rKH/strings.xml @@ -0,0 +1,34 @@ + + + + + "រួចរាល់" + "រកមើល​ទៅ​ដើម" + "រកមើល​ឡើងលើ" + "ជម្រើស​ច្រើន​ទៀត" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "ស្វែងរក" + "ស្វែងរក​សំណួរ" + "សម្អាត​សំណួរ" + "ដាក់​​​ស្នើ​សំណួរ" + "ការស្វែងរក​សំឡេង" + "ជ្រើស​កម្មវិធី​​" + "មើល​ទាំងអស់" + "ចែករំលែក​ជាមួយ %s" + "ចែករំលែក​ជាមួយ" + diff --git a/external/appcompat/res/values-kn-rIN/strings.xml b/external/appcompat/res/values-kn-rIN/strings.xml new file mode 100644 index 000000000..d472ff32e --- /dev/null +++ b/external/appcompat/res/values-kn-rIN/strings.xml @@ -0,0 +1,34 @@ + + + + + "ಮುಗಿದಿದೆ" + "ಮುಖಪುಟವನ್ನು ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ" + "ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ" + "ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "ಹುಡುಕು" + "ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ" + "ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು" + "ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು" + "ಧ್ವನಿ ಹುಡುಕಾಟ" + "ಒಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಯ್ಕೆಮಾಡಿ" + "ಎಲ್ಲವನ್ನೂ ನೋಡಿ" + "%s ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಿ" + "ಇವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಿ" + diff --git a/external/appcompat/res/values-ko/strings.xml b/external/appcompat/res/values-ko/strings.xml new file mode 100644 index 000000000..0a92a83dc --- /dev/null +++ b/external/appcompat/res/values-ko/strings.xml @@ -0,0 +1,34 @@ + + + + + "완료" + "홈 탐색" + "위로 탐색" + "옵션 더보기" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "검색" + "검색어" + "검색어 삭제" + "검색어 보내기" + "음성 검색" + "앱 선택" + "전체 보기" + "%s와(과) 공유" + "공유 대상" + diff --git a/external/appcompat/res/values-ky-rKG/strings.xml b/external/appcompat/res/values-ky-rKG/strings.xml new file mode 100644 index 000000000..b091f608c --- /dev/null +++ b/external/appcompat/res/values-ky-rKG/strings.xml @@ -0,0 +1,34 @@ + + + + + "Даяр" + "Үйгө багыттоо" + "Жогору" + "Көбүрөөк мүмкүнчүлүктөр" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Издөө" + "Издөө талаптары" + "Талаптарды тазалоо" + "Талап жөнөтүү" + "Үн аркылуу издөө" + "Колдонмо тандоо" + "Бардыгын көрүү" + "%s аркылуу бөлүшүү" + "Бөлүшүү" + diff --git a/res/values/colors_chatsecure.xml b/external/appcompat/res/values-land/bools.xml similarity index 73% rename from res/values/colors_chatsecure.xml rename to external/appcompat/res/values-land/bools.xml index d85cda61f..7d1a1af3f 100644 --- a/res/values/colors_chatsecure.xml +++ b/external/appcompat/res/values-land/bools.xml @@ -1,15 +1,12 @@ - - #CCCADAE9 + true diff --git a/external/appcompat/res/values-land/config.xml b/external/appcompat/res/values-land/config.xml new file mode 100644 index 000000000..d0d990dc0 --- /dev/null +++ b/external/appcompat/res/values-land/config.xml @@ -0,0 +1,18 @@ + + + + true + diff --git a/external/appcompat/res/values-land/dimens.xml b/external/appcompat/res/values-land/dimens.xml new file mode 100644 index 000000000..f0b6892a9 --- /dev/null +++ b/external/appcompat/res/values-land/dimens.xml @@ -0,0 +1,21 @@ + + + + + + 32dp + + \ No newline at end of file diff --git a/external/appcompat/res/values-land/dimens_material.xml b/external/appcompat/res/values-land/dimens_material.xml new file mode 100644 index 000000000..08c4b0d72 --- /dev/null +++ b/external/appcompat/res/values-land/dimens_material.xml @@ -0,0 +1,27 @@ + + + + + + 48dp + + 0dp + + 14dp + + 12dp + + \ No newline at end of file diff --git a/external/appcompat/res/values-large/bools.xml b/external/appcompat/res/values-large/bools.xml new file mode 100644 index 000000000..7d1a1af3f --- /dev/null +++ b/external/appcompat/res/values-large/bools.xml @@ -0,0 +1,19 @@ + + + + + true + diff --git a/external/appcompat/res/values-large/config.xml b/external/appcompat/res/values-large/config.xml new file mode 100644 index 000000000..c4f04a300 --- /dev/null +++ b/external/appcompat/res/values-large/config.xml @@ -0,0 +1,30 @@ + + + + + + + true + + + 440dp + \ No newline at end of file diff --git a/external/appcompat/res/values-large/dimens.xml b/external/appcompat/res/values-large/dimens.xml new file mode 100644 index 000000000..de1cefccc --- /dev/null +++ b/external/appcompat/res/values-large/dimens.xml @@ -0,0 +1,38 @@ + + + + + + 192dip + + 4 + + + 60% + + 90% + + 60% + + 90% + + diff --git a/external/appcompat/res/values-large/themes_base.xml b/external/appcompat/res/values-large/themes_base.xml new file mode 100644 index 000000000..aafef5fbd --- /dev/null +++ b/external/appcompat/res/values-large/themes_base.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/values-v11/styles_base_text.xml b/external/appcompat/res/values-v11/styles_base_text.xml new file mode 100644 index 000000000..4cf49666c --- /dev/null +++ b/external/appcompat/res/values-v11/styles_base_text.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/values-v11/themes_base.xml b/external/appcompat/res/values-v11/themes_base.xml new file mode 100644 index 000000000..ca583fab1 --- /dev/null +++ b/external/appcompat/res/values-v11/themes_base.xml @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/values-v14/themes_base.xml b/external/appcompat/res/values-v14/themes_base.xml new file mode 100644 index 000000000..3f26ca223 --- /dev/null +++ b/external/appcompat/res/values-v14/themes_base.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + diff --git a/external/appcompat/res/values-v17/styles_rtl.xml b/external/appcompat/res/values-v17/styles_rtl.xml new file mode 100644 index 000000000..0c7d86114 --- /dev/null +++ b/external/appcompat/res/values-v17/styles_rtl.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/values-v21/styles_base.xml b/external/appcompat/res/values-v21/styles_base.xml new file mode 100644 index 000000000..648dfd2d8 --- /dev/null +++ b/external/appcompat/res/values-v21/styles_base.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/values-v21/styles_base_text.xml b/external/appcompat/res/values-v21/styles_base_text.xml new file mode 100644 index 000000000..241a91b63 --- /dev/null +++ b/external/appcompat/res/values-v21/styles_base_text.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/values-vi/strings.xml b/external/appcompat/res/values-vi/strings.xml new file mode 100644 index 000000000..21dd88343 --- /dev/null +++ b/external/appcompat/res/values-vi/strings.xml @@ -0,0 +1,34 @@ + + + + + "Xong" + "Điều hướng về trang chủ" + "Điều hướng lên trên" + "Thêm tùy chọn" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Tìm kiếm" + "Tìm kiếm truy vấn" + "Xóa truy vấn" + "Gửi truy vấn" + "Tìm kiếm bằng giọng nói" + "Chọn một ứng dụng" + "Xem tất cả" + "Chia sẻ với %s" + "Chia sẻ với" + diff --git a/external/appcompat/res/values-w360dp/dimens.xml b/external/appcompat/res/values-w360dp/dimens.xml new file mode 100644 index 000000000..e5b2456c8 --- /dev/null +++ b/external/appcompat/res/values-w360dp/dimens.xml @@ -0,0 +1,22 @@ + + + + + + 3 + \ No newline at end of file diff --git a/external/appcompat/res/values-w480dp/bools.xml b/external/appcompat/res/values-w480dp/bools.xml new file mode 100644 index 000000000..470f89be3 --- /dev/null +++ b/external/appcompat/res/values-w480dp/bools.xml @@ -0,0 +1,18 @@ + + + + true + diff --git a/external/appcompat/res/values-w480dp/config.xml b/external/appcompat/res/values-w480dp/config.xml new file mode 100644 index 000000000..e95b6ff8c --- /dev/null +++ b/external/appcompat/res/values-w480dp/config.xml @@ -0,0 +1,18 @@ + + + + true + diff --git a/external/appcompat/res/values-w500dp/dimens.xml b/external/appcompat/res/values-w500dp/dimens.xml new file mode 100644 index 000000000..dd6458be7 --- /dev/null +++ b/external/appcompat/res/values-w500dp/dimens.xml @@ -0,0 +1,22 @@ + + + + + + 4 + \ No newline at end of file diff --git a/external/appcompat/res/values-w600dp/dimens.xml b/external/appcompat/res/values-w600dp/dimens.xml new file mode 100644 index 000000000..252ba6a7b --- /dev/null +++ b/external/appcompat/res/values-w600dp/dimens.xml @@ -0,0 +1,25 @@ + + + + + + 5 + + + 192dip + \ No newline at end of file diff --git a/external/appcompat/res/values-w720dp/bools.xml b/external/appcompat/res/values-w720dp/bools.xml new file mode 100644 index 000000000..05c5aabff --- /dev/null +++ b/external/appcompat/res/values-w720dp/bools.xml @@ -0,0 +1,19 @@ + + + + + false + \ No newline at end of file diff --git a/external/appcompat/res/values-xlarge-land/dimens.xml b/external/appcompat/res/values-xlarge-land/dimens.xml new file mode 100644 index 000000000..dea6c74da --- /dev/null +++ b/external/appcompat/res/values-xlarge-land/dimens.xml @@ -0,0 +1,22 @@ + + + + + + + 256dip + + diff --git a/external/appcompat/res/values-xlarge/bools.xml b/external/appcompat/res/values-xlarge/bools.xml new file mode 100644 index 000000000..05c5aabff --- /dev/null +++ b/external/appcompat/res/values-xlarge/bools.xml @@ -0,0 +1,19 @@ + + + + + false + \ No newline at end of file diff --git a/external/appcompat/res/values-xlarge/dimens.xml b/external/appcompat/res/values-xlarge/dimens.xml new file mode 100644 index 000000000..3eb29620b --- /dev/null +++ b/external/appcompat/res/values-xlarge/dimens.xml @@ -0,0 +1,40 @@ + + + + + + + 5 + + + 192dip + + + 50% + + 70% + + 60% + + 90% + + diff --git a/external/appcompat/res/values-zh-rCN/strings.xml b/external/appcompat/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..54e2c86f4 --- /dev/null +++ b/external/appcompat/res/values-zh-rCN/strings.xml @@ -0,0 +1,34 @@ + + + + + "完成" + "转到主屏幕" + "转到上一层级" + "更多选项" + "%1$s:%2$s" + "%1$s - %2$s:%3$s" + "搜索" + "搜索查询" + "清除查询" + "提交查询" + "语音搜索" + "选择应用" + "查看全部" + "通过%s分享" + "分享方式" + diff --git a/external/appcompat/res/values-zh-rHK/strings.xml b/external/appcompat/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..e35d46512 --- /dev/null +++ b/external/appcompat/res/values-zh-rHK/strings.xml @@ -0,0 +1,34 @@ + + + + + "完成" + "瀏覽主頁" + "向上瀏覽" + "更多選項" + "%1$s:%2$s" + "%1$s (%2$s):%3$s" + "搜尋" + "搜尋查詢" + "清除查詢" + "提交查詢" + "語音搜尋" + "選擇應用程式" + "顯示全部" + "與「%s」分享" + "分享對象" + diff --git a/external/appcompat/res/values-zh-rTW/strings.xml b/external/appcompat/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..24d530cb1 --- /dev/null +++ b/external/appcompat/res/values-zh-rTW/strings.xml @@ -0,0 +1,34 @@ + + + + + "完成" + "瀏覽首頁" + "向上瀏覽" + "更多選項" + "%1$s:%2$s" + "%1$s - %2$s:%3$s" + "搜尋" + "搜尋查詢" + "清除查詢" + "提交查詢" + "語音搜尋" + "選擇應用程式" + "查看全部" + "與「%s」分享" + "選擇分享對象" + diff --git a/external/appcompat/res/values-zu/strings.xml b/external/appcompat/res/values-zu/strings.xml new file mode 100644 index 000000000..a6a06ab2f --- /dev/null +++ b/external/appcompat/res/values-zu/strings.xml @@ -0,0 +1,34 @@ + + + + + "Kwenziwe" + "Zulazulela ekhaya" + "Zulazulela phezulu" + "Izinketho eziningi" + "%1$s, %2$s" + "%1$s, %2$s, %3$s" + "Sesha" + "Umbuzo wosesho" + "Sula inkinga" + "Hambisa umbuzo" + "Ukusesha ngezwi" + "Khetha uhlelo lokusebenza" + "Buka konke" + "Yabelana no-%s" + "Yabelana no-" + diff --git a/external/appcompat/res/values/attrs.xml b/external/appcompat/res/values/attrs.xml new file mode 100644 index 000000000..e2dbdea8a --- /dev/null +++ b/external/appcompat/res/values/attrs.xml @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/res/values/bools.xml b/external/appcompat/res/values/bools.xml new file mode 100644 index 000000000..79a5035ad --- /dev/null +++ b/external/appcompat/res/values/bools.xml @@ -0,0 +1,24 @@ + + + + + + true + false + true + + false + diff --git a/external/appcompat/res/values/colors.xml b/external/appcompat/res/values/colors.xml new file mode 100644 index 000000000..32142019f --- /dev/null +++ b/external/appcompat/res/values/colors.xml @@ -0,0 +1,22 @@ + + + + #7fa87f + @android:color/black + @android:color/black + + @android:color/black + \ No newline at end of file diff --git a/external/appcompat/res/values/colors_material.xml b/external/appcompat/res/values/colors_material.xml new file mode 100644 index 000000000..94448b580 --- /dev/null +++ b/external/appcompat/res/values/colors_material.xml @@ -0,0 +1,97 @@ + + + + + + #ff303030 + #ffeeeeee + #ff424242 + #ffeeeeee + + #ff212121 + #ffbdbdbd + #ff000000 + #ff757575 + + #40ffffff + #40000000 + + @color/material_deep_teal_500 + @color/material_deep_teal_200 + + #ff5a595b + #ffd6d7d7 + + #ffbdbdbd + #fff1f1f1 + + @android:color/white + @android:color/black + + #80ffffff + + #80000000 + @color/bright_foreground_material_light + @color/bright_foreground_material_dark + + #ffbebebe + #ff323232 + #80bebebe + #80323232 + + @color/bright_foreground_disabled_material_dark + @color/bright_foreground_disabled_material_light + + + #6680cbc4 + + #66009688 + + @color/material_deep_teal_200 + @color/material_deep_teal_500 + + + + + #de000000 + #8a000000 + + #ffffffff + #b3ffffff + + 0.26 + 0.30 + + + #39000000 + #24000000 + + + #4Dffffff + #36ffffff + + + + + #ff80cbc4 + #ff009688 + + #ff37474f + #ff263238 + #ff21272b + + diff --git a/external/appcompat/res/values/config.xml b/external/appcompat/res/values/config.xml new file mode 100644 index 000000000..a57f2e4a3 --- /dev/null +++ b/external/appcompat/res/values/config.xml @@ -0,0 +1,35 @@ + + + + + + + false + + + 320dp + + + true + + \ No newline at end of file diff --git a/external/appcompat/res/values/dimens.xml b/external/appcompat/res/values/dimens.xml new file mode 100644 index 000000000..54baac37d --- /dev/null +++ b/external/appcompat/res/values/dimens.xml @@ -0,0 +1,66 @@ + + + + + + + 2 + + + 180dp + + + 48dp + + 40dp + + 296dp + + + 160dip + + 320dip + + + 8dip + 8dip + + 32dip + + + + 320dp + + 320dp + + 80% + + 100% + + + 4dp + + 4dp + + diff --git a/external/appcompat/res/values/dimens_material.xml b/external/appcompat/res/values/dimens_material.xml new file mode 100644 index 000000000..a620b3107 --- /dev/null +++ b/external/appcompat/res/values/dimens_material.xml @@ -0,0 +1,53 @@ + + + + + + 56dp + + 4dp + + 16dp + + -3dp + + 5dp + + 36dp + 48dp + 48dp + + 112sp + 56sp + 45sp + 34sp + 24sp + 20sp + 16sp + 20dp + 16dp + 16sp + 16sp + 16sp + 12sp + 14sp + + 22sp + 18sp + 14sp + + diff --git a/external/appcompat/res/values/ids.xml b/external/appcompat/res/values/ids.xml new file mode 100644 index 000000000..2e6ef2497 --- /dev/null +++ b/external/appcompat/res/values/ids.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/external/appcompat/res/values/strings.xml b/external/appcompat/res/values/strings.xml new file mode 100644 index 000000000..5080070da --- /dev/null +++ b/external/appcompat/res/values/strings.xml @@ -0,0 +1,63 @@ + + + + + + Done + + Navigate home + + Navigate up + + More options + + + Collapse + + + %1$s, %2$s + + %1$s, %2$s, %3$s + + + Search + + Search query + + Clear query + + Submit query + + Voice search + + + + Choose an app + + See all + + Share with %s + + Share with + + \ No newline at end of file diff --git a/external/appcompat/res/values/styles.xml b/external/appcompat/res/values/styles.xml new file mode 100644 index 000000000..1b8b53b80 --- /dev/null +++ b/external/appcompat/res/values/styles.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/values/styles_rtl.xml b/external/appcompat/res/values/styles_rtl.xml new file mode 100644 index 000000000..fad129176 --- /dev/null +++ b/external/appcompat/res/values/styles_rtl.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/appcompat/res/values/themes.xml b/external/appcompat/res/values/themes.xml new file mode 100644 index 000000000..05b865745 --- /dev/null +++ b/external/appcompat/res/values/themes.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/appcompat/src/.readme b/external/appcompat/src/.readme new file mode 100644 index 000000000..4bcebad80 --- /dev/null +++ b/external/appcompat/src/.readme @@ -0,0 +1,2 @@ +This hidden file is there to ensure there is an src folder. +Once we support binary library this will go away. \ No newline at end of file diff --git a/external/asmack b/external/asmack index c13384a6e..d8f758eb6 160000 --- a/external/asmack +++ b/external/asmack @@ -1 +1 @@ -Subproject commit c13384a6e85ce691fd5202e89d37d4d94cff372b +Subproject commit d8f758eb6d0b9542e9ad7bf6bbf4ded625f7933c diff --git a/external/bho b/external/bho new file mode 160000 index 000000000..879ad45fb --- /dev/null +++ b/external/bho @@ -0,0 +1 @@ +Subproject commit 879ad45fb891e51a782155fc091f2de824459f19 diff --git a/external/cacheword b/external/cacheword index 3a8817089..925bfac9d 160000 --- a/external/cacheword +++ b/external/cacheword @@ -1 +1 @@ -Subproject commit 3a8817089cdd50bbd5e5da614c0c95c1606057fc +Subproject commit 925bfac9d63f84a73b72e14022f1d90389013c3e diff --git a/fix-translations b/fix-translations new file mode 100755 index 000000000..7b4631158 --- /dev/null +++ b/fix-translations @@ -0,0 +1,23 @@ +#!/bin/bash -x + +# Fix TypographyEllipsis programmatically +sed -i 's/\.\.\./…/g' res/values*/*.xml + +# Replace "--" with an "em dash" character +sed -i 's,\(\>[^\<]*\)--\([^\>]\),\1—\2,g' res/values*/*.xml + +# make sure apostrophes in strings are escaped +sed -i "s,\(>[^<]*[^\\]\)',\1\\\\',g" res/values*/*.xml + +# transifex totally messes up the tags +sed -i \ + -e 's,</xliff:g>,,g' \ + -e 's,<xliff:g\( [^&]*\)>,,g' \ + -e 's,\(]*\)\\"\([^\\>]*\)\\",\1"\2",g' \ + -e 's,\(]*\)\\",\1",g' \ + -e 's,>%1$s</xliff>,>%1$s,g' \ + -e 's,<xliff, - - \ No newline at end of file diff --git a/res/drawable/ic_launcher_chatsecure.png b/logo512.png similarity index 100% rename from res/drawable/ic_launcher_chatsecure.png rename to logo512.png diff --git a/make-release-build b/make-release-build new file mode 100755 index 000000000..a67d33823 --- /dev/null +++ b/make-release-build @@ -0,0 +1,71 @@ +#!/bin/bash +# bash is required because we need bash's printf to guarantee a cross-platform +# timestamp format. + +set -e +set -x + +# make sure we're on a signed tag that matches the version name +versionName=`sed -n 's,.*versionName="\([^"]*\)".*,\1,p' AndroidManifest.xml` +describe=`git describe` +if [ $versionName != $describe ]; then + echo "WARNING: building $describe, which is not the latest release ($versionName)" +else + # make a clearer warning above by putting this here + set +x + echo "" + echo "" + echo "Checking git tag signature for release build:" + git tag -v $versionName + echo "" + echo "" + set -x +fi + + +if [ -z $ANDROID_HOME ]; then + if [ -e ~/.android/bashrc ]; then + . ~/.android/bashrc + else + echo "ANDROID_HOME must be set!" + exit + fi +fi + +projectroot=`pwd` +projectname=`sed -n 's,.*name="app_name">\(.*\)<.*,\1,p' res/values/strings.xml` + +# standardize timezone to reduce build differences +export TZ=UTC + +git reset --hard +git clean -fdx +git submodule foreach git reset --hard +git submodule foreach git clean -fdx +git submodule sync +git submodule foreach git submodule sync +git submodule update --init + + +if [ -e ~/.android/ant.properties ]; then + cp ~/.android/ant.properties $projectroot/ +# TODO remove me once cacheword is updated to v0.1 + cat ~/.android/ant.properties >> $projectroot/external/cacheword/cachewordlib/ant.properties +else + echo "skipping release ant.properties" +fi + +./update-ant-build.sh +ant release + +apk=$projectroot/bin/$projectname-v$describe.apk + +if which gpg > /dev/null; then + if [ -z "`gpg --list-secret-keys`" ]; then + echo "No GPG secret keys found, not signing APK" + else + gpg --detach-sign $apk + fi +else + echo "gpg not found, not signing APK" +fi diff --git a/offline.png b/offline.png new file mode 100644 index 000000000..de7d05a71 Binary files /dev/null and b/offline.png differ diff --git a/otr-sample/.classpath b/otr-sample/.classpath index 1f55032df..92908dfb0 100644 --- a/otr-sample/.classpath +++ b/otr-sample/.classpath @@ -2,8 +2,8 @@ - - + + diff --git a/pom.xml b/pom.xml index 13700982f..c9f2b4c82 100644 --- a/pom.xml +++ b/pom.xml @@ -2,10 +2,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.google - Gibberbot + ChatSecure 0.4-SNAPSHOT apk - Gibberbot + ChatSecure @@ -87,7 +87,7 @@ asmack 4-SNAPSHOT system - ${basedir}/libs/asmack-android-7.jar + ${basedir}/libs/asmack-android-8.jar gp-bcc-lib diff --git a/proguard-project.txt b/proguard-project.txt deleted file mode 100644 index f2fe1559a..000000000 --- a/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/project.properties b/project.properties index 1218a3016..8e1b08375 100644 --- a/project.properties +++ b/project.properties @@ -1,25 +1,14 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - # Indicates whether an apk should be generated for each density. split.density=false +dex.force.jumbo=true + # Project target. -target=android-17 -android.library.reference.1=external/MemorizingTrustManager -android.library.reference.2=external/OnionKit/libonionkit -android.library.reference.3=external/ActionBarSherlock/actionbarsherlock -#proguard.config=proguard-project.txt -android.library.reference.4=external/SlidingMenu/library +target=android-21 +android.library.reference.1=external/AndroidPinning +android.library.reference.2=external/AndroidEmojiInput/library +android.library.reference.3=external/appcompat +android.library.reference.4=external/bho/TibetanTextLibrary android.library.reference.5=external/cacheword/cachewordlib -android.library.reference.6=external/AndroidPinning -android.library.reference.7=external/SlideListView/library -android.library.reference.8=external/ShowcaseView/library -android.library.reference.10=external/NineOldAndroids/library -android.library.reference.9=external/MessageBar/library -android.library.reference.11=external/AndroidEmojiInput/library +android.library.reference.6=external/MemorizingTrustManager +android.library.reference.7=external/SlidingMenu/library +android.library.reference.8=external/ViewPagerIndicator/library diff --git a/res/drawable-hdpi-v11/ic_secure_xfer.png b/res/drawable-hdpi-v11/ic_secure_xfer.png new file mode 100644 index 000000000..f01c8a90e Binary files /dev/null and b/res/drawable-hdpi-v11/ic_secure_xfer.png differ diff --git a/res/drawable-hdpi-v11/notify_chatsecure.png b/res/drawable-hdpi-v11/notify_chatsecure.png index 3800ad5fe..ef22ad92a 100644 Binary files a/res/drawable-hdpi-v11/notify_chatsecure.png and b/res/drawable-hdpi-v11/notify_chatsecure.png differ diff --git a/res/drawable-hdpi-v11/notify_chatsecure_offline.png b/res/drawable-hdpi-v11/notify_chatsecure_offline.png new file mode 100644 index 000000000..967eb23d5 Binary files /dev/null and b/res/drawable-hdpi-v11/notify_chatsecure_offline.png differ diff --git a/res/drawable-hdpi-v5/ic_tab_chats_selected.png b/res/drawable-hdpi-v5/ic_tab_chats_selected.png deleted file mode 100755 index 2eb4493f8..000000000 Binary files a/res/drawable-hdpi-v5/ic_tab_chats_selected.png and /dev/null differ diff --git a/res/drawable-hdpi-v5/ic_tab_chats_unselected.png b/res/drawable-hdpi-v5/ic_tab_chats_unselected.png deleted file mode 100755 index 2f1f68c2f..000000000 Binary files a/res/drawable-hdpi-v5/ic_tab_chats_unselected.png and /dev/null differ diff --git a/res/drawable-hdpi-v5/ic_tab_contacts_selected.png b/res/drawable-hdpi-v5/ic_tab_contacts_selected.png deleted file mode 100755 index b9ea260e8..000000000 Binary files a/res/drawable-hdpi-v5/ic_tab_contacts_selected.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_action_new.png b/res/drawable-hdpi/ic_action_new.png new file mode 100644 index 000000000..d866d6160 Binary files /dev/null and b/res/drawable-hdpi/ic_action_new.png differ diff --git a/res/drawable-hdpi/ic_chat_msg_status_failed.png b/res/drawable-hdpi/ic_chat_msg_status_failed.png index 222faa2cd..53fd3fba4 100644 Binary files a/res/drawable-hdpi/ic_chat_msg_status_failed.png and b/res/drawable-hdpi/ic_chat_msg_status_failed.png differ diff --git a/res/drawable-hdpi/ic_chat_msg_status_ok.png b/res/drawable-hdpi/ic_chat_msg_status_ok.png index f3c235bd0..4b605adfb 100644 Binary files a/res/drawable-hdpi/ic_chat_msg_status_ok.png and b/res/drawable-hdpi/ic_chat_msg_status_ok.png differ diff --git a/res/drawable-hdpi/ic_chat_msg_status_unread.png b/res/drawable-hdpi/ic_chat_msg_status_unread.png index 6f0629ce8..8bf2d0bd3 100644 Binary files a/res/drawable-hdpi/ic_chat_msg_status_unread.png and b/res/drawable-hdpi/ic_chat_msg_status_unread.png differ diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..aaddd883f Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/ic_menu_add_contact.png b/res/drawable-hdpi/ic_menu_add_contact.png index ea6773fac..a19fe8abf 100644 Binary files a/res/drawable-hdpi/ic_menu_add_contact.png and b/res/drawable-hdpi/ic_menu_add_contact.png differ diff --git a/res/drawable-hdpi/ic_menu_barcode.png b/res/drawable-hdpi/ic_menu_barcode.png index 16dc54b4d..ad1337690 100644 Binary files a/res/drawable-hdpi/ic_menu_barcode.png and b/res/drawable-hdpi/ic_menu_barcode.png differ diff --git a/res/drawable-hdpi/ic_menu_close.png b/res/drawable-hdpi/ic_menu_close.png index 86c46af29..65a8219a9 100644 Binary files a/res/drawable-hdpi/ic_menu_close.png and b/res/drawable-hdpi/ic_menu_close.png differ diff --git a/res/drawable-hdpi/ic_menu_encrypt.png b/res/drawable-hdpi/ic_menu_encrypt.png index a74b46ef8..e49c4b53a 100644 Binary files a/res/drawable-hdpi/ic_menu_encrypt.png and b/res/drawable-hdpi/ic_menu_encrypt.png differ diff --git a/res/drawable-hdpi/ic_menu_fingerprint.png b/res/drawable-hdpi/ic_menu_fingerprint.png index d430c0986..ed1e5f007 100644 Binary files a/res/drawable-hdpi/ic_menu_fingerprint.png and b/res/drawable-hdpi/ic_menu_fingerprint.png differ diff --git a/res/drawable-hdpi/ic_menu_info.png b/res/drawable-hdpi/ic_menu_info.png index b72fcc45e..5786fb51b 100644 Binary files a/res/drawable-hdpi/ic_menu_info.png and b/res/drawable-hdpi/ic_menu_info.png differ diff --git a/res/drawable-hdpi/ic_menu_key.png b/res/drawable-hdpi/ic_menu_key.png index 0c8b8995a..316c932fc 100644 Binary files a/res/drawable-hdpi/ic_menu_key.png and b/res/drawable-hdpi/ic_menu_key.png differ diff --git a/res/drawable-hdpi/ic_menu_msg_compose.png b/res/drawable-hdpi/ic_menu_msg_compose.png index 793ccf526..222844b34 100644 Binary files a/res/drawable-hdpi/ic_menu_msg_compose.png and b/res/drawable-hdpi/ic_menu_msg_compose.png differ diff --git a/res/drawable-hdpi/ic_menu_new_chat.png b/res/drawable-hdpi/ic_menu_new_chat.png index 1a5f2b15e..477b249f1 100644 Binary files a/res/drawable-hdpi/ic_menu_new_chat.png and b/res/drawable-hdpi/ic_menu_new_chat.png differ diff --git a/res/drawable-hdpi/ic_menu_search.png b/res/drawable-hdpi/ic_menu_search.png index 2235d047d..aaa244621 100644 Binary files a/res/drawable-hdpi/ic_menu_search.png and b/res/drawable-hdpi/ic_menu_search.png differ diff --git a/res/drawable-hdpi/ic_menu_settings.png b/res/drawable-hdpi/ic_menu_settings.png index 7a17f4cf6..2dc733f9f 100644 Binary files a/res/drawable-hdpi/ic_menu_settings.png and b/res/drawable-hdpi/ic_menu_settings.png differ diff --git a/res/drawable-hdpi/ic_menu_sign_in.png b/res/drawable-hdpi/ic_menu_sign_in.png index de073dc53..e23467592 100644 Binary files a/res/drawable-hdpi/ic_menu_sign_in.png and b/res/drawable-hdpi/ic_menu_sign_in.png differ diff --git a/res/drawable-hdpi/ic_menu_sign_out.png b/res/drawable-hdpi/ic_menu_sign_out.png index 249445813..c26fe8a31 100644 Binary files a/res/drawable-hdpi/ic_menu_sign_out.png and b/res/drawable-hdpi/ic_menu_sign_out.png differ diff --git a/res/drawable-hdpi/ic_menu_switch.png b/res/drawable-hdpi/ic_menu_switch.png index c24128558..2a857f8ea 100644 Binary files a/res/drawable-hdpi/ic_menu_switch.png and b/res/drawable-hdpi/ic_menu_switch.png differ diff --git a/res/drawable-hdpi/ic_menu_trash.png b/res/drawable-hdpi/ic_menu_trash.png index 35dc8294e..358563acd 100644 Binary files a/res/drawable-hdpi/ic_menu_trash.png and b/res/drawable-hdpi/ic_menu_trash.png differ diff --git a/res/drawable-hdpi/ic_menu_unencrypt.png b/res/drawable-hdpi/ic_menu_unencrypt.png index 25129a712..939748065 100644 Binary files a/res/drawable-hdpi/ic_menu_unencrypt.png and b/res/drawable-hdpi/ic_menu_unencrypt.png differ diff --git a/res/drawable-hdpi/ic_menu_verify.png b/res/drawable-hdpi/ic_menu_verify.png index 6ed4742e3..57aa66fb7 100644 Binary files a/res/drawable-hdpi/ic_menu_verify.png and b/res/drawable-hdpi/ic_menu_verify.png differ diff --git a/res/drawable-hdpi/ic_secure_xfer.png b/res/drawable-hdpi/ic_secure_xfer.png new file mode 100644 index 000000000..52de440f6 Binary files /dev/null and b/res/drawable-hdpi/ic_secure_xfer.png differ diff --git a/res/drawable-hdpi/ic_send_secure.png b/res/drawable-hdpi/ic_send_secure.png new file mode 100644 index 000000000..7860eb344 Binary files /dev/null and b/res/drawable-hdpi/ic_send_secure.png differ diff --git a/res/drawable-hdpi/ic_settings_language.png b/res/drawable-hdpi/ic_settings_language.png new file mode 100644 index 000000000..e27035ad5 Binary files /dev/null and b/res/drawable-hdpi/ic_settings_language.png differ diff --git a/res/drawable-hdpi/notify_chatsecure.png b/res/drawable-hdpi/notify_chatsecure.png index 05bd7a653..826a204b3 100644 Binary files a/res/drawable-hdpi/notify_chatsecure.png and b/res/drawable-hdpi/notify_chatsecure.png differ diff --git a/res/drawable-hdpi/notify_chatsecure_offline.png b/res/drawable-hdpi/notify_chatsecure_offline.png new file mode 100644 index 000000000..c9d8017d7 Binary files /dev/null and b/res/drawable-hdpi/notify_chatsecure_offline.png differ diff --git a/res/drawable-mdpi-v11/ic_secure_xfer.png b/res/drawable-mdpi-v11/ic_secure_xfer.png new file mode 100644 index 000000000..280db04cb Binary files /dev/null and b/res/drawable-mdpi-v11/ic_secure_xfer.png differ diff --git a/res/drawable-mdpi-v11/notify_chatsecure.png b/res/drawable-mdpi-v11/notify_chatsecure.png index c06ea6bb4..760750675 100644 Binary files a/res/drawable-mdpi-v11/notify_chatsecure.png and b/res/drawable-mdpi-v11/notify_chatsecure.png differ diff --git a/res/drawable-mdpi-v11/notify_chatsecure_offline.png b/res/drawable-mdpi-v11/notify_chatsecure_offline.png new file mode 100644 index 000000000..fa5cbaad5 Binary files /dev/null and b/res/drawable-mdpi-v11/notify_chatsecure_offline.png differ diff --git a/res/drawable-mdpi/ic_action_message.png b/res/drawable-mdpi/ic_action_message.png index 468c1220c..4ecb270b5 100644 Binary files a/res/drawable-mdpi/ic_action_message.png and b/res/drawable-mdpi/ic_action_message.png differ diff --git a/res/drawable-mdpi/ic_action_new.png b/res/drawable-mdpi/ic_action_new.png new file mode 100644 index 000000000..f17e7980e Binary files /dev/null and b/res/drawable-mdpi/ic_action_new.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..c8a7699fb Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_menu_add_contact.png b/res/drawable-mdpi/ic_menu_add_contact.png index 51e3386ae..1cdcec716 100644 Binary files a/res/drawable-mdpi/ic_menu_add_contact.png and b/res/drawable-mdpi/ic_menu_add_contact.png differ diff --git a/res/drawable-mdpi/ic_menu_barcode.png b/res/drawable-mdpi/ic_menu_barcode.png index be4217c8f..56a930795 100644 Binary files a/res/drawable-mdpi/ic_menu_barcode.png and b/res/drawable-mdpi/ic_menu_barcode.png differ diff --git a/res/drawable-mdpi/ic_menu_close.png b/res/drawable-mdpi/ic_menu_close.png index 4efe4a0ed..67d9eccc7 100644 Binary files a/res/drawable-mdpi/ic_menu_close.png and b/res/drawable-mdpi/ic_menu_close.png differ diff --git a/res/drawable-mdpi/ic_menu_encrypt.png b/res/drawable-mdpi/ic_menu_encrypt.png index 441841714..248b22935 100644 Binary files a/res/drawable-mdpi/ic_menu_encrypt.png and b/res/drawable-mdpi/ic_menu_encrypt.png differ diff --git a/res/drawable-mdpi/ic_menu_fingerprint.png b/res/drawable-mdpi/ic_menu_fingerprint.png index a3585bc4e..fb505c080 100644 Binary files a/res/drawable-mdpi/ic_menu_fingerprint.png and b/res/drawable-mdpi/ic_menu_fingerprint.png differ diff --git a/res/drawable-mdpi/ic_menu_info.png b/res/drawable-mdpi/ic_menu_info.png index 9169b640f..1a4e5d578 100644 Binary files a/res/drawable-mdpi/ic_menu_info.png and b/res/drawable-mdpi/ic_menu_info.png differ diff --git a/res/drawable-mdpi/ic_menu_key.png b/res/drawable-mdpi/ic_menu_key.png index 1d71666d7..f227dee22 100644 Binary files a/res/drawable-mdpi/ic_menu_key.png and b/res/drawable-mdpi/ic_menu_key.png differ diff --git a/res/drawable-mdpi/ic_menu_new_chat.png b/res/drawable-mdpi/ic_menu_new_chat.png index 99b3cd4e9..17053a839 100644 Binary files a/res/drawable-mdpi/ic_menu_new_chat.png and b/res/drawable-mdpi/ic_menu_new_chat.png differ diff --git a/res/drawable-mdpi/ic_menu_search.png b/res/drawable-mdpi/ic_menu_search.png index 6e3de212e..7aa7f8dca 100644 Binary files a/res/drawable-mdpi/ic_menu_search.png and b/res/drawable-mdpi/ic_menu_search.png differ diff --git a/res/drawable-mdpi/ic_menu_settings.png b/res/drawable-mdpi/ic_menu_settings.png index 2e88b0f65..7dd6a24f1 100644 Binary files a/res/drawable-mdpi/ic_menu_settings.png and b/res/drawable-mdpi/ic_menu_settings.png differ diff --git a/res/drawable-mdpi/ic_menu_sign_in.png b/res/drawable-mdpi/ic_menu_sign_in.png index 2e9aafb5a..34591a81b 100644 Binary files a/res/drawable-mdpi/ic_menu_sign_in.png and b/res/drawable-mdpi/ic_menu_sign_in.png differ diff --git a/res/drawable-mdpi/ic_menu_sign_out.png b/res/drawable-mdpi/ic_menu_sign_out.png index d301ea644..93e918644 100644 Binary files a/res/drawable-mdpi/ic_menu_sign_out.png and b/res/drawable-mdpi/ic_menu_sign_out.png differ diff --git a/res/drawable-mdpi/ic_menu_switch.png b/res/drawable-mdpi/ic_menu_switch.png index 3a213d9c4..e2fdcb5e8 100644 Binary files a/res/drawable-mdpi/ic_menu_switch.png and b/res/drawable-mdpi/ic_menu_switch.png differ diff --git a/res/drawable-mdpi/ic_menu_trash.png b/res/drawable-mdpi/ic_menu_trash.png index 33a753aec..ce2374701 100644 Binary files a/res/drawable-mdpi/ic_menu_trash.png and b/res/drawable-mdpi/ic_menu_trash.png differ diff --git a/res/drawable-mdpi/ic_menu_unencrypt.png b/res/drawable-mdpi/ic_menu_unencrypt.png index 426bdda59..8542697f2 100644 Binary files a/res/drawable-mdpi/ic_menu_unencrypt.png and b/res/drawable-mdpi/ic_menu_unencrypt.png differ diff --git a/res/drawable-mdpi/ic_menu_verify.png b/res/drawable-mdpi/ic_menu_verify.png index c1873047a..2b6d3e018 100644 Binary files a/res/drawable-mdpi/ic_menu_verify.png and b/res/drawable-mdpi/ic_menu_verify.png differ diff --git a/res/drawable-mdpi/ic_secure_xfer.png b/res/drawable-mdpi/ic_secure_xfer.png new file mode 100644 index 000000000..5d1c36bf0 Binary files /dev/null and b/res/drawable-mdpi/ic_secure_xfer.png differ diff --git a/res/drawable-mdpi/ic_settings_language.png b/res/drawable-mdpi/ic_settings_language.png new file mode 100644 index 000000000..604a71002 Binary files /dev/null and b/res/drawable-mdpi/ic_settings_language.png differ diff --git a/res/drawable-mdpi/ic_stat_status.png b/res/drawable-mdpi/ic_stat_status.png index 6d02dc003..b4d70602f 100644 Binary files a/res/drawable-mdpi/ic_stat_status.png and b/res/drawable-mdpi/ic_stat_status.png differ diff --git a/res/drawable-mdpi/notify_chatsecure.png b/res/drawable-mdpi/notify_chatsecure.png index 864310340..65cfaf050 100644 Binary files a/res/drawable-mdpi/notify_chatsecure.png and b/res/drawable-mdpi/notify_chatsecure.png differ diff --git a/res/drawable-mdpi/notify_chatsecure_offline.png b/res/drawable-mdpi/notify_chatsecure_offline.png new file mode 100644 index 000000000..9df0064d8 Binary files /dev/null and b/res/drawable-mdpi/notify_chatsecure_offline.png differ diff --git a/res/drawable-xhdpi-v11/ic_secure_xfer.png b/res/drawable-xhdpi-v11/ic_secure_xfer.png new file mode 100644 index 000000000..21e7a0df9 Binary files /dev/null and b/res/drawable-xhdpi-v11/ic_secure_xfer.png differ diff --git a/res/drawable-xhdpi-v11/notify_chatsecure.png b/res/drawable-xhdpi-v11/notify_chatsecure.png index 7ec045545..e7acffc5f 100644 Binary files a/res/drawable-xhdpi-v11/notify_chatsecure.png and b/res/drawable-xhdpi-v11/notify_chatsecure.png differ diff --git a/res/drawable-xhdpi-v11/notify_chatsecure_offline.png b/res/drawable-xhdpi-v11/notify_chatsecure_offline.png new file mode 100644 index 000000000..756eddd1c Binary files /dev/null and b/res/drawable-xhdpi-v11/notify_chatsecure_offline.png differ diff --git a/res/drawable-xhdpi/ic_action_new.png b/res/drawable-xhdpi/ic_action_new.png new file mode 100644 index 000000000..dde2141f2 Binary files /dev/null and b/res/drawable-xhdpi/ic_action_new.png differ diff --git a/res/drawable-xhdpi/ic_chat_msg_status_failed.png b/res/drawable-xhdpi/ic_chat_msg_status_failed.png index 3e78d988e..53fd3fba4 100644 Binary files a/res/drawable-xhdpi/ic_chat_msg_status_failed.png and b/res/drawable-xhdpi/ic_chat_msg_status_failed.png differ diff --git a/res/drawable-xhdpi/ic_chat_msg_status_ok.png b/res/drawable-xhdpi/ic_chat_msg_status_ok.png index 431907ded..4b605adfb 100644 Binary files a/res/drawable-xhdpi/ic_chat_msg_status_ok.png and b/res/drawable-xhdpi/ic_chat_msg_status_ok.png differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..8d4749cc5 Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ diff --git a/res/drawable-xhdpi/ic_secure_xfer.png b/res/drawable-xhdpi/ic_secure_xfer.png new file mode 100644 index 000000000..2a51af61d Binary files /dev/null and b/res/drawable-xhdpi/ic_secure_xfer.png differ diff --git a/res/drawable-xhdpi/ic_settings_language.png b/res/drawable-xhdpi/ic_settings_language.png new file mode 100644 index 000000000..4e44c0126 Binary files /dev/null and b/res/drawable-xhdpi/ic_settings_language.png differ diff --git a/res/drawable-xhdpi/notify_chatsecure.png b/res/drawable-xhdpi/notify_chatsecure.png index d8349e9fb..b801b69db 100644 Binary files a/res/drawable-xhdpi/notify_chatsecure.png and b/res/drawable-xhdpi/notify_chatsecure.png differ diff --git a/res/drawable-xhdpi/notify_chatsecure_offline.png b/res/drawable-xhdpi/notify_chatsecure_offline.png new file mode 100644 index 000000000..26902846c Binary files /dev/null and b/res/drawable-xhdpi/notify_chatsecure_offline.png differ diff --git a/res/drawable-xxhdpi-v11/ic_secure_xfer.png b/res/drawable-xxhdpi-v11/ic_secure_xfer.png new file mode 100644 index 000000000..a5b41d1eb Binary files /dev/null and b/res/drawable-xxhdpi-v11/ic_secure_xfer.png differ diff --git a/res/drawable-xxhdpi-v11/notify_chatsecure.png b/res/drawable-xxhdpi-v11/notify_chatsecure.png index 0e3ea1e33..abef7980e 100644 Binary files a/res/drawable-xxhdpi-v11/notify_chatsecure.png and b/res/drawable-xxhdpi-v11/notify_chatsecure.png differ diff --git a/res/drawable-xxhdpi-v11/notify_chatsecure_offline.png b/res/drawable-xxhdpi-v11/notify_chatsecure_offline.png new file mode 100644 index 000000000..ae0bc210a Binary files /dev/null and b/res/drawable-xxhdpi-v11/notify_chatsecure_offline.png differ diff --git a/res/drawable-xxhdpi/ic_action_new.png b/res/drawable-xxhdpi/ic_action_new.png new file mode 100644 index 000000000..c42c2bfb5 Binary files /dev/null and b/res/drawable-xxhdpi/ic_action_new.png differ diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..089f9068b Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/res/drawable-xxhdpi/ic_secure_xfer.png b/res/drawable-xxhdpi/ic_secure_xfer.png new file mode 100644 index 000000000..d33a52d7f Binary files /dev/null and b/res/drawable-xxhdpi/ic_secure_xfer.png differ diff --git a/res/drawable-xxhdpi/ic_settings_language.png b/res/drawable-xxhdpi/ic_settings_language.png new file mode 100644 index 000000000..3d6559d00 Binary files /dev/null and b/res/drawable-xxhdpi/ic_settings_language.png differ diff --git a/res/drawable-xxhdpi/notify_chatsecure.png b/res/drawable-xxhdpi/notify_chatsecure.png index 6e0234256..6943ed557 100644 Binary files a/res/drawable-xxhdpi/notify_chatsecure.png and b/res/drawable-xxhdpi/notify_chatsecure.png differ diff --git a/res/drawable-xxhdpi/notify_chatsecure_offline.png b/res/drawable-xxhdpi/notify_chatsecure_offline.png new file mode 100644 index 000000000..96cab3ab2 Binary files /dev/null and b/res/drawable-xxhdpi/notify_chatsecure_offline.png differ diff --git a/res/drawable-xxxhdpi-v11/notify_chatsecure.png b/res/drawable-xxxhdpi-v11/notify_chatsecure.png new file mode 100644 index 000000000..8a3bee5a1 Binary files /dev/null and b/res/drawable-xxxhdpi-v11/notify_chatsecure.png differ diff --git a/res/drawable-xxxhdpi-v11/notify_chatsecure_offline.png b/res/drawable-xxxhdpi-v11/notify_chatsecure_offline.png new file mode 100644 index 000000000..8d7742c52 Binary files /dev/null and b/res/drawable-xxxhdpi-v11/notify_chatsecure_offline.png differ diff --git a/res/drawable-xxxhdpi/ic_settings_language.png b/res/drawable-xxxhdpi/ic_settings_language.png new file mode 100644 index 000000000..c9447bde0 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_settings_language.png differ diff --git a/res/drawable-xxxhdpi/notify_chatsecure.png b/res/drawable-xxxhdpi/notify_chatsecure.png new file mode 100644 index 000000000..68fdb3c6e Binary files /dev/null and b/res/drawable-xxxhdpi/notify_chatsecure.png differ diff --git a/res/drawable-xxxhdpi/notify_chatsecure_offline.png b/res/drawable-xxxhdpi/notify_chatsecure_offline.png new file mode 100644 index 000000000..604997586 Binary files /dev/null and b/res/drawable-xxxhdpi/notify_chatsecure_offline.png differ diff --git a/res/drawable/action_about.png b/res/drawable/action_about.png new file mode 100644 index 000000000..8f39c428a Binary files /dev/null and b/res/drawable/action_about.png differ diff --git a/res/drawable/action_settings.png b/res/drawable/action_settings.png new file mode 100644 index 000000000..3e4580e05 Binary files /dev/null and b/res/drawable/action_settings.png differ diff --git a/res/drawable/alerts_and_states_error.png b/res/drawable/alerts_and_states_error.png new file mode 100644 index 000000000..4ae44491e Binary files /dev/null and b/res/drawable/alerts_and_states_error.png differ diff --git a/res/drawable/alerts_and_states_warning.png b/res/drawable/alerts_and_states_warning.png new file mode 100644 index 000000000..1fefdd8bd Binary files /dev/null and b/res/drawable/alerts_and_states_warning.png differ diff --git a/res/drawable/avatar_unknown.png b/res/drawable/avatar_unknown.png index 873aa86d8..0fd2cb78e 100644 Binary files a/res/drawable/avatar_unknown.png and b/res/drawable/avatar_unknown.png differ diff --git a/res/drawable/background_encrypted.xml b/res/drawable/background_encrypted.xml new file mode 100644 index 000000000..ace2b91aa --- /dev/null +++ b/res/drawable/background_encrypted.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/drawable/background_plaintext.xml b/res/drawable/background_plaintext.xml new file mode 100644 index 000000000..dbd34e5f5 --- /dev/null +++ b/res/drawable/background_plaintext.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/drawable/background_verified.xml b/res/drawable/background_verified.xml new file mode 100644 index 000000000..1e55a5e52 --- /dev/null +++ b/res/drawable/background_verified.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/drawable/bgcolor1.png b/res/drawable/bgcolor1.png deleted file mode 100644 index eac9aef67..000000000 Binary files a/res/drawable/bgcolor1.png and /dev/null differ diff --git a/res/drawable/bgfidelity.jpg b/res/drawable/bgfidelity.jpg deleted file mode 100644 index efcec0a24..000000000 Binary files a/res/drawable/bgfidelity.jpg and /dev/null differ diff --git a/res/drawable/bgsplash1.jpg b/res/drawable/bgsplash1.jpg deleted file mode 100644 index ae5c8c04a..000000000 Binary files a/res/drawable/bgsplash1.jpg and /dev/null differ diff --git a/res/drawable/broken_image_large.png b/res/drawable/broken_image_large.png new file mode 100644 index 000000000..9b24ff231 Binary files /dev/null and b/res/drawable/broken_image_large.png differ diff --git a/res/drawable/camera_button_active.xml b/res/drawable/camera_button_active.xml new file mode 100644 index 000000000..640fdc6c5 --- /dev/null +++ b/res/drawable/camera_button_active.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/drawable/camera_button_inactive.xml b/res/drawable/camera_button_inactive.xml new file mode 100644 index 000000000..d4fbbf169 --- /dev/null +++ b/res/drawable/camera_button_inactive.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/res/drawable/camera_button_selector.xml b/res/drawable/camera_button_selector.xml new file mode 100644 index 000000000..6cc6c1508 --- /dev/null +++ b/res/drawable/camera_button_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/check.png b/res/drawable/check.png new file mode 100644 index 000000000..5a324d1f9 Binary files /dev/null and b/res/drawable/check.png differ diff --git a/res/drawable/content_attachment.png b/res/drawable/content_attachment.png new file mode 100644 index 000000000..4a76157f3 Binary files /dev/null and b/res/drawable/content_attachment.png differ diff --git a/res/drawable/content_new_attachment.png b/res/drawable/content_new_attachment.png new file mode 100644 index 000000000..25d27e044 Binary files /dev/null and b/res/drawable/content_new_attachment.png differ diff --git a/res/drawable/content_new_attachment_dark.png b/res/drawable/content_new_attachment_dark.png new file mode 100644 index 000000000..28507da2d Binary files /dev/null and b/res/drawable/content_new_attachment_dark.png differ diff --git a/res/drawable/content_new_audio.png b/res/drawable/content_new_audio.png new file mode 100644 index 000000000..658b00f19 Binary files /dev/null and b/res/drawable/content_new_audio.png differ diff --git a/res/drawable/content_new_picture.png b/res/drawable/content_new_picture.png new file mode 100644 index 000000000..b79a1b9bc Binary files /dev/null and b/res/drawable/content_new_picture.png differ diff --git a/res/drawable/content_picture.png b/res/drawable/content_picture.png new file mode 100644 index 000000000..0f650ee25 Binary files /dev/null and b/res/drawable/content_picture.png differ diff --git a/res/drawable/content_take_picture.png b/res/drawable/content_take_picture.png new file mode 100644 index 000000000..d9d87393e Binary files /dev/null and b/res/drawable/content_take_picture.png differ diff --git a/res/drawable/device_access_secure.png b/res/drawable/device_access_secure.png new file mode 100644 index 000000000..58689b43a Binary files /dev/null and b/res/drawable/device_access_secure.png differ diff --git a/res/drawable/device_access_time.png b/res/drawable/device_access_time.png new file mode 100644 index 000000000..001549f38 Binary files /dev/null and b/res/drawable/device_access_time.png differ diff --git a/res/drawable/group_chat.png b/res/drawable/group_chat.png index c08d822d5..aa7153b03 100644 Binary files a/res/drawable/group_chat.png and b/res/drawable/group_chat.png differ diff --git a/res/drawable/group_chat_new.png b/res/drawable/group_chat_new.png deleted file mode 100644 index 6490d777f..000000000 Binary files a/res/drawable/group_chat_new.png and /dev/null differ diff --git a/res/drawable/ic_broken_image.png b/res/drawable/ic_broken_image.png new file mode 100644 index 000000000..f303bd5d0 Binary files /dev/null and b/res/drawable/ic_broken_image.png differ diff --git a/res/drawable/ic_chat_msg_status_failed.png b/res/drawable/ic_chat_msg_status_failed.png index 5beefd3ed..53fd3fba4 100644 Binary files a/res/drawable/ic_chat_msg_status_failed.png and b/res/drawable/ic_chat_msg_status_failed.png differ diff --git a/res/drawable/ic_chat_msg_status_ok.png b/res/drawable/ic_chat_msg_status_ok.png index 923d17469..4b605adfb 100644 Binary files a/res/drawable/ic_chat_msg_status_ok.png and b/res/drawable/ic_chat_msg_status_ok.png differ diff --git a/res/drawable/ic_chat_msg_status_unread.png b/res/drawable/ic_chat_msg_status_unread.png index 268fbe75a..8bf2d0bd3 100644 Binary files a/res/drawable/ic_chat_msg_status_unread.png and b/res/drawable/ic_chat_msg_status_unread.png differ diff --git a/res/drawable/ic_context_panic.png b/res/drawable/ic_context_panic.png index 042b405a1..c43e3f5cb 100644 Binary files a/res/drawable/ic_context_panic.png and b/res/drawable/ic_context_panic.png differ diff --git a/res/drawable/ic_drawer.png b/res/drawable/ic_drawer.png new file mode 100644 index 000000000..ff7b1def9 Binary files /dev/null and b/res/drawable/ic_drawer.png differ diff --git a/res/drawable/ic_file.png b/res/drawable/ic_file.png new file mode 100644 index 000000000..6f0166216 Binary files /dev/null and b/res/drawable/ic_file.png differ diff --git a/res/drawable/ic_launcher_gibberbot.png b/res/drawable/ic_launcher_gibberbot.png deleted file mode 100644 index e40991069..000000000 Binary files a/res/drawable/ic_launcher_gibberbot.png and /dev/null differ diff --git a/res/drawable/ic_menu_grid.png b/res/drawable/ic_menu_grid.png new file mode 100644 index 000000000..6b58c2dab Binary files /dev/null and b/res/drawable/ic_menu_grid.png differ diff --git a/res/drawable/ic_send_secure.png b/res/drawable/ic_send_secure.png new file mode 100644 index 000000000..7860eb344 Binary files /dev/null and b/res/drawable/ic_send_secure.png differ diff --git a/res/drawable/ic_sidebar_stop.png b/res/drawable/ic_sidebar_stop.png new file mode 100644 index 000000000..8045c474b Binary files /dev/null and b/res/drawable/ic_sidebar_stop.png differ diff --git a/res/drawable/ic_white_encrypted_and_verified.png b/res/drawable/ic_white_encrypted_and_verified.png index 4252650f6..8e850fb5f 100644 Binary files a/res/drawable/ic_white_encrypted_and_verified.png and b/res/drawable/ic_white_encrypted_and_verified.png differ diff --git a/res/drawable/ic_white_encrypted_not_verified.png b/res/drawable/ic_white_encrypted_not_verified.png index cd30acad5..5d853dca8 100644 Binary files a/res/drawable/ic_white_encrypted_not_verified.png and b/res/drawable/ic_white_encrypted_not_verified.png differ diff --git a/res/drawable/ic_white_unencrypted.png b/res/drawable/ic_white_unencrypted.png index c8f2b71ec..069b44b7b 100644 Binary files a/res/drawable/ic_white_unencrypted.png and b/res/drawable/ic_white_unencrypted.png differ diff --git a/res/drawable/icon_settings.png b/res/drawable/icon_settings.png deleted file mode 100644 index 090a5a42c..000000000 Binary files a/res/drawable/icon_settings.png and /dev/null differ diff --git a/res/drawable/launch_chatsecure.png b/res/drawable/launch_chatsecure.png deleted file mode 100644 index 3ab5377aa..000000000 Binary files a/res/drawable/launch_chatsecure.png and /dev/null differ diff --git a/res/drawable/lock16.png b/res/drawable/lock16.png new file mode 100644 index 000000000..cd5e35e99 Binary files /dev/null and b/res/drawable/lock16.png differ diff --git a/res/drawable/logo512.png b/res/drawable/logo512.png new file mode 100644 index 000000000..01713b05b Binary files /dev/null and b/res/drawable/logo512.png differ diff --git a/res/drawable/media_audio_play.png b/res/drawable/media_audio_play.png new file mode 100644 index 000000000..9c65f758a Binary files /dev/null and b/res/drawable/media_audio_play.png differ diff --git a/res/drawable/message_view_rounded_dark.xml b/res/drawable/message_view_rounded_dark.xml new file mode 100644 index 000000000..9a221bf56 --- /dev/null +++ b/res/drawable/message_view_rounded_dark.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/message_view_rounded_light.xml b/res/drawable/message_view_rounded_light.xml new file mode 100644 index 000000000..ee5dfcca6 --- /dev/null +++ b/res/drawable/message_view_rounded_light.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/navigation_cancel.png b/res/drawable/navigation_cancel.png new file mode 100644 index 000000000..cde36e1fa Binary files /dev/null and b/res/drawable/navigation_cancel.png differ diff --git a/res/drawable/navigation_refresh.png b/res/drawable/navigation_refresh.png new file mode 100644 index 000000000..479aca465 Binary files /dev/null and b/res/drawable/navigation_refresh.png differ diff --git a/res/drawable/radial_back.xml b/res/drawable/radial_back.xml new file mode 100644 index 000000000..0c2f3708e --- /dev/null +++ b/res/drawable/radial_back.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/shape_separator.xml b/res/drawable/shape_separator.xml index ea40d554f..e01708ace 100644 --- a/res/drawable/shape_separator.xml +++ b/res/drawable/shape_separator.xml @@ -18,7 +18,7 @@ */ --> - + - - - - - - - - - - - - - - - - - diff --git a/res/layout-ldpi/contact_view.xml b/res/layout-ldpi/contact_view.xml new file mode 100755 index 000000000..969cb3dba --- /dev/null +++ b/res/layout-ldpi/contact_view.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout-ldpi/message_view_left.xml b/res/layout-ldpi/message_view_left.xml new file mode 100755 index 000000000..197a045d3 --- /dev/null +++ b/res/layout-ldpi/message_view_left.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + diff --git a/res/layout-mdpi/contact_view.xml b/res/layout-mdpi/contact_view.xml new file mode 100755 index 000000000..07221dfbb --- /dev/null +++ b/res/layout-mdpi/contact_view.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout-mdpi/message_view_left.xml b/res/layout-mdpi/message_view_left.xml new file mode 100755 index 000000000..c6712eb0c --- /dev/null +++ b/res/layout-mdpi/message_view_left.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout-v14/account_view.xml b/res/layout-v14/account_view.xml index c3e23bcf8..e37ad0d3d 100755 --- a/res/layout-v14/account_view.xml +++ b/res/layout-v14/account_view.xml @@ -21,7 +21,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" - android:layout_height="?android:attr/listPreferredItemHeight"> + android:layout_height="?android:attr/listPreferredItemHeight" + > + \ No newline at end of file diff --git a/res/layout-v14/chat_view.xml b/res/layout-v14/chat_view.xml deleted file mode 100755 index d122a3f22..000000000 --- a/res/layout-v14/chat_view.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + \ No newline at end of file diff --git a/res/layout/chat_list_view.xml b/res/layout/chat_list_view.xml index 7babf5276..4e5560f1c 100755 --- a/res/layout/chat_list_view.xml +++ b/res/layout/chat_list_view.xml @@ -29,6 +29,7 @@ android:layout_height="fill_parent" android:background="#00000000" android:cacheColorHint="#00000000" + /> - + + - - - + android:layout_marginTop="?attr/actionBarSize" + > + + - + + + + + + - - - + android:name="info.guardianproject.otr.app.im.app.AccountsFragment" + /> + - - - - - - diff --git a/res/layout/fragment_drawer.xml b/res/layout/fragment_drawer.xml deleted file mode 100644 index 4f72fcf49..000000000 --- a/res/layout/fragment_drawer.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/layout/message_view_left.xml b/res/layout/message_view_left.xml new file mode 100755 index 000000000..348ab2995 --- /dev/null +++ b/res/layout/message_view_left.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/message_view_right.xml b/res/layout/message_view_right.xml new file mode 100755 index 000000000..7f82357f2 --- /dev/null +++ b/res/layout/message_view_right.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/missing_chat_file_store.xml b/res/layout/missing_chat_file_store.xml new file mode 100644 index 000000000..208027e85 --- /dev/null +++ b/res/layout/missing_chat_file_store.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + " - - " - - \ No newline at end of file diff --git a/res/layout/scrollingtext_view.xml b/res/layout/scrollingtext_view.xml deleted file mode 100644 index 2efc7bd00..000000000 --- a/res/layout/scrollingtext_view.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - diff --git a/res/layout/secure_camera.xml b/res/layout/secure_camera.xml new file mode 100644 index 000000000..4098f2387 --- /dev/null +++ b/res/layout/secure_camera.xml @@ -0,0 +1,42 @@ + + + + + + + + + - - diff --git a/res/layout/showcase_view_template.xml b/res/layout/showcase_view_template.xml deleted file mode 100644 index ad219ca25..000000000 --- a/res/layout/showcase_view_template.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/layout/signing_in_activity.xml b/res/layout/signing_in_activity.xml index a03c6c106..19c381ab6 100755 --- a/res/layout/signing_in_activity.xml +++ b/res/layout/signing_in_activity.xml @@ -19,16 +19,10 @@ --> - @@ -43,7 +37,7 @@ - - - - - - - - - - - - diff --git a/res/layout/tab_container.xml b/res/layout/tab_container.xml deleted file mode 100644 index 5336506a5..000000000 --- a/res/layout/tab_container.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout/welcome_activity.xml b/res/layout/welcome_activity.xml deleted file mode 100644 index dc31f1b87..000000000 --- a/res/layout/welcome_activity.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/menu/account_settings_menu.xml b/res/menu/account_settings_menu.xml index 41e2e4899..cbb448cb4 100755 --- a/res/menu/account_settings_menu.xml +++ b/res/menu/account_settings_menu.xml @@ -18,17 +18,19 @@ */ --> - + + - \ No newline at end of file diff --git a/res/menu/accounts_menu.xml b/res/menu/accounts_menu.xml index af5ee354b..bd2224896 100755 --- a/res/menu/accounts_menu.xml +++ b/res/menu/accounts_menu.xml @@ -17,63 +17,11 @@ * limitations under the License. */ --> - + - - - - - - - - - - - - + - - - - diff --git a/res/menu/chat_list_menu.xml b/res/menu/chat_list_menu.xml index 1074208a3..15af92430 100755 --- a/res/menu/chat_list_menu.xml +++ b/res/menu/chat_list_menu.xml @@ -17,32 +17,34 @@ * limitations under the License. */ --> - + + @@ -51,7 +53,7 @@ --> diff --git a/res/menu/chat_screen_menu.xml b/res/menu/chat_screen_menu.xml deleted file mode 100755 index c9367dc9b..000000000 --- a/res/menu/chat_screen_menu.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/menu/contact_info_menu.xml b/res/menu/contact_info_menu.xml index f866aa92d..d5164e430 100755 --- a/res/menu/contact_info_menu.xml +++ b/res/menu/contact_info_menu.xml @@ -17,31 +17,38 @@ * limitations under the License. */ --> - + + + + + + + + - - - - - + + + + diff --git a/res/menu/contact_list_menu.xml b/res/menu/contact_list_menu.xml index 3a02438b5..2315d88ab 100755 --- a/res/menu/contact_list_menu.xml +++ b/res/menu/contact_list_menu.xml @@ -17,16 +17,29 @@ * limitations under the License. */ --> - + - + + + --> + - - - - - - - - - - - - - diff --git a/res/menu/new_chat_menu.xml b/res/menu/new_chat_menu.xml new file mode 100755 index 000000000..9f100566f --- /dev/null +++ b/res/menu/new_chat_menu.xml @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/raw/notify.mp3 b/res/raw/notify.mp3 new file mode 100644 index 000000000..23c29da65 Binary files /dev/null and b/res/raw/notify.mp3 differ diff --git a/res/raw/notify_license.txt b/res/raw/notify_license.txt new file mode 100644 index 000000000..888e0f9e3 --- /dev/null +++ b/res/raw/notify_license.txt @@ -0,0 +1,7 @@ +Notify sound thanks to: Corsica_S + +Flopple Sessions » flopple11.aif +http://freesound.org/people/Corsica_S/sounds/50084/ + +License: +http://creativecommons.org/licenses/by/3.0/ diff --git a/res/values-ar/arrays.xml b/res/values-ar/arrays.xml deleted file mode 100644 index 89bedad50..000000000 --- a/res/values-ar/arrays.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - افرض/ إطلب - محاولة تلقائيا - بناءَ على الطلب - تعطيل / أبدا - - - force - auto - requested - disabled - - - الافتراضي - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - - diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index e56b58cbd..f94560648 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -1,439 +1,542 @@ - + + + شات سيكيور + شات سيكيور ChatSecure + قراءة الرسائل الفورية يسمح للتطبيقات بقراءة البيانات من مزود محتوى الرسائل الفورية. كتابة رسائل فورية يسمح للتطبيقات بكتابة البيانات إلى مزود محتوى الرسائل الفورية. - - شات سيكيور ChatSecure - - - دردشة -- حدد حساب - - عن جيبربوت - - إضافة حساب - - تحرير حساب - - إزالة حساب - - الخروج من الجميع - - - دردشة -- حدد حساب - - - - - إلغاء تسجيل الدخول - - - إضافة اسم - - حذف الاتصال - - حجب الاتصال - - محجوب - - الحساب - - محادثة جديدة - - حساب جديد - - إعدادات - - ابحث بالأسماء - - إبدأ الدردشة - - تسجيل الخروج - - تحقق - - - بدء التشفير - وقف التشفير - امسح سجل المحادثة - نهاية دردشة - إتصال أمن - - - قائمة الاتصال - - دعوة - - انتقل بين المحادثات - - أدخل ابتسامة - إعادة إرسال - - مسح بصمة المفتاح - البصمة الخاصة بمفتاحك - تحقق من البصمات - تحقق من السر - - القائمة + \" - - - + بدء تشغيل خدمة المراسلة الفورية + يسمح للتطبيقات ببدء تشغيل خدمة المحادثة عن طريق الIntent. + تأكيد - - هل تريد تسجيل الخروج من جميع الخدمات؟ - - - - - - - موافق - إلغاء - موافق - إلغاء - - - - - - - - - + التالي + عودة + اتصال + لعب + إعداد + + افتح + شات سيكيور مقفل + ضبط كلمة السر + تأكيد كلمة السر + كلمة السر + يمكنك ضبط كلمة سر أساسية لمنع الولوج إلى أصدقائك ورسائلك دون كلمة سر: + تأكيد كلمة السر الجديدة + العبارات السرية المدخلة لم تتطابق. حاول مرة أخرى. + التخطي >> + موجه المعلومات + أدخل كلمة السر + كلمة السر، من فضلك… + جملة السر + جملة السر (مجدّداً) + + حدد حساب + حدد حساب + (%1$d) + أضف حساب (%1$s) + حول التطبيق + الخروج من الجميع + هل تريد تسجيل الخروج من جميع الخدمات؟ + %1$s تم تسجيل خروجك من + لقد تم إخراجك من%1$s بسبب %2$s + هل هذه أول مرة تستخدم بها شات سيكيور (ChatSecure)؟ + غير قادر على الانتظار؟ + إبدأ + إعداد الحساب + إعداد كلمة السر + قبل أن نبدأ باستخدام جيبربوت، يرجى إدخال كلمة سر آمنة حتى تمنع الدخول غير المصرح به على بيانات جيبربوت لديك. + كلمة السر: + أدخل كلمة السر مرة أخرى: + أدخل عبارة سرية جديدة. يجب أن تحتوي على الاقل على حرف كبير capital و حرف صغير و رقم واحد، و أن تكون أطول من ست حروف. + يتم تشفير دفتر الملاحظات بالعبارة السرية الجديدة (الرجاء الانتظار)… + أدخل كلمة السر: + مرحباً! أدخل عبارة سرية قوية لتشفير دفتر ملاحظاتك. يجب أن تحتوي على الاقل على حرف كبير capital و حرف صغير و رقم واحد، و أن تكون أطول من ست حروف. + عبارتك السرية ليست طويلة بما يكفي. + لم تحتوي عبارتك السرية على أي حروف كبيرة. + لم تحتوي عبارتك السرية على أي حروف صغيرة. + لم تحتوي عبارتك السرية على أي أرقام. + + عن شات سيكيور (ChatSecure) + شات سيكيور (ChatSecure) هو تطبيق للرسائل الفورية يقوم بتوفير مميزات أمنية كثيرة تمنع الآخرين من التنصت على محادثاتك و اتصالاتك. +\n + يعمل هذا البرنامج مع العديد من خدمات الرسائل الفورية مثل جوجل توك أو Jabber.org. + + ما هي المميزات الأمنية؟ + التشفير دون تسجيل (خارج السجل) هو نظام أمني يستخدم لتوفير الخصوصية الكاملة عن طريق التشفير، اثبات الهوية، القدرة على إنكار وقوع المحادثة وضمان السرية المستقبلية. بروتوكول التشفير دون تسجيل يحفظ لك خصوصيتك وأمنك حتى لو تم الاستيلاء على جهازك أو أجهزة أصدقائك حيث لا يتم تخزين المحادثة المشفرة في جهازك أو جهاز صديقك أو أي موقع آخر.\n\nإن التشفير خارج السجل يتوافق مع تطبيقات الدردشة كAdium وPidgin. + + هل محادثاتي آمنة؟ + وظائف التشفير ببرنامج جيبربوت تعمل فقط عندما يقوم الطرف الآخر باستخدام برامج تدعم أيضاً مميزات التشفير دون تسجيل (خارج السجل) ، لذلك يجب عليك التأكد من قدرة الطرف الآخر على البدء بمحادثة مشفرة قبل الافصاح عن أي معلومات مهة. +\n +هل أنت جاهز للتحدث بكامل الخصوصية و الأمان؟\n هيا بنا! + + حساب جديد + إضافة حساب + تحرير حساب + إزالة حساب + حساب حالي + المستخدم @ المضيف - كلمة السر: - تذكر كلمة السر الخاصة بي. - تسجيل دخولي تلقائيا. - ليس لديك حساب؟ - + تسجيل الدخول لأمنك الخاص ، وإذا فقد أو سرق هاتفك ، انتقل إلى الموقع على جهاز الكمبيوتر الخاص بك و غير كلمة السر الخاصة بك. هذا الخيار يقوم بتسجيل الدخول تلقائياً كل مرة تفتح فيها هذا التطبيق. لتعطيل الخيار ،قم بتسجيل الخروج ثم امسح اختيار تسجيل الدخول التلقائي. - - تسجيل الدخول - - + تواصل عبر تور (يتطلب برنامج Orbot) + الاسم@العنوان + اسم مستخدم جديد + مقدم الخدمة (dukgo.com, jabber.ccc.de) + كلمة السر + تأكيد كلمة السر + إعدادات الحساب المتقدمة + نوع الحساب + تسجيل حساب + إصرار + تذكر كلمة المرور + تم حفظ كلمة السر مؤقتاً + لم يتم حفظ كلمة السر مؤقتاً + تسجيل الدخول تلقائيا + الاتصال تلقائياً عند تشغيل شات سيكيور (ChatSecure) + عدم الاتصال تلقائياً عند تشغيل شات سيكيور (ChatSecure) + تسجيل الدخول الآن + الاتصال بعد الانتهاء من إعداد الحساب + عدم الاتصال تلقائياً بعد إعداد الحساب + معلومات شخصية (اختياري) + الاسم المستعار + كيف تظهر حالة الحساب على الانترنت + الملف الشخصي + نبذة موجزة عن نفسك + أوجب استخدام التشفير، لا ترضى بالتحدث دون تشفير + عندما يكون ذلك ممكنا ، قم بتشفير محادثاتي تلقائياً + قم بتشفير المحادثات حسب الطلب + تعطيل تشفير المحادثات + التحقق من صحة البيانات… + توليد زوج المفاتيح السرية… + تسجيل الدخول… + المساعدة بإعداد الحساب + رقم حسابك + إعداد الموقع + هل أنت مستعد؟ + أدخل رقم حسابك لتضبيط شات سيكيور(ChatSecure) للاتصال بخدمة المحادثة (XMPP). رقم الحساب يشبه صيغة الايميل الالكتروني: + الرجاء إدخال رقم حسابك (اسم المستخدم @ الموقع) : + أدخل اسم موقع خدمة المحادثة و رقم المنفذ (رقم المنفذ الافتراضي هو 5222). + تم إعداد شات سيكيور (ChatSecure)، و حان الوقت للاتصال بالخدمة و التمتع بالمحادثة بكل أمان و سرية! + اوربوت/Orbot (تور/Tor) + اختر اسم نطاق الدردشة + جاري تسجيل حساب جديد… انطلق يا شات سيكيور! - - يتم تسجيل الدخول الآن - - - تم تعطيل البيانات الخلفية - - - - - - تمكين - - خروج - - - - - - + إلغاء تسجيل الدخول + يتم تسجيل الدخول الآن… + يتم تسجيل الخروج الآن… + + تفقد سجل SRV + استخدم نظام الأسماء للبحث عن خادم أكس أم بي بي من اسم النطاق + اسمح بإرسال إسم المستخدم و كلمة السر دون حماية عند استخدام الاتصال غير المشفر + اسمح بالتحقق من الهوية دون تشفير + تأكد من صلاحية الشهادة الإلكترونية + تأكيد TLS + إلزام استخدام TLS + تشفير الاتصال + كيف يتم ابتداء المحادثات المشفرة + الموقع الذي يتم الاتصال به، إذا دعت الحاجة + موقع الاتصال + منفذ ال TCP لخادم الإكس أم بي بي + منفذ الموقع + مصدر إكس أم بي بي XMPP + ليتم تمييز هذا الاتصال من الاتصالات الأخرى التي سجلت الدخول + أولوية مصدر اكس ام بي بي + الرسائل المرسلة لأصدقاء لديهم أكثر من مصدر فعال في نفس الوقت سترسل للمصدر ذو الاولوية الأعلى. + + المحادثات + لا توجد محادثات.\n +\n +اضغط هنا لبدأ واحدة! + لا توجد لديك حسابات تم ضبطها.\n +\n +اضغط هنا لإضافة حساب! + جهات الاتصال- %1$s + إضافة اسم + حذف الاتصال + حجب الاتصال + اﻹسم المستعار لجهة الاتصال + محجوب + اسم المستعار + سيتم حذف "%1$s" + سيتم حجب "%1$s" + سيتم إزالة الحجب عن "%1$s" + تمت إضافة "%1$s" + تم حذف "%1$s" + تم حجب "%1$s" + أزيل الحجب عن "%1$s" + محادثة جديدة + ابحث بالأسماء + أظهر الشبكة + إبدأ الدردشة + مشاهدة الملف الشخصي + تحقق من جهة الاتصال + المحادثات الصادرة (%1$d) + %1$d متصل دعوات الإضافة - (غير معروف) - فارغة - - لا توجد محادثات - - حدد جهات الاتصال ليتم دعوتها - اكتب اسم جهة الاتصال - لم يتم العثور على جهة الاتصال. - - - - لا توجد أسماء محظورة. - - + لا توجد محادثات\n\n +أنقر لبدأ الدردشة! + اتصال + أدخل الاسم المراد التحدث معه + انطلق + لا توجد محادثات حالياً. + إضافة اسم + عنوان البريد الإلكتروني للشخص الذي ترغب بإضافته: + إختر قائمة: + اكتب اسماً لإضافته من قائمة الأسماء. + إرسال دعوة معلومات جهة الاتصال - الحالة: - نوعية العميل: - الكمبيوتر - جوال - - - متصل - - مشغول - - بعيدا - - خامل - - غير متصل - - الظهور دون اتصال - - - - - + الأسماء المحجوبة %1$s + لا توجد أسماء محجوبة. + + تحدث مع %1$s أنا - - اكتب - - - - - - - - - - - - - - - - + %1$s متصل + %1$s بالخارج + %1$s مشغول + %1$s غير متصل. + %1$s قام بالدخول + %1$s قام بالخروج + أرسل صورة + إرسال ملف + أرسل ملف صوتي + أخذ صورة + نقل الملفات + اكتمال النقل + جاري النقل + قبول النقل؟ + يريد أن يرسل لك الملف + الاتصال غير متاح لإرسال مشاركتك ! إرسال - - لا يمكن إرسال هذه الرسالة. - - فقد الاتصال بمزود الخدمة. سوف ترسل الرسائل عندما يعود الاتصال. - - - - - + أرسل رسالة + أرسل رسالة آمنة + إعادة إرسال + نهاية دردشة + امسح سجل المحادثة + أدخل ابتسامة + انتقل بين المحادثات + القائمة + \" حدد الرابط - - لا توجد محادثات حالياً. - - البدء بالتشفير... - ايقاف التشفير... - - - إضافة اسم - - عنوان البريد الإلكتروني للشخص الذي ترغب بإضافته: - - إختر قائمة: - - اكتب اسماً لإضافته من قائمة الأسماء. - - إرسال دعوة - - جابر\ إكس أم بي بي - اكس ام بي بي محلي من دون خادم - + حذف + احفظ الملفات + حذف تخزين جلسة الدردشة؟ + سيتم حذف جميع الملفات الواردة والصادرة. +تحذير: لا يمكن التراجع بعد تنفيذ هذه العملية! + مسح النسخة الأصلية؟ + سيتم نسخ الملف إلى المخزن الآمن قبل إرساله. +هل تريد خذف الملف الأساسي من مخزن الهاتف الغير آمن؟ + إبق + صدّر + صدّر ملف الوسائط؟ + سيتم تصدير هذا الملف إلى %1$s + نهاية دردشة؟ + سبتم خذف كافة ملفات الوسائط في هذه الجلسة. لإصدار ملف وسائط، إضغط نقرة طويلة على الصورة المصغرة. + إنهاء الدرشة وحذف الملفات + + %1$s يدعوك لمحادثة جماعية. + تم ارسال دعوة ل٪ 1 $ ق. + قبول + رفض + دردشة جماعية + أنشأ أو انضم لدردشة جماعية. + الاتصال بمحادثة جماعية… + دعوة + حدد جهات الاتصال ليتم دعوتها + اكتب اسم جهة الاتصال + لم يتم العثور على جهة الاتصال. +\n\n +اضغط للاضافة. + إضافة %1$s ؟ + نعم + لا + فشلت محاولة تأكيد الاشتراك من %1$s . حاول مرة أخرى. + فشلت محاولة رفض الاشتراك من %1$s . حاول مرة أخرى. + + بدء التشفير + وقف التشفير + البدء بالتشفير… + ايقاف التشفير… + بصمة المفتاح + بصمة المفتاح + تسجيل الدخول + أعد توليد المفتاح + تم ايقاف التشفير + تم تشفيل التشفير (اضغط للتحقق) + قامت جهة الاتصال الأخرى بإيقاف التشفير بالمحادثة. + مشفر ومضمون! + يتم إنشاء زوج من مفاتيح التشفير خارج السجل OTR + + تم اكتشاف مفتاح OTR (تشفير خارج السجل) ليتم استيراده. هل تود القيام بمسح الQR كود الآن؟ + تشغيل KeySync + تم استيراد مفتاح التسجيل خارج السجل بنجاح + + مسح كود QR + البصمة الخاصة بمفتاحك + يدوي + سؤال + بصمة مفتاح الطرف الآخر (تم التحقق منها) + هل حقاً تريد التأكيد على بصمة هذا المفتاح؟ + تحقق من بصمة المفتاح؟ + تم التحقق من بصمة المفتاح الآخر. + بصمة مفتاحك + بصمة المفتاح ل + تثبيت ماسح الباركود؟ + هذا التطبيق بحاجة لماسح الباركود. هل تريد تثبيته؟ + + التحقق من الهوية + أدخل سؤال لصديقك، و الاجابة التي تتوقع منه وضعها، حتى تتحقق من هويته. + السؤال + الاجابة المتوقعة + طرف المحادثة الآخر نجح بالتحقق من هويتك. قم الآن بالتحقق من هويته عن طريق سؤالك الخاص. + سؤال\جواب للتحقيق OTR + تشفير الدردشة + إرسال + ألْغ + + إتصال آمن + إتصال آمن + أدخل حسابك في OStel.co أو أي حساب SIP لربط الاتصال. + إعداد الحساب + + الأمن والخصوصية التشفير و الخصوصية + تشفير On/Off + انتهاء المدة المؤقتة لحفظ كلمة السر + المدة التي يبقى بها تشفير البرنامح مفتوحاً. + + الوصلات القابلة للنقر عبر Tor + جعل الوصلات في المحادثات قابلة للنقر إذا كنت تستخدم حسابات عبر Tor (تحذير ! قد يكون هناك تسريب للخصوصية !) + واجهة المستخدم + اللغة + اللغات + استخدم إفتراضي النظام + استخدم قالب ألوان داكنة + غير ألوان الواجهة إلى الألوان الداكنة + مخزن الرسائل في الذاكرة فقط + إحفض الرسائل في الذاكرة فقط، وليس على ذاكرة الفلاش، للحماية ضد إستخراج الرسائل (قد يسبب خسارة بعض الرسائل) + صورة خلفية + حدد المسار (\"/sdcard/foo.jpg\") كصورة خلفية للبرنامج + أظهر شبكة جهات الاتصال + أظهر جهات الاتصال كشبكة من الصور الشخصية + نعم، اقبل الكل + حذف الملفات الغير آمنة + حذف الملف الأساسي تلقائياً من المخزن غير الآمن بعد استراده وتصديره + تخزين الوسائط في التخزين الخارجي + يمكن تخزين ملفات وسائط جلسات المحادثات في حاوية معماة داخل وسيلة تخزين داخلية أو خارجية. + + التخزين الخارجي غير موجود + إن سجلات المحادثات تخزَّن داخل بطاقة SD، لكن لا وجود لهذه البطاقة. يُرجى إدخال بطاقة SD الصحيحة، أو حذف سجل المحادثة الحالي ثم إعادة تشغيل ChatSecure. + مستودع وسائط المحادثات غير موجود + إن سجلات المحادثات تخزَّن داخل بطاقة SD، لكن لا وجود للملف في هذه البطاقة. يُرجى إدخال بطاقة SD الصحيحة، أو حذف سجل المحادثة الحالي ثم إعادة تشغيل ChatSecure. + حذف سجل المحادثة + + تعديلات أخرى + إبدأ شات سيكيور (ChatSecure) تلقائياً + دائماً إبدأ و ادخل بالحسابات تلقائياً اخفاء الأسماء الغير متصلة بالشبكة - - إعدادات التنبيهات - - تنبيهات المحادثة - - قم بالتنبيه بشريط التنبيه العلوي عندما تصل رسالة - استخدم أولوية المقدمة - قلل من احتمالية ان يقوم الاندرويد بإعادة الاتصال + قلل من احتمالية ان يقوم الاندرويد بإعادة تشغيل الإتصال. سيظهر إخطار في منطقة الإعلام. فترة النبض استخدم قيمة أعلى لحفظ بطاريتك. القيمة العالية قد تتسبب باغلاق اتصالك بسبب عدم نشاط الاتصال. + + إعدادات إخطارات + إخطارات المحادثة الفورية + قم بالتنبيه بشريط التنبيه العلوي عندما تصل رسالة اهتز - اهتز أيضا عند وصول الرسائل الفورية - الصوت - - كما يلعب النغمة عندما يصل الدردشة - - حدد النغمة - - - - - - - قبول - - انخفاض - - - - قبول - - انخفاض - - - - - - - - - - - - + لعب النغمة أيضاً عندما تصل محادثة فورية + استخدم نغمة خاصة بChatSecure + + تفعيل سجلات التنقيح + إخراج بيانات سجل التطبيق نحو الخرْج القياسي أو نحو logcat للتنقيح + + تم تعطيل بيانات الشبكة + بحاجة لاتصال على الشبكة (ولمعلومات خلفية) ليتم التفعيل وتسجيل الدخول. + تمكين + خروج + هل تريد تسجيل الخروج من جميع الخدمات و إغلاق جميع العمليات (hard exit)؟ + إنشاء حساب جديد؟ + إنشاء حساب محادثة جديد للمستخدم \'%1$s\' ؟ + معلومات الشهادة + شهادة: + صادر عن: + بصمة SHA1: + صادر في: + ينقضي في: + الانضمام إلى غرفة الدردشة؟ + تطبيق خارجي يحاول وصلك بغرفة دردشة. السماح؟ + اختر الصورة الخلفية + هل تريد انتقاء صورة خلفية من المعرض؟ + اختر الصورة + + رسائل جديدة من %1$s + %1$d الأحاديث الغير مقروءة + دعوة صديق جديد من %s + ملف جديد %1$s من %2$s دعوة لمحادثة جماعية - - - - - - - - - - - - بدء تشغيل خدمة المراسلة الفورية - يسمح للتطبيقات ببدء تشغيل خدمة المحادثة عن طريق الIntent. - - + دعوة محادثة مجموعة جديدة من %s + رسالة جديدة من + تشغيل خدمة ChatSecure… + تم التفعيل و فتح القفل + تم نسخ الرسالة إلى الحافظة + انتباه - - - + خطأ: + رمز الخطأ: %1$d + غير قادر على تسجيل الدخول إلى %1$s . حاول مرة أخرى لاحقاً. التفاصيل: %2$s + لم يتم إضافة القائمة. - لم يتم حظر جهة الاتصال - لم تتم إزالة الحظر عن جهة الاتصال - اختر جهة إتصال أولاً - قطع الاتصال! - خطأ بالخدمة! - لم يتم تحميل قائمة الاتصال. - لا يمكن الاتصال بالموقع. يرجى التحقق من اتصالك. - - - - - + %1$s موجود بقائمة الأسماء لديك. + تم حجب "%1$s" الرجاء الانتظار بينما نقوم بتحميل قائمة الاتصال الخاصة بك. - حدث خطأ في الشبكة. مطلوب اتصال واي فاي - الموقع لا يدعم هذه الوظيفة. - كلمة السر التي أدخلتها غير صالحة. - حصل خطأ بالموقع - الموقع لا يدعم هذه الوظيفة. - الموقع غير متوفر حاليا. - - لم يتم تنفيذ الطلب بالوقت المحدد - + لم يتم تنفيذ الطلب بالوقت المحدد. الموقع لا يدعم الإصدار الحالي. - قائمة انتظار الرسائل ممتلئة - الموقع لا يدعم إعادة التوجيه لاسم النطاق - لم يتم التعرف على اسم المستخدم الذي أدخلته. - عذرا ، تم حظر لك من قبل هذا المستخدم. - انتهت فترة الاتصال، الرجاء تسجيل الدخول مرة أخرى. - قمت بتسجيل الدخول ببرنامج آخر لقد قمت بتسجيل الدخول من برنامج آخر - عذرا ، تعذرت قراءة رقم الهاتف من البطاقة. يرجى الاتصال بمشغل الشبكة للحصول على مساعدة. - - لم تقم بتسجيل الدخول - - - + لست مسجَّلا حاليا. + واجه جيبربوت خطاً أثناء محاولة التحقق من إسم المستخدم أو كلمة السر - تأكد من صحتهما ثم حاول مجدَّدا. + حصل خطأ أثناء توليد زوج المفاتيح. + حصل خطأ أثناء محاولة الاتصال بالموقع. تأكد من إعدادات الاتصال ثم حاول مرة أخرى. + حصل خطأ أثناء محاولة الاتصال بالموقع. تأكد من اتصالك بالانترنت ثم حاول مرة أخرى. + فُقِد الاتصال بالشبكة + تجري الآن محاولة إعادة الاتصال + لم تُدخِل العنوان كاملا يجب أن ينتهي بصيغة @العنوان.كوم، جرب مرة أخرى! + العنوان الذي اخترته لا ينتهي ب دوت كوم أو دوت نيت أو ما يشابهه، تأكد منه مرة أخرى! + أدخل كلمة السر: + لأنك تستخدم شبكة تور، يجب أن تدخل عنوان خادم الاتصال XMPP Connect Server مباشرة بإعدادات الحساب المتقدمة + تحذير: تستخدم هذه الخدمة شهادة ذات مستوى تعمية ضعيفة. يُرجى تنبيه المسؤول لترقيتها. + لا تتطابق الشهادة المقدمة مع الشهادة المحددة: + تعذر إنشاء أو الانضمام لمحادثة مجموعة + عذرا، لا يمكن مشاركة هذا النوع من الملفات + يجب عليك تفعيل التشفير للتمكن من مشاركة الملفات + رجاءً، فعِّل تشفير المحادثات للتمكن من مشاركة الملفات + البيانات الواردة غير مدعومة، لا يمكن مشاركتها ! + كشف ChatSecure أنه تم محاولة إزالة عمليته. من المحتمل جداً أن ChatSecure سيتوقف عن العمل. يُرجى استخدام تسجيل خروج الكل من قائمة الحسابات بدلاً عن ذلك. + لا يوجد عارض لهذا الصنف من الملفات + يُرجى بدأ محادثة آمنة قبل مسح الرموز + لم تُستورَد محفظة مفاتيح OTR. تأكد من أن الملف بالصيغة أو المكان الصحيحين + يرجى نسخ ملف \'otr_keystore.ofcaes\' من سطح مكتب أداة KeySync إلى مجلد جذر جهاز تخزينك + لا يمكن إرسال هذه الرسالة. + لا يوجد اتصال حالياً. ستُرسَل الرسائل عند إعادة الاتصال. + %1$s غير متصل. أي رسائل ترسلها ستصل إلى %1$s عندما يقوم بالاتصال. + %1$s غير موجود بقائمة الأسماء لديك. + كلمات السر لا تتطابق + يجب على الأفضلية أن تكون رقم ضمن نطاق [0 … 127] + يجب على رقم المنفذ أن يكون رقماً + خطأ في النقل + لا يمكن قراءة مخزن الملفات + خطأ كبير: لا يمكن فتح أو تحميل قاعدة بيانات التطبيق. الرجاء إعادة تثبيت التطبيق أو محو البيانات. + لا وجود لتطبيق يدعم هذه الوصلة ! + إعدادات الحساب + + المجموعات + المحادثة (أوالمحادثات) المفتوحة + اخرج واقفل + فزع + أصدقاء + جهات الاتصال + قبول شهادة التعمية من الخادم؟ + اعرض بصمة مفتاحك + جاري التحميل… + + حول شات سيكيور\nhttps://guardianproject.info/apps/chatsecure/ + + قائمة الاتصال + إعدادات + الحساب + تسجيل الخروج + قائمة الاتصال + + Jabber (XMPP) + Local Area (Bonjour/ZeroConf) + حساب جوجل + dukgo.com + + + متصل + مشغول + بعيدا + خامل + غير متصل + الظهور دون اتصال + سعيد حزين - الرمش - اللسان تخرج + يرمش + اللسان خارج مندهش - تقبيل - صراخ - بارد - المال الفم - قدم في الفم - بالحرج + يقبل + يصرخ + جيد + يفعل ما يقول + يتحدث بغباء + محرَج ملاك - بعد - بكاء + ملتبَس عليه + يبكي شفاه مغلقة يضحك - الخلط + مضطرب - + سعيد حزين - الرمش - اللسان تخرج + يرمش + اللسان خارج مندهش - تقبيل + يقبل صراخ - بارد - المال الفم - قدم في الفم + جيد + يفعل ما يقول + يتحدث بغباء بالحرج ملاك - بعد + ملتبَس عليه بكاء شفاه مغلقة يضحك - الخلط + مضطرب :-) @@ -454,174 +557,33 @@ :-D o_O - - قائمة الاتصال - تشفير On/Off - تواصل عبر تور (يتطلب برنامج Orbot) - - شات سيكيور - هل هذه أول مرة تستخدم بها شات سيكيور (ChatSecure)؟ - غير قادر على الانتظار؟ - إبدأ - إعداد الحساب - - عن شات سيكيور (ChatSecure) - شات سيكيور (ChatSecure) هو تطبيق للرسائل الفورية يقوم بتوفير مميزات أمنية كثيرة تمنع الآخرين من التنصت على محادثاتك و اتصالاتك.\n\n يعمل هذا البرنامج مع العديد من خدمات الرسائل الفورية مثل جوجل توك. - - ما هي المميزات الأمنية؟ - التشفير دون تسجيل (خارج السجل) هو نظام أمني يستخدم لتوفير الخصوصية الكاملة عن طريق التشفير، اثبات الهوية، القدرة على إنكار وقوع المحادثة وضمان السرية المستقبلية. بروتوكول التشفير دون تسجيل يحفظ لك خصوصيتك وأمنك حتى لو تم الاستيلاء على جهازك أو أجهزة أصدقائك حيث لا يتم تخزين المحادثة المشفرة في جهازك أو جهاز صديقك أو أي موقع آخر. - - هل محادثاتي آمنة؟ - وظائف التشفير ببرنامج جيبربوت تعمل فقط عندما يقوم الطرف الآخر باستخدام برامج تدعم أيضاً مميزات التشفير دون تسجيل (خارج السجل) ، لذلك يجب عليك التأكد من قدرة الطرف الآخر على البدء بمحادثة مشفرة قبل الافصاح عن أي معلومات مهة. \n\nهل أنت جاهز للتحدث بكامل الخصوصية و الأمان؟ هيا بنا! - - إعداد كلمة السر - قبل أن نبدأ باستخدام جيبربوت، يرجى إدخال كلمة سر آمنة و محكمة حتى تمنع الدخول غير المصرح به على بيانات جيبربوت لديك. - كلمة السر: - أدخل كلمة السر مرة أخرى: - - الاسم@العنوان.كوم - اسم مستخدم جديد - مقدم الخدمة (dukgo.com, jabber.ccc.de) - كلمة السر - تأكيد كلمة السر - إعدادات الحساب المتقدمة - نوع الحساب - تسجيل حساب - - إصرار - تذكر كلمة المرور - تم حفظ كلمة السر مؤقتاً - لم يتم حفظ كلمة السر مؤقتاً - تسجيل الدخول تلقائيا - قم بالاتصال تلقائياً عند تشغيل شات سيكيور (ChatSecure) - لا تقم بالاتصال تلقائياً عند تشغيل شات سيكيور (ChatSecure) - تسجيل الدخول الآن - قم بالاتصال بعد الانتهاء من إعداد الحساب - لا تقم بالاتصال تلقائياً بعد إعداد الحساب - - معلومات شخصية (اختياري): - الاسم المستعار - كيف تظهر حالة الحساب على الانترنت - الملف الشخصي - نبذة موجزة عن نفسك - - أوجب استخدام التشفير، لا ترضى بالتحدث دون تشفير - عندما يكون ذلك ممكنا ، قم بتشفير محادثاتي - قم بتشفير المحادثات حسب الطلب - تعطيل تشفير المحادثات - - التحقق من صحة البيانات... - توليد زوج المفاتيح السرية... - تسجيل الدخول... - - واجه جيبربوت خطاً أثناء محاولة التحقق من البيانات، تأكد من صحتها ثم حاول مرة أخرى - حصل خطأ أثناء توليد زوج المفاتيح السرية. - حصل خطأ أثناء محاولة الاتصال بالموقع. تأكد من إعدادات الاتصال ثم حاول مرة أخرى - حصل خطأ أثناء محاولة الاتصال بالموقع. تأكد من اتصالك بالانترنت ثم حاول مرة أخرى - بصمة مفتاح الطرف الآخر - بصمة مفتاحك - تسجيل الدخول - أعد توليد المفتاح - تم فقد الاتصال بالشبكة - نقوم الآن بمحاولة إعادة الاتصال - تحذير: هذه المحادثة غير مشفرة - هذه المحادثة مشفرة لكن لم يتم بعد التحقق من هويات المشاركين بها - تحذير: تم إيقاف تشفير المحادثة - هذه المحادثة مشفرة و قد تم التحقق من هويات المشاركين بها - المساعدة بإعداد الحساب - رقم حسابك - إعداد الموقع - هل أنت مستعد؟ - أدخل رقم حسابك لتضبيط شات سيكيور(ChatSecure) للاتصال بخدمة المحادثة (XMPP). رقم الحساب يشبه صيغة الايميل الالكتروني: - الرجاء إدخال رقم حسابك (اسم المستخدم @ الموقع) : - أدخل اسم موقع مقدم خدمة المحادثة و رقم المنفذ (رقم المنفذ الافتراضي هو 5222). - تم إعداد شات سيكيور (ChatSecure)، و حان الوقت للاتصال بالخدمة و التمتع بالمحادثة بكل أمان و سرية! - لم تقم بإدخال العنوان كاملاً يجب أن ينتهي بصيغة @العنوان.كوم، جرب مرة أخرى! - العنوان الذي اخترته لا ينتهي ب دوت كوم أو دوت نيت أو ما يشابهه، تأكد منه مرة أخرى! - أدخل كلمة السر: - - إعدادات الحساب - defLoc - تحذير: هذه نسخة تجريبية من شات سيكيور (ChatSecure) و قد تحتوي على بعض الأخطاء أو الثغرات الأمنية. - - المجموعات - يتم إنشاء زوج من مفاتيح التشفير خارج السجل OTR - اتصال - أدخل الاسم المراد التحدث معه - انطلق - اللغة - اللغات - أي لغة تريد أن تعرض في InTheClear ؟ - تفقد سجل SRV - استخدم نظام الأسماء للبحث عن خادم أكس أم بي بي من اسم النطاق - اسمح بإرسال إسم المستخدم و كلمة السر دون حماية عند استخدام الاتصال غير المشفر - اسمح بالتحقق من الهوية دون تشفير - تأكد من صلاحية الشهادة الإلكترونية - تأكيد TLS - وجوب استخدام تشفير TLS/SSL - تشفير الاتصال - كيف يتم ابتداء المحادثات المشفرة - الموقع الذي يتم الاتصال به، إذا دعت الحاجة - موقع الاتصال - منفذ ال TCP لخادم الإكس أم بي بي - منفذ الموقع - مصدر إكس أم بي بي XMPP - ليتم تمييز هذا الاتصال من الاتصالات الأخرى التي سجلت الدخول - أولوية مصدر اكس ام بي بي - الرسائل المرسلة لأصدقاء لديهم أكثر من مصدر فعال في نفس الوقت سترسل للمصدر ذو الاولوية الأعلى. - التالي - عودة - المحادثات - رسالة جديدة من - التحقق من الهوية - أدخل سؤال لصديقك، و الاجابة التي تتوقع منهم وضعها، حتى تتحقق من هويتهم. - السؤال - الاجابة المتوقعة - طرف المحادثة الآخر نجح بالتحقق من هويتك. الآن قم بالتحقق من هويته عن طريق سؤالك الخاص. - لا توجد محادثات.\n\nاضغط هنا لبدأ واحدة! - استخدم قالب ألوان غامقة - لأنك تستخدم شبكة تور، يجب ان تدخل عنوان خادم الاتصال XMPP Connect Server مباشرة بإعدادات الحساب المتقدمة - - إبدأ شات سيكيور (ChatSecure) تلقائياً - دائماً إبدأ و ادخل بالحسابات تلقائياً - لا توجد لديك حسابات تم ضبطها.\n\nاضغط هنا لإضافة حساب! - حساب جوجل - مخزن الرسائل في الذاكرة فقط - فقط إحفض الرسائل في الذاكرة، وليس على ذاكرة الفلاش، للحماية ضد إستخراج الرسائل (قد يسبب خسارة بعض الرسائل) - صورة خلفية - حدد المسار (\"/sdcard/foo.jpg\") كصورة خلفية للبرنامج - الأمن والخصوصية - واجهة المستخدم - تعديلات أخرى - المحادثة/المحادثات المفتوحة - اوربوت/Orbot (تور/Tor) - اخرج - - هل تريد تسجيل الخروج من جميع الخدمات و إغلاق جميع العمليات (hard exit)؟ - أدخل عبارة سرية جديدة. يجب أن تحتوي على الاقل على حرف كبير capital و حرف صغير و رقم واحد، و أن تكون أطول من ست حروف. - يتم تشفير دفتر الملاحظات بالعبارة السرية الجديدة... - أدخل كلمة السر: - مرحباً! أدخل عبارة سرية قوية لتشفير دفتر ملاحظاتك. يجب أن تحتوي على الاقل على حرف كبير capital و حرف صغير و رقم واحد، و أن تكون أطول من ست حروف. - عبارتك السرية ليست طويلة بما يكفي. - لم تحتوي عبارتك السرية على أي حروف كبيرة. - لم تحتوي عبارتك السرية على أي حروف صغيرة. - لم تحتوي عبارتك السرية على أي أرقام. - افتح - شات سيكيور مقفل - أنشأ عبارة سرية - أدخل عبارة سرية - أدخل عبارة سرية جديدة - تأكيد العبارة السرية - العبارات السرية المدخلة لم تتطابق. حاول مرة أخرى. - إتصال أمن - أدخل حسابك في OStel.co أو أي حساب SIP لربط الاتصال. - dukgo.com - تم اكتشاف مفتاح OTR (تشفير خارج السجل) ليتم استيراده. هل تود القيام بمسح الQR كود الآن؟ - استيراد مفتاح تشفير خارج السجل OTR - تم استيراد مفتاح التسجيل خارج السجل بنجاح - لم يتم استيراد مفتاح التسجيل خارج السجل. تأكد من أن الملف بالصيغة أو المكان الصحيحين. - انتهاء المدة المؤقتة لحفظ كلمة السر - المدة التي يبقى بها تشفير البرنامح مفتوحاً. + حساب حالي + الاتصال بحسابي الموجود على خادم Jabber\XMPP محدد. + حساب جوجل + تواصل مع مستخدمي جوجل الآخرين باستخدام حسابك في جوجل. + الدردشة عبر شبكة WiFi. + دردش عبر شبكة WiFi دون الحاجة إلى خدمة الأنترنت أو خادم! + تفعيل الدردشة عبر WiFi + حساب جديد + قم بتسجيل حساب جديد ومجاني في خدمة ضمن لائحتنا الحالية، أو أي واحدة تختار. + إنشاء حساب جديد + الهوية السريّة! + خلق حساب شعلة، مجهول، من خلال نقرة بسيطة (بحاجة إلى Orbot: Tor for Android) + إنشاء هوية + هذه دردشة جماعية + [أُعيد إرسالِه] + [أُعيد إرسالِه] + لا يمكن مشاركة هذا الملف بطريقة آمنة + تثبيت Orbot؟ + يجب أن يكون Orbot مثبتا ومشغلا لتوكيل المرور عبره. هل تريد تثبيته ؟ + دائماً + إبدأ Orbot؟ + لا يبدو أن Orbot مشغَّل. هل تريد تشغيلِه والاتصال بشبكة Tor؟ + اللقب المُراد استخدامِه في الغرفة + إسم الغرفة المُراد إنشاؤها أو الانضمام إليها\" + خادم الدردشة الجماعية (conference.foo.com) + مخزن مفاتيح التعمية مشوَّه. الرجاء إعادة تثبيت شات سيكيور أو \'إفراغ البيانات\' لهذا التطبيق + وصلتك رسالة مشفرة غير قابلة للقراءة + لم أتمكن من فك تشفير الرسالة التي أرسلتها + < امسح يمنة ويسرة للمزيد من الخيارات > diff --git a/res/values-he/arrays.xml b/res/values-be/arrays.xml similarity index 100% rename from res/values-he/arrays.xml rename to res/values-be/arrays.xml diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml new file mode 100644 index 000000000..83c655060 --- /dev/null +++ b/res/values-be/strings.xml @@ -0,0 +1,136 @@ + + + + ChatSecure + ChatSecure + + Прачытаць паведамленні + Дазволіць прыладзе чатаць інфармацыю з сервіса імгненных паведамленняў + Напісаць паведамленні + Дазволіць прыладзе запісваць інфармацыю ў сервіс імгненных паведамленняў + Пачаць сервіс імгненных паведамленняў + Дазволіць прыладзе пачынаць сервіс імгненных паведамленняў праз інтэрнэт + + Пацвердзіць + Згода + Адмена + Згода + Адмена + Далей + Назад + Падлучыцца + + Адкрыць + ChatSecure заблакаваны + Задаць пароль + Пацвердзіць пароль + Пароль + Пры жаданні можна задаць майстар-пароль для ChatSecure каб прадухіліць доступ да вашых кантактаў і паведамленнях без пароля: + Пацвердзіць новы пароль + Пасворд не супадае, калі ласка праспрабуйце яшчэ раз + Прапусціць + Інфармацыя прадастаўлена + Увядзіце пароль + + Абярыце акаўнт + Абярыце акаўнт + Дадаць %1$s акаўнт + Пра прыладу + Выйсці адусюль + Вы жадаеце выйсці з усіх сервісаў + Вы выйшлі з %1$s. + Вы выйшлі з %1$s бо %2$s + Першы раз карыстаецеся ChatSecure? + Ня можаце дачакацца каб пачаць? + Пачаць + Налады акаўнта + Налады пароля + Перад тым як пачаць, калі ласка абярыце бяспечны пароль для абароны вашых дадзеных у ChatSecure ад несанкцыянаванага доступу. + Пароль: + Пароль (яшчэ раз): + Увядзіце новы пароль. Ён мусіць утрымліваць як мінімум адну вялікую, адну маленькую літару, адну лічбу, і быць ня меньш за 6 сімвалаў + Шыфраванне вашых існуючых нататак з новай парольнай фразай (трошкі цярпення …) + Увядзіце ваш пароль: + Запрашаем! Калі ласка, увядзіце пароль, каб абараніць свае нататкі. Ён мусіць ўтрымліваць як мінімум адну вялікую літару, адну маленькую і адну лічбу, і быць больш за 6 сімвалаў. + Ваш пароль не дастаткова доўгі + Ваш пароль не ўтрымлівае ніводнай вялікай літары + Ваш пароль не ўтрымлівае ніводнай маленькай літары + Ваш пароль не ўтрымлівае ніводнай лічбы + + Пра ChatSecure + ChatSecure гэта мабільная прылада для імгненных паведамленняў, з дапамогай якой вы абароніце сябе ад сачэнне іншых людзей за вашымі размовамі ды паведамленняў. \n\nПрылада падтрымлівае любыя сэрвіс імгненных паведамленняў, які выкарыстоўвае пратакол Jabber ці XMPP, напрыклад, Google Talk ці Jabber.org. + + Як праграма абараняе нас? + \"Off-The-Record Messaging\" - гэта сістэма, якая забяспечвае прыватнасць, імітуючы характарыстыкі прыватнай гутаркі ў рэальным свеце, у тым ліку шыфраванне, аўтэнтыфікацыю і прамую бяспеку.\n\nOTR-пратакол сумяшчальны з кліентамі для кампутараў, такімі як Adium або Pidgin. + + Ці бяспечны мой чат? + Функцыяй шыфравання ChatSecure працуе толькі тады, калі Вы абменьваецеся паведамленнямі з вашым суразмоўцам і ён таксама выкарыстоўваючы ChatSecure для смартфонаў ці Adium або Pidgin для кампутараў. Вы можаце дакладна наладзіць, як менавіта і калі ChatSecure спрабуе зашыфраваць вашыя гутаркі ў наладах акаўнта.\n\nДавайце пачнем! + + Новы акаўнт + Дадаць акаўнт + Рэдагаваць акаўнт + Выдаліць акаўнт + Існуючы акаўнт + + Пароль: + Запомніць мой пароль. + Уваходзіць аўтаматычна + Ці маецеn\u2019t акаўнт? + Увайсці + Калі Вы раптам згубілі тэлефон, ці ён быў скрадзены, то ў мэтах Вашай бяспекі, зайдзіце на сайт з кампутара і змяніце свой пароль. + Гэта опцыя для аўтаматычнага ўваходу пры кожным запуску прылады. Каб адключыць яе, выйдзеце з гэтага акаўнта і прыбярыце птушку \"Уваходзіць аўтаматычна\" + Падлучыцца праз TOR (неабходна прылада Orbot) + Новае імя карыстальніка + Сэрвіс правайдэр (dukgo.com, jabber.ccc.de) + пароль + пацвердзіць пароль + Дадатковыя налады акаўнта + Налады акаўнта + Рэгістрацыя акаўнта + Persistence + Запомніць пароль + Пароль захаваны + Пароль не захаваны + Уваходзіць аўтаматычна + Падлучаць ChatSecure пры старце + Не падлучаць ChatSecure пры старце + Падлучыцца зараз + Падлучыць наступныя налады акаўнта + Не падлучаць наступныя налады акаўнта + Асоба (апцыянальна) + Мянушка акаўнта (Вашае імя) + Як Ваш акаўнт з\'яўляецца онлайн + Профіль + Кароткая інфармацыя пра сябе + Прымусовае шыфраванне / забараніць адкрыты тэкст + + + + + + + + + Адмена + + + Налады акаўнта + + + + + + + + + + + + + + + + + Існуючы акаўнт + Новы акаўнт + diff --git a/res/values-bg/arrays.xml b/res/values-bg/arrays.xml index 264590084..444d89fc6 100644 --- a/res/values-bg/arrays.xml +++ b/res/values-bg/arrays.xml @@ -1,9 +1,9 @@ - + - - Необходима сили / - Автоматично Опит - Както се изисква - Никога увреждания / - + + Накарай / Изискай + Опитай Автоматично + Както е поискано + Изключено / Никога + diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml new file mode 100644 index 000000000..cf0c9020a --- /dev/null +++ b/res/values-bg/strings.xml @@ -0,0 +1,592 @@ + + + + ChatSecure + ChatSecure + + прочети съобщенията + +Разреши приложения да четат информация от компанията предоставяща чат услугата. + напиши съобщения + +Разреши приложения да предоставят информация за записване на компанията предоставяща чат услугата. + стартирай чат услуга + Разреши други програми да стартират чатове. + + Потвърди + ОК + Откажи + ОК + Откажи + Следващ + Назад + Свържи се + Пусни + Настройки + + Отворено + ChatSecure Заключен + Сложи Парола + Потвърди Паролата + Парола + Нова Парола + Потвърди Новата Паролата + Паролите не съвпадат, моля, опитай отново + Мързи ме (Без Парола!) + Информационно поле + Моля, въведи парола + Паролата моля… + Фразова парола + Фразова парола(повторно) + + Избери профил + Избери профил + (%1$d) + Добави %1$s профил + За програмата + Отпиши се от всички + Искаш ли да се отпишеш от всички профили? + Отписан си от %1$s. + Отписан си от %1$s, защото %2$s + За първи път с ChatSecure? + Нямаш търпение да започнеш? + Начало + Настройки на Профила + Задаване на Парола + Преди да започнем моля избери сигурна фразова парола, за да предпазиш своите ChatSecure данни от нежелан достъп. + Парола: + Парола (повтори): + Въведи *нова* парола. Тя трябва да се състои от поне една главна буква, една малка буква и една цифра, и да е по-дълга от 6 символа. + Зашифроване на съществуващите белeжки с новата парола (търпение…) + Въведи паролата: + Добре дошъл! Въведи сложна парола, за да подсигуриш бележките си. Тя трябва да се състои от поне една главна буква, една малка буква и една цифра, и да бъде по-дълга от 6 символа. + Паролата не е достатъчно дълга + В паролата липсват главни букви + В паролата липсват липсват малки букви + В паролата липсват липсват цифри + + За ChatSecure + ChatSecure е мобилна платформа за размяна на съобщения с добавени функции за сигурност, предпазване от подслушване на разговорите и дискусиите ти. + +Програмата подържа всеки профил, който е базиран на Jabber или XMPP протоколи, като Google GTalk или Jabber.org + + Как е подсигурен? + \'Off-the-Record Messaging\' е система за сигурност, която позволява поверителен разговор като предоставя характеристики типични за поверителен разговор в реалния свят като шифроване, проверка на самиличноста на събеседника, възможност да отречеш, че разговорът се е състоял, невъзможност за разкриптирване на разговора в бъдеще и други. + +OTR протоколът е съвместим с други приложения като Adium или Pidgin. + + Моите чатове сигурни ли са? + Шифроващата функция на ChatSecurure работи единствено с други съвместими програми, за това се увери, че събеседниците ти изплозват ChatSecure на мобилни устройства, Adium или Pidgin на компютър. Можеш да настроиш как и когат ChatSecure се опитва да криптира дискусиите ти в Настройки на Профила + +Да започваме! + + Нов Профил + Добави профил + Промени профила + Премахни профила + Съществуващ профил + + Потребител@Сървър + Парола: + Помни паролата ми. + Вписвай ме автоматично. + Нямаш профил? + Впиши ме + От съображения за сигурност, ако изгубиш телофона си или той бъде откраднат, влез в уеб сайта от компютъра си и промени паролата. + Тази опция те вписва автоматично всеки път, когато включиш програмата. За да изключиш тази опция, отпиши се и после премахни отметката пред \"Вписвай ме автоматично\". + Свържи се посредством Тор (Нуждае се от програмата Орбот) + user@domain.com + ново потребителско име + доставичик на услугата (dukgo.com, jabber.ccc.de) + парола + потвърди паролата + Допълнителни Настройки на Профила + Вид Профил + Регистрирай Профил + Запаметяване + Запомни Паролата + Паролата запаметена + Паролата не е запаметена + Автоматично Вписване + Свържи се при стартиране на ChatSecure + Не се свързвай при стартиране на ChatSecure + Впиши се Сега + Свържи се след завършване на настройките на профила + Не се свързвай се след завършване на настройките на профила + Лични (незадължителни) + Прякор на Профила (името ти) + Как да се държи акаунта ти, когато си на линия + Профил + Представи се накратко + Задължи шифроването / откажи несигурен текст + Когато е възможно, шифровай съобщенията автоматично + Шифровай съобщенията, когато това бъде поискано + Изключи шифроването на съобщенията + Удостоверяване на самоличността… + Генериране на ключ… + Вписване… + Стъпка по Стъпка Настройки на Профила + Идентификатор на Твоят Профил + Настрой Сървъра + Готов ли си? + Въведи своя индентификатор на профила, за да настроиш ChatSecure със своят XMPP доставичик. Прилича на имейл адрес: + Въведи профил идентификатора (потребител@сървър): + Моля, въведи или промени името на сървъра и порта(по подразбиране 5222) на твоя Jabber/XMPP сървър. + ChatSecure е нагласен и е време да те свържем, и започнеш да общуваш сигурно и поверително! + Орбот (Тор) + Избери доставчик + Регистрирване на нов профил… + подготвяне на ChatSecure… + Откажи вписването + Вписване\u2026 + Отписване\u2026 + + Търси с SRV + Използвай DNS SRV, за да откриеш реалните XMPP сървър и домейн. + Разреши изпращането на потребителското име и парола като видим от всекиго текст, когато използваш не шифрован транспорт + Разреши оторизиране с видим от всекиго текст + Провери дали сертификата се ползва с довереност + TLS Проверка + Изисква TLS връзка + Шифроване на Транспорта + как да започнеш шифрован чат + Сървър за свързване, в случай на нужда + Свържи се със Сървъра + TCP Порт за XMPP Сървър + Порт на Сървъра + XMPP Ресурс + да разграничиш това свързване от други програми, с които си вписан + Приоритет на XMPP Ресурса + Съобщения до клиенти с множество активни ресурса ще бъдат изпратени до ресурса с най-висок приоритет + + Дискусии + Няма дискусии.⏎ +⏎ +Чукни тук за да почнеш нова. + Нямаш⏎ +настроени профили.⏎ +⏎ +Чукни тук, за да добавиш! + Списък с Контакти - %1$s + Добави контакт + Изтрий контакта + Блокирай контакта + Прякор на контакта + Блокиран + Прякор + Контактът "%1$s" ще бъде премахнат. + Контактът "%1$s" ще бъде блокиран. + Контактът "%1$s" ще бъде разблокиран. + Контактът "%1$s" добавен. + Контактът "%1$s" изтрит. + Контактът "%1$s" блокиран. + Контактът "%1$s" разблокиран. + Нов Чат + Търси Контакти + Покажи в Таблица + Започни чат + Виж Профила + Потвърди Ключа + Текущи чатове (%1$d) + %1$d е на линия + Покана за приятелство + (Непознат) + Списъкът е празен + Дискусията е празна + Контакт + въведи името на контакта, с когото искаш да си пишеш + Давай + Няма текущи чатове. + Добави контакт + Имейл на човек, когото искаш да поканиш: + Избери списък: + Въведи име, за да добавиш от списъка с Контакти. + Изпрати покана + Профил на контакта + Присъствие: + Тип програма: + Компютър + Мобилно устройство + Блокирани контакти - %1$s + Няма блокирани контакти. + + Чат с %1$s + Аз + %1$s е на линия + %1$s отсъства + %1$s е зает + %1$s не е вписан + %1$s се присъедини + %1$s напусна + Изпрати Снимка + Изпрати Файл + Изпрати Аудио + Направи Снимка + Прехвърляне на Файл + Прехвърлянето Завършено + Прехвърля се в момента + Приеми прехвърляне? + иска да ти изпрати файл + Няма активна връзка за споделяне! + Изпрати + Прати съобщение + Прати поверително съобщение + Изпрати отново + Прекрати чата + Изчисти Чата + Вмъкни емотикон + Смени чата + Меню+ + Избери препратка + Изтрий + Запази файлове + Изтрий мястото на диска заделено за този чат? + Всички свалени и качени файлове в тази сесия ще бъдат невъзвратимо изтритие. +Предупреждение: тази операция не може да бъде върната. + Изтрий оригинала? + Файлът ще бъде копиран на специално подсигурено място на диска преди да бъде изпратен. Искате ли да изтриете оригиналния файл, който е на неподсигурено място на диска? + Запази + Експортирай + Експортирай медийния файл? + Този медиен файл ще бъде експортиран до %1$s + Прекрати чата? + Всички подсигурени елементи от тази сесия ще бъдат изтрити. Експортирайте медиен елемент като продължително натиснете иконата му. + Прекрати чата и изтрий файловете + + %1$s те кани да се присъединиш към групов чат. + Покана бе изпратена на %1$s. + Приеми + Откажи + Групов Чат + Създай или се Включи в Групова Чат + Свързване към групов чат… + Покани\u2026 + Избери контакт(и) за поканване + Пиши, за да намериш контакт + Не са намерени контакти. + +Чукни, за да поканиш. + Добави%1$s? + Да + Не + Неуспешен опит да потвърдиш познанство с %1$s. Моля, опитай отново по-късно. + Неуспешен опит да откажеш познанство с %1$s. Моля, опитай отново по-късно. + + Започни шифроването + Прекрати шифроването + Стартиране на шифрован чат… + Спиране на шифрован чат… + Сигурен Елeктронен Отпечатък + Сигурен Елeктронен Отпечатък + Впиши се + Регенерирай Ключа + Шифроването е изключено + Шифроването е включено (Чукни, за да провериш) + Твоят контакт прекрати шифрованият чат. + Зашифровано и проверено! + Генериране на нов OTR ключ… + + Открихме OTR ключов архив, който може да бъде добавен. Искаш ли да сканираш QR паролата сега? + Активирай KeySync + Успешно вмъкнат архив от OTR ключове + + Сканирай QR + Твоят елeктронен отпечатък + Ръчно + Въпрос + Електронен Отпечатък (Потвърден) + Сигурен ли си, че искаш да потвърдиш този електрoнен отпечатък? + Потвърди електронния отпечатък? + Дистанционния електронен отпечатък потвърден! + Електронен отпечатък за теб + Електронен отпечатък за + Инсталирай баркод скенер? + Тази програма се нуждае от скенер за баркодове. Желаете ли да го инстралирате? + + Проверка на Самоличостта + Въведи въпрос, който да бъде изпратен на контакта и отговорът, който очакваш да получиш от него. Това е с цел да провериш, че контактът е този, за който се представя. + въпроса ти + очакваният отговор + Твоят контакт потвърди самоличността ти. Сега ти потвърди неговата като му зададеш въпрос. + OTR Q&A проверка + Чат криптиране + Изпрати + Откажи + + Подсигурено Обаждане + Подсигурена Гласова Връзка + Въведи своя OStel.co или друг подсигурен SIP профил, за да интегрираш услугата гласова комуникация + + Настройки на Профила + + Сигурност и Неприкосновеност + Шифроване и Анонимност + Шифроване Вкл./Изкл. + Превишен период на неактивност за въвеждане на паролата + Продължителност на разшифрованост на програмата + + Аквитни препратки към Tor + За профили, използващи Тор, направи препратките активни(ПРЕДУПРЕЖДЕНИЕ, потенциален теч на информация за самоличноста) + Потребителски Интерфейс + Език + Езици + Използвай системата по подразбиране + Използвай Тъмна Тема + Използвай тъмната тема на програмата + Запаметяване Само във Времената Памет + Запаметявай съобщения само във временната памет, а не на диска. Това те предпазва от възможността някой да се сдобие със съобщенията ти. (Може да доведе до загуба на съобщенията) + Тапет + Задай пътя (\"/sdcard/foo.jpg\") до снимката за тапет + Покажи Таблица с Абонати + Покажи списъка с контакти като таблица с аватари + Да, Приеми Всички + Изтрий неподсигурения файл + След споделяне на снимка или файл, автоматично го изтрий от оригиналното, неподсигурено място на диска + Запази файла на SD карта + Файлове от час сесията се пазят в криптиран контейнер, който може да бъде съхраняван на вътрешната памет или SD карта. + + Липсва SD карта + Чат транскрипциите се пазят на SD карта, но такава не е намерена. Моля, вкарайте SD карта, или изтрийте съществуващите чат транскрипции и рестартирайте ChatSecure. + Липсва SD карта за съхранение на файлове + Чат транскрипциите се пазят на SD карта, но файлът не е намерен на текущата SD карта. Моля, вкарайте правилната SD карта, или изтрийте съществуващите чат транскрипции и рестартирайте ChatSecure. + Изтрий чат транскрипциите + + Други Настройки + Старирай ChatSecure Автоматично + Винаги стартирай и ме вписвай автоматично във всички профили, в които съм бил вписан преди + Скрий отписани абонати + Задай висок приоритет + Намалява шанса Android да преустанови връзката. Това ще добави известие в лентата за известия. + Интервал на сърцебиене + Задай по-високо стойност (в минути), за да пестиш батерията. Твърде високо число може да обърка собственика на услугата и да прекрати връзката поради липса на активност. + + Настройки за известията + Известия при съобщения + Уведоми в статус лентата, когато пристигне ново чат съобщение + Вибрирай + Също вибрирай, когато пристигне чат съобщение + Звук + Също прозвъни, когато пристигне съобщение + Използвай специален ChatSecure звуков сигнал + + Разреши Дебъг Докладите + Препращай информацията от логът към стандартния изход / логкат при дебъгване + + Интернетът е изключен + +Програмата се нуждае от връзка с интернет(включително и на заден фон), за да може да те вписва. + Разреши + Изход + Искаш ли да се отпишеш от всички профили И да спреш всички процеси(твърд изход)? + Създай нов профил? + Създай нов чат профил за потребител \'%1$s\'? + Информация за сертификата + Сертификат: + Издаден от: + SHA1 отпечатък: + Издаден: + Изтича: + Присъедини се в чат стаята: + Външна програма се опитва да те свърже с чат стая. Разреши? + Избери тапет + Желаете ли да изберете тапет от Галерия? + Изберете картинка + + Нови %1$s съобщения + %1$d непрочетени съобщения + Нова покана за приятелство от %s + Нов файл %1$s от %2$s + Покана за групов чат + Нова покана за групов чат от %s + Ново съобщение от + Стартиране на ChatSecure… + Активиран & отключен + съобщение копирано + + Внимание + Грешка: + Грешка с код %1$d + Неуспешен опит за вписване в %1$s. Моля, опитайте отново по-късно\n(Информация: %2$s) + Списъкът не е добавен. + Контактът не е блокиран. + Контактът не е разблокиран. + Моля, първо избери контакт. + Изгубена връзка! + Възникна проблем с услугата! + Списъкът с контакти не е зареден. + Неуспешен опит за връзка със сървъра. Моля, провери интернет връзката си. + "%1$s" е в списъка ти с Контакти. + Контактът "%1$s" е блокиран. + Моля, изчакай докато списъка ти с Контакти се зарежда. + Настъпи проблем с интернет връзката. + WiFi е нужно за тази връзка. + Сървърът не подържа тази функция. + Въведената парола е невалидна. + Възника вътрешен проблем със сървърът. + Сървърът не подържа тази функция. + Сървърът е временно недостъпен. + Връзка със сървърът отнема твърде дълго. + Сървърът не подържа тази версия. + Прекалено много съобщения чакат на опашка да бъдат изпратени от сървъра. + Сървърът не подържа препращане към този домейн. + Въвденото потребителско име не е разпознато. + Съжалявам, но си блокиран от потребителя. + Сесията изтече, впиши се отново. + вписа се от друга програма. + вписа се от друга програма. + Съжалявам, но телфонният ти номер не може да бъде прочетен от SIM картата. Моля, свържи се с оператора си. + В момента не си вписан. + ChatSecure се натъкна на грешка при проверката на потребителското име или паролата - провери ги и опитай отново. + ChatSecure се натъкна на проблем при генерирането на ключ. + ChatSecure се натъкна на проблем при свързването със сървъра - моля провери настройките и опитай отново. + ChatSecure се натъкна на проблем докато се свървзваше - моля провери интернет връзката и опитай отново. + ChatSecure загуби връзка с мрежата + ChatSecure се опитва да установи връзка + Не въведе @сървър.com частта от своят профилен идентификатор. Опитай отново! + .com или подобен идентификатор липсваше от края на сървъра. Опитай отново! + Въведи паролата: + Тъй като ползваш Тор, трябва да зададеш XMPP \'Connect Server\' сървър саморъчно в Допълните Настройки на Профила + ПРЕДУПРЕЖДЕНИЕ: Тази услуга използва сертификат със СЛАБА криптография. Моля, пишете на администратора да го обнови. + Полученият сертификат не съвпада с ВГРАДЕНИЯ сертификат: + Неуспешен опит за създаване или присъединяване към групов чат + Съжаляваме, но не можем да споделим този тип файл + Трябва да разрешиш шифроването, за да споделяш файлове + Моля включи шифроването, за да споделяш файлове + Неподържан поток от информация, споделянето невъзможно! + ChatSecure усети, че е отправена заявка за спиране на програмата. ChatSecure най-вероятно ще спре. За в бъдеще, моля, използвай опцията Отпиши от всички в менюто със списъка с профили. + Липсва подходяща програма за възпроизвеждане на файл в този формат + Моля, започнете подсигурен чат преди да сканирате кодове + OTR архив с ключове не е вмъкнат; Моля, провери дали файла съществува и дали е в правилния формат, и на правилното място + Моля копирай \'otr_keystore.ofcaes\' от компютърната програма KeySync до root директрията на твоето мобилно устройство + Това съобщение не може да бъде пратено. + Съобщенията ще бъдат пратени, когато се свържеш + %1$s не е вписан. Изпратени съобщения ще бъдат доставени, когато %1$s се появи на линия. + %1$s не е в списъка с Контакти. + Паролите не съвпадат + Приоритетът трябва да е число в диапозона [0..127] + Портът трябва да е число + Грешка в трансфера + Неуспешно прочитане на файл от диска + Голям проблем: Неуспешно отключванеи зареждане на базата данни. Моля, преинсталирайте програмата или изтрийте информацията. + Не е намерено инсталирано приложение, което да може да изпозлва тази препратки! + Account Settings + + Групи + текущи дискусии + Изход + Паника + Приятели + Контакти + Приеми Сертификата на Сървъра? + Електронен Отпечатък + зареждане… + + За ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Списък с Контакти + Настройки + Профили + Отпиши се + Списък с контакти + + Jabber (XMPP) + Локална Мрежа (Bonjour/ZeroConf) + Google Профил + dukgo.com + + + На линия + Зает + Отсъства + Бездейства + Отписан + Невидим + + + Щастлив + Тъжен + Намигващ + Изплезен език + Изненадан + Целуващ + Крещящ + Спокоен + Пари в устата + Изтървал се + Засрамен + Ангел + Нерешен + Плачещ + Пазещ тайна + Смеещ се + Объркан + + + + Щастлив + Тъжен + Намигващ + Изплезен език + Изненадан + Целуващ + Крещящ + Спокоен + Пари в устата + Изтървал се + Засрамен + Ангел + Нерешен + Плачещ + Пазещ тайна + Смеещ се + Объркан + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Съществуващ профил + Свържи ме със съществуващият ми профил на точно определен Jabber / XMPP сървър. + Google профил + Чатвай с други Google потребители използвайки съществуващия си Google профил. + WiFi мeш чат + Чатвай с други на същата WiFi мрежа или меш - не е нужна интернет връзка или сървър! + Включи WiFi чат + Нов Профил + Регистрирайте нов, безплатен профил от вградения ни списък с услуги, или която и да друга. + Създай нов профил + Тайна самоличност! + Създай анонимен, еднократен чат акаунт с едно лесно натискане (изисква Orbot: Tor за Андроид) + Създай самоличност + Това е групов чат + [повторно изпратено] + [повторно изпратено] + Не е възможно сигурно споделяне на този файл + Инсталирай Orbot? + Трябва да имате Orbot инсталиран и активиран, за да изпращате трафика си през него. Желаете ли да го инсталирате? + Винаги + Стартиране на Orbot? + Orbot не изглежда да е активен. Желаете ли да го стартирате и свържете към Tor? + прякор, който да бъде използван в тази стая + име на стаята за създаване или присъединяване\" + groupchat server (conference.foo.com) + Складът за ключове е развален. Моля, преинсталирайте ChatSecure или изберете \'изтриване на информацията\' за приложението + Получихте нечетливо криптирано съобщение + Не мога да разкриптирам съобщението, което изпратихте + < плъзнете наляво и надясно за допълнителни опции > + diff --git a/res/values-bo/arrays.xml b/res/values-bo/arrays.xml index a333f21ba..046b1aea7 100644 --- a/res/values-bo/arrays.xml +++ b/res/values-bo/arrays.xml @@ -6,10 +6,4 @@ རེ་སྐུལ་ཞུས་བ་ནང་བཞིན བྱེད་དཀའ་བའམ་ཡང་ན་ནམ་ཡང་བྱེད་མི་ཐུབ་པ། - - ཨུ་ཚུགས - རང་བཞིན་ནས - རེ་སྐུལ་བྱས - མི་ཐུབ་པ་བཟོས - diff --git a/res/values-bo/strings.xml b/res/values-bo/strings.xml index 76732eaee..602f63422 100644 --- a/res/values-bo/strings.xml +++ b/res/values-bo/strings.xml @@ -1,397 +1,501 @@ - - ད་ལྟ་འདིར་མེད། - - ཡིག་འཕྲིན་བྲིས། - ཉེར་སྤྱོད་དག་ལ་IM ནང་དོན་མཁོ་སྤྲོད་ཀྱི་སྒང་དུ་རེའུ་མིག་འབྲི་རུ་བཅུག - - - - གླེང་མོལ། - ཐོ་མཛོད་ཞིག་གདམ། - - དེའི་སྐོར། - - ཐོ་མཛོད་ཀྱི་སྐོར། - - ཐོ་མཛོད་འདི་རྩོམ་སྒྲིག་བྱེད། - - ཐོ་མཛོད་སུབ། - - ཆ་ཚང་སྒོ་རྒྱོབས། - - - གླེང་མོལ། - ཐོ་མཛོད་ཞིག་གདམ། - - - - - ནང་འཇུག་མཚམས་ཞོག - - - འབྲེལ་གཏུག་ནང་འཇུག - - འབྲེལ་མིང་སུབ། - - འབྲེལ་མིང་བཀག - - བཀག་ཟིན། - - ཡིག་སྒམ། - - གླེང་མོལ་གསར་བ། - - ཁ་བྱང་གསར་བ། - - སྒྲིག་བཟོ། - - འབྲེལ་མིང་འཚོལ། - - གླེང་མོལ་འགོ་འཛུགས། - - སྒོ་རྒྱོབས། - - བདེན་དཔང་བྱེད། - - - གསང་སྡོམ་འགོ་བརྩམ། - གསང་སྡོམ་མཚམས་འཇོག - གླེང་མོལ་གཙང་མ་བཟོས། - གླེང་མོལ་མཚམས་འཇོག - - - འབྲེལ་གཏུག་ཐོ་གཞུང་། - - མགྲོན་འབོད་བྱེད།\u2026 - - གླེང་མོལ་བརྗེ་པོ་རྒྱོབས། - - སེམས་ཁམས་མཚོན་རྟགས་འཇུག - བསྐྱར་དུ་ཐོངས། - - མཛུབ་རིས་ཞིབ་བརྟག་བྱེད། - ཁྱེད་རང་གི་མཛུབ་རིས། - མཛུབ་རིས་ཞིབ་བརྟག་བྱེད། - གསང་བ་ཞིབ་བརྟག་བྱེད། - - དེམ་ཐོ།+ - - - + + + ཉེན་སྲུང་གླེང་མོལ། + ཉེན་སྲུང་གླེང་མོལ། + + འཕྲིན་ཐུང་རྣམས་ཀློགས། + +འཕྲིན་ཐུང་གི་དཀར་ཆག་མཁོ་སྤྲོད་གྱི་རྩ་ནས་ཉེར་སྤྱོད་མ་ལག་ལ་ནང་གསེས་དོན་ཚན་ཁག་ཀློག་བཅུག + འཕྲིན་ཐུང་ཕྲིས། + +ཉེར་སྤྱོད་མ་ལག་དག་ལ་འཕྲིན་ཐུང་གི་དཀར་ཆག་མཁོ་སྤྲོད་ཀྱི་སྒང་དུ་བརྗོད་བྱ་དག་ནང་འཇུག་བྱེད་བཅུག + འཕྲིན་ཐུང་ཞབས་ཞུ་འགོ་ཚུགས། + ཉེར་སྤྱོད་ཁག་ལ། དྲ་ལམ་བརྒྱུད་འཕྲིན་ཐུང་ཞབས་ཞུ་བེད་སྤྱོད་གཏོང་བཅུག + ངེས་བརྟན་གྱིས། - - ཁྱེད་ཀྱིས་ཞབས་ཞུ་ཚང་མ་སྒོ་རྒྱག་འདོད་དམ། - - - - - - - འགྲིག - ཕྱིར་འཐེན་བྱེད། - འགྲིག - ཕྱིར་འཐེན་བྱེད། - - - - - - - - - + རྗེས་མ། + རྒྱབ་ངོས། + མཐུད་། + ཁ་ཕྱེས། + སྒྲིག + + ཁ་ཕྱེས། + ཉེན་སྲུང་གླེང་མོལ་སྒོ་བརྒྱབ་འདུག + གསང་ཚིག་བཟོས། + གསང་ཚིག་ངེས་གཏན་བཟོས། + གསང་ཚིག + ཁྱེད་རང་ལ་ཉེན་སྲུང་གླེང་མོལ་ཆེད་གསང་ཡིག་གཙོ་བོ་ཞིག་བཟོ་རྒྱུའི་གདམ་ཀ་ཡོད། དེ་བཟོས་ན་གསང་ཡིག་མེད་པར་གཞན་གྱིས་ཁྱེད་རང་གི་ཡིག་ཆུང་དང་འབྲེལ་ཐོར་བལྟ་མི་ཐུབ། + གསང་ཚིག་གསར་པ་ངེས་གཏན་བཟོས། + གསང་ཡིག་གཉིས་མཐུན་གྱི་མིན་འདུག བསྐྱར་དུ་བཟོས། + གཡུག་ཞོག + ལམ་སེང་གནས་ཚུལ། + སྐུ་མཁྱེན། གསང་ཚིག་ནང་འཇུག་བྱོས། + སྐུ་མཁྱེན། གསང་ཚིག་ནང་འཇུག་བྱོས། + གསང་ཚིག + གསང་ཚིག (སླར་ཡང་བཅུག) + + ཐོ་བྱང་ཞིག་འདེམས། + ཐོ་བྱང་ཞིག་འདེམས། + (%1$d) + ཐོ་བྱང་%1$sཡི་ཁར་སྣོན། + དེའི་སྐོར། + ཚང་མ་ནས་ཕྱིར་ཐོན། + ཁྱེད་རང་འཕྲིན་ཐུང་ཡོ་ཆས་ཚང་མ་ནས་ཕྱིར་ཐོན་གི་ཡིན་ནམ། + ཁྱེད་རང་%1$sནས་ཕྱིར་ཕུད་འདུག + ཁྱེད་རང་%1$sནས་ཕྱིར་ཕུད་འདུག རྒྱུ་མཚན་ནི་%2$s + ཐེངས་དང་པོར་ཉེན་སྲུང་གླེང་མོལ་བེད་སྤྱོད་གཏོང་གི་ཡོད་དམ། + འགོ་འཛུགས་པར་རྔམས་ཀྱི་འདུག་གམ། + འགོ་བཙུགས་། + ཐོ་བྱང་སྒྲིག་འཇུག + གསང་ཚིག་སྒྲིག་སྦྱོར། + ཁྱོད་ཀྱིས་འགོ་འཛུགས་མ་བྱས་གོང་ལ། ཉེན་སྲུང་གླེང་མོལ་ནང་གཞན་གྱིས་ལྐོག་འཛུལ་མ་ཐུབ་པའི་ཆེད་དུ་གསང་ཡིག་བརྟན་པོ་ཞིག་འདེམས། + གསང་ཚིག : + གསང་ཚིག (སླར་ཡང་བཅུག): + གསང་ཚིག་གསར་པ་བྲིས། དེར་ཉུང་མཐར་ཡིག་འབྲུ་དྲུག་དགོས་ལ་དེའི་ནང་ཡིག་ཆེན་གཅིག་དང་ཡིག་ཆུང་གཅིག ཨང་ཀི་གཅིག་བཅས་དགོས། + ཁྱེད་ཀྱི་གསང་ཚིག་གསར་བ་དེ་དང་ད་ཡོད་ཟིན་ཐོ་ཁག་གསང་སྡོམ་བྱེད་བཞིན་ཡོད། བཟོད་སྒོམ་གནང་རོགས། + ཁྱོད་ཀྱི་གསང་ཚིག་ནང་འཇུག་བྱོས། + བྱོན་པ་ལེགས། ཁྱེད་རང་གི་ཟིན་བྲིས་ཉེན་སྲུང་ཆེད་གསང་ཡིག་གསར་པ་བརྟན་པོ་ཞིག་བྲིས། དེ་ལ་ཉུང་མཐར་ཡང་ཡི་གེ་དྲུག་དང་དེའི་ནང་ཡིག་ཆེན་གཅིག་དང་ཡིག་ཆུང་གཅིག ཨང་ཀི་གཅིག་བཅས་དགོས། + ཁྱོད་ཀྱི་གསང་ཚིག་དེ་རིང་པོ་རེད་མི་འདུག + ཁྱེད་རང་གི་གསང་ཚིག་ནང་དུ་ཡིག་ཆེན་མི་འདུག + ཁྱེད་རང་གི་གསང་ཚིག་ནང་དུ་ཡིག་ཆུང་མི་འདུག + ཁྱོད་ཀྱི་གསང་ཚིག་ནང་ཨང་ཀི་མི་འདུག + + ཉེན་སྲུང་གླེང་མོལ་གྱི་སྐོར། + ཉེན་སྲུང་གླེང་མོལ་ནི་ལག་འཁྱེར་ཁ་པར་གྱི་འཕྲིན་ཐུང་ཉེར་སྤྱོད་མ་ལག་ཞིག་ཡིན་ལ། དེས་ཁྱེད་ཀྱི་གླེང་མོལ་དང་འབྲེལ་ལམ་ལ་གཞན་གྱིས་ལྐོག་བལྟ་བྱེད་པ་ལས་འགོག་གི་རེད། +ཉེར་སྤྱོད་མ་ལག་འདི་ཡིས་གླེང་མོལ་ཞབས་ཞུ་གཞན་ལ་བེད་སྤྱོད་བྱེད་ཐུབ། དཔེར་ན། Jabberདང་XMPPའཕྲིན་གཏོང་སྒྲིག་མཐུན་མ་ལག་ Google GTalkདང་ Jabber.org ལ་སོགས། + + དེའི་བདེ་འཇགས་ཇི་འདྲ་ཡིན་ནམ། + ཟིན་ཐོ་མེད་པའི་འཕྲིན་གཏོང་ནི་འཛམ་གླིང་ནང་གི་སྒེར་གྱི་གླེང་མོལ་ཚང་མ་གསང་བདེ་ཡོང་བའི་ཆེད་དུ་བཟོས་བའི་བདེ་འཇགས་མ་ལག་ཞིག་སྟེ། དེའི་ཁོངས་སུ་གསང་སྡོམ་དང་ར་སྤྲོད། ཕྱི་འདོར་། གསང་གཏོང་བཅས་ཚུད། + +OTR-protocol ཡིས་དྲ་ཐོག་གླེང་མོལ་ Adium དང་ཡང་ན་ Pidgin ལྟ་བུའི་མཉམ་དུ་གནས་ཐུབ། + + + + ངའི་གླེང་མོལ་དེ་དག་བཙན་པོ་ཡིན་ནམ། + ཉེན་སྲུང་གླེང་མོལ་གྱི་གསང་སྡོམ་གྱི་མཐུན་རྐྱེན་དེ་ཉེར་སྤྱོད་མ་ལག་གཅིག་པ་བེད་སྤྱོད་གཏོང་མཁན་ཁོ་ནའི་མཉམ་དུ་ལས་ཀ་བྱེད་ཀྱི་རེད། བྱས་ཙང་། ཁྱེད་ཀྱི་འབྲེལ་བ་བྱེད་ས་ཚོས་ཀྱང་ལག་འཁྱེར་ཁ་པར་དང་གློག་ཀླད་སྟེང་ཉེན་སྲུང་གླེང་མོལ་ངེས་པར་བེད་སྤྱོད་གཏོང་གི་ཡོད་པ་ཤེས་དགོས། ཁྱེད་ཀྱིས་ཐོ་མཛོད་སྒྲིག་བཟོའི་ཁོངས་སུ། ཉེན་སྲུང་གླེང་མོལ་མ་ལག་གིས་དུས་ནམ་ཞིག་དང་ཇི་ལྟར་ཁྱེད་ཀྱི་གླེང་མོལ་དག་གསང་སྡོམ་བྱེད་བྱེད་ཐུབ། +ད་འགོ་འཛུགས། + + ཐོ་བྱང་གསར་བ། + ཐོ་བྱང་ཞིག་ཁ་སྣོན་བྱོས། + ཐོ་བྱང་ལ་སྒྱུར་བཅོས་གྱིས། + ཐོ་བྱང་སུབ། + ད་ཡོད་ཐོ་བྱང་། + User@Host - གསང་ཚིག - - ངའི་གསང་ཚིག་དྲན་པར་བྱེད། - + ངའི་གསང་ཚིག་དྲན་པར་གྱིས།། ངར་ རང་འགུལ་གྱིས་ནང་དུ་འཇུག - - ཁྱེད་རང་ལ་ཐོ་མཛོད་མེད་དམ། \u2019 - - ཁྱེད་རང་གི་བདེ་འཇགས་ཆེད། གལ་ཏེ་ཁྱེད་ཀྱི་ལག་འཁེར་ཁ་པར་བརླགས་པའམ་བརྐུས་པ་སོགས་བྱུང་ན། རྩི་ས་འཁོར་ནས་དྲ་གནས་ནང་འཛུལ་ཏེ་གསང་ཚིག་བརྗེ་བོ་རྒྱོབས། - ཁྱེད་ཀྱིས་ཉེར་སྤྱོད་འདི་ཁ་ཕྱེས་ཐེངས་རེར་། གདམ་ཀ་འདིས་ཁྱེད་རང་ནང་འཇུག་བྱེད་གི་རེད་། གདམ་ཀ་དེ་ལས་མེད་བཟོ་ན། ཕྱི་ལ་ཐོན། དེ་ནས་\"ང་རང་བཞིན་གྱིས་ནང་འཇུག་བྱོས་\"ཟེར་བའི་སྒམ་ཆུང་ནང་གི་རྟགས་དེ་གསུབ། - + ཁྱེད་རང་ལ་ཐོ་བྱང་མེད་དམ། ནང་དུ་ཞུགས། - - - + གལ་སྲིད་ཁྱེད་ཀྱི་ལག་འཁྱེར་ཁ་པར་བརླགས་སོང་ན། ཁྱེད་རང་གི་བདེ་འཇགས་ཆེད་ཀམ་པུའུ་ཊ་རྒྱུར་དྲ་གནས་ནང་ཕྱིན་ཏེ་གསང་ཡིག་བརྗེ་དགོས། + གདམ་ཀ་འདིས་ཁྱེད་ཀྱིས་ཉེར་སྤྱོད་ག་དུས་འདི་ཁ་ཕྱེ་ཡང་རང་བཞིན་གྱི་ནང་དུ་འཛུལ་ཐུབ་ཀྱི་རེད། དེ་མེད་པ་བཟོ་འདོད་ན། དང་པོ་ཕྱི་ལ་ཐོན། དེ་ནས་༼ང་རང་བཞིན་གྱིས་ནང་འཇུག༽ཟེར་བའི་སྒམ་ཆུང་དེའི་ནང་གི་རྟགས་དེ་སུབ་དགོས། + TOR ཊོར་རྒྱུད་ནས་མཐུད། (འདིའི་ཆེད་༼ཨོར་རྦེ་ཌིOrbot༽ཉེར་སྤྱོད་མ་ལག་དགོས།) + + user@domain.com + བེད་སྤྱོད་བྱེད་མཁན་གྱི་མིང་གསར་པ། + ཞབས་ཞུ་སྤྲོད་མཁན (dukgo.com, jabber.ccc.de) + གསང་ཚིག + གསང་ཚིག་ངེས་གཏན་བཟོས། + ཚད་མཐོའི་ཐོ་བྱང་སྒྲིག་བཟོ། + ཐོ་བྱང་གི་རིགས། + ཐོ་བྱང་དེབ་སྐྱེལ་བྱོས། + རྒྱུན་མཐུད་། + གསང་ཚིག་དྲན་པར་གྱིས། + གསང་ཚིག་ཉར་ཟིན། + གསང་ཚིག་ཉར་མེད། + རང་འགུལ་གྱིས་ནང་དུ་འཛུལ། + ཉེན་སྲུང་གླེང་མོལ་གྱི་འགོ་འཛུགས་ལ་མཐུད། + ཉེན་སྲུང་གླེང་མོལ་གྱི་འགོ་འཛུགས་ལ་མ་མཐུད། + ད་ལྟ་ནང་དུ་ཞུགས། + གཤམ་གྱི་ཐོ་བྱང་སྒྲིག་འཇུག་དེར་མཐུད། + གཤམ་གྱི་ཐོ་བྱང་སྒྲིག་འཇུག་དེར་མ་མཐུད། + དོ་བདག་གི་(འདམ་ཀ།) + ཐོ་བྱང་གི་མིང་གཞན།(ཁྱེད་ཀྱི་མིང་།) + སོ་སོའི་ཐོ་བྱང་དྲ་ཐོག་ལ་ཇི་ལྟར་མངོན་གྱི་ཡོད་དམ། + ངོ་སྤྲོད། + སོ་སོའི་སྐོར་ཐུང་ཙམ་ཞིག + འཕྲིན་ཐུང་དཀྱུས་མ་ངོས་ལེན་མ་བྱོས/ གསང་སྡོམ་བཟོས། + གལ་སྲིད་ཐུབ་ན། གླེང་མོལ་ཆ་ཚང་རང་བཞིན་ནས་གསང་སྡོམ་ཐུབ་པ་བྱོས། + རེ་སྐུལ་ལྟར་གླེང་མོལ་གསང་སྡོམ་བྱས། + གླེང་མོལ་གསང་སྡོམ་བྱེད་མཚམས་ཞོག + རྒྱབ་ལྗོངས་ནུས་ལྡན་བཟོ་བཞིན་པ། + ལྡེ་མིག་གི་ཆ་བསྐྲུན་བྱེད་བཞིན་ཡོད།… + ནང་དུ་ཞུགས་བཞིན་ཡོད། + ཐོ་མཛོད་ཀྱི་སྣེ་ཤན། + ཁྱེད་ཀྱི་ཐོ་བྱང་གི་ངོ་རྟགས། + སྡེབ་སྒྲིག་ཞབས་ཞུའི་འཕྲུལ་ཆས། + ཁྱེད་རང་གྲ་སྒྲིག་ཡིན་ནམ། + ཁྱེད་ཀྱི་ XMPP ཡི་གླེང་མོལ་ཞབས་ཞུའི་ཆེད་དུ་། ཁྱེད་རང་གི་ཐོ་མཛོད་ཁ་བྱང་བཅུག་ནས་ཉེན་སྲུང་གླེང་མོལ་དེ་སྡེབ་སྒྲིག་བྱོས། དེ་གློག་འཕྲིན་ཁ་བྱང་ཞིག་དང་འདྲ་བོ་ཡོད། + ཁྱེད་རང་གི་ངོ་སྤྲོད་ཁ་བྱང་བཅུག (user@hostname) + ཁྱེད་རང་གི་ Jabber/XMPP གླེང་མོལ་ཞབས་ཞུའི་མིང་དང་ཨང་གྲངས་འཇུག་རོགས། (ཨང་གྲངས་5222 ནི་གཏན་འཇགས་ཡིན།) + ད་ཉེན་སྲུང་གླེང་མོལ་དེ་སྡེབ་སྒྲིག་བྱས་ཟིན་པ་རེད། ཁྱེད་ཀྱི་དྲ་འབྲེལ་ལ་མཐུད་ནས་བདེ་འཇགས་དང་། ཉེན་སྲུང་། གསང་རྒྱ་ལྡན་པའི་ངང་གླེང་མོལ་གྱིས། + Orbot (Tor) + མིང་གཙོ་བོ་དེ་གདམ། + ཐོ་མིང་གསར་བ་དེབ་སྐྱེལ་བྱེད་བཞིན་ཡོད། + ཉེན་སྲུང་གླེང་མོལ་མུ་མཐུད་བཞིན་པ། + ནང་འཇུག་མཚམས་ཞོག ནང་ཞུགས་བྱེད་བཞིན་ཡོད། \u2026 - - - རྒྱབ་ལྗོངས་ཀྱི་གཞི་གྲངས་ནུས་མེད་བཟོས། - - - - - - ཐུབ་པ་བཟོས། - - ཕྱིར་དོན། - - - - - - + ཕྱི་ལ་ཐོན་བཞིན་ཡོད།\u2026 + + SRVལ་ལྟོས། + དྲ་ཁོངས་ཀྱི་མིང་ནས་སྤྱིར་བཏང་གིXMPP ཞབས་ཞུ་དེ་འཚོལ་བར་། DRV བེད་སྤྱོད་ཐོངས་། + གསང་སྡོམ་མ་བྱས་བའི་འགྲུལ་བཞུད་སྐབས་སུ། མིང་དང་གསང་ཚིག་གཉིས་འཕྲིན་ཐུང་དཀྱུས་མ་ལྟར་ཐོངས། + འཕྲིན་ཐུང་དཀྱུས་མ་བེད་སྤྱོད་གཏོང་བའི་ཆོག་མཆན་སྤྲོད། + ལག་འཁྱེར་དེར་ཡིད་ཆེས་གཏད་ཆོག་པའི་ར་སྤྲོད་བྱོས། + TLS ར་སྤྲོད་། + TLS ཡི་འབྲེལ་མཐུད་དགོས། + གསང་སྡོམ་དེ་བསྐུར། + གསང་སྡོམ་ཅན་གྱི་གླེང་མོལ་གང་འདྲ་བྱས་ནས་འགོ་འཛུགས་ཀྱི་ཡོད་དམ། + གལ་སྲིད་དགོས་མཁོ་བྱུང་ན། འབྲེལ་མཐུད་བྱེད་སའི་ཞབས་ཞུ་འདི་རེད། + དྲ་ཐོག་ཞབས་ཞུར་མཐུད། + ཞབས་འཕྲུལ་XMPP ཡི་མཐུད་སྣེ TCP + དྲ་ཐོག་ཞབས་ཞུའི་མཐུད་ས། + XMPP ཡི་ཁོངས་མཛོད་། + འབྲེལ་མཐུད་འདི་ནང་འཛུལ་བྱས་ཟིན་པའི་བཀོལ་ཆས་གཞན་པ་དག་ལས་ལོགས་སུ་དབྱེ། + XMPP ཡི་ཡོང་ཁུངས་སྔོན་སྤྱོད་རིམ་པ། + ཐོན་ཁོངས་སྣ་མང་ཅན་གྱི་འཕྲིན་ཐུང་དེ་སྔོན་སྤྱོད་རིམ་པ་མཐོ་ཤོས་ཀྱི་ཁོངས་སུ་གཏོང་གི་རེད་། + + གླེང་མོལ་ཁག + གླེང་མོལ་མི་འདུག⏎ +⏎འདི་ནས་གཅིག་འགོ་ཚུགས། + ཁྱོད་ལ་སྡེབ་སྒྲིག་ཟིན་པའི་ཐོ་མཛོད་མི་འདུག⏎ +⏎འདི་ནས་གཅིག་བསྣོན། + འབྲེལ་མིང་ཐོ་གཞུང་། %1$s + འབྲེལ་གཏུགས་ལ་ཁ་སྣོན། + འབྲེལ་མིང་གསུབ། + འབྲེལ་མིང་བཀག + འབྲེལ་གཏུགས་ཀྱི་སྙུག་མིང་། + བཀག་ཟིན། + སྙུག་མིང་། + འབྲེལ་མིང་འདི་"%1$s"གསུབ་ཀྱི་རེད། + འབྲེལ་མིང་འདི་"%1$s" བཀག་གི་རེད། + འབྲེལ་གཏུགས"%1$s"ཁ་ཕྱེས་གི་རེད་། + འབྲེལ་མིང་ "%1$s" བསྣན་ཟིན། + འབྲེལ་མིང་ "%1$s" གསུབ་ཟིན། + འབྲེལ་མིང་ "%1$s" བཀག་ཟིན། + འབྲེལ་མིང་"%1$s"ཁ་ཕྱེས་ཟིན། + གླེང་མོལ་གསར་བ། + འབྲེལ་མིང་འཚོལ། + སྒྲོམ་གཞི་སྟོན། + གླེང་མོལ་འགོ་འཛུགས། + ངོ་སྤྲོད་ལ་གཟིགས། + འབྲེལ་གཏུག་ར་སྤྲོད་གྱིས། + ད་ལྟ་གླེང་མོལ་བྱེད་བཞིན་པ། (%1$d) + %1$d དྲ་ཐོག་ཏུ་ཡོད། ངོ་ཤེས་ཀྱི་མགྲོན་འབོད། - ( རྒྱུས་མེད།) - སྟོང་པ། - - གླེང་མོལ་མེད། - - མགྲོན་འབོད་བྱེད་རྒྱུའི་འབྲེལ་མིང་(རྣམས་)གདམ། - འབྲེལ་གཏུགས་དེ་རྙེད་པར་ཡི་གེ་བཏགས། - འབྲེལ་མིང་རྣམས་རྙེད་མ་བྱུང་། - - - - འབྲེལ་མིང་བཀག་པ་མི་འདུག - - + གླེང་མོལ་སྒོ་ཕྱེ་མེད། གླེང་མོལ་བྱེད་སྒོ་བཙུགས། + འབྲེལ་མིང་། + གླེང་མོལ་བྱེད་འདོད་ཡོད་ས་ཞིག་གི་མིང་ཕྲིས། + འགྲོ། / སོང་། + གླེང་མོལ་བྱེད་བཞིན་མི་འདུག + འབྲེལ་མིང་ཁ་སྣོན་བྱོས། + ཁྱེད་ཀྱིས་མགྲོན་འབོད་བྱེད་འདོད་ཡོད་པའི་གང་ཟག་དེའི་དྲ་རྒྱའི་ཁ་བྱང་། + མིང་ཐོ་ཞིག་གདམ།: + འབྲེལ་མིང་ཁོངས་ནས་མིང་དེ་བདམས་ནས་གླེང་མོལ་གྱི་ཁར་སྣོན། + མགྲོན་འབོད་ཐོངས། འབྲེལ་མིང་གི་ངོ་སྤྲོད་མདོར་བསྡུས། - - རྣམ་པ། : - - མཁོ་མཁན་གྱི་རིགས། - + གནས་བབས། + དོ་བདག་གི་རིགས། རྩིས་འཁོར། - ལག་འཁྱེར་ཁ་པར། - - - འཁོར་མཐུད། - - བྲེལ་བ་ཡོད། - - ད་ལྟ་འདིར་མེད། - - འགུལ་སྐྱོད་མེད་པར་བསྡད་ཡོད། - - འཁོར་བྲལ། - - དྲ་ཐོག་ན་མི་འདུག - - - - - - ངས། - - ཡི་གེ་བཏགས། - - - - - - - - - - - - - - - - + འབྲེལ་མིང་བཀག་པ་རྣམས། - %1$s + འབྲེལ་མིང་བཀག་པ་མི་འདུག + + %1$s དང་ལྷན་དུ་གླེང་མོལ་བྱེད། + ང་། + %1$s དྲ་ཐོག་ཏུ་ཡོད་། + %1$s ད་ལྟ་འདིར་མེད། + %1$s ལ་ད་ལྟ་བྲེལ་བའདུག + %1$s དྲ་ཐོག་ཏུ་མེད་། + %1$sཞུགས་སོང་། + %1$sཕྱིན་ཞག + འདྲ་པར་ཐོངས། + ཡིག་ཆ་ཐོངས། + སྐད་འཕྲིན་ཞིག་ཐོངས། + འདྲ་པར་རྒྱོབས། + ཡིག་ཆ་ཐོངས། + བཏང་ཟིན་པ་ཡིན། + གཏོང་བཞིན་ཡོད། + བཏང་བ་དེ་ཁས་ལེན་གྱི་ཡིན་ནམ། + ནས་ཁྱོད་ལ་ཡིག་ཆ་ཞིག་གཏོང་འདོད་། + ཁྱེད་ཀྱི་ཡིག་ཆ་དེ་གཏོང་བར་དྲ་འབྲེལ་མི་འདུག གཏོང་། - - འཕྲིན་ཐུང་འདི་བཏང་མི་ཐུབ། - - དྲ་ཐོག་ཞབས་ཞུའི་འཕྲུལ་ཆས་ལ་འབྲེལ་ལམ་ཆད་སོང་། འབྲེལ་ལམ་བསྐྱར་དུ་སྦྲེལ་མཚམས། འཕྲིན་ཐུང་དེ་གཏོང་ཐུབ། - - - - - + འཕྲིན་ཐུང་ཞིག་ཐོངས། + ཉེན་སྲུང་ཅན་གྱི་འཕྲིན་ཐུང་ཐོངས། + བསྐྱར་དུ་ཐོངས། + གླེང་མོལ་མཚམས་ཞོག + གླེང་མོལ་མེད་བ་བཟོས། + སེམས་ཁམས་མཚོན་རྟགས་འཇུག + གླེང་མོལ་བརྗེ་པོ་རྒྱོབས། + འདེམས་ཐོ། འབྲེལ་ཐག་འདེམས། - - འགུལ་སྐྱོད་བྱེད་བཞིན་པའི་གླེང་མོལ་མི་འདུག - - གསང་དམ་ཅན་གྱི་གླེང་མོལ་འགོ་ཚུགས་པ། - གསང་དམ་ཅན་གྱི་གླེང་མོལ་འགོག་པ། - - - འབྲེལ་གཏུག་ནང་འཇུག - - ཁྱེད་ཀྱི་མགྲོན་འབོད་བྱེད་འདོད་ཡོད་པའི་གང་ཟག་དེའི་དྲ་རྒྱའི་ཁ་བྱང་། : - - ཐོ་ཞིག་གདམ།: - - འབྲེལ་ཁོངས་ནས་མིང་དེ་བཏགས་ནས་སྣོན། - - མགྲོན་འབོད་གཏོང་། - - སྟོང་བཤད་། - གཞི་རིམ་དྲ་ཐོག་ཞབས་ཞུ་མེད་པའི་ XMPP - - ཐོ་མཛོད་སྒྲིག་འཇུག - གསང་སྡོམ་དང་བཀབ་མིང་། + གསུབ། + ཡིག་ཆ་རྣམས་ཉོར། + གླེང་མོལ་གྱི་ཉར་སྣོད་སུབ་ཀྱི་ཡིན་ནམ། + ད་ལྟ་བར་ཕབ་ལེན་དང་ནང་འཛུལ་བྱས་པ་ཚང་མ་རྩ་བ་ནས་མེད་པ་བཟོ་ཡི་རེད། ཉེན་བརྡཿ བྱ་བ་འདི་མེད་པ་བཟོ་མི་ཐུབ། + དངོས་གཞི་སུབ་རྒྱུ་ཡིན་ནམ། + ཡིག་ཆ་འདི་མ་བཏང་གོང་ལ་ཉེན་སྲུང་ཡོད་པའི་བང་མཛོད་དུ་ཉར་གྱི་ཡོད། ཁྱོད་ཀྱི་ཡིག་ཆ་ངོ་མ་དེ་ཉེན་སྲུང་མེད་པའི་བང་མཛོད་ནས་སུབ་ཀྱི་ཡིན་ནམ། + ཉོར། + ཕྱིར་ཐོངས། + རྒྱུད་ལམ་གྱི་ཡིག་ཆ་གཞན་ལ་དབོར་གྱི་ཡིན་ནམ། + %1$sལ་བརྒྱུད་ལམ་གྱི་ཡིག་ཆ་འདི་ཐོངས། + གླེང་མོལ་མཚམས་བཞག་གི་ཡིན་ནམ། + ཚོགས་ཐུན་འདིའི་ཉེན་སྲུང་ལྡན་པའི་བརྒྱུད་ལམ་ནང་གང་ཡོད་པ་ཚང་མ་སུབ་ཀྱི་རེད་། མཐེབ་རྟགས་ཀྱི་སྒང་ལ་བསྣུན་ནས་ཡིག་ཆ་ཕྱི་ལ་ཐོངས། + གླེང་མོལ་མཚམས་བཞག་ནས་ཡིག་ཆ་སུབ། + + %1$s ཡིས་ཁྱེད་རང་མཉམ་འདུས་གླེང་མོལ་ལ་མགྲོན་འབོད་བཏང་ཟིན། + %1$s ལ་མགྲོན་འབོད་བཏང་ཟིན། + མོས་མཐུན་ཡོད། + མོས་མཐུན་མེད། + མཉམ་འདུས་གླེང་མོལ། + མཉམ་འདུས་གླེང་མོལ་ལ་འཛུལ་བའམ་ཡང་ན་གསར་བ་ཞིག་འགོ་ཚུགས། + མཉམ་འདུས་གླེང་མོལ་ལ་འབྲེལ་མཐུད་བྱེད་བཞིན་ཡོད་། + མགྲོན་འབོད་བྱེད།\u2026 + མགྲོན་འབོད་བྱེད་རྒྱུའི་འབྲེལ་མིང་(རྣམས་)གདམ། + འབྲེལ་གཏུག་ཚོལ་ཆེད་མིང་བྲིས། + འབྲེལ་མིང་ཅི་ཡང་རྙེད་མ་སོང་།⏎ +⏎ +མགྲོན་འབོད་བྱེད་རྒྱུའི་འབྲེལ་མིང་དག་གདམ། + %1$s ཁ་སྣོན་བྱེད་ཀྱི་ཡིན་ནམ།། + ཡིན། + མིན། + %1$s ཡི་མངགས་ཆ་དེར་བཀའ་འཁྲོལ་ཐུབ་མ་སོང་། སྐུ་མཁྱེན། རྗེས་སུ་ཚོད་ལྟ་བྱེད་རོགས། + %1$s ཡི་མངགས་ཆ་དེ་ཕྱིར་འཐེན་བྱེད་ཐུབ་མ་སོང་། སྐུ་མཁྱེན། རྗེས་སུ་ཚོད་ལྟ་བྱེད་རོགས། + + གསང་སྡོམ་འགོ་བཙུགས། + གསང་སྡོམ་མཚམས་འཇོག + གསང་སྡོམ་ཅན་གྱི་གླེང་མོལ་འགོ་ཚུགས་བཞིན་པ། + གསང་སྡོམ་ཅན་གྱི་གླེང་མོལ་འགོག་བཞིན་པ། + ཉེན་སྲུང་གི་མཛུབ་རིས། + ཉེན་སྲུང་གི་མཛུབ་རིས། + ནང་དུ་འཛུལ། + ལྡེ་མིག་བསྐྱར་བཟོ། + གསང་སྡོམ་མེད་པ་བཟོས་ཡོད། + གསང་སྡོམ་འགོ་བཙུགས་ཡོད། (ར་སྤྲོད་ཆེད་དུ་མཛུ་གུས་བསྣུན།) + ཁྱོད་ཀྱི་འབྲེལ་མི་དེས་ཉེན་སྲུང་གླེང་མོལ་མཚམས་བཞག་འདུག + གསང་སྡོམ་དང་ཁུངས་སྐྱེལ་བྱས་ཟིན། + OTR ལྡེ་མིག་ཆ་གསར་བ་ཞིག་མཁོ་སྒྲུབ་བཞིན་པ། + + ང་ཚོས་གསང་སྡོམ་འཕྲིན་ཐུང་གི་ལྡེ་མིག་ཉར་མཛོད་ཞིག་ནང་འདྲེན་ཆེད་རྙེད་སོང་། ཁྱེད་ཀྱིས་ཨང་ཚབ་གསང་ཚིག་དེར་བརྟག་དཔྱད་བྱེད་ཀྱི་ཡིན་ནམ། + KeySyncདེ་བྱེད་ལས་ཅན་ཏུ་བསྒྱུར། + གསང་སྡོམ་འཕྲིན་ཐུང་གི་ལྡེ་མིག་འཁོར་ཐག་དེ་ནང་འདྲེན་ལེགས་འགྲུབ་བྱུང་སོང་། + + མགྱོགས་ལན་གསང་བརྡ་QRའཚག་སེལ་བྱོས། + ཁྱེད་རང་གི་མཛུབ་རིས། + ཐོ་གཞུང་། + དྲི་བ། + མཛུབ་རིས་ཉེན་སྲུང་།(བརྟག་དཔྱད་བྱས་ཟིན) + ཁྱོད་ཀྱིས་མཛུབ་རིས་འདི་ཏན་ཏན་ཁས་ལེན་གྱི་ཡིན་ནམ། + མཛུབ་རིས་བརྟག་ཞིབ་བྱེད་དགོས་སམ། + རྒྱང་བཀོལ་མཛུབ་རིས་དེ་ར་སྤྲོད་བྱས་ཟིན། + ཁྱེད་ཀྱི་ཆེད་དུ་མཛུབ་རིས། + ཆེད་དུ་མཛུབ་རིས། + ཨང་ཚབ་འཚག་སེལ་ཡར་བཅུག་གི་ཡིན་ནམ། + མཉེན་ཆས་མ་ལག་འདི་ལ་ཨང་ཚབ་འཚག་སེལ་དགོས་ཀྱི་འདུག ཁྱེད་ཀྱིས་དེ་ནང་འཇུག་བྱེད་གི་ཡིན་ནམ། + + ར་སྤྲོད་ཁུངས་སྐྱེལ། + ཁོང་ཚོ་སུ་ཡིན་མིན་ར་སྤྲོད་ཕྱིར་། སོ་སོའི་འབྲེལ་མིང་ལ་དྲི་བ་གཏོང་བ་དང་དྲི་ལན་འགྲིག་པ་དེ་ཡང་མཉམ་དུ་ཞོག + དྲི་བ་གཏོང་རྒྱུ་དེ། + དྲི་ལན་འགྲིག་པ་དེ། + ཁྱེད་ཀྱི་འབྲེལ་བ་དེས་ཁྱེད་རང་་དག་སྐྱེལ་ལེགས་འགྲུབ་བྱས་འདུག ད། ཁྱེད་ཀྱིས་ཁོང་ལ་དྲི་བ་དྲིས་ནས་ངོ་ཁྲོད་པ་བྱེད་དགོས། + OTR Q&ར་སྤྲོད་ཞིག + གླེང་མོལ་གསང་སྡོམ། + གཏོང་། + ཕྱིར་འཐེན་བྱེད། + + ཉེན་སྲུང་གླེང་མོལ། + ཉེན་སྲུང་ལྡན་པའི་སྐད་འཕྲིན། + ཁ་པར་ཟུང་འབྲེལ་གྱི་སླད་དུ། ཁྱོད་ཀྱི་OStel.coའམ་ཡང་ན་ཉེན་སྲུང་ཅན་གྱི་SIPཞབས་ཞུའི་ཐོ་མཛོད་དེ་ནང་འཇུག་བྱོས། + + ཐོ་བྱང་སྒྲིག་འཇུག + + ཉེན་སྲུང་དང་བདེ་འཇགས། + གསང་སྡོམ་དང་སྦས་མིང་། + གསང་བ་སྡོམ་/མི་སྡོམ། + གསང་ཚིག་གི་དུས་ཡུན་རྫོགས། + གསང་སྡོམ་ཉེར་སྤྱོད་མ་ལག་དེས་སྒོ་ཕྱེས་སོང་ལབ་དགོས་བའི་དུས་ཚོད་རེད། + + Tor ཡི་སྒང་གི་བསྣུན་ཆོག་པའི་འབྲེལ་ཐག་ཁག + Tor བེད་སྤྱོད་གཏོང་མཁན་གྱི་ཐོ་བྱང་རྣམས་ཀྱིས། གླེང་མོལ་ནང་གི་འབྲེལ་ཐག་ཁག་བསྣུན་ཆོག་པ་བཟོ་རོགས། (ཉེན་བརྡ།་སྒེར་གྱི་གསང་བ་ཕྱིར་གྱར་སྲིད།) + བེད་སྤྱོད་གཏོང་མཁན་གྱི་མཐུད་ངོས། + སྐད་ཡིག + སྐད་ཡིག་ཁག + མ་ལག་འཆར་ཅན་དེ་བེད་སྤྱོད་ཐོངས། + པར་གཞི་མུན་ནག་དེ་བེད་སྤྱོད་ཐོངས། + ཉེར་སྤྱོད་མ་ལག་གི་པར་གཞི་ནག་པོར་བསྒྱུར། + འཕྲིན་ཐུང་གི་གསོག་ཉར་བང་མཛོད་གཙོ་བོ་དེ། + འཕྲིན་ཐུང་ཕྱིར་གྱར་མ་འབྱུང་བའི་ཆེད་དུ། འཕྲིན་ཐུང་དག་གསོག་ཉར་ཡོ་ཆས་ཀྱི་ཁོངས་སུ་ཉོར། flashཧྤི་ལ་ཞེས་ཉར་གསོག་ཁོངས་སུ་མ་ཉར། (དེ་ཡིས་འཕྲིན་ཐུང་བརླག་པར་རྐྱེན་བྱེད་སྲིད) + རྒྱབ་ལྗོངས་ཀྱི་སྣང་བརྙན། + བཀོལ་སྤྱོད་མ་ལག་འདིའི་ཆེད་དུ། རྒྱབ་ལྗོངས་སྣང་བརྙན་དེ་ལ་ (\"/sdcard/foo.jpg\")ཁ་ཕྱོགས་སྟོན། + འབྲེལ་མིང་གི་སྒྲོམ་དེ་སྟོན། + འབྲེལ་མིང་གི་ཐོ་དེ་སྟོན། + རེད་། ཚང་མ་ཁས་ལེན་བྱོས། + ཉེན་སྲུང་མིན་པའི་བརྒྱུད་ལམ་དེ་གསུབ། + ཡིག་ཆའམ་ཡང་ན་འདྲ་པར་ཞིག་བགོ་བའི་རྗེས་སུ། དེ་ཁོངས་ཨ་མ་ནས་རང་བཞིན་གྱིས་གསུབ་འགྲོ་གི་རེད་། + + + ཉར་གསོག་གཞན་པ། + ཉེན་སྲུང་གླེང་མོལ་དེ་རང་བཞིན་གྱིས་འགོ་ཚུགས། + རྟག་དུ་འགོ་འཛུགས་པ་དང་རང་བཞིན་ནས་ཁ་སྔོན་ནང་འཛུལ་བྱས་བའི་ཐོ་མཛོད་དེ་ལ་ཐོ་འགོད་བྱོས། དྲ་ལམ་ན་མེད་པའི་འབྲེལ་གཏུགས་ཁག་སྦས། - - བརྡ་གཏོང་སྒྲིག་བཟོ། - - འཕྲིན་ཐུང་འབྱོར་བརྡ། - - འཕྲིན་ཐུང་འབྱོར་སྐབས་རྣམ་པའི་ཚན་བྱང་གི་ནང་དུ་སྟོན། - སྔོན་སྤྱོད་རིམ་པ་གལ་ཆེ་ཤོས་དེ་བེད་སྤྱོད་ཐོངས་། ཡིས་ང་ཚོའི་དྲ་འབྲེལ་ཞབས་ཞུ་བསྐྱར་དུ་འགོ་འཛུགས་བྱེད་པའི་གོ་སྐབས་ཉུང་དུ་ཐོངས་། འདི་ཡིས་བརྡ་གཏོང་ཁོར་ཡུག་ཏུ་བརྡ་འཇོག་གི་རེད་། སྙིང་གི་འཕར་ལྡིང་གི་བར་གླགས། + གློག་རྫས་གསོག་པ་ལ་རིམ་པ་མཐོ་བ་(སྐར་མའི་གྲངས)ཞིག་འདེམས། རིམ་པ་མཐོ་བ་དེས་མཁོ་སྤྲོད་པ་ལ་ཁྱེད་ཀྱི་འབྲེལ་ལམ་འགོག་སྲིད། + + བརྡ་གཏོང་སྒྲིག་བཟོ། + འཕྲིན་ཐུང་འབྱོར་བརྡ། + འཕྲིན་ཐུང་འབྱོར་སྐབས་ཚན་བྱང་གི་ནང་དུ་བརྡ་སྟོན། འདར་བཅུག - - འཕྲིན་ཐུང་འབྱོར་སྐབས་ཡང་འདར་བཅུག - + འཕྲིན་ཐུང་འབྱོར་སྐབས་འདར་བཅུག སྒྲ། - - འཕྲིན་ཐུང་འབྱོར་སྐབས་ཡང་དྲིལ་སྒྲ་གཏོང་། - - དྲིལ་སྒྲ་གདམ། - - - - - - - མོས་མཐུན་ཡོད། - - ཁས་མི་ལེན། - - - - མོས་མཐུན་ཡོད། - - ཁས་མི་ལེན། - - - - - - - - - - - - + འཕྲིན་ཐུང་འབྱོར་སྐབས་དྲིལ་སྒྲ་གཏོང་། + ཉེན་སྲུང་གླེང་མོལ་གྱི་དྲིལ་སྒྲ་བེད་སྤྱོད་ཐོངས། + + Debug བྱེད་ལས་ཅན་བཟོས། + Output app log data to standard out / logcat for debugging + + དྲ་འབྲེལ་གྱི་གནས་ཚུལ་ནུས་མེད་བཟོས། + +ཉེར་སྤྱོད་བཀོལ་ཆས་ནང་འཇུག་བྱེད་པ་ལ། དྲ་འབྲེལ་གྱི་ཐོའི་འབྲེལ་ལམ་དེ་དགོས་ཀྱི་ཡོད། + + + + ཐུབ་པ་བཟོས། + ཕྱིར་དོན། + ཁྱེད་ཀྱིས་ཞབས་ཞུ་ཚང་མ་སྒོ་བརྒྱབ་ནས་བརྒྱུད་རིམ་ཚང་མ་མེད་པར་གཏོང་འདོད་དམ། (hard exit)? + ཐོ་བྱང་གསར་བ་ཞིག་བཟོ་གི་ཡིན་ནམ། + %1$s\'་ཡི་མིང་ཐོག་ལ་གླེང་མོལ་ཐོ་བྱང་གསར་བ་ཞིག་བཟོ་གི་ཡིན་ནམ། + ལག་འཁྱེར་གྱི་སྐོར། + ལག་འཁྱེར། + ལག་འཁྱེར་སྤྲོད་མཁན། + SHA1མཛུབ་ཐེལ། + བཏོན་ཚར། + དུས་རྫོགས། + གླེང་མོལ་ལ་ཞུགས་སམ། + ཕྱི་རོལ་གྱི་མཉེན་ཆས་ཤིག་གིས་ཁྱོད་རང་གླེང་མོལ་ནང་མཐུད་ཐབས་བྱེད་ཀྱི་འདུག མཐུད་འཇུག་གམ། + རྒྱབ་ལྗོངས་འདེམས། + ཁྱེད་ཀྱིས་པར་མཛོད་ནས་རྒྱབ་ལྗོངས་ཀྱི་ཆེད་པར་ཞིག་འདེམས་ཀྱི་ཡིན་ནམ། + འདྲ་པར་འདེམས། + + འཕྲིན་ཐུང་གསར་པ %1$s + གླེང་མོལ་མ་ཀློག་པ་ %1$d + %s ནས་གྲོགས་པའི་མགྲོན་འབོད་ཞིག + %2$s ནས་ཡིག་ཆ་གསར་པ་ %1$s་འདུག མཉམ་འདུས་གླེང་མོལ་མགྲོན་འབོད་། - - - - - - - - - - - - IM ཞབས་ཞུ་འགོ་ཚུགས། - ཉེར་སྤྱོད་ཁག་ལ། དྲ་ལམ་བརྒྱུད་IM ཞབས་ཞུ་བེད་སྤྱོད་གཏོང་བཅུག - - - དོ་སྣང་། - - - + %sནས་གླེང་མོལ་གྱི་ཚོམས་གསར་བའི་མགྲོན་ཤོག་ཞིག + ནས་འཕྲིན་ཐུང་(རྣམས)གསར་པ། + ཉེན་སྲུང་གླེང་མོལ་གྱི་ཞབས་ཞུ་འགོ་ཚུགས། + & ནུས་ཅན་བཟོས་ཚར། སྒོ་ཕྱེས་ཡོད། + འཕྲིན་ཐུང་དེ་ནག་པང་ཆུང་ངུ་དེར་ཕབ་བཤུ་བྱས་ཚར། + + དོ་སྣང་བྱོས། + ནོར་འཁྲུལ། + ཨང་ཚབ་ནོར་བ། %1$d + %1$s འདི་ལ་འཛུལ་ཐུབ་ཀྱི་མི་འདུག རྗེས་སུ་བསྐྱར་དུ་ཚོད་ལྟ་བྱེད་རོགས།(ཞིབ་ཕྲ། %2$s) ཐོ་གཞུང་དེ་བསྣན་མི་འདུག - འབྲེལ་མིང་དེ་བཀག་མེད། - - འབྲེལ་གཏུགས་དེ་བཀག་འདུག - - སྐུ་མཁྱེན། དང་པོ་འབྲེལ་མིང་ཞིག་འདེམས། - - འབྲེལ་བ་བཅད་པ། - + བཀག་ཟིན་པའི་འབྲེལ་མིང་དེ་མུ་མཐུད་དུ་བཀག་ཡོད། + དང་པོ་འབྲེལ་མིང་ཞིག་འདེམས་རོགས། + +འབྲེལ་ཐག་བཅད་ཟིན། ཞབས་ཞུའི་ནོར་འཁྲུལ། - - འབྲེལ་གཏུགས་ཐོ་གཞུང་དེ་སྣོན་འཇུག་བྱས་མི་འདུག - - དྲ་ཐོག་ཞབས་ཞུར་སྦྲེལ་ཐུབ་གི་མི་འདུག སྐུ་མཁྱེན། དྲ་ལམ་ལ་བལྟ་རོགས། - - - - - - ཁྱེད་ཀྱི་འབྲེལ་གཏུགས་ཐོ་གཞུང་སྣོན་འཇུག་བྱེད་པའི་སྐབས་སུ། སྒུག་རོགས། - + འབྲེལ་མིང་ཐོ་གཞུང་དེ་སྣོན་འཇུག་བྱས་མི་འདུག + དྲ་ཐོག་ཞབས་ཞུར་མཐུད་ཐུབ་ཀྱི་མི་འདུག དྲ་འབྲེལ་ལ་བལྟ་རོགས། + "%1$s" ཁྱོད་ཀྱི་འབྲེལ་ཐོའི་ནང་ཡོད་བཞིན་པ་རེད་འདུག + འབྲེལ་མིང་ "%1$s" བཀག་ཟིན་འདུག + ཁྱེད་ཀྱི་འབྲེལ་གཏུགས་ཐོ་གཞུང་ལ་ཁ་སྣོན་བྱེད་བཞིན་ཡིན། སྒུག་རོགས། དྲ་ལམ་གྱི་སྐྱོན་ཞིག་བྱུང་སོང་། དྲ་འབྲེལ་འདི་ལ་ WiFi དགོས་ངེས་རེད། - དྲ་ལམ་ཞབས་ཞུ་ཡིས་བྱེད་ལས་འདིར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག - - ཁྱེད་ཀྱིས་ནང་འཇུག་བྱས་བའི་གསང་ཚིག་དེར་ངོས་ལེན་མི་འདུག - - དྲ་ལམ་ཞབས་ཞུ་དེ་དཀའ་ངལ་ཞིག་ལ་འཕྲད་སོང་། - - དྲ་ལམ་ཞབས་ཞུ་ཡིས་བྱེད་ལས་འདིར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག - + ཁྱེད་ཀྱིས་ནང་འཇུག་བྱས་བའི་གསང་ཚིག་དེ་ཚད་ལྡན་རེད་མི་འདུག + དྲ་ལམ་ཞབས་ཞུར་དཀའ་ངལ་ཞིག་འཕྲད་སོང་། + དྲ་ལམ་ཞབས་ཞུས་ད་ལྟ་བྱེད་ལས་འདིར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག གནས་སྐབས་རིང་དྲ་ལམ་ཞབས་ཞུ་དེ་ཡོང་གི་མི་འདུག - དྲ་ལམ་ཞབས་ཞུ་ཡི་དུས་ཚོད་རྫོགས་འདུག - - དྲ་ལམ་ཞབས་ཞུ་ཡིས་པར་གཞི་གསར་བ་འདིར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག - + དྲ་ལམ་ཞབས་ཞུ་ཡིས་ད་ཡོད་པར་གཞི་འདིར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག འཕྲིན་ཐུང་གི་རུ་སྟར་ཁེངས་འདུག - དྲ་ཐོག་ཞབས་ཞུ་ཡིས་དམིགས་བཀར་ཁྱབ་ཁོངས་སུ་གཏད་པར་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་མི་འདུག - - བེད་སྤྱོད་གཏོང་མཁན་གྱི་མིང་ཁྱེད་ཀྱིས་ནང་འཇུག་བྱས་བ་དེ་ངོས་འཛིན་བྱས་མ་སོང་། - - དགོངས་དག ཁྱེད་རང་བདག་པོས་བཀག་འདུག - - གཏམ་གླེང་དུས་ཡུན་རྫོགས་འདུག སྐུ་མཁྱེན། བསྐྱར་དུ་ནང་ཞུགས་བྱེད་རོགས། - - ཁྱེད་ཀྱིས་མཁོ་མཁན་གཞན་ཞིག་བརྒྱུད་ནས་ནང་འཛུལ་བྱས་འདུག - ཁྱེད་ཀྱིས་གཞན་བརྒྱུད་ནས་ནང་འཛུལ་བྱས་འདུག - - དགོངས་དག ཁྱེད་ཀྱི་བྱང་བུ་ནས་ཁ་པར་ཨང་གྲངས་བལྟ་ཐུབ་གི་མི་འདུག དེར་བརྟེན། ཁྱེད་ཀྱི་སྟངས་འཛིན་པའི་རྩ་ནས་རོགས་རམ་ལོངས། - + ཁྱེད་ཀྱིས་ད་ལྟ་མིང་ནང་འཇུག་བྱསཔ་བ་དེ་ངོས་འཛིན་བྱས་མ་སོང་། + དགོངས་དག ཁྱེད་རང་དོ་བདག་དེ་ཡིས་བཀག་འདུག + གཏམ་གླེང་གི་དུས་ཡུན་རྫོགས་སོང་། ཡང་བསྐྱར་ནང་ཞུགས་བྱེད་རོགས། + ཁྱེད་ཀྱིས་བཀོལ་ཆས་གཞན་ཞིག་ནས་ནང་འཛུལ་བྱས་འདུག + ཁྱེད་ཀྱིས་བཀོལ་ཆས་གཞན་ཞིག་བརྒྱུད་ནང་འཛུལ་བྱས་འདུག + དགོངས་དག ཁྱེད་ཀྱི་བྱང་བུ་ནས་ཁ་པར་ཨང་གྲངས་ཀློག་ཐུབ་ཀྱི་མི་འདུག ཁྱེད་ཀྱི་ཁ་པར་ཚོང་ལས་ཁང་ནས་རོགས་རམ་ལེན་རོགས། གནས་སྐབས་སུ་ཁྱེད་རང་ནང་འཛུལ་བྱས་མི་འདུག - - - + ཉེན་སྲུང་གླེང་མོལ་ལ་ཁྱོད་ཀྱི་མིང་དང་གསང་ཡིག་གཉིས་མཐུན་མིན་གྱི་དཀའ་ངལ་འཕྲད་སོང་། བསྐྱར་དུ་ཐབས་ཤེས་བྱེད་རོགས། + ཉེན་སྲུང་གླེང་མོལ་ལ་ལྡེ་མིག་ཆ་གཅིག་བཟོ་འདོན་སྐབས་དཀའ་ངལ་འཕྲད་སོང་། + གླེང་མོལ་ཞབས་ཞུ་དང་འབྲེལ་མཐུད་བྱེད་སྐབས་ཉེན་སྲུང་གླེང་མོལ་ལ་དཀའ་ངལ་འཕྲད་སོང་། ཁྱེད་ཀྱི་སྡེབ་སྒྲིག་མ་ལག་ལ་བལྟ་ཞིབ་བྱས་ནས་བསྐྱར་དུ་ཐབས་ཤེས་བྱེད་རོགས། + ཉེན་སྲུང་གླེང་མོལ་ལ་དྲ་འབྲེལ་གྱི་སྐབས་སུ་དཀའ་ངལ་འཕྲད་སོང་། ཁྱེད་ཀྱི་དྲ་བའི་འབྲེལ་མཐུད་ལ་ཞིབ་ལྟ་བྱས་ནས་བསྐྱར་དུ་ཐབས་ཤེས་བྱེད་རགོས། + ཉེན་སྲུང་གླེང་མོལ་གྱིས་དྲ་རྒྱ་དང་མཐུད་མིན་འདུག + ཉེན་སྲུང་གླེང་མོལ་གྱིས་དྲ་འབྲེལ་སླར་གསོ་ལ་འབད་བརྩོན་བྱེད་བཞིན་ཡོད། + ཁྱེད་ཀྱིས་ཐོ་བྱང་གི་ངོ་རྟགས་ནང་དུ་ hostname.comནང་འཇུག་བྱས་མི་འདུག བསྐྱར་དུ་ཚོད་ལྟ་བྱོས། + ཁྱེད་རང་གི་དྲ་ཐོག་ཞབས་ཞུར .com དང་ .net ཡང་ན་ཟུར་སྣོན་སོགས་ཅི་ཡང་མི་འདུག བསྐྱར་དུ་ཚོད་ལྟ་བྱོས། + ཁྱེད་རང་གི་གསང་ཚིག་ནང་འཇུག་བྱོས། + ཁྱེད་ཀྱིས་ Tor བེད་སྤྱོད་གཏོང་གི་ཡོད་པར་བརྟེན། ངེས་པར་དུ་ XMPP ཡི་འབྲེལ་མཐུད་མཁོ་སྤྲོད་མཁན་གྱི་མིང་གཙོ་བོ་དེ་ཐད་ཀར་རྒྱ་ཆེ་བའི་ཐོ་མཛོད་སྒྲིག་བཟོའི་ནང་དུ་འཇུག་དགོས། + ཉེན་བརྡ། ཞབས་ཞུ་འདི་ཡིས་ཤུགས་ཆུངས་བའི་གསང་ཚིག་ངོས་སྦྱོར་ཞིག་བེད་སྤྱོད་ཀྱི་འདུག དེར་བརྟེན་གསང་ཚིག་ཡར་རྒྱས་གཏོང་དགོས་པའི་རེ་སྐུལ་ཞུ་དགོས། + ད་ཡོད་ཀྱི་ངོས་སྦྱོར་དེ་དང་མཁོ་སྤྲོད་བྱས་བའི་ངོས་སྦྱོར་གཉིས་མཚུངས་ཀྱི་མི་འདུག + མཉམ་འདུས་གླེང་མོལ་ནང་འཛུལ་བའམ་ཡང་ན་གླེང་མོལ་གསར་བ་བཟོ་ཐུབ་ཀྱི་མི་འདུག + དགོངས་དག ང་ཚོས་ཡིག་ཆ་འདིའི་རིགས་བགོ་འགྲེམས་ཐུབ་ཀྱི་མེད། + ཡིག་ཆ་འདི་དག་བགོ་འགྲེམས་བྱེད་པ་ལ་ཁྱོད་ཀྱིས་ངེས་པར་དུ་གསང་སྡོམ་བྱེད་དགོས། + ཡིག་ཆ་བགོ་འགྲེམས་ཐུབ་པའི་ཆེད་དུ་གླེང་མོལ་གསང་སྡོམ་བྱོས། + རྒྱབ་སྐྱོར་མེད་པའི་ཡིག་ཆའི་རིགས་བགོ་འགྲེམས་བྱེད་མི་ཐུབ། + ཉེན་སྲུང་གླེང་མོལ་གྱིས་བྱེད་བཞིན་པའི་ལས་ཀ་ཚང་མ་མེད་པ་བཟོ་དགོས་པའི་རེ་བ་སྟོན་པ་ཤེས་བྱུང་། དེར་རྟེན་ཉེན་སྲུང་གླེང་མོལ་ལ་སྐྱོན་ཤོར་སྲིད། ཕྱིར་ཐོན་གྱི་ཐོ་མཛོད་བེད་སྤྱད་ནས་ཚང་མ་ནས་ཐོན་དགོས། + ཡིག་ཆ་འདིའི་རིགས་ལ་བལྟ་མཁན་མི་འདུག + ཨང་ཚབ་འཚག་སེལ་མ་བྱས་སྔོན་དུ་ཉེན་སྲུང་ཅན་གྱི་གླེང་མོལ་འགོ་འཛུགས་རོགས། + གསང་སྡོམ་འཕྲིན་ཐུང་གི་ལྡེ་མིག་འཁོར་ཐག་དེ་ནང་འདྲེན་བྱས་མི་འདུག ད་ཡོད་ཡིག་ཆ་ཁག་གི་བཟོ་བཀོད་དང་གནས་གཞི་ལ་ལྟ་ཞིབ་བྱེད་རོགས། + ཀམ་པུ་ཊར་གྱི་མདུན་ངོས་ཀྱི་ KeySync tool ནས་ཡིག་ཆ་\'otr_keystore.ofcaes དེ་ཕབ་ཞུས་བྱས་ནས་ཁྱོད་ཀྱི་ཀམ་པུ་ཊར་གྱི་ལམ་སྟོན་མཛོད་ཁང་ངོ་མ་དེའི་ནང་ཉོར། + འཕྲིན་ཐུང་འདི་བཏང་མི་ཐུབ། + བསྐྱར་མཐུད་བྱུང་མཚམས་འཕྲིན་ཐུང་དེ་གཏོང་གི་རེད། + %1$sདྲ་ཐོག་ཏུ་མི་འདུག ཁྱེད་ནས་བཏང་བའི་འཕྲིན་ཐུང་དེ་།%1$s དྲ་ལམ་དུ་སླེབས་པ་དང་སྐྱེལ་ཐུབ། + %1$sཁྱེད་ཀྱི་འབྲེལ་ཐོའི་ནང་མི་འདུག + ཁྱེད་ཀྱི་ནང་འཇུག་བྱས་པའི་གསང་ཡིག་དེ་མཐུན་གྱི་མི་འདུག + གལ་ཆེ་ཆུང་གི་གོ་རིམ་དེ་གཙོ་བོ་ ༠ ནས་ ༡༢༧ བར་ཡིན། [༠..༡༢༧] + གཞོགས་ཀྱི་ཨང་གྲངས་དེ་ངེས་པར་དུ་ཨང་ཀི་ཡིན་དགོས། + ནོར་འཁྲུལ་ཕར་ཐོངས། + ཡིག་ཆ་དེ་གསོག་ཉར་ལ་འཇོག་ཐུབ་ཀྱི་མི་འདུག + ནོར་འཁྲུལ་ཆེན་པོ། མཉེན་ཆས་ཀྱི་རེའུ་མིག་སྒོ་ཕྱེས་མི་ཐུབ་པའམ་ཡང་ན་དེའི་ནང་དུ་འཇུག་ཐུབ་ཀྱི་མི་འདུག མཉེན་ཆས་དེ་བསྐྱར་དུ་ནང་འཇུག་བྱེད་པའམ་ཡང་ན་གཙང་མ་བཟོ་རོགས། + འབྲེལ་ཐག་དེར་འགྲོ་ཐུབ་ཆེད་མཉེན་ཆས་མ་ལག་གཅིག་ཀྱང་མི་འདུག + ཐོ་བྱང་སྒྲིག་བཟོ། + + ཚན་པ་ཁག + སྒོ་ཕྱེ་ཡོད་པའི་གླེང་མོལ་ཁག + ཕྱིར་ཐོན། + ཉེན་ཁའི་མཐེབ་ཅུ། + གྲོགས་པོ་རྣམས། + འབྲེལ་ཐོ། + ཞབས་ཞུ་མཁན་གྱི་ངོས་སྦྱོར་ལ་མོས་མཐུན་བྱོས། + སོ་སོའི་མཛུབ་རིས་སྟོན། + ཕབ་ལེན་བྱེད་བཞིན་པ། + + ཉེན་སྲུང་གླེང་མོལ་གྱི་སྐོར།\nhttps://guardianproject.info/apps/chatsecure/ + + འབྲེལ་མིང་ཐོ་གཞུང་། + སྒྲིག་བཟོ། + ཐོ་མཛོད་འཛིན་སྐྱོང་། + སྒོ་རྒྱོབས། + འབྲེལ་གཏུག་ཐོ་གཞུང་། + + འཇེབ་པར། (XMPP) + ཉེ་འཁོར་གྱི་ས་ཆ། (Bonjour/ZeroConf) + གྷོ་རྒལ་ཐོ་བྱང་། + dukgo.com + + + དྲ་ཐོག་ཏུ་ཡོད། + བྲེལ་བ་ཡོད། + ད་ལྟ་འདིར་མེད། + ལས་ཀ་བྱེད་ཀྱི་མེད། + དྲ་ཐོག་ཏུ་མེད། + དྲ་ཐོག་ཏུ་མེད་པའི་རྣམ་པ་སྟོན། + དགའ་པོ། ཡིད་སྐྱོ་བོ། @@ -411,7 +515,7 @@ གད་མོ་ཤོར་བ། ཐེ་ཚོམ་སྐྱེས་པ། - + དགའ་པོ། ཡིད་སྐྱོ་བོ། @@ -450,128 +554,34 @@ :-D o_O - - འབྲེལ་གཏུག་ཐོ་གཞུང་། - གསང་བ་སྡོམ་/མི་སྡོམ། - ཀྲོར་བརྒྱུད་ནས་འབྲེལ་མཐུད་བྱོས། - - འགོ་འཛུགས་པར་རྔམས་ཀྱི་འདུག - འགོ་བཙུགས་། - བང་མཛོད་སྒྲིག་འཇུག - - - དེ་ཇི་ལྟར་བདེ་འཇགས་ཡིན་ནམ། - ཟིན་ཐོ་མེད་པའི་འཕྲིན་གཏོང་ནི་འཛམ་གླིང་ནང་གི་སྒེར་གྱི་གླེང་མོལ་ཚང་མ་གསང་བདེ་ཡོང་བའི་ཆེད་དུ་བཟོས་བའི་བདེ་འཇགས་མ་ལག་ཞིག་སྟེ། དེའི་ཁོངས་སུ་གསང་སྡོམ་དང་ར་སྤྲོད། ཕྱི་འདོར་། གསང་གཏོང་བཅས་ཚུད། \n\nOTR-protocol ཡིས་དྲ་ཐོག་གླེང་མོལ་ Adium དང་ཡང་ན་ Pidgin ལྟ་བུའི་མཉམ་དུ་བསྟུན་ཐུབ་།\n\n - - ངའི་གླེང་མོལ་དེ་བདེ་འཇགས་ཡིན་ནམ། - - གསང་ཚིག་སྒྲིག་སྦྱོར་བྱེད། - གསང་ཚིག : - གསང་ཚིག (སླར་ཡང་།): - - user@domain.com - གསང་ཚིག : - ཚད་མཐོའི་ཐོ་བྱང་སྒྲིག་བཟོ། - ཐོ་མཛོད་ཀྱི་རིགས། - - རྒྱུན་མཐུད་། - གསང་ཚིག་བསྐྱར་དྲན། - གསང་ཚིག་ཉར་བ། - གསང་ཚིག་ཉར་མི་འདུག - རང་འགུལ་གྱིས་ནང་དུ་འཛུལ་འཇུག - ད་ལྟ་ནང་དུ་ཞུགས། - གཤམ་གྱི་ཐོ་བྱང་སྒྲིག་འཇུག་དེར་མཐུད། - གཤམ་གྱི་ཐོ་བྱང་སྒྲིག་འཇུག་དེར་མ་མཐུད། - - དོ་བདག་གི་(འདམ་ཀ།) - ཐོ་བྱང་གི་མིང་གཞན། - ཇི་ལྟར་ཁྱེད་ཀྱི་ཐོ་བྱང་དྲ་ཐོག་ལ་མངོན་གྱི་ཡོད་དམ། - སོ་སོའི་ངོ་སྤྲོད། - ཁྱེད་ཀྱི་སྐོར་ལ་འགྲེལ་བརྗོད་མདོར་བསྡུས་ཤིག - - ཡི་གེ་ངེས་ལེན་མ་བྱས/ གསང་སྡོམ་བཟོས། - གལ་སྲིད་ཐུབ་ན། གླེང་མོལ་ཆ་ཚང་རང་བཞིན་ནས་གསང་སྡོམ་ཐུབ་པ་བྱོས། - རེ་སྐུལ་ལྟར་། གླེང་མོལ་གསང་སྡོམ་བྱས། - གླེང་མོལ་གསང་སྡོམ་བཤིག - - ཁྱད་ཆོས་ནུས་ལྡན་བཟོ་བ། - ལྡེ་མིག་གི་ཆ་བསྐྲུན་གྱི་ཡོད།... - ནང་དུ་ཞུགས་བཞིན་ཡོད། - - ཁོང་ཚོའི་མཛུབ་ཐེལ། - ཁྱེད་ཀྱི་མཛུབ་ཐེལ། - ནང་དུ་ཞུགས། - ལྡེ་མིག་གི་ཚབ། - ཉེན་བརྡ། གླེང་མོལ་འདི་གསང་སྡོམ་བྱེད་མེད། - གློག་མོལ་དེ་བདེ་འཇགས་ཡིན། འོན་ཀྱང་། གླེང་མོལ་བྱེད་མཁན་རྣམས་ཀྱི་ངོ་རྟགས་ར་སྤྲོད་བྱས་མེད་། - ཉེན་བརྡ། གླེང་མོལ་གྱི་གསང་སྡོམ་བཀག་ཟིན། - གླེང་མོལ་འདི་ལ་བདེ་འཇགས་དང་ཁག་ཐག་ཡོད་། - ཐོ་མཛོད་ཀྱི་སྣེ་ཤན། - ཁྱེད་ཀྱི་ཐོ་མཛོད་ཀྱི་ངོ་རྟགས། - སྡེབ་སྒྲིག་ཞབས་ཞུའི་འཕྲུལ་ཆས། - ཁྱེད་རང་གྲ་སྒྲིག་ཡིན་ནམ། - སྐུ་མཁྱེན། ཁྱེད་ཀྱི་ཐོ་མཛོད་ཀྱི་གསང་གྲངས་བཅུག (user@hostname) - སྐུ་མཁྱེན། ཁྱེད་རང་གི་ Jabber/XMPP གླེང་མོལ་ཞབས་ཞུའི་མིང་དང་ཨང་གྲངས་འཇུག་རོགས། \n(5222 ནི་གཏན་འཁེལ་ཡིན།) - ཁྱེད་ཀྱིས་ཁྱེད་ཀྱི་ཐོ་མཛོད་ཀྱི་ཁ་བྱང་གི་ཆེད་དུ་། hostname.com ལ་ནང་འཇུག་བྱས་མི་འདུག བསྐྱར་དུ་ཚོད་ལྟ་བྱོས། - ཁྱེད་རང་གི་དྲ་ཐོག་ཞབས་ཞུའི་མིང་ལ་ .com དང་ .net ཡང་ན་ཟུར་སྣོན་སོགས་ཅི་ཡང་མི་འདུག བསྐྱར་དུ་ཚོད་ལྟ་བྱོས། - ཁྱེད་རང་གི་གསང་ཡིག་འཇོག - - ཐོ་མཛོད་སྒྲིག་བཟོ། - སྒེ་ཧྥི་ལོ་ཁུ། - - ཚན་པ་ཁག - OTR ལྡེ་མིག་གསར་བ་ཞིག་མཁོ་སྒྲུབ་བཞིན་པ། - འབྲེལ་གཏུག - གླེང་མོལ་བྱེད་འདོད་ཡོད་པའི་འབྲེལ་གཏུག་གི་མིང་འཇུག - འཛུལ། - སྐད་ཡིག - སྐད་ཡིག་མི་འདྲ། - ནང་དུ་སྐད་ཡིག་གང་ཞིག་བསྟན་ཡོད་དམ། - Do SRV Lookup - དྲ་ཁོངས་ཀྱི་མིང་ནས་སྤྱིར་བཏང་གིXMPP ཞབས་ཞུ་དེ་འཚོལ་བར་། DRV བེད་སྤྱོད་ཐོངས་། - གསང་དམ་མ་བྱས་བའི་འགྲུལ་བཞུད་སྐབས་སུ། མིང་དང་གསང་ཚིག་གཉིས་ཡི་གེར་གཏོང་བཅུག - Allow Plain Text Auth - ལག་འཁྱེར་དེར་ཡིད་ཆེས་གཏད་ཆོག་པའི་ར་སྤྲོད་བྱོས། - TLS ར་སྤྲོད་། - TLS/SSL ཡི་འབྲེལ་མཐུད་དགོས། - གསང་སྡོམ་དེ་བསྐུར། - ཇི་ལྟར་གསང་སྡོམ་ཅན་གྱི་གླེང་མོལ་འགོ་འཛུགས་གི་རེད་། - གལ་སྲིད་དགོས་མཁོ་བྱུང་ན། འབྲེལ་མཐུད་རྒྱག་སའི་ཞབས་ཞུ་འདི་རེད། - དྲ་ཐོག་ཞབས་ཞུར་མཐུད། - ཞབས་འཕྲུལ་XMPP ཡི་མཐུད་སྣེ TCP - དྲ་ཐོག་ཞབས་ཞུའི་མཐུད་ས། - XMPP ཡི་ཁོངས་མཛོད་། - ཁ་བྱང་སྒོ་ཕྱེས་ཚར་བ་དེ་ནས་འབྲེལ་མཐུད་འདི་ལོགས་སུ་དབྱེ་བ། - XMPP ཡི་ཡོང་ཁུངས་སྔོན་སྤྱོད་རིམ་པ། - ཐོན་ཁོངས་སྣ་མང་ཅན་གྱི་འཕྲིན་ཐུང་དེ་སྔོན་སྤྱོད་རིམ་པ་མཐོ་ཤོས་ཀྱི་ཁོངས་སུ་གཏོང་གི་རེད་། - མདུན་ངོས། - རྒྱབ་ངོས། - གླེང་མོལ་བྱེད་པ་རྣམས། - འཕྲིན་ཐུང་(རྣམས་)གསར་པ་གཏོང་མཁན། - ར་སྤྲོད་ཁུངས་སྐྱེལ། - ཁོང་ཚོ་སུ་ཡིན་པ་ར་སྤྲོད་པའི་ཕྱིར་། ཁྱེད་ཀྱི་འབྲེལ་མཐུད་ཚོར་དྲི་བ་དང་དེ་ལ་བབས་པའི་ལན་བསྐུར་། - དྲི་བ་ནི། - ཚོད་དཔག་གི་ལན། - ཁྱེད་ཀྱི་འབྲེལ་བ་དེས་ཁྱེད་རང་་དག་སྐྱེལ་ལེགས་འགྲུབ་བྱས་འདུག ད། ཁྱེད་ཀྱི་དྲི་བ་དག་དྲིས་ནས་ཁྱེད་ཀྱི་འབྲེལ་བ་དེ་དག་སྐྱེད་བྱེད་དགོས། - གླེང་མོལ་མི་འདུག⏎\n⏎འདི་ནས་གཅིག་འགོ་ཚུགས། - པར་གཞི་ནག་པོ་དེ་བེད་སྤྱོད་ཐོངས། - ཁྱེད་ཀྱིས་ བེད་སྤྱོད་གཏོང་གི་ཡོད་པར་བརྟེན། ཁྱེད་ཀྱིས་ངེས་པར་དུ་ XMPP ཡི་འབྲེལ་མཐུད་མཁོ་སྤྲོད་མཁན་གྱི་མིང་གཙོ་བོ་དེ་ཐད་ཀར་རྒྱ་ཆེ་བའི་ཐོ་མཛོད་སྒྲིག་བཟོའི་ཁོངས་སུ་འཇུག་དགོས་རེད། - - རྟག་པར་དུ་འགོ་འཛུགས་པ་དང་རང་བཞིན་ནས་ཁ་སྔོན་ནང་འཛུལ་བྱས་བའི་ཐོ་བྱང་དེ་ལ་ཐོ་འགོད་བྱོས། - ཁྱོད་ལ་སྡེབ་སྒྲིག་ཟིན་པའི་ཐོ་མཛོད་མི་འདུག⏎\n⏎འདི་ནས་གཅིག་བསྣོན། - གྷོ་རྒལ་ཐོ་མཛོད། - ནང་གསོག་འཕྲིན་ཐུང་གི་མཛོད་གཅིག་པོ། - འཕྲིན་ཐུང་ཕྱིར་གྱར་མ་འབྱུང་བའི་ཆེད་དུ། འཕྲིན་ཐུང་དག་ནང་གསོག་ཡོ་ཆས་ཀྱི་ཁོངས་སུ་ཉོར། flashཧྤི་ལ་ཞེས་ཉར་ཀྱི་ཉར་གསོགས་ཁོངས་སུ་མ་ཉར། (དེ་ཡིས་འཕྲིན་ཐུང་བརླག་པར་རྐྱེན་བྱེད་སྲིད) - རྒྱབ་ལྗོངས་ཀྱི་སྣང་བརྙན། - བཀོལ་སྤྱོད་མ་ལག་འདིའི་ཆེད་དུ། རྒྱབ་ལྗོངས་སྣང་བརྙན་དེ་ལ་ (\"/sdcard/foo.jpg\")སྒྲིག་འཇུག་བྱོས། - ཉེན་སྲུང་དང་བདེ་འཇགས། - བེད་སྤྱོད་གཏོང་མཁན་གྱི་མཐུད་ངོས། - ཉར་གསོག་གཞན་པ། - གླེང་མོལ་སྒོ་ཕྱེས།(s) - ཨོར་བྷོ་ཁྲི(ཁྲོར) - ཕྱིར་ཐོན། - - ཁྱེད་ཀྱིས་ཞབས་ཞུ་ཚང་མ་སྒོ་བརྒྱབ་ནས་བརྒྱུད་རིམ་ཚང་མ་མེད་པར་གཏོང་འདོད་དམ། (hard exit)? + ད་ཡོད་ཐོ་བྱང་། + XMPP serverཡང་ན་ Jabber ཡི་སྒང་དུ་ཡོད་པའི་ངའི་ད་ཡོད་ཐོ་མིང་དེར་སྦྲེལ་རོགས། + གྷོ་རྒལ་ཐོ་མིང་། + ཁྱོད་ཀྱི་ད་ཡོད་གྷོ་རྒལ་ཐོ་མིང་རྒྱུད་གྷོ་རྒལ་བེད་སྤྱོད་གཏོང་མཁན་གཞན་དང་གླེང་མོལ་བྱེད་ཐུབ། + སྐུད་མེད་དྲ་འབྲེལ་གླེང་མོལ། + གཞན་པ་དག་དང་ལྷན་དུ་ས་གནས་ཀྱི་སྐུད་མེད་དྲ་འབྲེལ་ཐོག་གླེང་མོལ་བྱོས། + སྐུད་མེད་གླེང་མོལ་ཡོད་པ་བཟོས། + ཐོ་བྱང་གསར་བ། + ང་ཚོས་བཟོས་བའམ་ཡང་ན་སོ་སོའི་འདོད་མོས་ལྟར་ཐོ་མིང་གསར་པ་དེབ་སྐྱེལ་བྱེད་ཆོག + ཐོ་བྱང་གསར་བ་ཞིག་བཟོས། + གསང་བའི་ངོ་བོ། + གཞན་གྱི་ཧ་གོ་མི་ཐུབ་པའི་གླེང་མོལ་ཐོ་མིང་ཞིག་ལས་སླ་པོའི་ངང་སྒོ་ཕྱེས། (དེ་ལ Orbot: Tor for Android དགོས) + ངོ་བོ་ཞིག་བསྐྲུན། + འདི་མཉམ་འདུས་གླེང་མོལ་རེད། + [བསྐྱར་དུ་ཐོངས།] + [བསྐྱར་དུ་ཐོངས] + ཡིག་ཆ་འདི་ཉེན་སྲུང་ལྡན་པའི་ཐོག་བསྐུར་ཐུབ་ཀྱི་མི་འདུག + Orbot ནང་འཇུག་བྱེད་ཀྱི་ཡིན་ནམ། + ཁྱེད་ལ་ཨང་ཚབ་ཅན་གྱི་གླེང་མོལ་སོགས་ཀྱི་ཆེད་ངེས་པར་དུ་ Orbot ནང་འཇུག་བྱས་ནས་དེ་ནུས་ལྡན་བཟོ་དགོས། ཁྱེད་ཀྱིས་དེ་ནང་འཇུག་བྱེད་རྒྱུར་མོས་མཐུན་ཡོད་མ། + དུས་རྟག་དུ། + Orbot འགོ་ཚུགས་ཀྱི་ཡིན་ནམ། + Orbot ལས་ཀ་བྱེད་ཀྱི་མེད་པ་འདྲ་པོ་འདུག ཁྱེད་ཀྱིས་དེ་ཡང་བསྐྱར་འགོ་བཙུགས་ནས་ Tor ལ་མཐུད་འདོད་ཡོད་དམ། + གླེང་མོལ་གྱི་ཆེད་བེད་སྤྱོད་གཏོང་རྒྱུའི་མིང་བརྫུན་མ། + གླེང་མོལ་དུ་འཛུལ་རྒྱུའི་དྲ་ཁང་གི་མིང་དང་། དེ་མིན་མིང་གླེང་མོལ་ཁང་གསར་པ་ཞིག་བཟོས། + མཉམ་འདུས་གླེང་མོལ་གྱི་ཞབས་ཞུ། (conference.foo.com) + ཁྱོད་ཀྱི་ཉར་ཁང་ལྟེ་བ་དེ་འཕྲོ་བརླག་ཕྱིན་འདུག དེར་རྟེན་ཉེན་སྲུང་གླེང་མོལ་ཡང་བསྐྱར་ནང་འཇུག་བྱོས། དེ་མིན་མཉེན་ཆས་མ་ལག་ཆེད་དུ་ལས་གཞི་རིའུ་མིག་ \'clear data\' གཙང་མ་བཟོས། + + ཁྱོད་ལ་ཀློག་མི་ཐུབ་པའི་གསང་སྡོམ་འཕྲིན་ཐུང་ཞིག་འབྱོར་འདུག + ཁྱོད་ཀྱིས་ང་ལ་བཏང་བའི་འཕྲིན་ཐུང་དེ་བལྟ་ཐུབ་གི་མི་འདུག + གདམ་ཁ་མང་པོའི་ཆེད་གཡས་དང་གཡོན་ལ་ཤུགས་ཀྱི་གཞུས། diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml index 632d94475..90172bb3e 100644 --- a/res/values-ca/arrays.xml +++ b/res/values-ca/arrays.xml @@ -6,64 +6,4 @@ Com s\'ha sol·licitat Inhabilitat / Mai - - força - automàtic - demanat - inhabilitat - - - Predeterminat - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 5c17d5399..04d9ddedd 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -1,364 +1,384 @@ - + + + ChatSecure + ChatSecure + llegiu missatges instantanis - \nPermet als programes llegir dades del proveïdor de contingut de missatgeria instantània.\n + Permet als programes llegir dades del proveïdor de contingut de missatgeria instantània. escriviu missatges instantanis - \nPermet a les aplicacions escriure dades al proveïdor de contingut de missatgeria instantània.\n - - ChatSecure - - - Trieu un compte - - Quan a - - Afegeix un compte - - Edita un compte - - Esborra un compte - - Surt del tot - - - Trieu un compte - - - - Cancel·la l\'autenticació - - - Afegeix un contacte - - Esborra un contacte - - Bloqueja un contacte - - Bloquejat - - Comptes - - Nou xat - - Nou compte - - Paràmetres - - Cerca contactes - - Inicia un xat - - Surt - - Verifica - - - Inicia el xifrat - Atura el xifrat - Neteja el xat - Finalitza el xat - Trucada segura - - - Llistat de contactes - - Convida\u2026 - - Canvia de xat - - Insereix una emoticona - Torna a enviar - - Escaneja l\'empremta dactilar - La vostra empremta dactilar - Verifica l\'empremta digital - Verifica el secret - - Menú+ - - - + Permet a les aplicacions escriure dades al proveïdor de contingut de missatgeria instantània. + inicia el servei de MI + Permet a les aplicacions iniciar el servei de MI mitjançant intent + Confirmeu - - Voleu sortir de tots els serveis? - - - - D\'acord - Cancel·la - D\'acord - Cancel·la - - - - - - + Següent + Enrera + Connecta + Reprodueix + Configuració + + Obre + ChatSecure és bloquejat + Estableix una contrasenya + Confirmeu la contrasenya + Contrasenya + Opcionalment, podeu definir una contrasenya mestra pel ChatSecure per prevenir l\'accés als vostres contactes i missatges sense contrasenya: + Confirmeu la nova contrasenya + Les contrasenyes no coincideixen. Torneu-ho a provar + Omet >> + Indicador d\'informació + Introduïu la contrasenya + Contrasenya: + Contrasenya + Contrasenya (un altre cop): + + Trieu un compte + Trieu un compte + (%1$d) + Afegeix el compte %1$s + Quan a + Surt del tot + Voleu sortir de tots els serveis? + Heu tancat la sessió %1$s. + S\'ha tancat la vostra sessió %1$s per %2$s + El primer cop que empreu ChatSecure? + No podeu esperar per començar? + Comenceu + Paràmetres dels comptes + Paràmetres de contrasenya + Abans de començar, trieu una contrasenya segura per a protegir les vostres dades del ChatSecure d\'un accés fraudulent. + Contrasenya: + Contrasenya (un altre cop): + Introduïu una contrasenya *nova*. Ha de tenir com a mínim una majúscula, una minúscula i un nombre i ser més llarga de sis caràcters. + S\'estan xifrant les vostres notes amb la nova contrasenya (sigueu pacients…) + Introduïu la vostra contrasenya: + Benvinguts! Introduïu una contrasenya forta per assegurar les vostres anotacions. Ha de tenir com a mínim una majúscula, una minúsculam un nombre i ser més llarga de sis caràcters. + La vostra contrasenya no és prou llarga + La vostra contrasenya no té cap majúscula + La vostra contrasenya no té cap minúscula + La vostra contrasenya no té cap nombre + + Quant al ChatSecure + ChatSecure és una aplicació mòbil de missatgeria instantània que proporciona funcionalitats extres de seguretat que eviten que d\'altres puguin xafardejar les vostres converses i comunicacions. +La aplicació dóna suport a qualsevol servei de xat que empri els protocols Jabber o XMPP, com Google GTalk o Jabber.org. + + Quant n\'és de segur? + La \"missatgeria Off-the-Record\" és un sistema de seguretat dissenyat per permetre privacitat mimetitzant les característiques d\'una conversa privada al món real, incloent xifrat, autenticació, rebuig i Forward Secrecy.\n\nEl protocol OTR és compatible amb clients de xat d\'escriptori com l\'Adium o el Pidgin. + + + Els meus xats són segurs? + La funcionalitat de xifrat de xat de ChatSecure només funciona quan s\'està xatejant amb altres que emprin programes compatibles, així que us hauríeu d\'assegurar que els vostres contactes empren ChatSecure per a mòbil i Adium o Pidgin a l\'ordinador. Podeu afinar la configuració de com i quan prova el ChatSecure de xifrar els xats a \'Paràmetres del compte\'. + + + Nou compte + Afegeix un compte + Edita un compte + Esborra un compte + Compte existent + User@Host - Contrasenya: - Recorda la meva contrasenya. - Autentifica\'m automàticament. - No teniu un compte? - + Entreu Per la vostra seguretat, si perdeu el telèfon o us el roben, aneu al lloc web al vostre ordinador i canvieu la contrasenya. Aquesta opció us autentifica automàticament cada cop que obriu aquesta aplicació. Per inhabilitar-la, sortiu i desmarqueu la caixa \"Autentifica\'m automàticament\". - - Entreu - - - arrencant el ChatSecure... - + Connecta amb Tor (requereix l\'aplicació Orbot) + usuari@domini + nou nom d\'usuari + proveïdor del servei (dukgo.com, jabber.ccc.de) + contrasenya + confirmeu la contrasenya + Paràmetres avançats de compte + Paràmetres dels comptes + Registreu un compte + Persistència + Recorda la contrasenya + Contrasenya a memòria cau + No s\'ha desat la contrasenya a la memòria cau + Entra automàticament + Connecta a l\'arrencada del ChatSecure + No connectis a l\'arrencada del ChatSecure + Entra ara + Connecta amb els paràmetres del següent compte + No connectis amb els paràmetres del següent compte + Personal (opcional) + Àlies del compte (el vostre nom) + Com es veu en línia el vostre compte + Perfil + Un breu escrit sobre tu + Força el xifrat / refusa el text en pla + Quan sigui possible, xifra els xats automàticametn + Xifra els xats quan es demani + Inhabilita el xifrat del xat + S\'estan validant les credencials… + S\'està generant el parell de claus… + S\'està accedint… + Assistent de comptes + La ID del vostre compte + Configureu el servidor + Esteu a punt? + Introduïu la vostra ID del compte per configurar el ChatSecure pel vostre servei de xat XMPP. S\'assembla a una adreça de correu electrònic: + Introduïu la vostra ID del compte (usuari@nom_de_màquina) + Introduïu o editeu el nom de màquina del vostre servidor de xat jabber/xmpp i el número de port (el predeterminat és el 5222). + S\'ha configurat correctament el ChatSecure i ara podeu connectar-vos al vostre servei i començar a xatejar de forma segura i privada! + Orbot (Tor) + Domini del servei de xat + S\'està registrant un compte nou… + anem fent… + Cancel·la l\'autenticació Entrant\u2026 - - - S\'han inhabilitat les dades de xarxa - - - \nEs necessita connectivitat de dades de xarxa (incloent dades en rerefons) per a que l\'aplicació pugui accedir.\n - - - Habilita - - Tanca - - - - + S\'està sortint\u2026 + + Fes una cerca SRV + Empra DNS SRV per trobar el servidor XMPP del nom de domini + Permet que el nom d\'usuari i la contrasenya s\'enviïn en text pla quan s\'utilitzi un transport sense xifrat + Permet l\'autenticació en text pla + Verifica que el certificat és confiable + Verificació TLS + Requereix una connexió TLS + Xifrat del transport + com s\'inicien els xats xifrats + El servidor al que connectar, si s\'escau + Connecta amb el servidor + Port TCP pel servidor XMPP + Port del servidor + Recurs XMPP + per diferenciar aquesta connexió d\'altres clients que també hi hagin accedit + Prioritat de recursos XMPP + Els missatges a clients amb diversos recursos actius s\'enviaran al recurs amb la prioritat més alta + + Converses + No hi ha converses.\n\nToqueu per a començar-ne una! + No teniu cap configurat cap compte.\n\nToqueu per afegir-ne un! + Llista de contactes - %1$s + Afegeix un contacte + Esborra el contacte + Bloca el contacte + Àlies del contacte + Bloquejat + Àlies + S\'eliminarà el contacte "%1$s". + Es blocarà el contacte "%1$s". + Es desblocarà el contacte "%1$s". + S\'ha afegit el contacte "%1$s". + S\'ha esborrat el contacte "%1$s". + S\'ha blocat el contacte "%1$s". + S\'ha desblocat el contacte "%1$s". + Nou xat + Cerca contactes + Mostra la graella + Inicia un xat + Mostra el perfil + Verifica el contacte + Xats actius (%1$d) + %1$d en línia Invitacions d\'amics - (Desconegut) - Buit - - Cap conversa - - Seleccioneu els contactes que voleu convidar - Escriviu per cercar un contacte - No s\'ha trobat cap contacte. - - - No teniu bloquejat cap contacte. - - + No hi ha cap conversa en curs! + Contate + escriviu el nom del contacte amb el que voleu xatejar + Som-hi + No hi ha xats actius. + Afegeix un contacte + Nom d\'usuari o JabberID de la persona a afegir: + Compte al que s\'ha d\'afegir: + Escriviu un nom per afegir dels contactes. + Envia la invitació Perfil de contacte - Estat: - Tipus de client: - Ordinador - Mòbil - - - En línia - - Ocupat - - Absent - - Inactiu - - Fora de línia - - Que sembli fora de línia - - - - + Contactes blocats - %1$s + No teniu bloquejat cap contacte. + + Xategeu amb %1$s Jo - - Escriviu per redactar - - - - - - - - - + %1$s és en línia + %1$s no hi és + %1$s està ocupat/da + %1$s no està connectat/da + %1$s s\'ha unit + %1$s ha marxat + Envia la fotografia + Envia el fitxer + Envia l\'àudio + Fes una fotografia + Transferència de fitxer + Ha finalitzat la transferència + S\'està efectuant la transferència + Accepteu la transferència? + us vol enviar un fitxer + No hi ha cap connexió disponible per fer l\'enviament! Envia - - No s\'ha pogut enviar el missatge - - S\'ha perdut la connexió amb el servidor. Els missatges s\'enviaran quan es torni a ser en línia. - - - + Envia un missatge + Envia un missatge segur + Torna a enviar + Finalitza el xat + Neteja el xat + Insereix una emoticona + Canvia de xat + Menú+ Seleccioneu un enllaç - - No hi ha xats actius. - - S\'està iniciat una sessió de xat xifrat... - S\'està aturant la sessió de xat xifrat... - - - Afegeix un contacte - - Adreça de correu electrònic de la persona a qui voleu convidar: - - Trieu una llista: - - Escriviu un nom per afegir dels contactes. - - Envia la invitació - - Jabber (XMPP) - Xat de xarxa local (Bonjour/ZeroConf) - + Esborra + Conserva els fitxers + Voleu esborrar l\'emmagatzematge segur de la sessió del xat? + S\'esborrarà de forma permanent tots els fitxers pujats i baixats de la sessió. Avís: l\'acció no es pot desfer! + Voleu esborrar l\'original? + Aquest fitxer es copiarà a l\'emmagatzematge segur abans d\'enviar-se. Voleu esborrar el fitxer original de l\'emmagatzematge segur del dispositiu? + Conserva + Exporta + Voleu exportar el fitxer multimèdia? + S\'exportarà aquest fitxer multimèdia a %1$s + Voleu tancar el xat? + + Accepta + Rebutja + Xat de grup + Creeu o afegiu-vos a un xat de grup + Convida\u2026 + Seleccioneu els contactes que voleu convidar + Escriviu per cercar un contacte + + Inicia el xifrat + Atura el xifrat + Accediu + Regenera la clau + El xifratge està desactivat + El xifratge està activat (toqueu per a verificar) + Xifrat i verificat + + S\'ha importat correctament el magatzem de claus OTR + + La vostra empremta dactilar + + Autenticació + Introduïu una pregunta per enviar al vostre contacte i la resposta que espereu que us doni, de cara a verificar que és qui diu ser. + la pregunta a fer + la resposta esperada + El vostre contacte us ha autentificat correctament. Ara autentifiqueu el vostre contacte fent la vostra pregunta. + Envia + Cancel·la + + Trucada segura + Secure Voice + Introduïu aquí el vostre compte OStel.co o d\'un altre servei SIP segur per integració de trucades + Paràmetres del compte + + Seguretat i privacitat Xifrat i anonimat + Xifrat Sí/No + Temps d\'espera excedit per contrasenya + Estona que el xifrat de l\'aplicació ha de dir desbloquejat + + Interfície d\'usuari + Idioma + Idiomes + Empra el tema fosc + Desa els missatges només a la memòria + Només desa els missatges a la memòria, no a l\'emmagatzematge flash, per evitar que puguin accedir-hi. (Pot causar pèrdua de missatges) + Imatge de fons + Defineix el camí (\"/sdcard/foo.jpg\") a una imatge de fons per l\'aplicació + + + Altres afinaments + Comença ChatSecure automàticament + Arrenca i entra automàticament sempre a comptes als que hagi entrat abans Amaga els contactes fora de línia - - Paràmetres de les notificacions - - Notificacions de la missatgeria instantània - - Notifica a la barra d\'estat quan arribi un MI - Empra prioritat de primer pla Redueix la possibilitat de que l\'Android reiniciï la nostra connexió al servei. Això mostrarà una notificació a la barra de notificacions. Interval Heartbeat Empreu un valor més gran (en minuts) per conservar bateria. Un valor gran pot fer que el proveïdor tanqui la vostra connexió per inactivitat. + + Paràmetres de les notificacions + Notificacions de la missatgeria instantània + Notifica a la barra d\'estat quan arribi un MI Vibra - Vibra també quan arribi un MI - So - També toca un to quan arribi un MI - - Trieu el to - - - - - Accepta - - Rebutja - - - Accepta - - Rebutja - - - - - - - + + + S\'han inhabilitat les dades de xarxa + Habilita + Tanca + Voleu sortir de tots els serveis i aturar tots els processos (sortida radical)? + Invitació de xat de grup - - - - - - - inicia el servei de MI - Permet a les aplicacions iniciar el servei de MI mitjançant intent - - + Nou(s) missatge(S) de + Atenció - - No s\'ha afegit la llista. - No s\'ha bloquejat el contacte. - No s\'ha desbloquejat el contacte. - Primer trieu un contacte. - - S\'ha desconnectat!\n - S\'ha produït un error en el servei! - No s\'ha pogut carregar la llista de contactes. - No s\'ha pogut connectar amb el servidor. Reviseu la vostra connexió. - - - Espereu mentre es carrega la vostra llista de contactes. - S\'ha produït un error de xarxa. Es requereix WiFi per a aquesta connexió. - El servidor no dóna suport a aquesta funcionalitat. - La contrasenya no és vàlida. - S\'ha produït un error de servidor. - El servidor no dóna suport a aquesta funcionalitat. - El servidor no és disponible en aquest moment. - Temps excedit al servidor. - El servidor no dóna suport a aquesta versió. - La cua de missatges és plena. - El servidor no dóna suport a reenviar al domini. - No es reconeix aquest usuari. - Heu estat bloquejats per l\'usuari. - La sessió ha expirat, torneu a entrar. - heu entrat des d\'un altre client. heu entrat des d\'un altre client. - No es pot llegir el número de telèfon de la vostra targeta SIM. Contacteu amb la vostra operadora per suport. - No us heu autentificat. - - + ChatSecure ha tingut problemes per validar el vostre nom d\'usuari o contrasenya - verifiqueu les dades i torneu-ho a provar. + ChatSecure ha tingut un problema en generar un parell de claus. + S\'ha produït un error al ChatSecure mentre connectava amb el servidor de xat - repasseu la configuració i torneu-ho a provar. + S\'ha produït un error al ChatSecure mentre connectava - verifiqueu que teniu connexió de dades i torneu-ho a provar. + El ChatSecure està intentant restablir la connexió + No heu introduït la part @nom_de_màquina per la vostra ID del compte. Torneu-hi! + El nom de màquina del vostre servidor no té un apèndix .com, .net o similar. Torneu-hi! + Introduïu la vostra contrasenya: + Com que esteu emprant Tor, heu d\'entrar el nom de màquina del servidor de connexió XMPP directament a \'Paràmetres avançats del compte\' + No ha estat possible crear o afegir-se al xat de grup + No s\'ha importat el magatzem de claus OTR. Verifiqueu que el fitxer existeix i que és al lloc i en el format adient + No s\'ha pogut enviar el missatge + Paràmetres del compte + + Grups + conversa(es) oberta(es) + Pànic + Amics + + + Llista de contactes + Paràmetres + Surt + Llistat de contactes + + Jabber (XMPP) + Compte de Google + dukgo.com + + + En línia + Ocupat + Absent + Inactiu + Fora de línia + Alegria Tristesa @@ -378,7 +398,7 @@ Rient Confusió - + Alegria Tristesa @@ -417,173 +437,6 @@ :-D o_O - Llista de contactes - Xifrat Sí/No - Connecta amb Tor (requereix l\'aplicació Orbot) - - ChatSecure - El primer cop que empreu ChatSecure? - No podeu esperar per començar? - Comenceu - Paràmetres dels comptes - - Quant al ChatSecure - ChatSecure és una aplicació mòbil de missatgeria instantània que proporciona funcionalitats extres de seguretat que eviten que d\'altres puguin xafardejar les vostres converses i comunicacions.\nLa aplicació dóna suport a qualsevol servei de xat que empri els protocols Jabber o XMPP, com Google GTalk o Jabber.org. - - Quant n\'és de segur? - La \"missatgeria Off-the-Record\" és un sistema de seguretat dissenyat per permetre privacitat mimetitzant les característiques d\'una conversa privada al món real, incloent xifrat, autenticació, rebuig i Forward Secrecy.\n - - Els meus xats són segurs? - La funcionalitat de xifrat de xat de ChatSecure només funciona quan s\'està xatejant amb altres que emprin programes compatibles, així que us hauríeu d\'assegurar que els vostres contactes empren ChatSecure per a mòbil i Adium o Pidgin a l\'ordinador. Podeu afinar la configuració de com i quan prova el ChatSecure de xifrar els xats a \'Paràmetres del compte\'.\n - - Paràmetres de contrasenya - Abans de començar, trieu una contrasenya segura per a protegir les vostres dades del ChatSecure d\'un accés fraudulent. - Contrasenya: - Contrasenya (un altre cop): - - usuari@domini.com - nou nom d\'usuari - proveïdor del servei (dukgo.com, jabber.ccc.de) - contrasenya - confirmeu la contrasenya - Paràmetres avançats de compte - Tipus de compte - Registreu un compte - - Persistència - Recorda la contrasenya - Contrasenya a memòria cau - No s\'ha desat la contrasenya a la memòria cau - Entra automàticament - Connecta a l\'arrencada del ChatSecure - No connectis a l\'arrencada del ChatSecure - Entra ara - Connecta amb els paràmetres del següent compte - No connectis amb els paràmetres del següent compte - - Personal (opcional) - Àlies del compte (el vostre nom) - Com es veu en línia el vostre compte - Perfil - Un breu escrit sobre tu - - Força el xifrat / refusa el text en pla - Quan sigui possible, xifra els xats automàticametn - Xifra els xats quan es demani - Inhabilita el xifrat del xat - - S\'estan validant les credencials... - S\'està generant el parell de claus... - Accedint... - - ChatSecure ha tingut problemes per validar el vostre nom d\'usuari o contrasenya - verifiqueu les dades i torneu-ho a provar. - ChatSecure ha tingut un problema en generar un parell de claus. - S\'ha produït un error al ChatSecure mentre connectava amb el servidor de xat - repasseu la configuració i torneu-ho a provar. - S\'ha produït un error al ChatSecure mentre connectava - verifiqueu que teniu connexió de dades i torneu-ho a provar. - La seva empremta digital - La vostra empremta digital - Accediu - Regenera la clau - El ChatSecure ha perdut la connexió - El ChatSecure està intentant restablir la connexió - Advertiment: aquest xat NO és xifrat - Aquest xat és segur però alguna de les identitats dels participants no ha estat verificada - Advertència: s\'ha aturat el xifrat del xat - Aquest xat és segur i verificat - Assistent de comptes - La ID del vostre compte - Configureu el servidor - Esteu a punt? - Introduïu la vostra ID del compte per configurar el ChatSecure pel vostre servei de xat XMPP. S\'assembla a una adreça de correu electrònic: - Introduïu la vostra ID del compte (usuari@nom_de_màquina) - Introduïu o editeu el nom de màquina del vostre servidor de xat jabber/xmpp i el número de port (el predeterminat és el 5222). - S\'ha configurat correctament el ChatSecure i ara podeu connectar-vos al vostre servei i començar a xatejar de forma segura i privada! - No heu introduït la part @nom_de_màquina per la vostra ID del compte. Torneu-hi! - El nom de màquina del vostre servidor no té un apèndix .com, .net o similar. Torneu-hi! - Introduïu la vostra contrasenya: - - Paràmetres del compte - defLoc - ADVERTÈNCIA: aquesta és una versió primerenca del ChatSecure i pot ser que contingui errors o forats de seguretat. - - Grups - S\'està generant un nou parell de claus OTR... - Contate - escriviu el nom del contacte amb el que voleu xatejar - Som-hi - Idioma - Idiomes - Quin idioma hauria de mostrar InTheClear? - Fes una cerca SRV - Empra DNS SRV per trobar el servidor XMPP del nom de domini - Permet que el nom d\'usuari i la contrasenya s\'enviïn en text pla quan s\'utilitzi un transport sense xifrat - Permet l\'autenticació en text pla - Verifica que el certificat és confiable - Verificació TLS - Requereix una connexió TLS/SSL - Xifrat del transport - com s\'inicien els xats xifrats - El servidor al que connectar, si s\'escau - Connecta amb el servidor - Port TCP pel servidor XMPP - Port del servidor - Recurs XMPP - per diferenciar aquesta connexió d\'altres clients que també hi hagin accedit - Prioritat de recursos XMPP - Els missatges a clients amb diversos recursos actius s\'enviaran al recurs amb la prioritat més alta - Següent - Enrera - Converses - Nou(s) missatge(S) de - Autenticació - Introduïu una pregunta per enviar al vostre contacte i la resposta que espereu que us doni, de cara a verificar que és qui diu ser. - la pregunta a fer - la resposta esperada - El vostre contacte us ha autentificat correctament. Ara autentifiqueu el vostre contacte fent la vostra pregunta. - No hi ha converses.\n\nToqueu per a començar-ne una! - Empra el tema fosc - Com que esteu emprant Tor, heu d\'entrar el nom de màquina del servidor de connexió XMPP directament a \'Paràmetres avançats del compte\' - - Comença ChatSecure automàticament - Arrenca i entra automàticament sempre a comptes als que hagi entrat abans - No teniu cap configurat cap compte.\n\nToqueu per afegir-ne un! - Compte de Google - Desa els missatges només a la memòria - Només desa els missatges a la memòria, no a l\'emmagatzematge flash, per evitar que puguin accedir-hi. (Pot causar pèrdua de missatges) - Imatge de fons - Defineix el camí (\"/sdcard/foo.jpg\") a una imatge de fons per l\'aplicació - Seguretat i privacitat - Interfície d\'usuari - Altres afinaments - conversa(es) oberta(es) - Orbot (Tor) - Surt - - Voleu sortir de tots els serveis i aturar tots els processos (sortida radical)? - Introduïu una contrasenya *nova*. Ha de tenir com a mínim una majúscula, una minúscula i un nombre i ser més llarga de sis caràcters. - S\'estan xifrant les vostres notes amb la nova contrasenya (sigueu pacients...) - Introduïu la vostra contrasenya: - Benvinguts! Introduïu una contrasenya forta per assegurar les vostres anotacions. Ha de tenir com a mínim una majúscula, una minúsculam un nombre i ser més llarga de sis caràcters. - La vostra contrasenya no és prou llarga - La vostra contrasenya no té cap majúscula - La vostra contrasenya no té cap minúscula - La vostra contrasenya no té cap nombre - Obre - ChatSecure és bloquejat - Crea una contrasenya - Introduïu la contrasenya - Introduïu la nova contrasenya - Confirmeu la nova contrasenya - Les contrasenyes no coincideixen. Torneu-ho a provar - Secure Voice - Introduïu aquí el vostre compte OStel.co o d\'un altre servei SIP segur per integració de trucades - dukgo.com - S\'ha detectat un magatzem de claus OTR per importar. Voleu escanejar la contrasenya QR ara? - Importa el magatzem de claus OTR - S\'ha importat correctament el magatzem de claus OTR - No s\'ha importat el magatzem de claus OTR. Verifiqueu que el fitxer existeix i que és al lloc i en el format adient - Temps d\'espera excedit per contrasenya - Estona que el xifrat de l\'aplicació ha de dir desbloquejat + Compte existent + Compte nou diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml index 760823673..efe3185f8 100644 --- a/res/values-cs/arrays.xml +++ b/res/values-cs/arrays.xml @@ -1,15 +1,9 @@ - Vyžadovat síly / - Automaticky pokusí + Vyžadovat / Stále + Automaticky Jak bylo požadováno Zakázáno / Nikdy - - vynutit - auto - požadovat - zakázat - diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 64603afb5..580b27f35 100755 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -1,390 +1,493 @@ - - číst rachlé zprávy - \n Umožňuje aplikacím číst data od IM poskytovatele obsahu.\n + + + ChatSecure + ChatSecure + + číst rychlé zprávy + Umožňuje aplikacím číst data od IM poskytovatele obsahu. psát rychlé zprávy - \n Umožňuje aplikacím psát data na IM poskytovatele obsahu.\n - - - - Chat - Vyberte účet - - O - - Přidat účet - - Upravit účet - - Odstranit účet - - Odhlásit všechny - - - Chat - Vyberte účet - - - - Zrušit přihlašování - - - Přidat kontakt - - Smazat kontakt - - Blokovat kontakt - - Blokovaní uživatelé - - Seznam účtů - - Voný Chat - - Nový účet - - Nastavení - - Najít kontakty - - Začít chat - - Odhlásit se - - Zobrazit profil - - - Začátek šifrování - Konec šifrování - Vyčistit Chat - Ukončit chat - - - Seznam kontaktů - - Pozvat... - - Přepnout chat - - Vložit smajlík - Odeslat znovu - - Scan otisku - Váš otisk - - Menu+ - - - + Umožňuje aplikacím psát data na IM poskytovatele obsahu. + spustit službu Chat + Povoluje aplikacím spustit Chat přes službu intent. + Potvrdit - - Chcete odhlásit všechny služby? - - - - - - - OK - Zrušit - OK - Zrušit - - - - - - - - - + Další + Zpět + Připojit + Přehrát + Nastavení + + Otevřít + ChatSecure uzamčen + Nastavit heslo + Potvrdit heslo + Heslo + Můžete volitelně nastavit hlavní heslo pro ChatSecure, aby se zabránilo přístupu k vašim kontaktům a zprávám bez hesla. + Potvrdit nové heslo + Hesla se neshodují, prosím zkuste to znovu + Přeskočit >> + Informační výzva + Prosím, zadejte heslo + Heslo prosím… + Ověřovací heslo + Ověřovací heslo (znova) + + Chat - Vyberte účet + Chat - Vyberte účet + (%1$d) + Přidat účet %1$s + O + Odhlásit všechny + Chcete odhlásit všechny služby? + Byli jste odhlášeni ze služby %1$s. + Byli jste odhlášeni z %1$s, protože %2$s + Používáte ChatSecure poprvé? + Už se nemůžu dočkat, jak začít? + Jak začít + Nastavení účtu + Nastavení ověřovacího hesla + Než začnete, prosím vyberte si bezpečné ověřovací heslo pro ochranu vašich ChatSecure dat proti neoprávněnému přístupu. + Ověřovací heslo: + Ověřovací heslo (znovu): + Zadejte nový ověřovací heslo. Musí obsahovat čísla, malé a velké znaky a musí být delší než šest znaků. + Šifrujeme vaše existující poznámky s novým ověřovacím heslem (trpělivost…) + Zadejte vaše ověřovací heslo: + Vítejte! Zadejte silné ověřovací heslo pro zabezpečení vašich poznámek. Musí obsahovat čísla, velké a malé písmena a musí být délší než šest znaků. + Váše ověřovací heslo není dostatečně dlouhé + Váše ověřovací heslo neobsahuje žádné velké písmena + Váše ověřovací heslo neobsahuje žádné málé písmena + Váše ověřovací heslo neobsahuje žádné číslo + + O ChatSecure + ChatSecure je mobilní aplikace pro rychlé doručování zpráv (instant messaging), která poskytuje zvláštní bezpečnostní prvky, které zabraňují ostatním uživatelům špehování vašich rozhovorů a komunikace.\n\nAplikace podporuje všechny chatovací služby, které používají Jabber nebo XMPP protokol, jako je například Google GTalk nebo Jabber.org. + + Jak je to bezpečné? + \'Off-the-Record Messaging\' je bezpečnostní systém navržen tak, aby napodobil prvky soukromí bezpečného rozhovoru v reálném světě, včetně šifrování, autentizace, zapíratelnosti a zachování tajemství.\n\n OTR protokol je kompaktibilní s pořítačovými klienty jako je Audium či Pidgin. + + Jsou moje Chaty bezpečné? + Šifrovací funkce aplikace ChatSecure mohou pracovat pouze když to podporuje druhá strana. Druhá strana by na telefonu měla používat ChatSecure či na počítači Pidgin a nebo Audium. V nastavení si můžete doladit, kdy a jak se ChatSecure pokusí šifrovat zprávy.\n\nPustě se do toho! + + Nový účet + Přidat účet + Upravit účet + Odstranit účet + Existujcí účet + Uživatelské jméno: - Heslo: - Zapamatovat heslo. - Přihlašovat automaticky. - Nemáte účet? - + Přihlásit se Pokud je telefon ztracen, nebo ukraden, přejděte na webovou stránku a změňte pro větší bezpečnost vaše heslo. Pokud povolíte tuto možnost, budete automaticky přihlášeni pokaždé, když spustíte tuto aplikaci. Chcete-li tuto možnost zakázat, odhlaste se a zrušte zaškrtnutí políčka Přihlašovat automaticky. - - Přihlásit se - - - - Přihlašování... - - - Přenos dat na pozadí je zakázán - - - - - - Povolit - - Ukončit - - - - - - + Připojit přes Tor (Vyžaduje aplikaci Orbot) + user@domain.com + Nové uživatelské jméno + poskytovatel služeb (dukgo.com, jabber.ccc.de) + heslo + potvrdit heslo + Pokročilé nastavení účtu + Typ účtu + Zaregistrovat účet + Vytrvalost + Zapamatovat si heslo + Heslo ukládat do mezipaměti + Heslo není kešováno + Automaticky přihlásit + Připojt ChatSecure po spuštění + Nepřipojovat na ChatSecure po spoštení + Přihlásit se nyní + Připojte následující nastavení účtu + Nemůžu se připojit s následujcím nastavením účtu + Osobní (volitelné) + Alias účtu (vaše jméno) + Jak se váš účet objeví online + Profil + Stručné info o vás + Striktně šifrovat / odmítnout plaintext + Když to bude možné, šifrovat chaty automaticky + Šifrovat chaty jak bylo požadováno + Vypnout šifrování chatu + Ověření pověření… + Generování dvojice klíčů … + Podepisování … + Průvodce účtem + ID vašeho účtu + Nastavit server + Jste připraveni? + Zadejte ID vašeho účtu pro konfiguraci ChatSecure pro vaší XMPP službu. Vypadá jako emailová adresa: + Prosím zadejte svůj ID účtu (uživatel@jménohosta): + Prosím zadejte nebo upravte váš jabber/xmmp server a číslo portu (5222 je výchozí). + ChatSecure byl nastaven, nyní je čas se připojit k vašim službám a začít chatovat bezpečně, spolehlivě a soukromě! + Orbot (Tor) + Doména chat služby + Registruji nový učet… + jak se bude… + Zrušit přihlašování + Přihlašování… + Odhlašování… + + Provést SRV vyhledání + Pomocí DNS SRV najít aktuální XMPP server z názvu domény + Povolte uživatelské jméno a heslo, které bude odesláno jako prostý text při použití nešifrované komunikace + Povolit autorizaci v plain textu + Ověřte, zda certifikát je důvěryhodný + TLS ověření + TLS připojení vyžadováno + Šifrovat přenos + jaké šifrované chaty jsou spuštěny + Server se připojí v případě potřeby + Připojit server + TCP Port pro XMPP Server + Port Serveru + Zdroj XMPP + rozlišovat toto připojení z jiných klientů, které jsou také zaznamenány v + Priority XMPP zdroje + Zprávy pro klienty s více aktivními zdroji budou zaslány na zdroj s nejvyšší prioritou. + + Konverzece + Žádné konverzace.\n\nKlepnutím sem začněte! + Nemáte žádné \nnastavené účty.\n\nKleknutím sem jeden přidejte! + Seznam kontaktů - %1$s + Přidat kontakt + Smazat kontakt + Blokovat kontakt + Kontaktní přezdívka + Blokovaní uživatelé + Přezdívka + Kontakt %1$s bude smazán. + Kontakt %1$s bude blokován. + Kontakt %1$s bude odblokován. + Kontakt %1$s byl přidán. + Kontakt %1$s byl smazán. + Kontakt %1$s je blokován. + Kontakt %1$s je odblokován. + Nový Chat + Najít kontakty + Zobrazit mřížku + Začít chat + Zobrazit Profil + Ověřit klíč + Probíhající chaty: %1$d + Kontakty online: %1$d Nabídky přátelství - (\"\"Neznámý uživatel\"\") - Žádné kontakty - Skupina je prázdná - - Vyberte kontakty, které chcete pozvat - Zadejte hledaný kontakt - Nebyly nalezeny žádné kontakty. - - - - Žádné kontakty nejsou blokovány. - - + Kontakt + zadejte jméno kontaktu pro chatování + Jdi + Neprobíhá žádný chat. + Přidat kontakt + E-mailová adresa osoby, kterou chcete pozvat: + Zvolte seznam: + Zadejte jméno uživatele, kterého chcete přidat z Kontaktů. + Odeslat pozvánku Profil kontaktu - Stav: - Typ klienta: - počítač - Mobil - - - Online - - Zaneprázdněn - - Pryč - - Nečinný - - Offline - - Zobrazit jako offline - - - - - + Blokované kontakty – %1$s + Žádné kontakty nejsou blokovány. + + Chat s uživatelem %1$s - - Sem zadejte zprávu - - - - - - - - - - - - - - - - + uživatel %1$s je online + uživatel %1$s je pryč + %1$s je zaneprázdněn + uživatel %1$s je offline + Uživatel %1$s se připojil + uživatel %1$s odešel + Poslat fotku + Poslat soubor + Poslat audio + Pořídit fotku + Přenos souborů + Přenos proběhl úspěšně + Probíhá přenos + Potvrdit přenos? + chce Vám poslat soubor + Žádné připojení není dostupné pro odesílání vašich sdílení! Odeslat - - Zprávu nelze odeslat. - - Spojení se serverem bylo ztraceno. Zpráva bude odeslána, jakmile se jej podaří obnovit. - - - - - + Poslat zprávu + Poslat bezpečnostní zprávu + Odeslat znovu + Ukončit chat + Vyčistit Chat + Vložit smajlík + Přepnout chat + Menu+ Zvolit odkaz - - Neprobíhá žádný chat. - - Zahájení šifrované relace chatu ... - Ukončení šifrované relace chatu ... - - - Přidat kontakt - - E-mailová adresa osoby, kterou chcete pozvat: - - Zvolte seznam: - - Zadejte jméno uživatele, kterého chcete přidat z Kontaktů. - - Odeslat pozvánku - - Jabber/XMPP - + Smazat + Zachovat soubory + Odstranit zabezpečené úložiště sezení chatu? + Všechny stažené a nahrané soubory z relací budou smazány. Upozornění: tato operace je nevratná! + Smazat original? + Tento soubor bude zkopírován do zabezpečeného úložiště ještě před odesláním. Chcete odstranit původní soubor z nezabezpečeného úložiště přístroje? + Zachovat + Export + Exportovat média soubor? + Tento média soubor byl exportován do %1$s + Ukončit chat + Všechny zabezpečené media položky z tohoto sezení byly smazány. Exportujte media položku použitím dlouhého stisku na ikonce náhledu. + Ukončit chat a odstranit soubory + + Uživatel %1$s vás pozval k hromadnému chatu. + Uživateli %1$s byla zaslána pozvánka. + Přijmout + Odmítnout + Skupinový chat + Vytvořit nebo se připojit ke skupinovému chatu + Připojování ke skupinovému chatu… + Pozvat… + Vyberte kontakty, které chcete pozvat + Zadejte hledaný kontakt + Žádné kontakty nenalezeny + +Klikněte pro pozvání. + Přidat %1$s? + Ano + Ne + Přihlášení k odběru uživatele %1$s se nepodařilo schválit. Zkuste to prosím znovu později. + Odmítnutí odběru uživatele %1$s se nezdařilo. Zkuste to prosím znovu později. + + Začátek šifrování + Konec šifrování + Zahájení šifrované relace chatu … + Ukončení šifrované relace chatu … + Bezpečnostní otisk + Bezpečnostní otisk + Přihlásit + Přegenerovat klíč + Šifrování je vypnuto + Šifrování je zapnuto (Klikněte pro ověření) + Váš kontakt zastavil šifrovný chat. + Šifrováno a ověřeno! + Generuji nový pár OTR klíčů. + + Nalezli jsme úložiště OTR klíčů k importu. Chcete teď naskenovat QR heslo? + Aktivovat KeySync + Importování OTR klíčeny proběhlo úspěšně + + Scan QR + Váš otisk + Manuál + Otázka + Bezpečností otisk (Ověřený) + Jste si jisti, že chcete potvrdit tento otisk? + Ověřit otisk? + Vzdálený otisk byl ověřen! + Otisk pro vás + Otisk pro + Nainstalovat Barcode Scanner? + Tato aplikace vyžaduje Barcode Scanner. Chtěli byste ji nainstalovat? + + Ověření + Zadejte takovou otázku, kterou když zašlete kontaktu, tak budete očekávat předem danou odpověď, abyste ověřili, že je to opravdu ten, kdo tvrdí, že je. + položit otázku + očekávaná odpověď + Váš kontakt si vás úspěšně ověřil. Nyní si ověřte svůj kontakt tím, že zadáte svou vlastní otázku. + OTR Q&Ověření + Šifrování chatu + Odeslat + Zrušit + + Zabezpečené volání + Zabezpečená hlasová zpráva + Zadejte váš OStel.co nebo další bezpečné SIP účty pro integraci volání. + Nastavit účet + + Bezpečnost a soukromí Šifrování a anonymita + Šifrování Zap / Vyp + Časový limit hesla + Čas kdy šifrování aplikace mělo být odemčeno + + Klikací odkazy na Tor + Pro účty používající Tor, umožnit rozkliknutí odkazu v chatu (VAROVÁNÍ, je tu možnost úniku soukromí!) + Uživatelské rozhraní + Jazyk + Jazyky + Použít systémově výchozí + Použít tmavý vzhled + Změní téma aplikace na tmavé + Zprávy pouze v paměti + Ukládat zprávy pouze v paměti RAM, nikoliv na flash paměti, na obranu proti zpětnému získání zpráv. (Můžete přijít o zprávy) + Obrázek pozadí + Nastavte cestu (\"/sdcard/foo.jpg\") obrázku, který bude použit na tapetu pozadí této aplikace + Zobrazit mřížku kontaktů + Zobrazí list kontaktů jako avatar mřížku + Ano, příjmout vše + Smazat nezabezpečené média + Po nasdílení fotek a souboru, automaticky smazat originál z nezabezpečeného úložiště. + Ukládat média na externím úložišti + Multimediální soubory z relací konverzace jsou uloženy v šifrované kontejneru, který lze uložit na interní nebo externí úložiště. + + Chybí externí úložiště + Záznamy vašich chatů jsou uloženy na SD kartě, ale žádná SD karta není k dispozici. Prosím vložte správnou SD kartu, nebo odstraňte existující záznamy chatu a spusťte ChatSecure znovu. + Chybí úložiště chatovacích médií + Záznamy vašich chatů jsou uloženy na SD kartě, ale na aktuální SD kartě chybí. Prosím vložte správnou SD kartu, nebo odstraňte existující záznamy chatu a spusťte ChatSecure znovu. + Smazat záznam chatu + + Ostatní nastavení + Spusit ChatSecure automaticky + Vždy při startu automaticky přihlásit do posledně přihlášených účtů Skrýt kontakty offline - + Použít prioritu popředí + Sníží pravděpodobnost, že Android bude restartovávat připojení naši služby. Umístí oznámení do oznamovací oblasti. + Heartbeat interval + Použíjte vyšší hodnoty (v minutách) pro menší spotřebu baterie. Vyšší hodnoty ale můžou způsobit, že poskytovatel ukončí spojení z důvodu nečinnosti. + Nastavení oznámení - Oznamování chatových zpráv - Příchod chatové zprávy oznamovat na stavovém panelu. - Vibrace - Oznámit příchozí chatovou zprávu také vibracemi - Zvuk - Oznámit příchozí chatovou zprávu také vyzváněním - - Vybrat vyzváněcí tón - - - - - - - Přijmout - - Odmítnout - - - - Přijmout - - Odmítnout - - - - - - - - - - - - + Vybrat vlastní zvonění + + Zapnout ladící logy + Výstupní log dat aplikace na standardní odchozí / logcat pro ladění + + Přenos dat na pozadí je zakázán + Poskytovatel %1$s vyžaduje povolení přenosu dat na pozadí. + Povolit + Ukončit + Chcete se odhlásit ze všech služeb a zabít všechny procesy (tvrdě ukončit)? + Vytvořit nový učet? + Vytvořit nový účet pro přezdívku \'%1$s\'? + Informace o certifikátu + Certifikáty: + Vydal: + SHA1 Otisk: + Vydáno: + Expirace: + Připojit k chat místnosti? + Externí aplikace se Vas pokouší připojit k místnosti. Povolit? + Zvolte pozadí + Chcete vybrat obrázek na pozadí z Galerie? + Vyberte obrázek + + Nové zprávy od poskytovatele %1$s + %1$d nepřečtené chaty + Nové přátelské pozvání od %s + Nový soubor %1$s od %2$s Pozvánka k hromadnému chatu - - - - - - - - - - - - spustit službu Chat - Povoluje aplikacím spustit Chat přes službu intent. - - + Nová pozvánka do skupinového chatu od%s + Nová zpráva(y) od + Spouštění služby ChatSecure… + Aktivováno & odemknuto + Zpráva zkopírována do schránky + Upozornění - - - + Chyba: + Kód chyby: %1$d + Nepodařilo se přihlásit ke službě %1$s. Zkuste to prosím znovu později."\n"(Podrobnosti: %2$s) Seznam se nepodařilo přidat. - Blokování kontaktu se nezdařilo. - Odblokování kontaktu se nezdařilo. - Nejprve zvolte kontakt. - - \"Odpojeno\"\n - + \"Odpojeno\" + Chyba služby. - Seznam kontaktů se nepodařilo načíst. - K serveru se nelze připojit. Zkontrolujte své připojení. - - - - - + %1$s již je ve vašem seznamu kontaktů. + Kontakt %1$s byl zablokován. Počkejte, než se seznam kontaktů načte. - Došlo k chybě sítě. Pro toto připojení je nutné WiFi. - Server tuto funkci nepodporuje. - Zadané heslo není platné. - Na serveru došlo k chybě. - Server tuto funkci nepodporuje. - Server je momentálně nedostupný. - Vypršel časový limit serveru. - Server nepodporuje aktuální verzi. - Fronta zpráv je plná. - Server nepodporuje předávání této doméně. - Zadané uživatelské jméno nebylo rozpoznáno. - Tento uživatel vás blokuje. - Této relaci již vypršela platnost, přihlaste se znovu. - jste přihlášeni z jiného klienta. již jste přihlášeni z jiného klienta. - Z karty SIM nelze číst vaše telefonní číslo. Požádejte o pomoc operátora. - Momentálně nejste přihlášeni. - - - + ChatSecure zjistil chybu při ověřování vašeho uživatelského jména nebo hesla - prosím zkontrolujte jej a zkuste to znova. + U ChatSecure došlo k chybě při generování párů klíčů. + U ChatSecure došlo k chybě při připojování k serveru - prosím zkontrolujte nastavení a zkuste to znovu. + U ChatSecure došlo k chybě při připojování - prosím zkontrolujte připojení k síti a zkuste to znovu. + Síť je nedostupná + ChatSecure se pokouší znovu navázat spojení + Nezadali jste část @hostname.com u vašeho ID účtu. Zkuste to znovu! + Váš server hostname nemá .com, .net, .cz nebo podobnou koncovku. Zkuste to znovu! + Vložte vaše heslo: + Vzhledem k tomu, že používáte Tor, musíte zadat XMPP hostname \"Připojit k serveru\" přímo do \"Rozšířeného nastavení účtu\" + Upozornění: Tato služba používá certifikát se SLABOU kryptografii. Prosím, informujte správce upgradu. + Pokud certifikát neodpovídá ZADANÉMU certifikátu: + Nelze vytvořit skupinový chat nebo se k němu nelze připojit + Je nám líto, ale nelze sdílet tento typ souboru + Pro sdílení souborů musíte mít zapnuto šifrování. + Prosím pro sdílení souboru zapnětě šifrování chatu + Nepodporované příchozí data, nelze sdílet! + ChatSecure zjistil, že žádost byla podána na odstranění svého úkolu. ChatSecure nejspíš skončí havárií. Prosím použijte Odhlásit všechny položky z nabídky seznamu účtů na obrazovce. + Pro tento formát souborů není k dispozici žádný prohlížeč + Prosím začnětě zabezpečenou konverzaci před skenováním kódů + OTR klíčenka není importována; Zkontrolujte zda-li soubor existuje ve správném formátu a umístění + Prosím, zkopírujte soubor \'otr_keystore.ofcaes\' z počítače KeySync nástroje do kořenového adresáře vašeho paměťovém zařízení + Zprávu nelze odeslat. + Zprávy budou odeslány při opětovném připojení + %1$s je offline. Odeslané zprávy budou doručeny, jakmile se %1$s přihlásí online. + %1$s není ve vašem seznamu kontaktů. + Vaše hesla se neshodují + Priorita musí být číslo v rozmezí [0 .. 127] + Číslo portu musí být číslo + Chyba přenosu + Soubor nelze na úložišti přečíst + Kritická chyba: Nelze odemknout nebo načíst databázy aplikace. Nainstalujte prosím znovu aplikaci nebo vyčistěte data. + Žádná aplikace která dokáže zpracovat tento odkaz není nainstalována! + Nastavení účtu + + Skupiny + otevřít konverzaci(e) + Vypnout & Uzamknout + Panika + Přátelé + Kontakty + Přijmout certifikát serveru? + Zobrazit Váš otisk + načítání… + + O ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Seznam kontaktů + Nastavení + Seznam účtů + Odhlásit se + Seznam kontaktů + + Jabber/XMPP + Blízké okolí (Bonjour/ZeroConf) + Google účet + dukgo.com + + + Online + Zaneprázdněn + Pryč + Nečinný + Offline + Zobrazit jako offline + Happy Smutný @@ -404,7 +507,7 @@ Smějící se Zmatený - + Happy Smutný @@ -443,75 +546,33 @@ :-D o_O - Seznam kontaktů - Šifrování On / Off - Připojit přes Tor (Vyžaduje aplikaci Orbot) - - Už se nemůžu dočkat, jak začít? - Jak začít - Nastavení účtu - - - Jak je to bezpečné? - - Jsou moje Chaty bezpečné? - - Nastavení přístupové fráze - Přístupová fráze: - Přístupová fráze (znovu): - - user@domain.com - heslo - Pokročilé nastavení účtu - - Zapamatovat si heslo - Heslo ukládat do mezipaměti - Heslo neukládat do mezipaměti - Automaticky přihlásit - Přihlásit se nyní - - Osobní (volitelné) - Alias účtu (vaše jméno) - Profil - - - Generování dvojice klíčů ... - Podepisování ... - - Přihlásit - Přegenerovat klíč - Upozornění: Tento chat NENÍ šifrován - Tento chat je bezpečný, ale totožnost účastníků NEBYLA ověřena - Upozornění: šifrování chatu bylo zastaveno. - Tento chat je zabezpečen a ověřen - Průvodce účtem - ID vašeho účtu - Nastavit server - Jste připraveni? - Prosím zadejte svůj ID účtu (uživatel@jménohosta): - Vložte vaše heslo: - - Nastavení účtu - - Skupiny - Kontakt - Jazyk - Jazyky - Jaký jazyk by měl InTheClear zobrazovat? - Ověřte, zda certifikát je důvěryhodný - TLS ověření - vyžadovat TLS/SSL připojení - Šifrovat přenos - Připojit server - TCP Port pro XMPP Server - Port Serveru - Zdroj XMPP - Další - Zpět - Konverzece - Nová zpráva(y) od - - + Existujcí účet + Připojit k mému existujcímu účtu na specifickém Jabber / XMPP serveru. + Google účet + Chatujte s ostatními Google uživateli použitím vašeho existujcího Google účtu. + WiFi Mesh Chat + Chat s ostatními na stejné lokální WiFi sítí - není vyžadován Interet ani server. + Zapnout WiFi Chat + Nový účet + Zaregistrovat nový účet zdarma pro služby z našeho seznamu, nebo si vyberete jakýkoli. + Vytvořit nový účet + Tajná identita! + Vytvořte anonymní, jednorázový \"burner\" chat účet na jedno kliknutí (vyžaduje Orbot: Tor pro Android) + Vygenerovat identitu + Toto je skupinový chat + [přeposláno] + [přeposláno] + Tento soubor nelze sdílet zabezpečeně + Nainstalovat Orbot? + Musíte mít nainstalován Orbot a aktivován na proxy provoz. Chtěli by jste jej nainstalovat? + Vždy + Spustit Orbot? + Orbot nezdá být spuštěn. Chtěli byste jej spustit a připojit se k síti Tor? + přezdívka pro tuto místnost + název místnosti pro vytvoření nebo připojení\" + server skupinového chatu (conference.foo.com) + Vaše úložiště klíčů je poškozeno. Nainstalujte znovu ChatSecure nebo zkuste \"Vymazat data\" aplikace + Obdrželi jste nečitelnou zašifrovanou zprávu + Nemohl jsem dešifrovat zprávu, kterou jste poslal + < přejeďte do leva a do prava pro více možností > diff --git a/res/values-da/arrays.xml b/res/values-da/arrays.xml index c7b559b97..4ab2aa4db 100644 --- a/res/values-da/arrays.xml +++ b/res/values-da/arrays.xml @@ -1,9 +1,9 @@ - + - - Force / Kræv - Automatisk forsøge - Som ønsket - Handicappede / Aldrig - + + Gennemtving / altid + Forsøg automatiskt + Efter forespørgsel + Deaktiveret / aldrig + diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 35aa0a38d..d71717d6c 100755 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -1,362 +1,453 @@ - - - - - - - - - - - - - - - Annuller login - - - Tilføj kontakt - - Slet kontakt - - Bloker kontakt - - Blokeret - - Kontoliste - - - - Indstillinger - - - Start chat - - Log ud - - Vis profil - - - Afslut chat - - - Liste over kontakter - - Inviter ... - - Skift chats - - Indsæt smiley - - - Menu+ - - - + + + ChatSecure + ChatSecure + + læs beskeder + +Tillader applikationer at læse data fra IM-indholdsleverandøren. + + skriv beskeder + +Tillader applikationer at skrive data til IM-indholdsleverandøren. + start IM-tjeneste + Lader programmer starte IM-tjeneste efter hensigt. + Bekræft - - - - - - - - OK - Annuller - OK - Annuller - - - - - - - - - - Brugernavn: - + Næste + Tilbage + Forbind + + Åben + ChatSecure låst + Sæt adgangskode + Bekræft adgangskode + Adgangskode + Ny adgangskode + Bekræft ny adgangskode + Adgangskoder var ikke identiske, prøv igen + Jeg er så doven (ingen adgangskode!) + Indtast din adgangskode + Adgangskode … + Løsen + Løsen (igen) + + Vælg en konto + Vælg en konto + (%1$d) + Tilføj %1$s konto + Om + Log alle af + Vil du logge ud af alle tjenester? + Du er blevet logget ud af %1$s. + Du er blevet logget ud af %1$s, fordi %2$s. + Førstegangsbruger? + Utålmodig for at komme i gang? + Kom i gang + Kontoopsætning + Opsætning af adgangsfrase + Før du starter skal du vælge en sikker adgangsfrase som skal beskytte dine ChatSecure-data fra uønsket adgang. + Adgangsfrase: + Adgangsfrase (bekræftelse): + Skriv en *ny* adgangsfrase. Den skal indeholde mindst et stort bogstav, et lille bogstav, et tal og skal være længere end seks karakterer. + Krypterer eksisterende noter med den nye adgangsfrase (vær tålmodig…) + Skriv din adgangsfrase: + Velkommen! Skriv en stærk adgangsfrase for at sikre dine noter. Den skal indeholde et stort bogstav, et lille bogstav, et tal og skal være længere end seks karakterer. + Din adgangsfrase var ikke lang nok + Din adgangsfrase indeholdt ikke store bogstaver + Din adgangsfrase indeholdt ikke små bogstaver + Din adgangsfrase indeholdt ikke nogle ta + + Om ChatSecure + ChatSecure er en mobil chat-applikation (også kendt som instant messaging) som indeholder ekstra sikkerhedsfaciliteter, der forhindrer aflytning af dine samtaler og kommuikation. + +Applikationen understøtter enhver chat service der anvender Jabber eller XMPP protokollen, såsom Google GTalk eller Jabber.org. + + Hvordan er den sikker? + \'Off-the-Record tekstudveksling\' er et sikkerhedssystem designet til at forøge din digitale brevhemmelighed, ved at efterligne egenskaberne for en privat samtale i den fysiske verden. Dette inkluderer kryptering, autentifikation, benægtbarhed og fremadrettet hemmelighed. + +OTR-protokollen er kompatibel med chatklienter til computere, såsom Adium eller Pidgin. + + Er mine chat-samtaler sikre? + Chatsecures krypteringsfeature virker kun i samtaler med andre som anvender kompatible klienter, så du bør sikre dig at dine kontakter bruger ChatSecure til mobile enheder og Adium eller Pidgin til computere. Du kan kontrollere præcist hvordan og hvornår ChatSecure forsøger at kryptere dine samtaler i Kontoindstillinger. + +Lad os så komme i gang! + + Ny kont + Tilføj konto + Rediger konto + Fjern konto + Eksisterende konto + + Bruger@Server: Adgangskode: - Husk min adgangskode. - - Log mig automatisk ind. - + Log automatisk ind. Har du ikke en konto? - - Denne valgmulighed logger dig automatisk ind, hver gang du åbner dette program. For at deaktivere denne valgmulighed skal du logge ud og så fjerne markeringen i afkrydsningsfeltet \\"Log mig ind automatisk\\". - Log ind - - - - Logger ind ... - - - Baggrundsdata er deaktiveret - - - - - - Aktiver - - Afslut - - - - - - + For din egen sikkerheds skyld, bør du ændre adgangskode ved at gå til hjemmesiden via en computer, hvis du mister din telefon. + Denne valgmulighed logger dig automatisk ind, hver gang du åbner dette program. For at deaktivere denne valgmulighed skal du logge ud og så fjerne markeringen i afkrydsningsfeltet \"Log automatisk ind\". + Forbind via Tor (kræver Orbot app) + bruger@domæne.dk + nyt brugernavn + serviceudbyder (dukgo.com, jabber.ccc.de) + adgangskode + bekræft adgangskode + Avancerede kontoindstillinger + Kontotype + Registrer konto + Vedvarenhed + Husk adgangskode + Adgangskode huskes + Adgangskode huskes ikke + Log automatisk ind + Forbind ved opstart af ChatSecure + Forbind ikke ved opstart af ChatSecure + Log ind nu + Forbind med følgende kontoopsætning + Forbind ikke med følgende kontoopsætning + Personlige oplysninger (valgfrit) + Kontoalias (dit navn) + Hvordan din konto vises på nettet + Profil + En kort svada om dig selv + Gennemtving kryptering / afvis råtekst-overførsel + Krypterer samtaler automatiskt, hvis muligt. + Krypter samtaler som anmodet + Deaktiver samtalekryptering + Validerer brugerlogin.. + Genererer nøglepar… + Logger ind… + Konto-guide + Dit konto-ID + Konfigurer server + Er du klar? + Skriv dit konto-ID for at konfigurere ChatSecure til din XMPP chat-service. Dit ID ligner en email-adresse: + Skriv dit konto-ID (brugernavn@servernavn): + Skriv eller rediger dit Jabber/XMPP chat-service servernavn og portnummer (5222 is default). + ChatSecure er nu konfigureret, og det er tid til at forbinde til din service og begynde at samtale sikkert og privat! + Orbot (Tor) + Vælg et domæne + Registrerer ny konto… + sætter gang i ChatSecure… + Annuller login + Logger ind … + Logger ud\u2026 + + Brug SRV-opslag + Brug DNS SRV til at finde XMPP-servere ud fra domænenavn + Tillad at brugernavn og adgangskode sendes ukrypteret når der anvendes en ukrypteret forbindelse + Tillad råtekst autentificering + Kontroller at der er tillid til certifikatet + TLS-verficering + Gennemtving TLS-krypteret forbindelse + Transport-kryptering + hvorledes krypterede samtaler startes + Serveren der skal forbindes til, hvis nødvendig + Forbindelses-server + TCP-port for XMPP-server + Server-port + XMPP-ressource + for at adskille denne forbindelse fra andre klienter der også er logget ind + XMPP-ressourceprioritet + Beskeder til klienter med flere aktive ressourcer vil blive afleveret til ressourcen med højeste prioritet + + Samtaler + Ingen samtaler. + +Tryk her for at starte e! + Du har ingen +konfigurerede konti. + +Tryk her for at tilføje! + Kontaktliste - %1$s + Tilføj kontakt + Slet kontakt + Bloker kontakt + Kontakt kaldenavn + Blokeret + Øgenavn + Kontakten \"%1$s\" slettes. + Kontakten "%1$s" blokeres. + Blokeringen af kontakten "%1$s" ophæves. + Kontakten \"%1$s\" er tilføjet. + Kontakten \"%1$s\" er slettet. + Kontakten \"%1$s\" er blokeret. + Kontakten \"%1$s\" er ikke længere blokeret. + Ny chat + Søg i kontakter + Vis gitter + Start chat + Vis profil + Bekræft nøgle + Igangværende chats (%1$d) + %1$d er online Venneinvitationer - (\"\"Ukendt\"\") - Tom - Der er ingen samtaler - - Vælg kontakt(er), der skal inviteres - Indtast for at finde kontakt - Der blev ikke fundet nogen kontakter. - - - - Der er ingen blokerede kontakter. - - + Kontakt + skriv navnet på en kontakt at chatte med + Udfør + Der er ingen aktive chats. + Tilføj kontakt + Brugernavn eller Jabber-id på den person, du ønsker at invitere: + Konto som kontakten skal tilføjes til: + Indtast et navn for at tilføje fra Kontakter. + Send invitation Kontaktens profil - Status: - Klienttype: - Computer - Mobil - - - Online - - Optaget - - Ikke til stede - - Ikke aktiv - - Offline - - Vis som offline - - - - - + Blokerede kontakter – %1$s + Der er ingen blokerede kontakter. + + Chat med %1$s Mig - - Indtast for at skrive - - - - - - - - - - - - - - - - + %1$s er online + %1$s er ikke til stede + %1$s er optaget + %1$s er offline + %1$s har tilsluttet sig + %1$s er gået + Send billede + Send fil + Send lyd + Tag billede + Filoverførsel + Overførsel færdig + Overførsel i gang + Accepter overførsel? + vil sende dig filen Send - - Beskeden kunne ikke sendes. - - Forbindelsen til serveren er gået tabt. Beskeden sendes, når du er online. - - - - - + Send besked + Send sikker besked + Gensend + Afslut chat + Ryd chat + Indsæt smiley + Skift til chat + Menu+ Vælg link - - Der er ingen aktive chats. - - - - Tilføj kontakt - - E-mail-adresse på den person, du ønsker at invitere: - - Vælg en liste: - - Indtast et navn for at tilføje fra Kontakter. - - Send invitation - - + Slet + Behold filer + + %1$s har inviteret dig til at deltage i en gruppechat. + Invitationen er sendt til %1$s. + Accepter + Afvis + Gruppesamtale + Opret eller deltag i gruppesamtale + Forbinder til gruppesamtale + Inviter … + Vælg kontakt(er), der skal inviteres + Indtast for at finde kontakt + Ingen kontakter fundet. + +Rør skærm for at invitere. + Tilmelding fra %1$s kunne ikke godkendes. Prøv igen senere. + Tilmelding fra %1$s kunne ikke afvises. Prøv igen senere. + + Start Kryptering + Stop Kryptering + Starter krypteret samtale… + Stopper krypteret samtale… + Sikkerhedsfingeraftryk + Sikkerhedsfingeraftryk + Log ind + Regenerer nøgle + Kryptering er deaktiveret + Kryptering er aktiveret (rør for at verificere) + Din kontakt har abrudt den krypterede samtale. + Krypteret og verificeret! + Genererer nyt OTR-nøglepar… + + Vi kan se der eksisterer et OTR-nøglelager der kan importeres. Vil du scanne QR-adgangskoden nu? + Aktiver KeySync + OTR-nøglering importeret + + Skan QR + Dit Fingeraftryk + Manuelt + Spørgsmål + Sikkerhedsfingeraftryk (verificeret) + Er du sikker på at du vil bekræfte dette fingeraftryk? + Bekræft fingeraftryk? + Fingeraftryk for fjernforbindelse blev bekræftet! + Dit fingeraftryk + Fingeraftryk for + + Autentifikation + Skriv et spørgsmål som sendes til din kontakt, og det forventede svar, for at kontrollere at vedkommende er identisk med den person vedkommende udgiver sig for at være. + spørgsmålet der skal stilles + det forventede svar + Din kontakt har autentificeret dig. Nu skal du autentificere din kontakt ved at stille dit eget spørgsmål til vedkommende. + Send + Annuller + + Sikkert opkald + Sikker stemmeopkald + Skriv din OStel.co eller anden sikker SIP-servicekonto her for integration af opkald + + Kontoopsætning + + Sikkerhed og privatliv + Kryptering og Anonymitet + Kryptering Aktiveret/Deaktiveret + Adgangskodetidsfrist udløbet + Tid hvori krypteringen skal vise oplåst + + Brugerinterface + Sprog + Sprog + Brug mørkt tema + Ændre applikationstemaet til mørk + Brug kun hukommelse til lagring af beskeder + Gem kun beskeder i dynamisk hukommelse, ikke i statisk flash-lager. Dette forhindrer angreb hvor beskeder ville kunne udtrækkes via analyse af flash-mediet. (kan forårsage beskedtab) + Baggrundsbillede + Sæt filsti (\"/sdcard/foo.jpg\") til et billede der bruges som baggrund i applikationen. + Vis kontakt-tern + Vis kontaktliste som profilbilledgitter + Ja, accepter alle + + + Anden fintuning + Start ChatSecure automatiskt + Start altid og log automatisk ind med sidst anvendte konti Skjul offline kontakter - + Brug forgrundsprioritet + Reducer risikoen for at Android genstarter forbindelsesservicen. Dette vil placere en permanent notifikation i notifikationsområdet. + Heartbeat-interval + Brug en højere værdi (i minutter) for at spare batterikapacitet. En høj værdi vil måske forårsage at din udbyder lukker forbindelsen pga. inaktivitet. + Meddelelsesindstillinger - IM-meddelelser - Underret i statuslinjen, når der kommer en IM - Vibrer - Vibrer også, når der kommer en IM - Lyd - Spil også ringetone, når der kommer en IM - - Vælg ringetone - - - - - - - Accepter - - Afvis - - - - Accepter - - Afvis - - - - - - - - - - - - + Brug ChatSecures egen beskedtone + + Aktiver fejlsøgningslog + Omdirriger applikationens log til standard out / logcat for fejlsøgning + + Baggrundsdata er deaktiveret + %1$s kræver, at baggrundsdata aktiveres. + Aktiver + Afslut + Vil du logge ud af alle services OG afslutte alle kørende processer (hård afslutning)? + Opret ny konto? + Opret en ny chat-konto for brugernavn \'%1$s\'? + Certifikatinformation + Certifikat: + Udstedt af: + SHA1-fingeraftryk: + Udstedt: + Udløber: + + Nye %1$s-beskeder Gruppechatinvitation - - - - - - - - - - - - start IM-tjeneste - Lader programmer starte IM-tjeneste efter hensigt. - - + Ny(e) besked(er) fra + Starter ChatSecure servicen… + Aktiveret & låst op + Bemærk - - - + Fejl: + Fejlkode %1$d + Der kunne ikke logges ind på tjenesten %1$s. Prøv igen senere."\n"(Detalje: %2$s) Listen blev ikke tilføjet. - Kontakten blev ikke blokeret. - Blokeringen af kontakten blev ikke ophævet. - Vælg en kontakt først. - - \"Afbrudt!\"\n - + Forbindelse afbrudt!\n Servicefejl! - Listen over kontakter blev ikke indlæst. - Der kan ikke oprettes forbindelse til serveren. Kontroller din forbindelse. - - - - - + %1$s findes allerede på din liste over kontakter. + Kontakten \"%1$s\" er blevet blokeret. Vent, mens din liste over kontakter indlæses. - Der opstod en netværksfejl. - + WiFi er nødvendig for denne forbindelse. Serveren understøtter ikke denne funktionalitet. - Den adgangskode, du indtastede, er ikke gyldig. - Serveren registrerede en fejl. - Serveren understøtter ikke denne funktionalitet. - Serveren er ikke tilgængelig i øjeblikket. - Der opstod timeout for serveren. - Serveren understøtter ikke den aktuelle version. - Beskedkøen er fuld. - Serveren understøtter ikke videresendelse til domænet. - Det indtastede brugernavn kunne ikke genkendes. - Beklager! Du er blokeret af brugeren. - Sessionen er udløbet. Log ind igen. - du er logget ind fra en anden klient. du er allerede logget ind fra en anden klient. - Beklager! Telefonnummeret kan ikke læses fra dit SIM-kort. Kontakt din udbyder for at få hjælp. - Du er ikke logget ind i øjeblikket. - - - + ChatSecure stødte på en fejl under validering af brugernavn eller adgangskode - kontroller dem og prøv igen. + ChatSecure stødte på en fejl under generering af nøglepar. + ChatSecure stødte på en fejl under etablering af forbindelse til server - kontoller din konfiguration og prøv igen. + ChatSecure stødte på en fejl under etablering af forbindelse - kontoller din netværksforbindelse og prøv igen. + ChatSecure har mistet en forbindelse til netværket + ChatSecure prøver at genetablere en forbindelse + Du skrev ikke @servernavn delen af dit konto-ID. Prøv venligst igen! + Dit servernavn har ikke et .com, .net eller lignende efter sig. Prøv venligst igen! + Skriv din adgangskode: + Siden du anvender Tor, skal du angive XMPP \'Forbindelses-server\' direkte i avancerede kontoindstillinger + ADVARSEL: Denne tjeneste benytter sig af et certifikat med SVAG underliggende kryptografi. Bed venligst administratoren om at opgradere den. + Kunne ikke oprette eller deltage i gruppesamtale + Beklager, vi kan ikke dele filer af denne type + Du skal aktivere kryptering for at dele filer + Aktiver samtale-kryptering for at dele filer + ChatSecure har modtaget en forespørgsel om at fjerne sin operativsystemprocess. ChatSecure vil nu formentlig fejle. Vær venlig at bruge \'Log alle af\' funktionen fra kontilisteskærmen i stedet. + Der er ingen fremviser tilgængelig for dette filformat + Start venligst en sikker samtale inden du scanner koder + OTR-nøglering ike importeret; kontroller at filen eksisterer på den korrekte sti og i det korrekte format + Kopier filen \'otr_keystore.ofcaes\' fra computerværktøjet KeySync til rod-mappen af din enheds lager + Beskeden kunne ikke sendes. + Beskeder afsendes ved genetablering af forbindelse + %1$s er offline. Dine sendte beskeder sendes, når %1$s er online igen. + %1$s er ikke på din liste over kontakter. + Adgangskoderne stemmer ikke overens + Prioritet skal være et tal mellem 0 og 127 + Account Settings + + Grupper + åbne samtaler + Afslut + Panik + Kontakter + Kontakter + Accepter servercertifikat? + Fingeraftryk + + + Kontaktliste + Indstillinger + Kontoliste + Log ud + Kontaktliste + + Jabber (XMPP) + Lokalområde (Bonjour/ZeroConf) + Google-konto + dukgo.com + + + Online + Optaget + Ikke til stede + Ikke aktiv + Offline + Vis som offline + Glad Trist @@ -376,7 +467,7 @@ Griner Forvirret - + Glad Trist @@ -396,21 +487,28 @@ Griner Forvirret - - - - - - - - - - - - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Eksisterende konto + Forbind til en eksisterende konto på en specifik Jabber/XMPP-server. + Google-konto + Snak med andre Google-brugere ved hjælp af en eksisterende Google-konto. + Ny kont diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index 89064efc2..456502769 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -1,15 +1,9 @@ - Vorraussetzen - Automatisch - Manuell - Nie - - - Vorraussetzen - Automatisch - Manuell - Nie + Erzwingen + Automatisch versuchen + Wie angefordert + Deaktiviert diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 4173ae1d4..aa4930ce3 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1,400 +1,499 @@ - - Nachricht lesen - \n Erlaubt Anwendungen Daten vom IM-Provider zu lesen.\n - Instant Messages schreiben - \n Erlaubt Anwendungen Daten zom IM-Provider zu schreiben.\n - + + + ChatSecure ChatSecure - - - Chat - Wählen Sie ein Konto - + + Sofortnachrichten lesen + Erlaubt es Anwendungen, Daten vom Sofortnachrichteninhalteanbieter zu lesen. + Sofortnachrichten schreiben + Erlaubt es Anwendungen, Daten zum Sofortnachrichtinhalteanbieter zu schreiben. + Sofortnachrichtendienst starten + Ermöglicht Anwendungen das eigenständige Starten des Sofortnachrichtendienstes. + + Bestätigen + OK + Abbrechen + OK + Abbrechen + Weiter + Zurück + Verbinden + Abspielen + Einrichtung + + Öffnen + ChatSecure gesperrt + Passwort einstellen + Passwort bestätigen + Passwort + Sie können optional ein Hauptpasswort für ChatSecure einstellen, um den Zugriff auf Ihre Kontakte und Nachrichten zu verhindern: + Neues Passwort bestätigen + Passwort stimmt nicht überein, bitte versuchen Sie es erneut + Überspringen >> + Informationsaufforderung + Bitte Passwort eingeben + Passphrase bitte … + Passphrase + Passphrase (nochmal) + + Konto auswählen + Konto auswählen + (%1$d) + %1$s-Konto hinzufügen Über - + Alle abmelden + Von allen Diensten abmelden? + Sie wurden von %1$s abgemeldet. + Sie wurden aus folgendem Grund von %1$s abgemeldet: %2$s + Benutzen Sie ChatSecure das erste Mal? + Endlich beginnen? + Einführung + Kontoeinrichtung + Passphraseneinrichtung + Bevor Sie starten, bitte eine sichere Passphrase festlegen, um Ihre ChatSecure-Daten vor unberechtigtem Zugriff zu schützen. + Passphrase: + Passphrase (Wiederholung) : + Neue Passphrase eingeben. Dieses muss mindestens einen Großbuchstaben, einen Kleinbuchstaben und eine Ziffer enthalten und muss länger als sechs Zeichen sein. + Ihre bestehenden Mitteilungen werden mit der neuen Passphrase verschlüsselt (ein wenig Geduld bitte …) + Bitte Ihre Passphrase eingeben: + Willkommen! Bitte eine starke Passphrase eingeben, um Ihre Nachrichten abzusichern. Dieses muss mindestens einen Großbuchstaben, einen Kleinbuchstaben und eine Ziffer enthalten und muss länger als sechs Zeichen sein. + Ihre Passphrase war nicht lang genug + Ihre Passphrase enthält keine Großbuchstaben + Ihre Passphrase enthält keine Kleinbuchstaben + Ihre Passphrase enthält keine Ziffern + + Über ChatSecure + ChatSecure ist eine mobile Sofortnachrichten-App mit zusätzlichen Sicherheitsfunktionen zur Verhinderung des Ausspähens der Kommunikation. + +Hierbei werden alle auf dem Jabber- bzw. XMPP-Protokoll basierenden Dienste unterstützt, wie z. B. Google Talk oder Jabber.org. + + Wie ist es sicher? + »Off-the-Record Messaging« - zu deutsch: inoffizielle, vertrauliche, nicht für die Öffentlichkeit bestimmte Nachrichtenvermittlung - ist ein Verschlüsselungsprotokoll, das zur Nachbildung von vertraulichen Gesprächen in der realen Welt entwickelt wurde. +Dieses beinhaltet a) Verschlüsselung (Encryption): Verhinderung unberechtigten Mitlesens, b) Beglaubigung (Authentication): Sicherstellung der Identität des Gesprächspartners, c) Abstreitbarkeit (Deniability): Möglichkeit zur nachträglichen Verleugnung innerhalb der Unterhaltung gemachter Aussagen sowie d) Folgenlosigkeit (Perfect Forward Secrecy): Bereits getätigte Kommunikation ist selbst bei Kenntnis des privaten Langzeit-Schlüssels nicht nachträglich entschlüsselbar. + +Das eingesetzte OTR-Protokoll ist kompatibel mit Desktop-Anwendungen wie z. B. Adium oder Pidgin. + + Über Verschlüsselung + Die Verschlüsselungsfunktion von ChatSecure kann nur dann funktionieren, wenn die Gegenseite eine kompatible Anwendung benutzt. Daher sollten Sie sicherstellen, dass Ihr Kontakt entweder ChatSecure auf dem Handy oder Adium bzw. Pidgin auf dem Rechner verwendet. In den Kontoeinstellungen können Sie im Detail angeben, wie und wann ChatSecure Ihre Unterhaltungen verschlüsseln soll. + +Lassen Sie uns beginnen! + + Neues Konto Konto hinzufügen - Konto bearbeiten - - Konto löschen - - Alle abmelden - - - Chat - Wählen Sie ein Konto - - - + Konto entfernen + Vorhandenes Konto + + Benutzer@Rechner + Passwort: + Passwort speichern. + Automatisch anmelden. + Besitzen Sie kein Konto? + Anmelden + Bei Verlust oder Diebstahl Ihres Handys bitte Ihr Passwort über die entsprechende Internetseite ändern. + Mit dieser Option werden Sie bei jedem Öffnen der Anwendung automatisch angemeldet. Wenn Sie die Option deaktivieren möchten, melden Sie sich ab und deaktivieren Sie das Kontrollkästchen »Automatisch anmelden«. + Über Tor verbinden (Orbot-App wird benötigt) + Benutzer@Domäne + Neuer Benutzername + Dienstanbieter (dukgo.com, jabber.ccc.de) + Passwort + Passwort bestätigen + Erweiterte Kontoeinstellungen + Kontoeinrichtung + Konto registrieren + Persistenz + Passwort speichern + Passwort zwischengespeichert + Passwort nicht zwischengespeichert + Automatisch anmelden + Beim Start von ChatSecure verbinden + Beim Start von ChatSecure nicht verbinden + Jetzt anmelden + Nach Kontoeinrichtung verbinden + Nach Kontoeinrichtung nicht verbinden + Persönliche Daten (optional) + Konto-Alias (Ihr Name) + Wie Ihr Konto im Netz erscheint + Profil + Ein kurze Information zu Ihrer Person + Verschlüsselung erzwingen / Klartext verweigern + Unterhaltungen falls möglich automatisch verschlüsseln + Unterhaltungen auf Anforderung verschlüsseln + Verschlüsselung deaktivieren + Anmeldeinformationen werden überprüft … + Schlüsselpaar wird erzeugt … + Anmeldevorgang … + Kontoassistent + Ihre Kontokennung + Server konfigurieren + Sind Sie bereit? + Bitte die Kontokennung eingeben, um ChatSecure für den XMPP-Dienst zu konfigurieren. Diese sieht wie eine E-Mail-Adresse aus: + Bitte geben Sie Ihre Kontokennung ein (user@hostname): + Bitte geben Sie den Host-Namen und die Port-Nummer (5222 ist Standard) Ihres Jabber/XMPP-Servers ein. + ChatSecure wurde gerade konfiguriert - jetzt ist es an der Zeit, sich mit Ihrem Dienst zu verbinden und sich sicher, geschützt und privat zu unterhalten! + Orbot (Tor) + Domäne auswählen + Neues Konto wird registriert … + ChatSecure zum Laufen bringen … Anmeldung abbrechen - - + Anmeldevorgang \u2026 + Abmeldevorgang \u2026 + + SRV-Lookup durchführen + DNS-SRV nutzen, um tatsächlichen XMPP-Server anhand des Domänennamens zu finden + Nutzername und Passwort dürfen bei einer unverschlüsselten Verbindung als Klartext versendet werden + Klartextlegitimierung erlauben + Das Zertifikat auf Vertrauenswürdigkeit überprüfen + TLS-Überprüfung + TLS-Verbindung notwendig + Transportverschlüsselung + Wie verschlüsselte Unterhaltungen gestartet werden + Falls notwendig mit diesem Server verbinden + Server verbinden + TCP-Port für XMPP-Server + Server-Port + XMPP-Client + zur Unterscheidung dieser Verbindung von anderen, zeitgleich angemeldeten Clients + Priorität des XMPP-Clients + Nachrichten an Empfänger mit mehreren aktiven Clients werden zu dem Client mit der höchsten Priorität gesendet + + Gespräche + Keine Gespräche.\n\nHier antippen, um welche zu beginnen! + Sie haben keine +Konten konfiguriert. + +Hier antippen um eines hinzuzufügen! + Kontaktliste - %1$s Kontakt hinzufügen - Kontakt löschen - Kontakt sperren - + Kontaktspitzname Gesperrt - - Konten - - Neuer Chat - - Neues Konto - - Einstellungen - - Suche Kontakt - - Chat starten - - Abmelden - - Profil anzeigen - - - Starte OTR - Beende OTR - Clear Chat - Chat beenden - Sicherer Anruf - - - Kontakte - - Einladen\u2026 - - Chat wechseln - - Smiley einfügen - Erneut senden - - Scan Fingerprint - Mein Fingerabdruck - Fingerprint überprüfen - Überprüfen Geheimnis - - Menü+ - - - - Bitte Bestätigen - - Von allen Diensten abmelden? - - - - - - - - OK - - Abbrechen - - OK - - Abbrechen - - - - - - - - - - Benutzername: - - Passwort: - - Passwort speichern - - Automatisch anmelden - - Besitzen Sie kein Konto? - - Bei Verlust Ihres Handys ändern Sie bitte Ihr Passwort über die entsprechende Webseite. - Mit dieser Option werden Sie bei jedem Öffnen der Anwendung automatisch angemeldet. Wenn Sie die Option deaktivieren möchten, melden Sie sich ab, und deaktivieren Sie das Kontrollkästchen \\"Automatisch anmelden\\". - - Anmelden - - - ChatSecure am Laufen halten... - - Anmeldung wird ausgeführt... - - - Hintergrunddaten deaktiviert - - - - - - Aktivieren - - Beenden - - - - - - + Spitzname + Kontakt »%1$s« wird gelöscht. + Kontakt »%1$s« wird gesperrt. + Kontakt »%1$s« wird nicht mehr gesperrt. + Kontakt »%1$s« hinzugefügt. + Kontakt »%1$s« gelöscht. + Kontakt »%1$s« gesperrt. + Sperrung des Kontaktes »%1$s« aufgehoben. + Neue Unterhaltung + Kontakte suchen + Raster anzeigen + Unterhaltung starten + Profil ansehen + Schlüssel überprüfen + Laufende Unterhaltungen (%1$d) + %1$d verbunden Freundschaftseinladungen - - (\"\"Unbekannt\"\") - + (Unbekannt) Leer - - Keine Chats - - Kontakte für Einladung auswählen - Gesuchten Kontakt eingeben - Keine Kontakte gefunden. - - - - Keine gesperrten Kontakte. - - + Keine Gespräche offen.\n\nHier antippen, um Unterhaltung zu beginnen! + Kontakt + Den Namen des Kontakts eingeben, mit dem Sie sich unterhalten möchten. + Auf geht\'s! + Keine aktiven Unterhaltungen. + Kontakt hinzufügen + E-Mail-Adresse der einzuladenden Person: + Konto hinzufügen zu: + Geben Sie einen Namen ein, der aus den Kontakten hinzugefügt werden soll. + Einladung senden Kontaktprofil - Status: - - Clienttyp: - - Computer - + Programmtyp: + Rechner Mobil - - - Online - - Beschäftigt - - Abwesend - - Untätig - - Offline - - Als offline anzeigen - - - - - + Gesperrte Kontakte - %1$s + Keine gesperrten Kontakte. + + Mit %1$s unterhalten Ich - - Nachricht hier eintippen - - - - - - - - - - - - - - - - + %1$s is verbunden + %1$s ist abwesend + %1$s ist beschäftigt + %1$s is getrennt + %1$s ist beigetreten + %1$s hat die Unterhaltung verlassen + Bild senden + Datei senden + Ton senden + Foto aufnehmen + Dateiübertragung + Dateiübertragung vollständig + Dateiübertragung läuft + Übertragung akzeptieren? + will Ihnen eine Datei senden + Keine Verbindung verfügbar, um Ihre Freigabe zu senden! Senden - - Die Nachricht kann nicht gesendet werden. - - Verbindung zum Server verloren. Nachrichten werden gesendet, sobald eine Online-Verbindung besteht. - - - - - - Link auswählen - - Keine aktiven Chats - - Verschlüsselte Chat Session wird gestartet... - Verschlüsselte Chat Session wird gestoppt... - - - Kontakt hinzufügen - - E-Mail-Adresse der Person, die Sie einladen möchten: - - Liste auswählen: - - Geben Sie einen Namen ein, der aus den Kontakten hinzugefügt werden soll. - - Einladung senden - - Jabber/XMPP - Lokaler Netzwerk Chat (Bonjour/ZeroConf) - - Konto-Setup + Nachricht senden + Sichere Nachricht senden + Erneut senden + Unterhaltung beenden + Unterhaltung löschen + Emoji einfügen + Unterhaltungen wechseln + Menü+ + Verknüpfung auswählen + Löschen + Dateien behalten + Sicheren Speicher der Unterhaltung löschen? + Alle hoch- und heruntergeladenen Dateien der Unterhaltung werden dauerhaft gelöscht. Warnung: Dies kann nicht rückgängig gemacht werden! + Original löschen? + Diese Datei wird vor dem Versenden in den sicheren Speicher kopiert. Möchten Sie die Originaldatei aus dem unsicheren Gerätespeicher löschen? + Behalten + Export + Mediendatei exportieren? + Diese Mediendatei wird exportiert an %1$s + Unterhaltung beenden? + Alle verschlüsselten Medienelemente dieser Sitzung werden gelöscht. Um ein Element zu exportieren, bitte lange auf das entsprechende Vorschaubild drücken. + Unterhaltung beenden und Dateien löschen? + + %1$s hat Sie zu einer Gruppenunterhaltung eingeladen. + Einladung wurde an %1$s gesendet. + Annehmen + Ablehnen + Gruppenunterhaltung + Gruppenunterhaltung erstellen oder beitreten + Zur Gruppenunterhaltung wird verbunden … + Einladen \u2026 + Kontakte für Einladung auswählen + Gesuchten Kontakt eingeben + Keine Kontakte gefunden. + +Zum Einladen antippen. + Kontakt hinzufügen? + Ja + Nein + Anmeldung von %1$s kann nicht genehmigt werden. Bitte versuchen Sie es später erneut. + Anmeldung von %1$s kann nicht abgelehnt werden. Bitte versuchen Sie es später erneut. + + Verschlüsselung starten + Verschlüsselung beenden + Verschlüsselte Unterhaltung wird gestartet … + Verschlüsselte Unterhaltungssitzung wird beendet … + Sicherheitsfingerabdruck + Sicherheitsfingerabdruck + Anmelden + Schlüssel erneuern + Verschlüsselung ist deaktiviert + Verschlüsselt (zum Überprüfen antippen) + Ihr Kontakt hat die verschlüsselte Unterhaltung beendet. + Verschlüsselt und überprüft! + Neues OTR-Schlüsselpaar wird erzeugt … + + Ein vorhandener OTR-Schlüsselspeicher wurde entdeckt und kann importiert werden. Möchten Sie jetzt das QR-Passwort einlesen? + Schlüsselsync aktivieren + OTR-Schlüsselbund erfolgreich importiert + + QR-Code scannen + Ihr Fingerabdruck + Manuell überprüfen + Frage + Sicherheitsfingerabdruck (überprüft) + Diesen Fingerabdruck wirklich bestätigen? + Fingerabdruck überprüfen? + Fingerabdruck des Kontakts wurde überprüft! + Fingerabdruck für Sie + Fingerabdruck für + Barcode-Scanner installieren? + Diese Anwendung benötigt einen Barcode-Scanner. Möchten Sie diesen installieren? + + Legitimierung + Eine Frage und eine Antwort eingeben, um Sie Ihrem Kontakt zu senden und um zu überprüfen, dass dieser die richtige Person ist. + zu stellende Frage + erwartete Antwort + Ihr Gesprächspartner hat Sie erfolgreich überprüft. Überprüfen Sie jetzt Ihren Gesprächspartner, indem Sie Ihre eigene Frage stellen. + OTR-F&A-Überprüfung + Unterhaltungsverschlüsselung + Senden + Abbrechen + + Sicherer Anruf + Sicherer Sprachdienst + Geben Sie hier Ihre Kontodaten von OStel.co oder eines anderen sicheren SIP-Dienstes zur Anrufintegration ein + + Kontoeinrichtung + + Sicherheit und Datenschutz Verschlüsselung und Anonymität - Offline-Kontakte ausblenden - + Verschlüsseln An/Aus + Zeitüberschreitung beim Passwort + Zeitraum, in dem die App-Verschlüsselung entsperrt bleiben soll + + Anklickbare Verknüpfungen über Tor + Für Konten die Tor benutzen, Verknüpfung in Unterhaltungen anklickbar machen (ACHTUNG, mögliches Leck beim Datenschutz!) + Benutzeroberfläche + Sprache + Sprachen + Systemvorgabe benutzen + Dunkles Design benutzen + Zu dunklem Design wechseln + Nachrichtenspeicherung nur im Arbeitsspeicher + Nachrichten nur im Arbeitsspeicher - nicht auf dem Flash-Speicher - speichern, um sich gegen eine Extrahierung von Nachrichten abzusichern. (Kann zum Verlust von Nachrichten führen). + Hintergrundbild + Den Pfad (»/sdcard/beispiel.jpg«) für ein Hintergrundbild der App einstellen + Kontaktraster anzeigen + Kontaktliste als Kontaktbildraster anzeigen + Ja, alle akzeptieren + Unsichere Medien löschen + Nach dem Import eines geteilten Fotos oder einer Datei wird das unverschlüsselte Original gelöscht. + + Andere Feineinstellungen + ChatSecure autom. starten + Start und Anmeldung automatisch in vorher angemeldeten Konten + Getrennte Kontakte ausblenden + Dienst mit Vordergrundpriorität + Durch Neustart des Dienstes verursachte Verbindungsabbrüche reduzieren. Es wird eine Benachrichtigung in der Statusleiste angezeigt. + Herzschlagintervall + Einen höheren Wert (in Minuten) verwenden, um den Akku zu schonen. Ein hoher Wert kann dazu führen, dass der Anbieter die Verbindung wegen Inaktivität beendet. + Benachrichtigungseinstellungen - - Chat-Benachrichtigung - - Bei Nachrichteneingang Benachrichtigung in Statuszeile anzeigen - - Vordergrundpriorität verwenden - Reduzieren Sie die Chance, dass Android eine Verbindung neustartet. Dies wird eine Meldung im Infobereich platzieren. - Heartbeat interval - Verwenden Sie einen höheren Wert (in Minuten), um Batterie zu sparen. Ein hoher Wert kann dazu führen das der Anbieter, die Verbindung wegen Inaktivität beendet. + Benachrichtigung über Sofortnachrichten + Nachrichteneingang in der Statusleiste anzeigen Vibrieren - Bei Nachrichteneingang vibrieren - - Ton - - Bei Nachrichteneingang Klingelton wiedergeben - - Klingelton auswählen - - - - - - - Annehmen - - Ablehnen - - - - Annehmen - - Ablehnen - - - - - - - - - - - - - Einladung zum Gruppenchat - - - - - - - - - - - - Chat-Service starten - Ermöglicht Anwendungen das eigenständige Starten von Chat-Services. - - + Klang + Bei Nachrichteneingang Ton wiedergeben + Benachrichtigungston auswählen + + Fehlerprotokolle aktivieren + Protokolldaten an Standardausgabe ausgeben - logcat zur Fehlersuche + + Netzwerkdaten deaktiviert + +Für die Anmeldung wird eine Datenverbindung (inklusive Hintergrunddaten) benötigt. + + Aktivieren + Beenden + Wollen Sie sich von allen Diensten abmelden UND alle Prozesse beenden (vollständiges Schließen) ? + Neues Konto erstellen? + Ein neues Unterhaltungskonto für den Benutzernamen »%1$s« erstellen? + Zertifikatsinformation + Zertifikat: + Ausgestellt von: + SHA1-Fingerabdruck: + Ausgestellt am: + Läuft ab: + Chatraum beitreten? + Eine externe Anwendung möchte dich zu einem Chatraum hinzufügen. Erlauben? + Hintergrund auswählen + Möchten Sie ein Hintergrundbild aus der Galerie auswählen? + Bild auswählen + + Neue %1$s Nachrichten + %1$d ungelesene Unterhaltungen + Neue Freundschaftseinladung von %s + Neue Datei %1$s von %2$s + Einladung zur Gruppenunterhaltung + Neue Einladung zur Gruppenunterhaltung von %s + Neue Nachricht(en) von + ChatSecure-Dienst wird gestartet … + Aktiviert und entsperrt + Nachricht in die Zwischenablage kopiert + Achtung - - - + Fehler: + Fehlercode %1$d + Anmeldung beim %1$s-Dienst nicht möglich. Bitte versuchen Sie es später erneut. (Details: %2$s) Die Liste wurde nicht hinzugefügt. - Kontakt wurde nicht gesperrt. - - Blockierung des Kontakts wurde nicht aufgehoben. - - Wählen Sie zunächst einen Kontakt aus. - - Getrennt - - Servicefehler - + Sperrung des Kontakts wurde nicht aufgehoben. + Bitte zuerst einen Kontakt auswählen. + Getrennt!\n + + Dienstfehler! Kontaktliste wurde nicht geladen. - Verbindung zum Server kann nicht hergestellt werden. Überprüfen Sie die Verbindung. - - - - - + »%1$s« ist bereits in der Kontaktliste vorhanden. + Kontakt »%1$s« wurde gesperrt. Kontaktliste wird geladen, bitte warten. - Ein Netzwerkfehler ist aufgetreten. - WiFi ist nötig für diese Verbindung. - + Für diese Verbindung ist WLAN notwendig. Diese Funktion wird vom Server nicht unterstützt. - - Das eingegebene Kennwort ist ungültig. - + Das eingegebene Passwort ist ungültig. Beim Server ist ein Fehler aufgetreten. - Diese Funktion wird vom Server nicht unterstützt. - Der Server ist derzeit nicht verfügbar. - - Timeout beim Verbindungsaufbau zum Server - + Zeitüberschreitung beim Verbindungsaufbau zum Server Die aktuelle Version wird vom Server nicht unterstützt. - Die Nachrichtenwarteschlange ist voll. - Das Weiterleiten an die Domäne wird vom Server nicht unterstützt. - Der eingegebene Benutzername wird nicht erkannt. - - Sie werden leider vom Benutzer gesperrt. - + Sie wurden leider vom Benutzer gesperrt. Die Sitzung ist abgelaufen, bitte melden Sie sich erneut an. - - Sie haben sich von einem anderen Client aus angemeldet. - Sie haben sich bereits von einem anderen Client aus angemeldet. - - Die auf der SIM-Karte gespeicherte Telefonnummer kann nicht gelesen werden. Bitte wenden Sie sich an den Betreiber. - + Sie haben sich mit einem anderen Programm angemeldet. + Sie sind bereits mit einem anderen Programm angemeldet. + Die auf der SIM-Karte gespeicherte Telefonnummer kann nicht gelesen werden. Bitte wenden Sie sich an den Netzbetreiber. Sie sind derzeit nicht angemeldet. - - - + Bei der Überprüfung von Benutzername und Passwort ist ein Fehler aufgetreten – bitte kontrollieren Sie beide und versuchen Sie es erneut. + Bei der Erzeugung des Schlüsselpaars ist ein Fehler aufgetreten. + Beim Verbinden mit dem Server ist ein Fehler aufgetreten – bitte überprüfen Sie die Server-Konfiguration und versuchen Sie es erneut. + Beim Verbinden ist ein Fehler aufgetreten – bitte prüfen Sie die Netzwerkverbindung und versuchen Sie es erneut. + ChatSecure hat die Verbindung zum Netzwerk verloren + ChatSecure versucht die Verbindung wieder aufzubauen + An Ihrer Benutzerkennung fehlt ein Servername in der Form »@hostname.com« + An Ihrer Benutzerkennung fehlt die Top-Level-Domäne, beispielsweise ».de« + Bitte Ihr Passwort eingeben: + Da Sie Tor verwenden, müssen Sie den Hostnamen des XMPP-Servers direkt in »Erweiterte Kontoeinstellungen« eingeben + WARNUNG: Dieser Dienst verwendet ein Zertifikat mit SCHWACHER Verschlüsselung. Bitten Sie den Administrator um Aktualisierung. + Das Zertifikat des Anbieter stimmt nicht mit dem Zertifikat, dem vertraut wird überein. + Gruppenunterhaltung konnte nicht erstellt oder betreten werden + Dieser Dateityp kann leider nicht geteilt werden + Zum Teilen von Dateien muss die Verschlüsselung aktiviert werden + Um Dateien freizugeben, bitte die Unterhaltungsverschlüsselung aktivieren + Nicht unterstützte eingehende Daten, können nicht freigegeben werden! + ChatSecure hat eine Anfrage zum Entfernen seines Prozesses entdeckt. ChatSecure wird wahrscheinlich abstürzen. Bitte nutzen Sie stattdessen »Alle abmelden« im Menü der Kontoliste. + Für dieses Dateiformat ist kein Betrachter vorhanden + Bitte ein sicheres Gespräch starten, bevor Sie Codes einlesen + Der OTR-Schlüsselbund wurde nicht importiert. Bitte prüfen Sie die Korrektheit der Datei auf Format und Ort + Bitte die Datei »otr_keystore.ofcaes« von der Desktop-Software »KeySync« in das Stammverzeichnis des Gerätespeichers kopieren + Die Nachricht konnte nicht gesendet werden. + Nachrichten werden nach Wiederherstellung der Verbindung gesendet. + %1$s ist getrennt. Von Ihnen gesendete Nachrichten werden zugestellt sobald %1$s verbunden ist. + %1$s ist nicht in Ihrer Kontaktliste enthalten. + Ihre Passwörter stimmen nicht überein + Priorität muss eine Zahl im Bereich [0 … 127] sein + Portnummer muss eine Zahl sein + Übertragungsfehler + Das Lesen der Datei ist fehlgeschlagen. + Schwerwiegender Fehler: Das Entsperren oder Laden der Datenbank ist fehlgeschlagen. Bitte installieren Sie die App erneut oder wählen Sie »Daten löschen« in Androids App-Einstellungen. + Keine App installiert, die diese Verknüpfung öffnen kann! + Kontoeinstellungen + + ACHTUNG: Dieses ist eine vorläufige ChatSecure-Version, die möglicherweise noch Sicherheitslücken und Fehler enthält. + + Gruppen + Gespräch(e) öffnen + Schließen und sperren + Panik + Freunde + Kontakte + Server-Zertifikat annehmen? + Fingerabdruck anzeigen + Ladevorgang … + + Über ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Kontaktliste + Einstellungen + Konten verwalten + Abmelden + Kontaktliste + + Jabber (XMPP) + Lokales Netzwerk (Bonjour/ZeroConf) + Google-Konto + dukgo.com + + + Verbunden + Beschäftigt + Abwesend + Untätig + Getrennt + Als getrennt anzeigen + Glücklich Traurig @@ -414,7 +513,7 @@ Lachen Verwirrt - + Glücklich Traurig @@ -448,179 +547,38 @@ :-[ O:-) :-\\ - :\'( + :\\\'( :-X :-D o_O - - Kontaktliste - Verschlüsseln An/Aus - Verbinden über Tor (Benötigt Orbot app) - - ChatSecure - Benutzen Sie ChatSecure das erste mal? - Intro überspringen! - Über Gibberbot - Konto-Setup - - Über ChatSecure - ChatSecure ist eine Instant Messaging App für Mobilgeräte mit Sicherheitsfunktionen, die verhindern, daß Konversationen und Kommunikation ausspioniert werden.\n\nAlle Chat Dienste, welche das Jabber oder XMPP Protokoll benutzen werden unterstützt, wie zum Beispiel Gtalk oder Jabber.org. - - Wie funktioniert OTR? - \'Off-the-Record Messaging\' ist ein Verschlüsselungssystem, daß entworfen wurde, die Privatheit der Kommunikation in der echten Welt nachzubilden.\nIm einzelnen sind dies: Verschlüsselung um Menschen außer Reichweite auszusperren, Beglaubigung (Authentication) um der Identität des Gesprächspartners sicher zu sein, Abstreitbarkeit (Deniability) um im Nachhinein verleugnen zu können bestimmte Aussagen getroffen zu haben, Folgenlosigkeit (Perfect Forward Secrecy) damit der Verlust des privaten Schlüssels es nicht ermöglicht Kommunikation der Vergangenheit nachträglich zu enschlüsseln.\n\nDas eingesetzte OTR-Protokoll ist kompatibel mit Desktop-IM-Clients wie Adium, Pidgin ua. - - Sind meine Chats sicher? - ChatSecures Chatverschlüsselung funktioniert nur wenn die Gegenseite ein kompatibles Programm benutzt. Deswegen sollten Sie darauf achten, dass Ihr Kontakt entweder auf dem Handy ChatSecure und Adium oder Pidgin auf dem Desktop benutzt. Feineinstellungen wie und wann ChatSecure verschlüsselt können in den Kontoeinstellungen vorgenommen werden.\n\nLassen Sie uns beginnen! - - Passphrase Setup - Bevor Sie starten legen Sie bitte ein sicheres Passwort fest um Ihre ChatSecure Daten vor unberechtigtem Zugang zu schützen. - Passwort: - Passwort (Wiederholung) : - - user@domain.com - Neuer Benutzername - Dienstanbieter (dukgo.com, jabber.ccc.de) - Passwort - Passwort bestätigen - Erweiterte Konten-Einstellungen - Konto Typen - Konto registrieren - - Persistenz - Passwort speichern - Passwort zwischengespeichert - Passwort nicht zwischengespeichert - Automatisch anmelden - Beim Start von ChatSecure verbinden - Beim Start von ChatSecure nicht verbinden - Jetzt anmelden - Verbinden Sie folgendes Konto - Verbinden Sie folgendes Konto nicht - - Persönliche Daten (optional) - Account Alias (Mein Name) - Wie mein Konto online erscheint - Profil - Eine kurzes Wort über mich - - Verschlüsselung vorraussetzen / Klartext verweigern - Wenn möglich, Chats automatisch verschlüsseln - Chats auf Anforderung verschlüsseln - Verschlüsselung abschalten - - Überprüfe Anmeldeinformationen ... - Generiere Schlüsselpaar ... - Melde an... - - ChatSecure erlitt bei der Überprüfung von Benutzername und Passwort eine Störung – bitte kontrollieren Sie beides und wiederholen Sie den Vorgang. - ChatSecure erlitt bei der Generierung des Schlüsselpaars eine Störung. - ChatSecure erfuhr beim Verbinden mit dem Chat Server einen Fehler – bitte prüfen Sie die Einstellungen und versuchen Sie es nochmals. - ChatSecure erfuhr beim Verbinden einen Fehler – bitte prüfen Sie die Netzwerkverbindung und versuchen Sie es nochmals. - Fingerabdruck des Gegenübers - Mein Fingerabdruck - Anmelden - Schlüssel erneuern - ChatSecure hat die Verbindung zum Netzwerk verloren - ChatSecure versucht die Verbindung wiederaufzubauen - Achtung: Dieser Chat ist nicht verschlüsselt - Dieser Chat ist sicher, aber die Identität der Teilnehmer wurde noch nicht überprüft - Achtung: Die Chat-Verschlüsselung wurde beendet. - Dieser Chat ist gesichert und überprüft. - Konto-Assistent - Meine Konto-ID - Server konfigurieren - Sind Sie bereit? - Geben Sie die Konto ID ein um ChatSecure für den XMPP Chat Dienst zu konfigurieren. Es sieht wie eine Mailadresse aus: - Bitte geben Sie Ihre Konto-ID (user @ hostname) : - Bitte geben Sie Host und Port (5222 ist Standard) ihres \njabber/xmpp Servers ein. - ChatSecure wurde konfiguriert und ist nun für einen sicheren und privaten Chat verbindungsbereit. - An Ihrer Benutzerkennung fehlt ein Servername der Form @hostname.com - An Ihrer Benutzerkennung fehlt die Top-Level-Domain, beispielsweise .com - Geben Sie Ihr Passwort ein: - - Account-Einstellungen - defLoc - WARNUNG: Dies ist eine frühe ChatSecure Veröffentlichung, die möglicherweise noch Sicherheitslöcher und Fehler enthält. - - Gruppen - Erzeuge neues OTR Schlüsselpaar... - Kontakt - Geben Sie den Namen des Kontakts ein, mit dem Sie chatten möchten. - Auf gehts! - Sprache - Sprachen - Welche Sprache sollte im InTheClear Display angezeigt werden? - SRV Betrachtung - Nutze - Dürfen Nutzername und Passwort unverschlüsselt versendet weden, wenn die Verbindung nicht verschlüsselt ist? - Unverschlüsselten Login erlauben - Überprüfen Sie, ob das Zertifikat vertrauenswürdig ist - TLS Verifikation - TLS/SSL Verbindung nötig - Transport Verschlüsselung - wie verschlüsselte Chats gestartet werden - Server verbinden, wen nötig - Server verbinden - TCP Port für XMPP Server - Server Port - XMPP-Ressource - diese Verbindung von anderen Clients, die auch angemeldet sind unterscheiden - XMPP-Ressource Priorität - Nachrichten für Kunden mit mehreren aktiven Ressourcen auf die Ressource mit der höchsten Priorität gesendet werden - Nächstes - Zurück - Chats - Neue Nachricht(en) von - Authentifizierung - Geben Sie eine Frage ein die ihrem Kontakt gesendet wird und die Antwort die Sie erwarten, um zu überprüfen, wer das ist. - Frage stellen - die erwartete Antwort - Ihr Ansprechpartner wurde erfolgreich überprüft. Jetzt überprüfen Sie Ihre Kontaktdaten, indem Sie Ihre eigene Frage stellen. - No Chats.⏎\n⏎\nHier klicken um eines zu starten! - Dark Theme benutzen - Da Sie Tor verwenden, müssen Sie dens XMPP \' Connect Server \' hostnamen direkt in die Erweiterte Kontoeinstellungen eingeben - - ChatSecure automatisch starten - Automatischer Start und Anmeldung in vorher angemeldeten Konten - Sie haben keine Konten konfiguriert.⏎\n⏎\nKlicken Sie hier um eines zu konfigurieren! - Google Konto - In-Memory Message Storage Nur - Nachrichten im Speicher behalten, nicht auf Flash-Speicher, um gegen Nachrichten, die extrahiert wurden zu verteidigen. (Nachrichten können verloren werden) - Hintergrund Bild - Pfad benutzen (\"/ sdcard / foo.jpg\"), um ein Hintergrundbild für den App zu benutzen - Sicherheit und Datenschutz - Benutzeroberfläche - Andere Feineinstellungen - Chat(s) öffnen - Orbot (Tor) - Schliessen - - Wollen Sie sich von allen Diensten abmelden UND alle Prozesse killen (hartes Schliessen) ? - Neue Passphrase eingeben. Es muss mindesten einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten und muss länger wie sechs Ziffern sein. - Ihre bestehenden Mitteilungen mit der neuen Passphrase verschlüsseln (bitte warten...) - Eingabe Ihrer Passphrase: - Willkommen! Geben Sie zur Sicherung Ihrer Mitteilungen eine starke Passphrase ein. Es muss mindesten einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten und muss länger wie sechs Ziffern sein. - Ihre Passphrase war nicht lang genug - Ihre Passphrase enthält keine Großbuchstaben - Ihre Passphrase enthält keine Kleinbuchstaben - Ihre Passphrase enthält keine Zahlen - Öffnen - ChatSecure gesperrt - Passphrase erstellen - Passphrase eingeben - Neue Passphrase eingeben - Passphrase bestätigen - Passphrase stimmt nicht überein, bitte versuchen Sie es nochmal - Sprachsicherung - Geben Sie hier Sipgate oder einen anderen sicheren SIP Dienst zur Anrufinetgration ein - dukgo.com - Ein vorhandener OTR Key wurde entdeckt und kann importiert werden. Möchten Sie nun das QR Passwort scannen? - Importiere OTR Keystore - OTR Keyring erfolgreich importiert - Der OTR Keyring ist nicht importiert; Bitte prüfen Sie die Korrektheit der Datei auf Format und Ort. - Passwort Timeout - Dauer während die App Verschlüsselung entsperrt sein soll + Vorhandenes Konto + Mit meinem bereits bestehenden Konto auf einem bestimmten Jabber-/XMPP-Server Verbinden verbinden. + Google-Konto + Mit Google-Benutzern unterhalten, durch Ihr vorhandenes Google-Konto. + Vermaschte WLAN-Unterhaltung + Mit anderen Nutzern im selben lokalen WLAN-Netzwerk oder vermaschten Netz unterhalten. Es wird keine Internetverbindung benötigt! + WLAN-Unterhaltung aktivieren + Neues Konto + Ein neues kostenloses Konto, von der integrierten Liste oder einem beliebigen anderen Anbieter, erstellen. + Neues Konto erstellen + Geheime Identität! + Ein anonymes Einwegkonto durch einfaches Antippen erstellen (benötigt »Orbot: Vermittlung mit Tor«) + Identität erzeugen + Dieses ist eine Gruppenunterhaltung + [erneut gesendet] + [erneut gesendet] + Das verschlüsselte Teilen der Datei ist fehlgeschlagen. + Orbot installieren? + Sie müssen Orbot installiert und aktiviert haben, um den Datenverkehr über den Proxy leiten zu können. Möchten Sie es installieren? + Immer + Orbot starten? + Orbot wurde offensichtlich nicht gestartet. Möchten Sie es jetzt starten und mit Tor verbinden? + Im Raum zu benutzender Spitzname + Name des zu betretenden oder erstellenden Raumes + Gruppenunterhaltungsserver (konferenz.blabla.de) + Der Schlüsselspeicher ist fehlerhaft. Bitte installieren Sie ChatSecure erneut oder wählen Sie »Daten löschen« in Androids App-Einstellungen + Nicht lesbare verschlüsselte Nachricht erhalten + Nachricht konnte nicht entschlüsselt werden + < Nach Rechts und Links wischen, für mehr Informationen > diff --git a/res/values-el/arrays.xml b/res/values-el/arrays.xml index 8131f35f0..c0e745c28 100644 --- a/res/values-el/arrays.xml +++ b/res/values-el/arrays.xml @@ -1,9 +1,9 @@ - + - - Δύναμη / Απαίτηση - Αυτόματα Απόπειρα - Όπως ζητήθηκε - Άτομα με ειδικές ανάγκες Ποτέ μην / - + + Εξαναγκασμένο / Απαίτηση + Αυτόματη Απόπειρα + Έπειτα απο αίτηση + Απενεργοποιημένο / Ποτέ + diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index ff1e553ea..5c807264b 100755 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -1,362 +1,501 @@ - - - - - - - - - - - - - - - Ακύρωση σύνδεσης - - - Προσθήκη επαφής - - Διαγραφή επαφής - - Αποκλεισμός επαφής - - Αποκλεισμένος/η - - Λίστα λογαριασμών - - - - Ρυθμίσεις - - - Έναρξη συζήτησης - - Αποσύνδεση - - Προβολή προφίλ - - - Τερματισμός συζήτησης - - - Λίστα επαφών - - Πρόσκληση... - - Εναλλαγή συζητήσεων - - Εισαγ.εικον.smiley - - - Μενού+ - - - + + + ChatSecure + ChatSecure + + διάβασε τα άμεσα μηνύματα + +Επιτρέπει στις εφαρμογές να διαβάζουν δεδομένα απο τον πάροχο IM. + γράψε άμεσα μηνύματα + +Επιτρέπει στις εφαρμογές να γράφουν δεδομένα στον πάροχο IM + Έναρξη υπηρεσίας ανταλλαγής άμεσων μηνυμάτων (IM) + Επιτρέπει στις εφαρμογές να εκκινούν την υπηρεσία ανταλλαγής άμεσων μηνυμάτων (IM) μέσω του intent. + Επιβεβαίωση - - - - - - - - OK - Ακύρωση - OK - Ακύρωση - - - - - - - - - + Επόμενο + Πίσω + Σύνδεση + Αναπαραγωγή + Εγκατάσταση + + Άνοιγμα + Κλειδωμένο ChatSecure + Ρύθμιση Κωδικού + Επικύρωση Κωδικού + Κωδικός + Νέος Κωδικός + Επιβεβαίωση Νέου Κωδικού + Ο κωδικός δεν ταιριάζει, δοκιμάστε ξανά + Βαριέμαι (μη χρήση κωδικού) + Πληροφορίες εντολών + Εισάγετε τον κωδικό παρακαλώ + Τον κωδικό παρακαλώ + Φράση πρόσβασης + Φράση πρόσβασης (ξανά) + + Επιλογή λογαριασμού + Επιλογή λογαριασμού + (%1$d) + Προσθήκη λογαριασμού %1$s + Σχετικά με + Αποσύνδεση όλων + Αποσύνδεση απο όλες τις υπηρεσίες; + Πραγματοποιήσατε έξοδο από το %1$s. + Πραγματοποιήθηκε έξοδός σας από το %1$s because %2$s + Πρώτη φορά χρήσης του ChatSecure? + Ανυπομονείτε να ξεκινήσετε; + Ξεκινήστε + Στήσιμο Λογαριασμού + Ρύθμιση κωδικού ασφαλείας + Πριν ξεκινήσετε επιλέξτε μια ασφαλή φράση για την προστασία των δεδομένων σας από το ChatSecure. + Κωδικός: + Κωδικός (ξανά): + Εισάγετε εναν *νέο* κωδικό ασφαλείας. Πρέπει να περιέχει τουλάχιστον ένα κεφαλαίο, ένα πεζό και ένα νούμερο και να έχει πάνω από 6 χαρακτήρες. + Κρυπτογράφηση των υπάρχοντων σημειώσεων με τον νέο κωδικό ασφαλείας (υπομονή…) + Εισάγετε τον κωδικό ασφαλείας: + Καλώς ήρθατε! Εισάγετε έναν ισχυρό κωδικό ασφαλείας για να ασφαλίσετε τις σημειώσεις σας. Πρέπει να περιέχει τουλάχιστον ένα κεφαλαίο, ένα πεζό και ένα νούμερο και να έχει πάνω από 6 χαρακτήρες. + Ο κωδικός ασφαλείας δεν είναι αρκετά μεγάλος + Ο κωδικός ασφαλείας δεν περιέχει καθόλου κεφαλαία γράμματα + Ο κωδικός ασφαλείας δεν περιέχει καθόλου πεζά γράμματα + Ο κωδικός ασφαλείας δεν περιέχει καθόλου νούμερα + + Σχετικά με το ChatSecure + Το ChatSecure είναι μια κινητή instant messaging εφαρμογή που παρέχει επιπλέον χαρακτηριστικά ασφαλείας που εμποδίζουν τους άλλους από το να υποκλέπτουν τις συνομιλίες και τις επικοινωνίες σας.\N\nΗ εφαρμογή υποστηρίζει οποιαδήποτε υπηρεσία συνομιλίας που χρησιμοποιεί το Jabber ή XMPP πρωτόκολλο, όπως το Google GTalk ή Jabber.org. + + Πώς και είναι ασφαλές; + Το \'Off-the-Record Messaging\' είναι ενα σύστημα ασφαλείας σχεδιασμένο να παρέχει ιδιωτικότητα μιμούμενο τα χαρακτηριστικά μιας ιδιωτικής συζήτησης στον αληθινό κόσμο και συμπεριλαμβάνει Κρυπτογραφία, Αυθεντικοποίηση, Πιστοποίηση και Πρόωθηση Απορρήτου + +Το OTR πρωτόκολλο είναι συμβατό και με εφαρμογές chat όπως το Adium και το Pidgin. + + Είναι ασφαλείς οι συζητήσεις μου; + Χαρακτηριστικό κρυπτογράφησης συνομιλίας ChatSecure δουλεύει μόνο όταν μηνύματα Με άλλους χρησιμοποιώντας μια συμβατή εφαρμογή ή πρόγραμμα, οπότε θα πρέπει να εξασφαλίζουν τις επαφές σας χρησιμοποιώντας ChatSecure για κινητά και Adium ή Pidgin για την επιφάνεια εργασίας. Μπορείτε να ρυθμίσετε ακριβώς πώς και πότε ChatSecure προσπαθεί να κρυπτογραφήσει τις συζητήσεις σας στο Ρυθμίσεις λογαριασμού. + + Νέος λογαριασμός + Προσθήκη λογαριασμού + Αλλαγή λογαριασμού + Διαγραφή λογαριασμού + Υπάρχον λογαριασμός + Όνομα χρήστη: - Κωδικός πρόσβασης: - Απομνημόνευση του κωδικού πρόσβασης. - Αυτόματη σύνδεση. - Δεν έχετε λογαριασμό; - - Αυτή η επιλογή σάς συνδέει αυτόματα κάθε φορά που ανοίγετε αυτήν την εφαρμογή. Για να απενεργοποιήσετε αυτήν την επιλογή, αποσυνδεθείτε και αφήστε κενό το πλαίσιο επιλογής \\"Αυτόματη σύνδεση\\". - Σύνδεση - - - - Σύνδεση... - - - Τα δεδομένα παρασκηνίου είναι απενεργοποιημένα - - - - - - Ενεργοποίηση - - Τερματισμός - - - - - - + Για την ασφάλεια σας, αν το τηλέφωνο σας χαθεί η κλαπεί, πηγαίνετε στην ιστοσελίδα στον υπολογιστή σας και αλλάξτε τον κωδικό ασφαλείας σας. + Αυτή η επιλογή σάς συνδέει αυτόματα κάθε φορά που ανοίγετε αυτήν την εφαρμογή. Για να απενεργοποιήσετε αυτήν την επιλογή, αποσυνδεθείτε και αφήστε κενό το πλαίσιο επιλογής \\"Αυτόματη σύνδεση\\". + Σύνδεση μέσω Tor (απαιτεί την εφαρμογή Orbot) + χρήστης@σελίδα.com + νέο όνομα χρήστη + πάροχος υπηρεσίας (dukgo.com, jabber.ccc.de) + κωδικός + επιβεβαιώστε τον κωδικό + Προχωρημένες Ρυθμίσεις Λογαριασμού + Τύπος λογαριασμού + Εγγραφή λογαριασμού + Επιμονή + Να θυμάσαι τον κωδικό + O κωδικός αποθηκεύτηκε στην cache + O κωδικός δεν αποθηκεύτηκε στην cache + Αυτόματη Σύνδεση + Σύνδεση κατα την εκκίνηση του ChatSecure + Αποφυγή σύνδεσης κατα την εκκίνηση του ChatSecure + Σύνδεση Τώρα + Σύνδεση μετά την ρύθμιση λογαριασμού + Αποφυγή σύνδεσης μετά την ρύθμιση λογαριασμού + Προσωπικά (προαιρετικό) + Ψευδώνυμο λογαριασμού (όνομα) + Πώς εμφανίζεται ο λογαριασμός σας online + Προφίλ + Μία σύντομη περιγραφή του εαυτού σας + Χρήση μόνο κρυπτογραφίας / απόρριψη σκέτου κειμένου + Όταν είναι δυνατό, κρυπτογράφησε τα chat αυτόματα + Κρυπτογραφία chat έπειτα από ζήτηση + Απενεργοποίηση κρυπτογραφίας του chat + Επικύρωση στοιχείων… + Παραγωγή ζεύγους κλειδιών… + Γίνεται σύνδεση… + Οδηγός Λογαριασμού + ID λογαριασμού + Ρύθμιση Server + Είστε έτοιμος; + Πληκτρολογήστε το αναγνωριστικό του λογαριασμού σας για να ρυθμίσετε το ChatSecure για την υπηρεσία XMPP συνομιλίων. Μοιάζει με μια διεύθυνση email: + Εισάγετε το ID του λογαριασμού σας(user@hostname): + Παρακαλούμε εισάγετε ή επεξεργαστε Jabber / XMPP το όνομα του chat διακομιστή και τον αριθμό θύρας (5222 είναι προεπιλογή). + Το ChatSecure έχει διαμορφωθεί, και τώρα ήρθε η ώρα για να συνδεθείτε με την υπηρεσία και να ξεκινήσετε τη συνομιλίες με ασφάλεια! + Orbot (Tor) + Επιλογή domain + Καταχώριση νέου λογαριασμού… + εκκίνηση του ChatSecure… + Ακύρωση σύνδεσης + Σύνδεση… + Γίνεται απεγγραφή\u2026 + + Αναζήτηση SRV + Χρήση του SRV DNS για να βρείτε τον πραγματικό διακομιστή XMPP από το όνομα τομέα + Επέτρεψε την αποστολή του ονόματος και κωδικού χρήστη ως απλό κείμενο όταν χρησιμοποιείτε μη κρυπτογραφημένη μεταφορά. + Επέτρεψε αυθεντικοποίηση με σκέτο κείμενο + Έλεγχος για την εγκυρότητα του πιστοποιητικού + TLS Επικύρωση + Απαίτηση Σύνδεσης TLS + Κρυπτογράφηση μεταφοράς + πώς ξεκινούν οι κρυπτογραφημένες συζητήσεις + Ο server στον οποίον συνδέεστε, αν χρειαστεί + Σύνδεση σε Server + Πόρτα TCP για τον XMPP Server + Πόρτα διακομιστή + XMPP Resource + για διάκριση αυτής της σύνδεσης απο άλλους λογαριασμούς που είναι συνδεδεμένοι + Προτεραιότητα πόρων XMPP + Μηνύματα προς τους πελάτες με πολλαπλα ενεργους πόρους θα σταλούν στον πόρο με την υψηλότερη προτεραιότητα + + Συζητήσεις + Δεν υπάρχουν συζητήσεις + +Πατήστε εδώ για να ξεκινήσετε μία! + Δεν έχετε ρυθμισμένους +λογαριασμούς + +Πατήστε εδώ για να προσθέσετε έναν! + Λίστα επαφών - %1$s + Προσθήκη επαφής + Διαγραφή επαφής + Αποκλεισμός επαφής + Ψευδώνυμο επαφής + Αποκλεισμένος/η + Ψευδώνυμο + Η επαφή \"%1$s\" θα διαγραφεί. + Θα γίνει αποκλεισμός της επαφής \"%1$s\". + Θα γίνει κατάργηση αποκλεισμού της επαφής \"%1$s\". + Έγινε προσθήκη της επαφής \"%1$s\". + Η επαφή \"%1$s\" διαγράφηκε. + Έγινε αποκλεισμός της επαφής \"%1$s\". + Κατάργηση αποκλεισμού της επαφής \"%1$s\". + Νέο Chat + Αναζήτηση Επαφών + Εμφάνιση πλέγματος + Έναρξη συζήτησης + Εμφάνιση Προφίλ + Επαλήθευση κλειδιού + Ενεργές συζητήσεις (%1$d) + %1$d συνδεδεμένες επαφές Προσκλήσεις φίλων - (\"\"Άγνωστος\"\") - Κενή - Δεν υπάρχουν συνομιλίες - - Επιλογή επαφής (επαφών) για αποστολή πρόσκλησης - Πληκτρολογήστε για να βρείτε την επαφή - Δεν βρέθηκαν επαφές. - - - - Δεν υπάρχουν αποκλεισμένες επαφές. - - + Επαφή + εισάγετε το όνομα μιας επαφής για συνομιλία + Εμπρος + Δεν υπάρχουν ενεργές συζητήσεις. + Προσθήκη επαφής + Διεύθυνση ηλεκτρονικού ταχυδρομείου του ατόμου που θέλετε να προσκαλέσετε: + Επιλέξτε μια λίστα: + Πληκτρολογήστε ένα όνομα για να το προσθέσετε από τις επαφές. + Αποστολή πρόσκλησης Προφίλ επαφής - Κατάσταση: - Τύπος προγράμματος-πελάτη: - Υπολογιστής - Κινητό - - - Συνδεδεμένος - - Απασχολημένος/η - - Μακριά από τον υπολογιστή - - Αδρανής - - Εκτός σύνδεσης - - Να εμφανίζομαι εκτός σύνδεσης - - - - - + Αποκλεισμένες επαφές - %1$s + Δεν υπάρχουν αποκλεισμένες επαφές. + + Συζήτηση με τον χρήστη %1$s Εγώ - - Για σύνθεση κειμένου, πληκτρολογήστε - - - - - - - - - - - - - - - - + Ο χρήστης %1$s συνδέθηκε + Ο χρήστης %1$s βρίσκεται μακριά από τον υπολογιστή του + Ο χρήστης %1$s είναι απασχολημένος/η + Ο χρήστης %1$s βρίσκεται εκτός σύνδεσης + Ο χρήστης %1$s συνδέθηκε + Ο χρήστης %1$s αποχώρησε + Αποστολή Φωτογραφίας + Αποστολή αρχείου + Αποστολή Ήχου + Λήψη φωτογραφίας + Αποστολή Αρχείου + Αποστολή Επιτυχής + Αποστολή εν εξέλιξη + Αποδοχή μεταφοράς; + θέλει να σας στείλει το αρχείο + Δεν υπάρχει διαθέσιμη σύνδεση για την αποστολή του μεριδίου σας! Αποστολή - - Δεν ήταν δυνατή η αποστολή αυτού του μηνύματος. - - Χάθηκε η σύνδεση με τον διακομιστή. Η αποστολή των μηνυμάτων θα γίνει μόλις επανέλθει η σύνδεση. - - - - - + Στείλτε ένα μήνυμα + Αποστολή ασφαλούς μηνύματος + Επαναποστολή + Τερματισμός συζήτησης + Καθαρισμός του chat + Εισαγ.εικον.smiley + Εναλλαγή συζητήσεων + Μενού+ Επιλογή συνδέσμου - - Δεν υπάρχουν ενεργές συζητήσεις. - - - - Προσθήκη επαφής - - Διεύθυνση ηλεκτρονικού ταχυδρομείου του ατόμου που θέλετε να προσκαλέσετε: - - Επιλέξτε μια λίστα: - - Πληκτρολογήστε ένα όνομα για να το προσθέσετε από τις επαφές. - - Αποστολή πρόσκλησης - - + Διαγραφή + Διατήρηση αρχείων + Διαγραφή συνεδρίας chat, ασφαλής αποθήκευσης; + Όλες οι συνεδρίες ανεβάζουν και να κατεβάζουν αρχεία θα διαγραφούν οριστικά. Προειδοποίηση: Αυτή η λειτουργία δεν μπορεί να αναιρεθεί! + Διαγραφή πρωτότυπου; + Αυτό το αρχείο θα αντιγραφεί στην ασφαλή αποθήκευση πριν από την αποστολή. Θέλετε να διαγράψετε το αρχικό αρχείο από μη ασφαλή φύλαξη της συσκευής; + Διατήρηση + Εξαγωγή + Εξαγωγή αρχείου πολυμέσων; + Αυτό το αρχείο πολυμέσων θα εξαχθεί προς %1$s + Τέλος συζήτησης; + Όλα τα σιγουρα στοιχεία πολυμέσων αυτής της συνεδρίασης θα διαγραφούν. Εξαγωγή ενός αντικείμενου πολυμέσων χρησιμοποιώντας ένα μακρύ κλικ στο εικονίδιο μικρογραφίας. + Τερματισμός συνομιλίας και διαγραφή αρχείων + + Ο χρήστης %1$s σας προσκάλεσε να συμμετέχετε σε ομαδική συζήτηση. + Έγινε αποστολή πρόσκλησης στον χρήστη: %1$s. + Αποδοχή + Απόρριψη + Ομαδικό Chat + Φτιάξτε η μπείτε σε Ομάδα Συζήτησης + Σύνδεση στο ομαδικό chat… + Πρόσκληση… + Επιλογή επαφής (επαφών) για αποστολή πρόσκλησης + Πληκτρολογήστε για να βρείτε την επαφή + Δεν βρέθηκαν επαφές. + +Πατήστε για να προσθέσετε κάποιες. + Προσθήκη %1$s? + Ναι + Οχι + Δεν ήταν δυνατή η έγκριση συνδρομής από τον χρήστη %1$s. Δοκιμάστε ξανά αργότερα. + Δεν ήταν δυνατή η απόρριψη συνδρομής από τον χρήστη %1$s. Δοκιμάστε ξανά αργότερα. + + Έναρξη κρυπτογραφίας + Παύση Κρυπτογραφίας + Εκκίνηση κρυπτογραφημένης συζήτησης… + Τερματισμός κρυπτογραφημένης συζήτησης… + Αποτύπωμα Ασφαλείας + Αποτύπωμα Ασφαλείας + Σύνδεση + Ανανέωση Κλειδιού + Απενεργοποιημένη κρυπτογραφία + Ενεργή κρυπτογραφία (πατήστε για επιβεβαίωση) + Η επαφή σας έχει σταματήσει την κρυπτογραφημένη συνομιλία. + Κρυπτογραφημένο και επικυρωμένο! + Παραγωγή νέου ζεύγους κλειδιών OTR… + + Έχουμε εντόπιση ενα OTR αποθήκευσης κλειδιών για εισαγωγή. Θέλετε να σαρώσετε τον κωδικό QR τώρα; + Ενεργοποίηση KeySync + Επιτυχής εισαγωγή ζεύγους κλειδιών OTR + + Σκανάρετε ένα QR + Το αποτυπωμά σου + Χειροκίνητα + Ερώτηση + Αποτύπωμα Ασφαλείας (εγκεκριμένο) + Σίγουρα θέλετε να επικυρώσετε αυτό το αποτύπωμά; + Επικύρωση αποτυπώματος; + Το απομακρυσμένο αποτύπωμα έχει επικυρωθεί! + Αποτύπωμα για εσάς + Αποτύπωμα για + Εγκατάσταση Barcode Scanner; + Αυτή η εφαρμογή απαιτεί εναν σαρωτή γραμμωτών κωδίκων. Θέλετε να την εγκαταστήσετε; + + Αυθεντικοποίηση + Εισάγετε μια ερώτηση για αποστολή στην επαφή σας, και την απάντηση που περιμένετε να εισάγουν, για να πιστοποιήσετε οτι είναι αυτοί που ισχυρίζονται οτι είναι + ερώτηση ασφαλείας + αναμενώμενη απάντηση + Η επαφή σας έχει περασει με επιτυχία τον έλεγχο ταυτότητας. Τώρα για την επικύρωση, καντε την δική σας ερώτηση. + OTR Q&A Επικύρωση + Κρυπτογράφηση συνομιλίας + Αποστολή + Ακύρωση + + Ασφαλής κλήση + Ασφαλής κλήση + Εισάγετε τον Ostel.co ή κάποιον άλλον ασφαλή SIP λογαριασμό εδώ για πραγματοποίηση κλήσεων + + Στήσιμο Λογαριασμού + + Ασφάλεια και Ιδιωτικότητα + Κρυπτογραφία και ανωνυμία + Κρυπτογράφηση On/Off + Λήξη Κωδικού Ασφαλείας + Χρόνος στον οποιο η κρυπτογράφηση εφαρμογής θα πρέπει να ξεκλειδωθεί + + Συνδέσεις στο Tor + Για λογαριασμούς που χρησιμοποιούν το Tor, κάντε για τις συνδέσεις σε συνομιλίες κλικ (ΠΡΟΣΟΧΗ, πιθανή διαρροή της ιδιωτικής ζωής!) + Διεπαφή Χρήστη + Γλώσσα + Γλώσσες + Χρήση προκαθορισμένων ρυθμίσεων + Χρήση Σκοτεινού Θέματος + Αλλαγή θέματος εφαρμογής σε σκοτεινό + Αποθήκευση μηνυμάτων μόνο στην μνήμη + Αποθήκευση μηνυμάτων μόνο στην μνήμη, όχι στην κάρτα μνήμης, για άμυνα ενάντια απόσπαση μηνυμάτων. (μπορεί να προκαλέσει απώλεια μηνυμάτων) + Εικόνα φόντου + Ρύθμιση διαδρομής (\"/sdcard/foo.jpg\") σε μια wallpaper εικόνα φόντου για την εφαρμογή + Εμφάνιση πλέγματος επαφών + Εμφάνιση λίστας επαφών σαν εικονίδια + Ναι, αποδοχή όλων + Διαγραφη μη ασφαλών πολυμέσων; + Αφού μοιράστηκατε μια φωτογραφία ή ένα αρχείο, αυτοματη διαγραφη από το πρωτότυπο, ανασφαλής αποθήκευση μετά την εισαγωγή + Αποθηκεύση από εξωτερικές συσκευές αποθήκευσης + Τα αρχεία πολυμέσων από συνεδρίες chat αποθηκεύονται σε κρυπτογραφημένη περιέκτη που μπορούν να αποθηκευτούν στην εσωτερική ή εξωτερική αποθήκευση. + + Εξωτερικά μέσα αποθήκευσης λείπουν + Τα αρχεία καταγραφής συνομιλίας είναι αποθηκευμένα σε μια κάρτα SD, αλλά δεν υπάρχει κάρτα SD που να είναι παρούσα. Παρακαλουμε εισάγετε την σωστή κάρτα SD, ή διαγράψτε την υπάρχουσα καταγραφή συνομιλίας και εκκινήστε το ChatSecure ξανά. + Συνομιλία Μέσων που λείπουν + Τα αρχεία καταγραφής συνομιλίας είναι αποθηκευμένα σε μια κάρτα SD, αλλά δεν υπάρχει κάρτα SD που να είναι παρούσα. Παρακαλουμε εισάγετε την σωστή κάρτα SD, ή διαγράψτε την υπάρχουσα καταγραφή συνομιλίας και εκκινήστε το ChatSecure ξανά. + Διαγραφή Chat καταγραφής + + Άλλη Ρύθμιση + Αυτόματη εκκίνηση του ChatSecure + Πάντα εκκίνηση και αυτόματη σύνδεση στους προηγούμενα συνδεδεμένους λογαριασμούς Απόκρυψη επαφών εκτός σύνδεσης - + Χρήση υψηλότερης προτεραιότητας προσκήνιου + Μείωση της πιθανότητας του Android να επανεκκινήσει το service με τις συνδέσεις. Αυτό θα προσθέσει και μια ειδοποίηση στην περιοχή ειδοποιήσεων. + Διάστημα μεταξύ χτύπων + Χρησιμοποιήστε μεγαλύτερη τιμή (σε λεπτά) για εξοικονόμιση μπαταρίας. Μια υψηλότερη τιμή μπορεί να προκαλέσει τον πάροχο να κλείσει την σύνδεση λόγω αδράνειας της. + Ρυθμίσεις ειδοποιήσεων - Ειδοποιήσεις άμεσων μηνυμάτων (IM) - Να εμφανίζεται ειδοποίηση στη γραμμή κατάστασης όταν γίνεται λήψη άμεσου μηνύματος (IM) - Δόνηση - Δόνηση κατά τη λήψη άμεσων μηνυμάτων (IM) - Ήχος - Αναπαραγωγή ήχου κλήσης κατά τη λήψη άμεσου μηνύματος (IM) - - Επιλογή ήχου κλήσης - - - - - - - Αποδοχή - - Απόρριψη - - - - Αποδοχή - - Απόρριψη - - - - - - - - - - - - + Χρήση ήχου κλήσης του ChatSecure + + Ενεργοποίηση Debug Logs + Δεδομένα καταγραφής εφαρμογών εξόδου στην βασική έξοδο για τον εντοπισμό σφαλμάτων + + Τα δεδομένα παρασκηνίου είναι απενεργοποιημένα + %1$s χρειάζεται ενεργοποιημένα τα δεδομένα παρασκηνίου. + Ενεργοποίηση + Τερματισμός + Αποσύνδεση απο όλες τις υπηρεσίες ΚΑΙ τερματισμός όλων των διεργασιών (hard exit) ; + Δημιουργία νέο λογαριασμού; + Δημιουργία ένος νέου chat λογαριασμού για τον χρήστη \'%1$s\'; + Πληροφορίες πιστοποιητικού + Πιστοποιητικό + εκδόθηκε από: + SHA1 Αποτύπωμα + Εκδόθηκε: + Λήξη: + Εγγραφή στο Chat Room; + Μια εξωτερική εφαρμογή προσπαθεί να σας συνδέσει με ένα chatroom. Να επιτρέπεται; + Επιλογή φόντου + Θέλετε να επιλέξετε μια εικόνα φόντου από τη Συλλογή; + Επιλογή εικόνας + + Νέα μηνύματα %1$s + %1$d μη αναγνωσμένες συζητήσεις + Νέα πρόσκληση φίλου από %s + Νέο αρχείο %1$s from %2$s Πρόσκληση σε ομαδική συζήτηση - - - - - - - - - - - - Έναρξη υπηρεσίας ανταλλαγής άμεσων μηνυμάτων (IM) - Επιτρέπει στις εφαρμογές να εκκινούν την υπηρεσία ανταλλαγής άμεσων μηνυμάτων (IM) μέσω του intent. - - + Νέα πρόσκληση για ομαδική συζήτηση από %s + Νέα μηνύματα από + Ξεκινάει η υπηρεσία ChatSecure… + Ενεργοποιημένο και ξεκλείδωτο + μήνυμα αντιγράφεται στο πρόχειρο + Προσοχή - - - + Σφάλμα: + Κωδικός σφάλματος %1$d + Δεν είναι δυνατή η σύνδεση στην υπηρεσία %1$s. Δοκιμάστε ξανά αργότερα."\n"(Λεπτομέρειες: %2$s) Δεν έγινε προσθήκη της λίστας. - Δεν έγινε αποκλεισμός της επαφής. - Δεν έγινε κατάργηση αποκλεισμού της επαφής. - Επιλέξτε πρώτα μια επαφή. - \"Χωρίς σύνδεση!\"\n - Σφάλμα υπηρεσίας! - Δεν έγινε φόρτωση της λίστας επαφών. - Δεν είναι δυνατή η σύνδεση με τον διακομιστή. Ελέγξτε τη σύνδεσή σας. - - - - - + Ο χρήστης %1$s υπάρχει ήδη στη λίστα επαφών σας. + Έγινε αποκλεισμός της επαφής \"%1$s\". Περιμένετε μέχρι να φορτώσει η λίστα επαφών σας. - Προέκυψε σφάλμα δικτύου. - + Απαιτείται WiFi για αυτήν την συνεδρία Ο διακομιστής δεν υποστηρίζει αυτήν τη δυνατότητα. - Ο κωδικός πρόσβασης που εισάγατε δεν είναι έγκυρος. - Παρουσιάστηκε σφάλμα στον διακομιστή. - Ο διακομιστής δεν υποστηρίζει αυτήν τη δυνατότητα. - Ο διακομιστής δεν είναι διαθέσιμος αυτήν τη στιγμή. - Η περίοδος σύνδεσης με τον διακομιστή έληξε. - Ο διακομιστής δεν υποστηρίζει την τρέχουσα έκδοση. - Η ουρά μηνυμάτων είναι πλήρης. - Ο διακομιστής δεν υποστηρίζει την προώθηση προς τον τομέα διαδικτύου. - Το όνομα χρήστη που εισάγατε δεν αναγνωρίζεται. - Λυπούμαστε, αλλά έχετε αποκλειστεί από τον χρήστη. - Η περίοδος σύνδεσης έληξε, συνδεθείτε ξανά. - συνδεθήκατε από άλλο πρόγραμμα-πελάτη. έχετε ήδη συνδεθεί από άλλο πρόγραμμα-πελάτη. - Λυπούμαστε, δεν είναι δυνατή η ανάγνωση του αριθμού τηλεφώνου από την κάρτα SIM. Επικοινωνήστε με την εταιρεία κινητής τηλεφωνίας για βοήθεια. - Δεν είστε συνδεδεμένοι αυτήν τη στιγμή. - - - + To ChatSecure αντιμετώπισε σφάλμα κατά την επαλήθευση του ονόματος ή του κωδικού σας - παρακαλούμε ελέγξτε τα ξανά και δοκιμάστε πάλι. + Τo ChatSecure αντιμετώπισε ένα σφάλμα κατά την παραγωγή του ζεύγους κλειδιών. + To ChatSecure αντιμετώπισε ένα σφάλμα κατά την σύνδεση - ξαναελέγξτε την σύνδεση σας στο δίκτυο και δοκιμάστε ξανά. + To ChatSecure αντιμετώπισε ένα σφάλμα κατά την σύνδεση - ξαναελέγξτε την σύνδεση σας στο δίκτυο και δοκιμάστε ξανά. + Το ChatSecure έχασε μια σύνδεση στο δίκτυο + Το ChatSecure προσπαθεί να ξαναεγκαθιδρύσει την σύνδεση + Δεν έχετε εισάγει ένα μέρος του @ hostname.com για το αναγνωριστικό του λογαριασμού σας. Προσπαθήστε ξανά! + Το όνομα του διακομιστή σας δεν έχει .com, .net ή παρόμοιο παράρτημα. Προσπαθήστε ξανά! + Εισάγετε τον κωδικό: + Εφόσον χρησιμοποιείτε το Tor πρέπει να εισάγετε στις Προηγμένες ρυθμίσεις λογαριασμού το hostname στο πεδίο XMPP \"Server πρός σύνδεση.\" + ΠΡΟΣΟΧΗ: Η υπηρεσία αυτή χρησιμοποιεί ένα πιστοποιητικό με ασθενή κρυπτογράφηση. Παρακαλείσθε να ενημερώσετε τον διαχειριστή για την αναβάθμιση. + Μήπως το πιστοποιητικό που παρέχεται να μην ταιριάζει στο καρφιτσωμένο Πιστοποιητικό: + Αδυναμία δημιουργίας η σύνδεσης σε ομάδα συζήτησης + Δυστηχώς δεν μπορεί να διαμοιραστεί αυτός ο τύπος αρχείου + Πρέπει να ενεργοποιήσετε την κρυπτογραφία για να διαμοιράσετε αρχεία + Ενεργοποιήστε κρυπτογραφίας συζήτησης για διαμοιρασμό αρχείων + Μη υποστηριζόμενα εισερχόμενα δεδομένα, δεν μπορούν να μοιραστούν! + Το ChatSecure ανίχνευσε ένα αίτημα αφαίρεσης του απο τις εκτελούμενες εφαρμογές. Το ChatSecure πιθανότατα θα σταματήσει να λειτουργεί. Παρακαλούμε χρησιμοποιήστε την επιλογή \"Απεγγραφή απο όλα\" απο την οθόνη των λογαριασμών αντ\'αυτού. + Δεν υπάρχει διαθέσιμο για αυτήν τη μορφή αρχείου πρόγραμμα προβολής + Παρακαλούμε ξεκινήσετε μια ασφαλή συνομιλία πριν την σάρωση κωδικών + OTR κλειδοθήκη δεν εισαχθεί; Παρακαλώ ελέγξτε το αρχείο αν υπάρχει στην κατάλληλη μορφή και θέση + Παρακαλούμε αντιγράψτε το αρχείο «otr_keystore.ofcaes» από την επιφάνεια εργασίας εργαλείων KeySync στο ριζικό κατάλογο της συσκευής αποθήκευσης + Δεν ήταν δυνατή η αποστολή αυτού του μηνύματος. + Το μήνυμα θα σταλεί ξανά όταν επανασυνδεθείτε + Ο χρήστης %1$s βρίσκεται εκτός σύνδεσης. Τα μηνύματα που στέλνετε θα παραδοθούν όταν ο χρήστης %1$s συνδεθεί στο διαδίκτυο. + Η επαφή %1$s δεν υπάρχει στη λίστα επαφών σας. + Οι κωδικοί πρόσβασης δεν ταιριάζουν + Προτεραιότητα πρέπει να είναι ένας αριθμός στο εύρος [0 .. 127] + Ο αριθμός θύρας πρέπει να είναι ένας αριθμός + Σφάλμα μεταφοράς + Δεν είναι δυνατή η ανάγνωση του αρχείου για την αποθήκευση + Σημαντικο Σφάλμα: Αδυναμία ξεκλείδωματος ή φόρτωσης τηςβάση δεδομένων εφαρμογής. Εγκαταστήστε ξανά την εφαρμογή. + Καμία εφαρμογή δεν εχει εγκατασταθεί που μπορεί να χειριστεί το σύνδεσμο! + Ρυθμίσεις λογαριασμού + + Ομάδες + ανοιχτές συζητήσεις () + Έξοδος + Πανικός + Φίλοι + Επαφές + Έγκριση Πιστοποιητικού Server; + Αποτύπωμα + φόρτωση… + + Σχετικά με το ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Λίστα επαφών + Ρυθμίσεις + Λίστα λογαριασμών + Αποσύνδεση + Λίστα επαφών + + Jabber (XMPP) + Τοπική Περιοχή (Bonjour/ZeroConf) + Λογαριασμός Google + dukgo.com + + + Συνδεδεμένος + Απασχολημένος/η + Μακριά από τον υπολογιστή + Αδρανής + Εκτός σύνδεσης + Να εμφανίζομαι εκτός σύνδεσης + Είμαι χαρούμενος Είμαι λυπημένος @@ -376,7 +515,7 @@ Γελάω Είμαι μπερδεμένος - + Είμαι χαρούμενος Είμαι λυπημένος @@ -396,21 +535,52 @@ Γελάω Είμαι μπερδεμένος - - - - - - - - - - - - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Υπάρχον λογαριασμός + Σύνδεση με τον υπάρχοντα λογαριασμό μου σε έναν συγκεκριμένο διακομιστή Jabber / XMPP. + Google λογαριασμός + Συνομιλία με άλλους χρήστες του Google χρησιμοποιούν το υπάρχον λογαριασμό σας στο Google. + WiFi Mesh Chat + Συνομιλία με άλλους στο ίδιο τοπικό δίκτυο WiFi ή πλέγμα - όχι Διαδίκτυο ή διακομιστή αν απαιτούνται! + Ενεργοποίηση του WiFi Chat + Νέος λογαριασμός + Καταχωρήστε ένα νέο, δωρεάν λογαριασμό σε μια υπηρεσία από τις δομημένες στη λίστα μας, ή οποιοδήποτε που θα επιλέξετε. + Δημιουργία νέο λογαριασμού + Μυστική ταυτότητα! + Δημιουργία ενος ανώνυμου, μιας χρήσεως \"καυστήρα\" συνομιλίας λογαριασμού με ενα απλο πατημα (απαιτείται Orbot: Tor για Android) + Δημιουργία ταυτότητας + Πρόκειται για μια ομάδα συνομιλίας + [δυσφοροια] + [δυσφοροια] + Αδυναμία κοινής χρήσης με ασφάλεια αυτού του αρχείου + Εγκατάσταση του Orbot; + Πρέπει να έχετε το Orbot εγκατεστημένο και ενεργο στην κυκλοφορία μεσολάβησης μέσα από αυτό. Θέλετε να το εγκαταστήσετε; + Πάντα + Έναρξη του Orbot? + Το Orbot δεν φαίνεται να είναι σε λειτουργία. Θέλετε να ξεκινήσει και να συνδεθείτε στο Tor; + ψευδώνυμο για να χρησιμοποιήσετε στο δωμάτιο + το όνομα του δωματίου για να δημιουργήσετε ή να συμμετάσχετε \" + διακομιστής ομαδικής συζήτησης (conference.foo.com) + Η αποθήκευση κλειδιών είναι κατεστραμμένη. Εγκαταστήστε ξανά το ChatSecure + Λάβατε ένα δυσανάγνωστο κρυπτογραφημένο μήνυμα + Εγώ δεν μπορεσα να αποκρυπτογραφήσω το μήνυμα που αποστέλλεται + < περάστε αριστερά και δεξιά για περισσότερες επιλογές > diff --git a/res/values-en-rGB/arrays.xml b/res/values-en-rGB/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-en-rGB/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml new file mode 100644 index 000000000..22ef85d34 --- /dev/null +++ b/res/values-en-rGB/strings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-es-rCO/arrays.xml b/res/values-es-rCO/arrays.xml new file mode 100644 index 000000000..d51e0439f --- /dev/null +++ b/res/values-es-rCO/arrays.xml @@ -0,0 +1,9 @@ + + + + Forzar / Exigir + Intentar automáticamente + Si se exige + Deshabilitado / Nunca + + diff --git a/res/values-es-rCO/strings.xml b/res/values-es-rCO/strings.xml new file mode 100644 index 000000000..039b3ccf7 --- /dev/null +++ b/res/values-es-rCO/strings.xml @@ -0,0 +1,462 @@ + + + + ChatSecure + ChatSecure + + leer mensajes instantáneos + +Permite a las aplicaciones leer datos desde el proveedor de contenidos de mensajería instantánea. + + + escribir mensajes instantáneos + +Permite a las aplicaciones escribir datos en el proveedor de contenidos de mensajería instantánea. + + Iniciar servicio de Mensajería Instantánea + Permite a las aplicaciones iniciar servicio de MI por medio de un intent. + + Confirmar + OK + Cancelar + OK + Cancelar + Siguiente + Atrás + Conectar + + Abrir + ChatSecure bloqueado + Establecer contraseña + Confirmar contraseña + Contraseña + Nueva contraseña + Confirmar nueva contraseña + La contraseña no coincide, por favor intente de nuevo + Tengo pereza (¡Sin contraseña!) + + Seleccionar una cuenta + Seleccionar una cuenta + (%1$d) + Agregar cuenta de %1$s + Acerca de + Cerrar todas las sesiones + ¿Quiere cerrar sesión en todos los servicios? + Se ha cerrado su sesión de %1$s. + Se ha cerrado la sesión de %1$s porque %2$s + ¿Primera vez que usa ChatSecure? + ¿No puede esperar para empezar? + Empezar + Configuración de la cuenta + Configuración de contraseña + Antes de empezar por favor escoja una contraseña segura para proteger sus datos de ChatSecure de accesos no autorizados. + Contraseña: + Contraseña (de nuevo): + Ingrese una *nueva* contraseña. Esta debe tener al menos una letra mayúscula, una minúscula y un número, y tener más de seis caracteres. + Cifrando sus notas con la nueva contraseña (paciencia…) + Ingrese su contraseña: + ¡Bienvenido! Ingrese una contraseña fuerte para asegurar sus notas. Esta debe contener al menos una letra mayúscula, una minúscula y un número, y tener más de seis caracteres de longitud. + Su contraseña no es lo suficientemente larga + Su contraseña no contiene ninguna letra mayúscula + Su contraseña no contiene ninguna letra minúscula + Su contraseña no contiene ningún número. + + Acerca de ChatSecure + ChatSecure es una aplicación móvil de mensajería instantánea que provee funcionalidades de seguridad extra para evitar que otros chismoseen sus conversaciones y comunicaciones. + +La aplicación soporta cualquier servicio de chat que utilice los protocolos Jabber o XMPP, como Google GTalk o Jabber.org. + + + ¿Son seguras mis conversaciones? + La función de cifrado de ChatSecure solo funciona cuando se intercambian mensajes con otros que utilicen una aplicación o programa compatible, así que se debe asegurar de que sus contactos estén usando ChatSecure para móviles y Adium o Pidgin para computadores. Usted puede afinar exactamente como y cuando ChatSecure intenta cifrar sus chats en Configuraciones de cuenta. + +¡Empiece! + + Nueva cuenta + Agregar cuenta + Editar cuenta + Quitar cuenta + + Usuario + Contraseña: + Recordar mi contraseña. + Iniciar sesión automáticamente. + ¿No tiene una cuenta? + Iniciar sesión + Por su seguridad, si su teléfono se pierde o es robado, vaya al sitio web desde su computador y cambie su contraseña. + Esta opción automáticamente inicia sesión cada vez que abra esta aplicación. Para deshabilitarla, cierre sesión, luego desmarque la casilla \"Iniciar sesión automáticamente\". + Conectar usando Tor (Requiere la aplicación Orbot) + usuario@dominio.com + nuevo nombre de usuario + proveedor del servicios (dukgo.com, jabber.ccc.de) + contraseña + confirmar contraseña + Configuración avanzada de la cuenta + Tipo de cuenta + Registrar cuenta + Persistencia + Recordar contraseña + Contraseña guardad en caché + Contraseña no guardad en caché + Inicio de sesión automático + Conectar cuando inicia ChatSecure + No conectar cuando inicia ChatSecure + Iniciar sesión ahora + Iniciar sesión en la siguiente cuenta + No iniciar sesión en la siguiente cuenta + Personal (opcional) + Alias de la cuenta (su nombre) + Cómo aparece su cuenta en línea + Perfil + Unos breves palabras sobre usted + Forzar cifrado / rechazar texto claro + Cuando sea posible, cifrar los chats automáticamente + Cifrar los chats que se soliciten + Deshabilitar cifrado de chat + Validando credenciales + Generando par de claves + Iniciando sesión… + Asistente de cuenta + Su ID de cuenta + Configurar servidor + ¿Está listo? + Ingrese su ID de cuenta para configurar ChatSecure con su servicio de chat XMPP. Es similar a una dirección de correo electrónico: + Por favor ingrese su ID de cuenta (user@hostname): + Por favor ingrese o edite el nombre del servidor de chat jabber/xmpp y el número de puerto (5222 es el valor por defecto). + ChatSecure ha sido configurado, y ahora es tiempo de conectarse a su servicio, y empezar a chatear de forma segura, ¡Segura y privada! + Orbot (Tor) + Elija un dominio + poniendo en marcha ChatSecure… + Cancelar inicio de sesión + Iniciando sesión\u2026 + + Hacer búsqueda SRV + Realizar una búsqueda SRV para encontrar el servidor XMPP a partir del nombre de dominio + Permitir que se envíe el nombre de usuario y la contraseña en texto plano cuando se use un transporte no cifrado + Permitir autenticación en texto plano + Verificar que el certificado es de confianza + Verificación TLS + Requiere conexión TLS + Cómo se inician los chats cifrados + El servidor al que se conecta, si es necesario + Conectar al servidor + Puerto TCP para el servidor XMPP + Puerto del servidor + Recurso XMPP + para diferenciar esta conexión de otros clientes que también han iniciado sesión + Los mensajes para clientes con múltiples recursos activos serán enviados al recurso con la prioridad más alta + + Conversaciones + No hay conversaciones. + +¡Toque aquí para iniciar una! + Usted no tiene +cuentas configuradas. + +¡Toque acá para agregar una! + Lista de contactos - %1$s + Agregar contacto + Borrar contacto + Bloquear contacto + Bloqueados + El contacto "%1$s" será borrado. + El contacto "%1$s" será bloqueado. + El contacto "%1$s" será desbloqueado. + Contacto "%1$s" agregado. + Contacto "%1$s" borrado. + Contacto "%1$s" bloqueado. + Contacto "%1$s" desbloqueado. + Nuevo chat + Buscar contactos + Iniciar chat + Chats activos (%1$d) + %1$d conectados + Invitaciones de amigos + (Desconocido) + Vacío + No hay conversaciones + Contacto + escriba el nombre de un contacto para chatear + Ir + No hay chats activos. + Agregar contacto + Dirección de correo de la persona que quiere invitar: + Escoja una lista: + Escriba un nombre para agregar desde contactos. + Enviar invitación + Perfil del contacto + Estado: + Tipo de cliente: + Computador + Móvil + Contactos bloqueados - %1$s + No hay contactos bloqueados. + + Chat con %1$s + Yo + %1$s está conectado + %1$s está ausente + %1$s está ocupado + %1$s está desconectado + %1$s se ha unido + %1$s se ha ido + Enviar archivo + Transferencia de archivos + Transferencia completa + Transferencia en progreso + ¿Aceptar transferencia? + quiere enviarle un archivo + Enviar + Enviar un mensaje + Enviar mensaje seguro + Reenviar + Finalizar chat + Borrar chat + Insertar emoticono + Ir a otro chat + Menú+ + Seleccionar enlace + + %1$s lo ha invitado a unirse a un chat de grupo. + Se han enviado invitaciones a %1$s. + Aceptar + Rechazar + Chat en grupo + Crear o unirse a un chat en grupo + Invitar\u2026 + Seleccionar contacto(s) para invitar + Escriba para encontrar contacto + No se encontraron contactos. + +Toque para invitar. + No se pudo aprobar la suscripción de %1$s. Por favor intente más tarde. + No se pudo rechazar la suscripción de %1$s. Por favor intente más tarde. + + Iniciar cifrado + Detener cifrado + Iniciando sesión de chat cifrado… + Finalizando la sesión de chat cifrado… + Huella de seguridad + Huella de seguridad + Iniciar sesión + Regenerar clave + Cifrado está apagado + Cifrado está encendido (Tocar para verificar) + Su contacto ha detenido el chat cifrado. + ¡Cifrado y verificado! + Generando nuevo par de claves OTR… + + Activar + Llavero OTR importado satisfactoriamente + + Escanear QR + Su huella digital + Manual + Pregunta + Huella de seguridad (Verificada) + ¿Está seguro que quiere confirmar esta huella digital? + ¿Verificar huella digital? + ¡La huella digital remota ha sido verificada! + Huella digital para usted + Huella digital para + + Autenticación + Ingrese una pregunta para enviar a su contacto, y la respuesta que este debería dar, con el fin de verificar que es quien dice ser. + la pregunta + la respuesta esperada + Su contacto lo ha autenticado satisfactoriamente. Ahora autentíquelo haciéndole su propia pregunta. + Enviar + Cancelar + + Llamada segura + Voz segura + Ingrese aquí su OStel.co u otra cuenta de servicios de SIP seguro para integración de llamadas + + Ajustes de cuenta + + Seguridad y privacidad + Cifrado y Anonimato + Cifrar On/Off + Tiempo límite de contraseña + + Interfaz de usuario + Idioma + Idiomas + Usar tema oscuro + Cambiar el tema de la aplicación a oscuro + Almacenamiento de mensajes solo en-memoria + Solo almacenar mensajes en memoria, no en almacenamiento flash, para evitar que los mensajes sean extraídos. (Puede causar pérdida de mensajes) + Imagen de fondo + Ingresar ruta (\"/sdcard/foo.jpg\") a una imagen de fondo para la aplicación + + Otros ajustes + Iniciar ChatSecure automáticamente + Ocultar contactos desconectados + Reduce la posibilidad de que Android reinicie nuestro servicio de conexión. Esto cargará una notificación en el área de notificaciones. + Use un valor más alto (en minutos) para ahorrar batería. Un valor alto puede causar que el proveedor cierre su conexión por inactividad. + + Ajustes de notificaciones + Notificaciones MI + Notificar en la barra de estado cuando llega un mensaje instantáneo + Vibrar + También vibrar cuando llega un mensaje instantáneo + Sonido + También reproducir tono de timbre cuando llega un mensaje instantáneo + Utilice el tono de llamada personalizado de ChatSecure + + + Datos de red deshabilitados + +Es necesario tener conexión a la red de datos (incluyendo datos en segundo plano) para que la aplicación inicie sesión.⏎ + Habilitar + Salir + ¿Quiere cerrar sesión en todos los servicios Y detener todos los procesos (salida forzada)? + + Nuevos mensajes de %1$s + Invitación a chat de grupo + Nuevo mensaje(s) de + Arrancando el servicio ChatSecure… + + Atención + Código de error %1$d + La lista no fue agregada. + El contacto no fue bloqueado. + El contacto no fue desbloqueado. + Por favor seleccione un contacto primero. + ¡Desconectado! + + ¡Error del servicio! + La lista de contactos no cargó. + No se puede conectar al servidor. Por favor revise su conexión. + "%1$s" ya está en su lista de contactos. + El contacto "%1$s" ha sido bloqueado. + Por favor espere mientras carga su lista de contactos. + Ha ocurrido un error de red. + Se requiere WiFi para esta conexión. + El servidor no soporta esta funcionalidad. + La contraseña que ingresó no es válida. + El servidor encontró un error. + El servidor no soporta esta funcionalidad. + El servidor no está disponible en este momento. + El servidor ha excedido el tiempo de respuesta. + El servidor no soporta la versión actual. + La cola de mensajes está llena. + El servidor no soporta reenvío al dominio. + El nombre de usuario que ingresó no es reconocido. + Lo sentimos, usted ha sido bloqueado por el usuario. + La sesión ha expirado, por favor inicie sesión de nuevo. + usted ha iniciado sesión desde otro cliente. + usted ha iniciado sesión desde otro cliente. + Lo sentimos, el número telefónico no puede ser leído desde su tarjeta SIM. Por favor contacte a su operador para que le ayude. + Usted no ha iniciado sesión. + ChatSecure encontró un error mientras validaba su nombre o contraseña - por favor revise los datos e intente de nuevo. + ChatSecure encontró un error mientras generaba un par de claves. + ChatSecure encontró un error mientras se conectaba al servidor de chat - por favor revise su configuración e intente de nuevo. + ChatSecure encontró un error mientras se conectaba - por favor compruebe su conexión de red e intente de nuevo. + ChatSecure ha perdido la conexión a la red + ChatSecure está tratando de restablecer la conexión + No ingresó una parte con @hostname.com para el ID de su cuenta. ¡Intente de nuevo! + El nombre de su servidor no tenía un .com, .net o terminación similar. ¡Intente de nuevo! + Ingrese su contraseña: + No es posible crear o unirse a un chat en grupo + Lo sentimos, no podemos compartir ese tipo de archivo + Debe habilitar el cifrado para compartir archivos + Por favor habilite el cifrado de chat para compartir archivos + Llavero OTR no importado; Por favor revise que el archivo exista en la ubicación y el formato apropiado. + Por favor copie el archivo \'otr_keystore.ofcaes\' desde la herramienta de escritorio KeySync al directorio raíz del almacenamiento de su dispositivo + Este mensaje no pudo ser enviado. + Los mensajes serán enviados al recuperar la conexión + %1$s está desconectado. Los mensajes que envíe serán entregados cuando %1$s se conecte. + %1$s no está en su lista de contactos. + Configuración de cuenta + + ADVERTENCIA: Esta es una versión temprana de ChatSecure que todavía podría tener fallos de seguridad o bugs. + + Grupos + conversación(es) abierta(s) + Salir + Pánico + Amigos + Contactos + ¿Aceptar certificado del servidor? + Huella digital + + + Lista de contactos + Ajustes + Cuentas + Cerrar sesión + Lista de contactos + + Jabber (XMPP) + Cuenta de Google + dukgo.com + + + Conectado + Ocupado + Ausente + Inactivo + Desconectado + Aparecer desconectado + + + Feliz + Triste + Picando un ojo + Lengua afuera + Sorprendido + Besando + Gritando + Bacano + Plata en la boca + Pié en la boca + Avergonzado + Ángel + Indeciso + Llorando + Labios sellados + Riendo + Confundido + + + + Feliz + Triste + Picando un ojo + Lengua afuera + Sorprendido + Besando + Gritando + Bacano + Plata en la boca + Pié en la boca + Avergonzado + Ángel + Indeciso + Llorando + Labios sellados + Riendo + Confundido + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Nueva cuenta + diff --git a/res/values-es-rES/arrays.xml b/res/values-es-rES/arrays.xml index 363fcf5f9..78e5c96c8 100644 --- a/res/values-es-rES/arrays.xml +++ b/res/values-es-rES/arrays.xml @@ -6,64 +6,4 @@ Solo si se solicita Nunca - - forzar - automático - solicitado - inhabilitado - - - Por defecto - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-es-rES/strings.xml b/res/values-es-rES/strings.xml index 71db4dad9..ef82caa3d 100644 --- a/res/values-es-rES/strings.xml +++ b/res/values-es-rES/strings.xml @@ -1,399 +1,491 @@ - + + + ChatSecure + ChatSecure + leer mensajes instantáneos - \nPermitir a las aplicaciones leer del proveedor de contenido de mensajería instantánea.\n + Permite a las aplicaciones leer datos desde el proveedor de contenido de mensajería instantánea. escribir mensajes - \nPermitir a las aplicaciones escribir en el proveedor de contenido de mensajería instantánea. - - ChatSecure - - - Seleccionar una cuenta - - Acerca de - - Añadir cuenta - - Editar cuenta - - Eliminar cuenta - - Cerrar sesión - - - Seleccionar una cuenta - - - - Cancelar inicio de sesión - - - Añadir contacto - - Eliminar contacto - - Bloquear contacto - - Bloqueados - - Lista de cuentas - - Nueva conversación - - Nueva cuenta - - Ajustes - - Buscar Contactos - - Iniciar conversación - - Cerrar sesión - - Verficar - - - Iniciar cifrado - Finalizar cifrado - Borrar Conversación - Finalizar conversación - Llamada segura - - - Lista de contactos - - Invitar… - - Cambiar de conversación - - Insertar emoticono - Reenviar - - Examinar huellas digitales - Su huella digital - Verificar huella digital - Verificar secreto - - MENU+ - - - + Permite a las aplicaciones escribir datos en el proveedor de contenido de mensajería instantánea. + iniciar servicio de MI + Permitir que las aplicaciones inicien el servicio de MI mediante la ejecución de un intento. + Confirmar - - ¿Quiere cerrar sesión en todos los servicios? - - - - - - - Aceptar - Cancelar - Aceptar - Cancelar - - - - - - - - - + Siguiente + Atrás + Conectarse + Reproducir + Configuración + + Abierto + ChatSecure bloqueado + Establecer contraseña + Confirmar contraseña + Contraseña + Opcionalmente puede establecer una contraseña maestra para ChatSecure, para evitar el acceso a sus contactos y mensajes sin una contraseña. + Confirmar nueva contraseña + La contraseña no coincide, por favor inténtelo de nuevo + Omitir >> + Solicitud de información + Por favor introduzca la contraseña + Contraseña por favor… + Frase-contraseña + Frase-contraseña (de nuevo) + + Seleccionar una cuenta + Seleccionar una cuenta + (%1$d) + Añadir cuenta %1$s + Acerca de + Cerrar sesión + ¿Quiere cerrar sesión en todos los servicios? + Se ha cerrado su sesión de %1$s. + Se ha cerrado su sesión de %1$s a causa de %2$s + ¿Es la primera vez utilizando ChatSecure? + Comenzar directamente + Comenzar + Configuración de cuenta + Configuración de contraseña + Antes de comenzar asegúrese de elegir una contraseña segura para proteger sus datos de ChatSecure. + Contraseña : + Contraseña (otra vez) : + Introduzca una *nueva* contraseña. Debe está formada al menos por una mayúscula, una minúscula y un número. Además debe tener más de 6 caracteres. + Cifrando sus notas con la nueva contraseña (tenga paciencia…) + Introduzca su contraseña: + ¡Bienvenido! Introduzca una contraseña fuerte para asegurar sus notas. Debe contener al menos una mayúscula, una minúscula y un número además de tener más de seis caracteres. + Su contraseña no es lo suficientemente larga + Su contraseña no contiene mayúsculas + Su contraseña no contiene minúsculas + Su contraseña no contiene números + + Acerca de ChatSecure + ChatSecure es una aplicación de mensajería instantánea móvil que proporciona características extra de seguridad que evitan que otros fisguen en sus conversaciones y comunicaciones.\n\nLa aplicación soporta cualquier servicio de chat que use el protocolo Jabber o el XMPP, como Google GTalk or Jabber.org. + + ¿Es seguro? + \'Off-the-Record Messaging\' es un sistema de seguridad diseñado para generar privacidad imitando las características de las conversaciones privadas en el mundo real, incluyendo cifrado, autentificación, cifrado negable, y confidencialidad hacia delante (forward secrecy).\n\nEl protocolo OTR es compatible con clientes de chat de escritorio como Adium o Pidgin. + + ¿Es seguro? + La característica de cifrado del chat de ChatSecure sólo funciona cuando se mensajea con otros que usan una aplicación o programa compatible, así que debe asegurarse de que sus contactos desde dispositivos móviles estén usando ChatSecure, y desde escritorio estén usando Adium o Pidgin. Puede ajustar con precisión en la Configuración de Cuenta cómo y cuándo intenta ChatSecure cifrar sus conversaciones .\n\n¡Comencemos! + + Nueva cuenta + Añadir cuenta + Editar cuenta + Eliminar cuenta + Cuenta existente + Nombre de usuario: - Contraseña: - Recordar - Iniciar sesión automáticamente. - ¿Aún no tiene una cuenta? - + Iniciar sesión Por su seguridad, si pierde el teléfono o se lo roban, visite el sitio Web en su equipo y cambie su contraseña. Esta opción le permite iniciar sesión automáticamente cada vez que abra la aplicación. Para desactivarla, cierre sesión y desactive la casilla de verificación \\"Acceder automáticamente\\". - - Iniciar sesión - - - iniciando ChatSecure... - + Conectar a través de Tor (Requiere la aplicación Orbot) + usuario@dominio.com + nuevo usuario + proveedor de servicios (dukgo.com, jabber.ccc.de) + contraseña + confirmar contraseña + Configuración avanzada de la cuenta + Configuración de cuenta + Registrar cuenta + Persistencia + Recordar contraseña + Almacenar contraseña en caché + No almacenar contraseña en caché + Iniciar sesión automáticamente + Conectar ChatSecure durante el inicio + No conectar ChatSecure durante el inicio + Iniciar sesión ahora + Iniciar sesión en la cuenta siguiente + No iniciar sesión en la cuenta siguiente + Personal (opcional) + Cuenta Alias (su nombre) + Cómo aparece su cuenta en línea + Perfil + Breve descripción del usuario + Forzar cifrado / rechazar texto plano + Cifrar conversaciones automáticamente cuando sea posible + Cifrar conversaciones si se solicita + Inhabilitar el cifrado en las conversaciones + Validando credenciales … + Generando un par de claves … + Iniciando sesión… + Asistente para cuenta + Su ID de la cuenta + Configurar el servidor + ¿Está listo? + Introduzca su ID de cuenta para configurar ChatSecure a partir de su servicio de chat XMPP. Tiene la misma estructura que una dirección de correo electrónico: + Por favor, introduzca su ID de cuenta (usuario @ nombre de host) : + Por favor, introduzca o edite el nombre del servidor y número de puerto (5222 por defecto). + ChatSecure se ha configurado y ahora puede conectarse a su servicio de chat ¡y hablar de forma privada y segura! + Orbot (Tor) + Dominio del servicio de chat + Registrando nueva cuenta… + iniciando ChatSecure… + Cancelar inicio de sesión Iniciando sesión - - - Datos de red inhabilitados - - - - - - Habilitar - - Salir - - - - - + Cerrar sesión\u2026 + + Hacer búsqueda SRV + Realizar una búsqueda SRV para encontrar el servidor XMPP a partir del nombre de dominio + Permitir que el usuario y la contraseña sean enviados en texto plano cuando no se utiliza cifrado + Texto plano + Verificar que el certificado es confiable + Verificación TLS + Requiere conexión TLS + Capa de transporte + De qué manera comienzan las conversaciones cifradas + El servidor al que conectarse, si es necesario + Servidor + Puerto TCP del servidor XMPP + Puerto de servidor + Recurso XMPP + distinguir esta conexión de otros clientes que también han iniciado sesión + Prioridad del recurso + Los mensajes a clientes con recursos múltiples activos serán enviados al recurso con la prioridad más alta + + Conversaciones + No hay conversaciones activas.\n\n¡Pulse aquí para comenzar una! + No tiene +cuentas configuradas.\n\n¡Pulse aquí para añadir una! + Lista de contactos - %1$s + Añadir contacto + Borrar contacto + Bloquear contacto + Alias (nickname) de contacto + Bloqueados + Apodo + El contacto \"%1$s\" se eliminará. + El contacto \"%1$s\" se bloqueará. + El contacto \"%1$s\" se desbloqueará. + Contacto \"%1$s\" añadido. + Contacto \"%1$s\" eliminado. + Contacto \"%1$s\" bloqueado. + Contacto \"%1$s\" desbloqueado + Nueva conversación + Buscar Contactos + Mostrar malla + Iniciar conversación + Ver perfil + Verificar contacto + Conversaciones activas (%1$d) + %1$d conectados Invitaciones de amigos - (\"\"Desconocido\"\") - Vacío - - Ninguna conversación - - Seleccionar contactos a los que se dirige la invitación - Escriba el nombre del contacto que quiera localizar - No se ha encontrado ningún contacto. - - - - No hay ningún contacto bloqueado. - - + No hay conversaciones abiertas\n\n¡Pulse para iniciar conversación! + Contacto + escriba el nombre del contacto para comenzar una conversación + Ir + No hay ninguna conversación activa. + Añadir contacto + Nombre de usuario o identificación de Jabber de la persona a añadir: + Cuenta a añadir a (lista): + Escriba el nombre del contacto que quiera añadir. + Enviar invitación Perfil del contacto - Estado: - Tipo de cliente: - Equipo - Móvil - - - Conectado - - Ocupado - - Ausente - - Inactivo - - Desconectado - - Aparecer desconectado - - - - - + Contactos bloqueados - %1$s + No hay ningún contacto bloqueado. + + Conversar con %1$s Yo - - Escriba el texto aquí - - - - - - - - - - - - - - - - + %1$s está conectado + %1$s está ausente + %1$s está ocupado + %1$s está desconectado + %1$s se ha unido a la conversación + %1$s se ha desconectado + Enviar foto + Enviar archivo + Enviar audio + Tomar fotografía + Transferencia de fichero + Transferencia completa + Transferencia en marcha + ¿Aceptar transferencia? + quiere enviarle el fichero + ¡No hay conexiones disponibles para enviar su compartido! Enviar - - No se ha podido enviar el mensaje. - - Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando se restablezca la conexión. - - - - - + Enviar un mensaje + Enviar mensaje seguro + Reenviar + Finalizar conversación + Borrar conversación + Insertar emoticono + Cambiar de conversación + MENU+ Seleccionar enlace - - No hay ninguna conversación activa. - - Comenzando conversación cifrada... - Deteniendo conversación cifrada... - - - Añadir contacto - - Dirección de correo electrónico de la persona a la que desea invitar: - - Seleccionar una lista: - - Escriba el nombre del contacto que quiera añadir. - - Enviar invitación - - Jabber (XMPP) - Sesión de chat en red local (Bonjour/ZeroConf) - + Borrar + Mantener ficheros + ¿Borrar el almacenamiento asegurado de la sesión de chat? + Todos los ficheros de subida y bajada de la sesión se borrarán permanentemente. Advertencia: ¡Esta operación no puede deshacerse! + ¿Borrar el original? + Este fichero se copiará en el almacenamiento asegurado antes de ser enviado. ¿Desea borrar el fichero original del almacenamiento no asegurado del dispositivo? + Mantener + Exportar + ¿Exportar fichero de audiovisual? + Este fichero de audiovisual se exportará a %1$s + ¿Finalizar conversación? + Todos los elementos audiovisuales asegurados de esta sesión se borrarán. Para exportar un elemento audiovisual haga clic de forma sostenida sobre el icono de miniatura. + Finalizar conversacion y borrar ficheros + + %1$s le ha invitado a unirse a un chat en grupo. + Se ha enviado invitación a %1$s. + Aceptar + Rechazar + Chat de Grupo + Crear o unirse a un chat de grupo + Conectando a grupo de chat… + Invitar… + Seleccionar contactos a los que se dirige la invitación + Escriba el nombre del contacto que quiera localizar + No se encontraron contactos.\n\nPulse para invitar. + ¿Añadir contacto? + + No + No se pudo aprobar la suscripción de %1$s. Inténtelo de nuevo más tarde. + No se pudo rechazar la suscripción de %1$s. Inténtelo de nuevo más tarde. + + Iniciar cifrado + Finalizar cifrado + Comenzando conversación cifrada… + Deteniendo conversación cifrada… + Huella digital de seguridad + Huella de validacion de seguridad + Iniciar sesión + Regenerar clave + La encriptación está desactivada. + La encriptación está activada (presioná para verificar) + Su contacto ha detenido el chat cifrado. + Encriptado y verificado! + Generando un nuevo par de claves OTR… + + Detectamos un almacén de claves OTR para importar. ¿Te gustaría escanear la contraseña QR ahora? + Activar KeySync + El anillo de claves de OTR se ha importado con éxito + + Escanear código QR + Su huella digital + Manual + Pregunta + Huella digital (verificada) + ¿Está seguro de querer confirmar esta huella digital? + ¿Verificar huella digital? + Se ha verificado la huella digital + Identificador algorítmico (\'fingerprint\') para usted + Identificador algorítmico (\'fingerprint\') para + ¿Instalar Barcode Scanner? + Esta aplicación requiere Barcode Scanner. ¿Desea instalarlo? + + Autenticación + Introduzca una pregunta para enviar a su contacto junto a la respuesta que espera recibir para verificar que se trata de la persona que dice ser. + la pregunta que formular + la respuesta que se espera + Su contacto le ha autenticado. Ahora valide a su contacto mediante una pregunta. + Preguntas y respuestas de verificación del cifrado OTR + Cifrado de chat + Enviar + Cancelar + + Llamada segura + Comunicación segura por voz + Introduzca su OStel.co u otro servicio SIP seguro para integrar el soporte de llamadas + Configuración de cuenta + + Seguridad y privacidad Cifrado y Anonimato + Cifrar Encendido/Apagado + Tiempo de expiración de la contraseña + Tiempo de expiración de la clave en caché + + URLs pulsables sobre Tor + Para cuentas que utilizan Tor, hace que las URLs en el texto de los chats sean enlaces pulsables (¡ADVERTENCIA, posible filtración de detalles privados!) + Interfaz de usuario + Idioma + Idiomas + Usar la configuración por defecto del sistema + Utilizar tema oscuro + Cambiar el tema de la aplicación a oscuro + Guardar conversaciones + No almacenar los mensajes en flash para evitar que puedan ser recuperados. (Advertencia: esto puede provocar la pérdida de mensajes) + Imagen de fondo + Establecer una ruta para la imagen de fondo de la aplicación + Mostrar malla de contacto + Muestra la lista de contactos como una malla de avatares + Sí, aceptar todo + Borrar audiovisuales no asegurados + Después de compartir una foto o fichero, borrarlo automáticamente del almacenamiento no asegurado original depués de importarlo + Almacenar ficheros de medios en almacenamiento externo + Los ficheros de medios (audiovisuales) de sesiones de chat se almacenan en un contenedor cifrado que puede guardarse en un almacenamiento interno o externo. + + Almacenamiento externo ausente + Sus registros (logs) de chat se almacenan en una tarjeta SD, pero no hay ninguna tarjeta SD presente. Por favor inserte la tarjeta SD correcta, o borre el log de chat existente y vuelva a iniciar ChatSecure. + El almacenamiento de ficheros de medios del chat está ausente + Sus registros (logs) se almacenan en la tarjeta SD, pero el fichero está ausente de la actual tarjeta SD. Por favor inserte la tarjeta SD correcta, o borre el log de chat existente y vuelva a iniciar ChatSecure. + Borrar log de chat + + Otros ajustes + Arrancar ChatSecure automáticamente + Iniciar sesión automáticamente Ocultar desconectados - - Configuración de notificaciones - - Notificaciones de MI - - Notificar cuando se recibe un mensaje instantáneo en la barra de estado - Prioridad primer plano Disminuye la probabilidad de que Android reinicie la conexión. Esto añadirá una entrada en el área de notificación Intervalo de latidos Utilizar un valor más alto (en minutos) para ahorrar batería. Esto puede provocar que su proveedor cierre la conexión por inactividad + + Configuración de notificaciones + Notificaciones de MI + Notificar cuando se recibe un mensaje instantáneo en la barra de estado Vibrar - Vibrar al recibir un mensaje - Sonido - Reproducir un tono al recibir mensaje instantáneo - - Seleccionar tono - - - - - - - Aceptar - - Rechazar - - - - Aceptar - - Rechazar - - - - - - - - - - - - + Seleccionar timbre de llamada personalizado + + Habilitar registros (\'logs\') de depuración + Datos de registro (\'log\') de salida de la aplicación a la salida estándar / logcat para depuración + + Datos de red inhabilitados + Para que la aplicación abra sesión, se necesita conectividad de datos de red (incluyendo datos en segundo plano). + Habilitar + Salir + ¿Quiere cerrar sesión y acabar con todos los procesos? + ¿Crear nueva cuenta? + ¿Crear una nueva cuenta de chat con el nombre de usuario \'%1$s\'? + Información del certificado + Certificado: + Publicado por: + Huella de validación SHA1: + Publicado: + Expira: + ¿Unirse a la sala de chat? + Una aplicación externa está intentando conectar a su sala de chat. ¿Permitir? + Elegir fondo + ¿Quiere seleccionar una imagen de fondo de la Galería? + Seleccionar imagen + + Mensajes nuevos de %1$s + %1$d conversaciones no leídas + Nueva invitación a incluir como amigo de %s + Nuevo fichero %1$s de %2$s Invitación a conversación en grupo - - - - - - - - - - - - iniciar servicio de MI - Permitir que las aplicaciones inicien el servicio de MI mediante la ejecución de un intento. - - + Nueva invitación para conversación en grupo de %s + Nuevo mensaje de: + Comenzando el servicio de ChatSecure… + Activado y desclausurado + mensaje copiado al portapapeles + Atención - - - + Error: + Código de error %1$d + No se pudo abrir sesión en el servicio %1$s. Inténtelo de nuevo más tarde.\n(Detalle: %2$s) La lista no se ha añadido. - No se ha podido bloquear el contacto. - No se ha podido desbloquear el contacto. - Seleccione primero un contacto. - - ¡Desconectado!\n\n - + ¡Desconectado!\n ¡Error del servicio! - No se ha podido cargar la lista de contactos. - No se puede establecer conexión con el servidor. Comprueba la conexión. - - - - - + %1$s ya figura en su lista de contactos. + El contacto \"%1$s\" ha sido bloqueado. Por favor, espera mientras se carga la lista de contactos. - Se ha producido un error de red. Se necesita una conexión WiFi. - El servidor no admite esta función. - La contraseña introducida no es válida. - Se ha producido un error en el servidor. - El servidor no admite esta función. - El servidor no está disponible en este momento. - Se ha agotado el tiempo de espera del servidor. - El servidor no admite la versión actual. - La cola de mensajes está llena. - El servidor no admite el reenvío al dominio. - El nombre de usuario introducido no se reconoce. - El usuario le ha bloqueado. - La sesión ha expirado. Inicie sesión de nuevo. - ha iniciado sesión desde otro cliente. ha iniciado sesión desde otro cliente. - No se puede leer el número de teléfono de la tarjeta SIM. Póngase en contacto con su operador para obtener ayuda. - No ha iniciado sesión. - - - + ChatSecure encontró un error mientras se validaba su nombre de usuario o su contraseña - por favor, revíselos y pruebe de nuevo. + ChatSecure encontró un error mientras generaba un par de claves. + ChatSecure encontró un error mientras se conectaba al servidor de chat - por favor revise su configuración y pruebe de nuevo. + ChatSecure ha encontrado un error mientras se conectaba - por favor revise su conectividad de red y vuelva a intentarlo. + ChatSecure ha perdido la conexión a la red + ChatSecure está intentando restablecer la conexión + No se ha introducido un nombre de dominio en su ID de cuenta, ¡vuélvalo a intentar! + Su nombre de host no contiene un .com, .net u otro valor, ¡vuélvalo a intentar! + Introduzca su contraseña: + Dado que está utilizando Tor, debe introducir el nombre de host del servidor XMPP en Configuración avanzada de la cuenta + ADVERTENCIA: Este servicio utiliza un certificado con criptografía DÉBIL. Por favor dígale al administrador que actualice. + El certificado proporcionado no coincide con el certificado FIJADO: + Imposible de crear o unirse al chat de grupo + Perdón, pero no podemos compartir ese tipo de archivo + Debe activar el cifrado para compartir archivos + Por favor habilite el cifrado del chat para compartir ficheros + ¡Datos entrantes no soportados, no se pudo compartir! + ChatSecure detectó que se hizo una petición para eliminar su tarea. ChatSecure probablemente fallará. Por favor use el elemento del menú Cerrar Todas las Sesiones de la pantalla de lista de cuentas en su lugar. + No hay ningún visor disponible para este formato de fichero + Por favor inicie una conversación segura antes de escanear códigos + El anillo de claves de OTR no se ha importado. Por favor, compruebe que el archivo existe y tiene el formato apropiado + Por favor copie el archivo \'otr_keystore.ofcaes\' desde el escritorio de la herramienta KeySync al directorio raíz de su dispositivo de almacenamiento + No se ha podido enviar el mensaje. + Está desconectado. Los mensajes se reenviarán al volver a conectarse. + %1$s está desconectado. Los mensajes enviados se entregarán cuando %1$s vuelva a conectarse. + %1$s no figura en su lista de contactos. + Sus contraseñas no coinciden + La prioridad debe ser un número en el rango [0 … 127] + El número de puerto debe ser un número + Error de transferencia + No se pudo leer el fichero a almacenar + Error importante: No se pudo desbloquear o cargar la base de datos de la aplicación. Por favor reinstale la aplicación o borre los datos. + ¡No hay ninguna aplicación instalada que pueda manejar ese enlace! + Configuración de la cuenta + + Grupos + conversaciones abiertas + Cerrar y bloquear + Pánico + Amigos + Contactos + ¿Aceptar certificado del servidor? + Mostrar su huella de validación + cargando… + + Acerca de ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Lista de contactos + Ajustes + Administrar cuentas + Cerrar sesión + Lista de contactos + + Jabber (XMPP) + Área local (Bonjour/ZeroConf) + Cuenta de Google + dukgo.com + + + Conectado + Ocupado + Ausente + Inactivo + Desconectado + Aparecer desconectado + Feliz Triste @@ -413,7 +505,7 @@ Riendo Confundido - + Feliz Triste @@ -452,173 +544,33 @@ :-D o_O - Lista de contactos - Cifrar Encendido/Apagado - Conectar a través de Tor (Requiere la aplicación Orbot) - - ChatSecure - ¿Es la primera vez utilizando ChatSecure? - Comenzar directamente - Comenzar - Configuración de cuenta - - Acerca de ChatSecure - ChatSecure es una aplicación de mensajería instantánea que evita que otros puedan escuchar sus conversaciones.\n\nEsta aplicación soporta cualquier servicio de chat basado en Jabber y XMPP como Google GTalk or Jabber.org. - - ¿Es seguro? - \'Off-the-Record Messaging\' es un protocolo seguro que proporciona privacidad de forma similar a las conversaciones del mundo real aportando cifrado, autenticación, negación y confidencialidad directa.\n\nEl protocolo OTR es compatible con clientes de escritorio como Adium y Pidgin. - - ¿Es seguro? - El cifrado de ChatSecure solo funciona cuando sus contactos utilizan una aplicación compatible. Debe asegurarse de que sus contactos están usando ChatSecure para dispositivos móviles o Adium/Pidgin en su escritorio. Puede configurar cómo ChatSecure cifra sus conversaciones en Configuración de cuenta.\n\n¡Comience! - - Configuración de contraseña - Antes de comenzar asegúrese de elegir una contraseña segura para proteger sus datos de ChatSecure. - Contraseña : - Contraseña (otra vez) : - - usuario@dominio.com - nuevo usuario - proveedor de servicios (dukgo.com, jabber.ccc.de) - contraseña - confirmar contraseña - Configuración avanzada de la cuenta - Tipo de cuenta - Registrar cuenta - - Persistencia - Recordar contraseña - Almacenar contraseña en caché - No almacenar contraseña en caché - Iniciar sesión automáticamente - Conectar ChatSecure durante el inicio - No conectar ChatSecure durante el inicio - Iniciar sesión ahora - Iniciar sesión en la cuenta siguiente - No iniciar sesión en la cuenta siguiente - - Personal (opcional) - Cuenta Alias (su nombre) - Cómo aparece su cuenta en línea - Perfil - Breve descripción del usuario - - Forzar cifrado / rechazar texto plano - Cifrar conversaciones automáticamente cuando sea posible - Cifrar conversaciones si se solicita - Inhabilitar el cifrado en las conversaciones - - Validando credenciales ... - Generando un par de claves ... - Iniciando sesión... - - ChatSecure encontró un error mientras se validaba su nombre de usuario o su contraseña - por favor, revíselos y pruebe de nuevo. - ChatSecure encontró un error mientras generaba un par de claves. - ChatSecure encontró un error mientras se conectaba al servidor de chat - por favor revise su configuración y pruebe de nuevo. - ChatSecure ha encontrado un error mientras se conectaba - por favor revise su conectividad de red y vuelva a intentarlo. - Sus huellas digitales - Su huella digital - Iniciar sesión - Regenerar clave - ChatSecure ha perdido la conexión a la red - ChatSecure está intentando restablecer la conexión - Advertencia: el cifrado no está activado en esta conversación - Esta conversación es segura, pero la identidad de los participantes NO ha sido verificada - Advertencia: el cifrado en esta conversación ha sido desactivado. - Esta conversación es segura y se encuentra verificada - Asistente para cuenta - Su ID de la cuenta - Configurar el servidor - ¿Está listo? - Introduzca su ID de cuenta para configurar ChatSecure a partir de su servicio de chat XMPP. Tiene la misma estructura que una dirección de correo electrónico: - Por favor, introduzca su ID de cuenta (usuario @ nombre de host) : - Por favor, introduzca o edite el nombre del servidor y número de puerto (5222 por defecto). - ChatSecure se ha configurado y ahora puede conectarse a su servicio de chat ¡y hablar de forma privada y segura! - No se ha introducido un nombre de dominio en su ID de cuenta, ¡vuélvalo a intentar! - Su nombre de host no contiene un .com, .net u otro valor, ¡vuélvalo a intentar! - Introduzca su contraseña: - - Configuración de la cuenta - defLoc - Advertencia: Esta es una primera versión de ChatSecure y puede que aun contenga fallos de seguridad y errores. - - Grupos - Generando un nuevo par de claves OTR... - Contacto - escriba el nombre del contacto para comenzar una conversación - Ir - Idioma - Idiomas - ¿Qué idioma debería utilizar InTheClear? - Hacer búsqueda SRV - Realizar una búsqueda SRV para encontrar el servidor XMPP a partir del nombre de dominio - Permitir que el usuario y la contraseña sean enviados en texto plano cuando no se utiliza cifrado - Texto plano - Verificar que el certificado es confiable - Verificación TLS - Utilizar TLS/SSL - Capa de transporte - De qué manera comienzan las conversaciones cifradas - El servidor al que conectarse, si es necesario - Servidor - Puerto TCP del servidor XMPP - Puerto de servidor - Recurso XMPP - distinguir esta conexión de otros clientes que también han iniciado sesión - Prioridad del recurso - Los mensajes a clientes con recursos múltiples activos serán enviados al recurso con la prioridad más alta - Siguiente - Atrás - Conversaciones - Nuevo mensaje de: - Autenticación - Introduzca una pregunta para enviar a su contacto junto a la respuesta que espera recibir para verificar que se trata de la persona que dice ser. - la pregunta que formular - la respuesta que se espera - Su contacto le ha autenticado. Ahora valide a su contacto mediante una pregunta. - No hay conversaciones activas.\n\n¡Pulse aquí para comenzar una! - Utilizar tema oscuro - Dado que está utilizando Tor, debe introducir el nombre de host del servidor XMPP en Configuración avanzada de la cuenta - - Arrancar ChatSecure automáticamente - Iniciar sesión automáticamente - No tiene ninguna cuenta configurada.\n\n¡Pulse aquí para añadir una! - Cuenta de Google - Guardar conversaciones - No almacenar los mensajes en flash para evitar que puedan ser recuperados. (Advertencia: esto puede provocar la pérdida de mensajes) - Imagen de fondo - Establecer una ruta para la imagen de fondo de la aplicación - Seguridad y privacidad - Interfaz de usuario - Otros ajustes - conversaciones abiertas - Orbot (Tor) - Salir - - ¿Quiere cerrar sesión y acabar con todos los procesos? - Introduzca una *nueva* contraseña. Debe está formada al menos por una mayúscula, una minúscula y un número. Además debe tener más de 6 caracteres. - Cifrando sus notas con la nueva contraseña (tenga paciencia...) - Introduzca su contraseña: - ¡Bienvenido! Introduzca una contraseña fuerte para asegurar sus notas. Debe contener al menos una mayúscula, una minúscula y un número además de tener más de seis caracteres. - Su contraseña no es lo suficientemente larga - Su contraseña no contiene mayúsculas - Su contraseña no contiene minúsculas - Su contraseña no contiene números - Abierto - ChatSecure bloqueado - Crear contraseña - Introducir contraseña - Introduzca su nueva contraseña - Confirme su nueva contraseña - Las contraseñas no coinciden, por favor, inténtelo de nuevo - Comunicación segura por voz - Introduzca su OStel.co u otro servicio SIP seguro para integrar el soporte de llamadas - dukgo.com - Se ha detectado un repositorio de certificados de seguridad de OTR a importar. ¿Quiere escanear la clave a través de QR? - Importar repositorio de certificados de seguridad de OTR - El anillo de claves de OTR se ha importado con éxito - El anillo de claves de OTR no se ha importado. Por favor, compruebe que el archivo existe y tiene el formato apropiado - Tiempo de expiración de la contraseña - Tiempo de expiración de la clave en caché + Cuenta existente + Conectar con mi cuenta existente en un servidor Jabber / XMPP específico. + Cuenta de Google + Charle con otros usuarios de Google usando su cuenta de Google existente. + Chat en red de malla o en WiFi + Chatee con otros en la misma red WiFi o red de malla (mesh) local - ¡no se requiere Internet ni servidor! + Habilitar chat por WiFi + Nueva cuenta + Registra una nueva cuenta gratuita en un servicio de nuesta lista integrada, o en cualquier otro que elija. + Crear nueva cuenta + ¡Identidad secreta! + Crea una cuenta de chat anónima y desechable (\"burner\") simplemente al pulsar un botón (requiere Orbot: Tor para Android) + Generar identidad + Esto es un grupo de chat + [reenviado] + [reenviado] + No se pudo compartir este fichero de forma segura + ¿Instalar Orbot? + Debe tener Orbot instalado y activado para proxyficar tráfico a través de él. ¿Desea instalarlo? + Siempre + ¿Iniciar Orbot? + Orbot no parece estar ejecutándose. ¿Desea inicializarlo y conectarlo a Tor? + apodo a usar en la sala + nombre de la sala a crear o a la que unirse\" + servidor de chat en grupo (conference.foo.com) + Su almacen de claves está estropeado. Por favor re-instale ChatSecure o limpie los datos para la aplicación + Ha recibido un mensaje cifrado ilegible + No pude descifrar el mensaje que envíó + < haga un gesto de barrido a la izquierda y la derecha para más opciones > diff --git a/res/values-es-rMX/arrays.xml b/res/values-es-rMX/arrays.xml new file mode 100644 index 000000000..61ba9df09 --- /dev/null +++ b/res/values-es-rMX/arrays.xml @@ -0,0 +1,9 @@ + + + + Forzar / Requerir + Intentar Automáticamente + Como solicitado + Desactivado / Nunca + + diff --git a/res/values-es-rMX/strings.xml b/res/values-es-rMX/strings.xml index 42214eb2a..12d3a0724 100644 --- a/res/values-es-rMX/strings.xml +++ b/res/values-es-rMX/strings.xml @@ -1,362 +1,223 @@ - - - - - - - - - - - - - - - Cancelar inicio de sesión - - - Agregar contacto - - Eliminar contacto - - Bloquear contacto - - Bloqueado - - Lista de cuentas - - - - Configuración - - - Iniciar chat - - Cerrar sesión - - Ver perfil - - - Finalizar chat - - - Lista de contactos - - Invitar a... - - Modificar chats - - Insertar ícono gestual - - - Menú+ - - - + + + + leer mensajes instantáneas + ⏎ +Deja que las aplicaciones lean datos de proveedor de contenido MI.⏎ + escribe mensajes instantáneas +escribir mensajes instantáneas +escriba mensajes instantáneas + +Deja que las aplicaciones escriban datos al proveedor de contenido MI. + iniciar servicio de mensajería instantánea + Permite a las aplicaciones iniciar el servicio de mensajería instantánea por medio de intent. + Confirmar - - - - - - - - Aceptar - Cancelar - Aceptar - Cancelar - - - - - - - - - + Siguiente + Atrás + + + Agregar cuenta %1$s + Sobre + Cerrar todas las sesiónes + Se ha cerrado la sesión de %1$s. + Tipo de Cuenta + + ChatSecure es una app de mensajería instantanea para móvil que da extra seguridad y previene que los otros husmeen tus conversaciones y comunicaciones. + +La app apoya qualquier servicio de chat que utilizca Jabber o el protocolo XMPP, como el Google GTalk o Jabber.org + + + La encripción de chat de ChatSecure solo funciona mientras comunicando con otros utilizando una app o programa compatible, así que debes averiguar que tus contactos están usando ChatSecure para móvil y Adium o Pidgin para su escritorio. Tú puedes ajustar exacatamente cómo y cuándo ChatSecure intenta encriptar tus chats en tus Configuraciones de Cuenta. + +¡Empezamos! + + Nueva Cuenta + Añadir cuenta + Editar cuenta + Quitar cuenta + Nombre de usuario: - Contraseña: - Recordar mi contraseña. - Iniciar sesión automát. - ¿No tienes una cuenta? - - Esta opción inicia la sesión automáticamente cada vez que abres la aplicación. Para desactivarla, sal de la sesión y quita la marca de verificación de la casilla \\"Iniciar sesión automáticamente\\". - Inicia sesión - - - - Iniciando sesión... - - - Datos de fondo desactivados - - - - - - Habilitar - - Salir - - - - - - + Para tu seguridad, si se te pierda o si se te roba el teléfono, ir al sitio Web en tu computadora para cambiar tu contraseña. + Esta opción inicia la sesión automáticamente cada vez que abres la aplicación. Para desactivarla, sal de la sesión y quita la marca de verificación de la casilla \\"Iniciar sesión automáticamente\\". + Connecta via Tor (Requiere Orbot (app)) + Tipo de Cuenta + Por favor inicia or edita tu nombre de servidor de chat jabber/xmpp y número de puerto (5222 es el estándar). + Cancelar inicio de sesión + Iniciando sesión… + + Mensajes a clientes con multiples recursos activos irán al recurso con alta prioridad. + + Conversaciones + Añadir contacto + Eliminar contacto + Bloquear contacto + Bloqueado + El contacto \"%1$s\" se eliminará. + El contacto \"%1$s\" se bloqueará. + El contacto \"%1$s\" se desbloqueará. + Contacto \"%1$s\" agregado. + Contacto \"%1$s\" eliminado. + Contacto \"%1$s\" bloqueado. + Contacto \"%1$s\" desbloqueado. + Nuevo Chat + Buscar por tus Contactos + Iniciar chat + Chats en curso (%1$d) + %1$d en línea Invitaciones de amigos - (\"\"Desconocida\"\") - Vacío - No hay conversaciones - - Seleccionar contacto(s) para invitar - Escribir para buscar contacto - No se encontraron contactos. - - - - No hay contactos bloqueados. - - + No hay chats activos. + Agregar contacto + Dirección de correo electrónico de la persona que deseas invitar: + Selecciona una lista: + Escribe un nombre de Contactos para agregarlo. + Enviar invitación Perfil de contacto - Estado: - Tipo de cliente: - Computadora - Celular - - - En línea - - Ocupado - - Ausente - - Inactivo - - Desconectado - - Aparecer desconectado - - - - - + Contactos bloqueados - %1$s + No hay contactos bloqueados. + + Chatear con %1$s Yo - - Escribir para componer - - - - - - - - - - - - - - - - + %1$s está en línea + %1$s está ausente + %1$s está ocupado + %1$s está desconectado + %1$s se ha unido + %1$s se ha ido Enviar - - Este mensaje no pudo enviarse. - - Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando estés en línea. - - - - - + Reenviar + Finalizar chat + Insertar ícono gestual + Modificar chats + Menú+ Seleccionar vínculo - - No hay chats activos. - - - - Agregar contacto - - Dirección de correo electrónico de la persona que deseas invitar: - - Selecciona una lista: - - Escribe un nombre de Contactos para agregarlo. - - Enviar invitación - - + + %1$s te ha invitado a unirte a un grupo de chat. + La invitación se ha enviado a %1$s + Aceptar + Rechazar + Invitar a… + Seleccionar contacto(s) para invitar + Escribir para buscar contacto + No se ha podido aprobar la suscripción de %1$s. Vuelve a intentarlo más tarde. + No se ha podido declinar la suscripción de %1$s. Vuelve a intentarlo más tarde. + + Iniciar Encripción + Parar Encripción + Iniciando sesión de chat encriptado… + Terminando sesión de chat encriptado… + + + Tus Huellas + + Autenticación + Introducir una pregunta para mandar a tu contacto, y la respuesta que esperes que te diera, para poder verificar que es realmente quién te declara quien es. + la pregunta para preguntar + la respuesta que esperes + Enviar + Cancelar + + + Tipo de Cuenta + + Encripción y Anonimidad + + Idioma + Idiomas + Ocultar contactos desconectados - + Configuración de notificación - Notific. de mensaj. instantánea - Notificar en la barra de estado cuando llega mensajería instantánea - Vibrar - Vibrar también cuando llega mensajería instantánea - Sonido - Reproducir también tono de timbre cuando llega mensajería instantánea - - Seleccionar tono de timbre - - - - - - - Aceptar - - Rechazar - - - - Aceptar - - Rechazar - - - - - - - - - - - - + + + Datos de fondo desactivados + %1$s necesita que se habiliten datos de fondo. + Habilitar + Salir + + Mensajes nuevos de %1$s Invitación para chat en grupo - - - - - - - - - - - - iniciar servicio de mensajería instantánea - Permite a las aplicaciones iniciar el servicio de mensajería instantánea por medio de intent. - - + Nuevo(s) mensaje(s) de + Atención - - - + Código de error %1$d + No es posible iniciar sesión en el servicio %1$s. Vuelve a intentarlo más tarde."\n"(Detalle:%2$s) La lista no se agregó. - El contacto no se bloqueó. - El contacto no se desbloqueó. - Selecciona primero un contacto. - - \"Desconectado\"\n - + \"Desconectado\" + Error del servidor - La lista de contactos no se cargó. - No es posible conectarse al servidor. Verifica tu conexión. - - - - - + %1$s ya existe en tu lista de contactos. + El contacto \"%1$s\" ha sido bloqueado. Espera mientras se carga tu lista de contactos - Se ha producido un error en la red. - + Se requiere WiFi para esta conección. El servidor no admite esta funcionalidad. - La contraseña ingresada no es válida. - EL servidor encontró un error. - El servidor no admite esta funcionalidad. - El servidor no está disponible en este momento. - El tiempo del servidor se ha agotado. - El servidor no admite la versión actual. - La lista de mensajes está llena. - El servidor no admite reenviar al dominio. - El nombre de usuario que has ingresado no está reconocido. - Lo sentimos, el usuario te ha bloqueado. - La sesión ha expirado. Vuelve a iniciar sesión - has iniciado sesión desde otro cliente. ya has iniciado sesión desde otro cliente. - El número de teléfono no puede leerse de tu tarjeta SIM. Ponte en contacto con tu operador para obtener ayuda. - Actualmente, no has iniciado sesión. - - - + Introducir tu contraseña: + Este mensaje no pudo enviarse. + %1$s está desconectado. Los mensajes que le envíes los recibirá cuando %1$s vuelva a conectarse. + %1$s no está en tu lista de contactos. + Configuración de Cuentas + + Grupos + + + Configuración + Lista de cuentas + Cerrar sesión + Lista de contactos + + + + En línea + Ocupado + Ausente + Inactivo + Desconectado + Aparecer desconectado + Feliz Triste @@ -376,7 +237,7 @@ Riendo Confundido - + Feliz Triste @@ -396,21 +257,5 @@ Riendo Confundido - - - - - - - - - - - - - - - + Nueva Cuenta diff --git a/res/values-es-rUS/arrays.xml b/res/values-es-rUS/arrays.xml deleted file mode 100644 index 3c3a568d5..000000000 --- a/res/values-es-rUS/arrays.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Fuerza / Requerir - Automáticamente Ocasión - Como requerido - Nunca / personas de movilidad reducida - - diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index b2edb6756..f0ff01ffe 100755 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -27,11 +27,11 @@ "Ver perfil" "Finalizar chat" "Lista de contactos" - "Invitar a..." + "Invitar a…" "Modificar chats" "Insertar ícono gestual" "Menú+" - "Entrada" + "Confirmar" "El contacto \"%1$s\" se eliminará." "El contacto \"%1$s\" se bloqueará." @@ -51,7 +51,7 @@ "Esta opción inicia la sesión automáticamente cada vez que abres la aplicación. Para desactivarla, sal de la sesión y quita la marca de verificación de la casilla \"Iniciar sesión automáticamente\"." "Inicia sesión" "Iniciando sesión en %1$s" - "Iniciando sesión..." + "Iniciando sesión…" "Datos de fondo desactivados" "%1$s necesita que se habiliten datos de fondo." "Habilitar" @@ -87,7 +87,7 @@ "%1$s está desconectado" "%1$s se ha unido" "%1$s se ha ido" - "'Enviado el 'EEEE' a las 'hhmma" + "Enviar" "Este mensaje no pudo enviarse." "Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando estés en línea." @@ -100,7 +100,7 @@ "Selecciona una lista:" "Escribe un nombre de Contactos para agregarlo." "Enviar invitación" - "Configuración general" + "Ocultar contactos desconectados" "Configuración de notificación" "Notific. de mensaj. instantánea" diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml index 363fcf5f9..78e5c96c8 100644 --- a/res/values-es/arrays.xml +++ b/res/values-es/arrays.xml @@ -6,64 +6,4 @@ Solo si se solicita Nunca - - forzar - automático - solicitado - inhabilitado - - - Por defecto - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 40892bf7b..b17605939 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -1,397 +1,342 @@ - + + + ChatSecure + ChatSecure + leer mensajes \nPermitir a las aplicaciones leer del proveedor de contenido de mensajería instantánea.\n escribir mensajes \nPermitir a las aplicaciones escribir en el proveedor de contenido de mensajería instantánea. - - Gibberbot - - + iniciar servicio de MI + Permitir que las aplicaciones inicien el servicio de MI mediante la ejecución de un intento. + + Confirmar + Aceptar + Cancelar + Aceptar + Cancelar + Siguiente + Atrás + Conectar + + Seleccionar una cuenta - + Seleccionar una cuenta + Añadir cuenta %1$s Acerca de - + Salir + ¿Quiere cerrar sesión en todos los servicios? + Se ha cerrado su cuenta de %1$s. + Se ha cerrado la cuenta de %1$s porque %2$s. + ¿Es la primera vez que utiliza ChatSecure? + Comenzar directamente + Comenzar + Configuración de cuenta + Configuración de contraseña + Antes de empezar por favor, elija una contraseña segura para proteger sus datos contra el acceso ChatSecure injusto. + Contraseña : + Contraseña (otra vez) : + + Acerca de ChatSecure + ChatSecure es una aplicación de mensajería instantánea que permite que nadie pueda escuchar sus conversaciones.\n\nEsta aplicación funciona con cualquier servicio de mensajería instantánea que utilice el protocolo XMPP como Google GTalk y Jabber.org. + + ¿Es seguro? + \'Off-the-Record Messaging\' es un protocolo seguro que proporciona privacidad de forma similar a las conversaciones del mundo real aportando cifrado, autenticación, negación y confidencialidad directa.\n\nEl protocolo OTR es compatible con clientes de escritorio como Adium y Pidgin. + + ¿Es seguro? + El cifrado de conversaciones en ChatSecure solo funciona con usuarios que utilizan una aplicación compatible. \nVerifique que sus contactos utilizan ChatSecure en su dispositivo móvil o Adium/Pidgin en su escritorio. Puede ajustar de qué manera ChatSecure intenta cifrar sus conversaciones en Configuración de la cuenta. + + Nueva cuenta Añadir cuenta - Editar cuenta - Eliminar cuenta - - Salir - - - Seleccionar una cuenta - - - + + Nombre de usuario: + Contraseña: + Recordar + Iniciar sesión automáticamente. + ¿Aún no tiene una cuenta? + Iniciar sesión + Por su seguridad, si pierde el teléfono o se lo roban, visite el sitio Web en su equipo y cambie su contraseña. + Esta opción le permite iniciar sesión automáticamente cada vez que abra la aplicación. Para desactivarla, cierre sesión y desactive la casilla de verificación \\"Acceder automáticamente\\". + Conectar a través de Tor (Requiere la aplicación Orbot) + usuario@dominio.com + contraseña + Configuración avanzada de la cuenta + Tipo de cuenta + Persistencia + Recordar contraseña + Almacenar contraseña en caché + No almacenar contraseña en caché + Iniciar sesión automáticamente + Conectar ChatSecure al iniciar + No conectar ChatSecure al iniciar + Iniciar sesión ahora + Iniciar sesión en la cuenta siguiente + No iniciar sesión en la cuenta siguiente + Personal (opcional) + Cuenta Alias (su nombre) + Cómo aparece su cuenta en línea + Perfil + Breve descripción del usuario + Forzar cifrado / rechazar texto plano + Cifrar conversaciones automáticamente cuando sea posible + Cifrar conversaciones si se solicita + Inhabilitar el cifrado en las conversaciones + Validando credenciales … + Generando un par de claves … + Iniciando sesión… + Asistente para cuenta + Su ID de la cuenta + Configurar el servidor + ¿Está listo? + Introduzca su ID de cuenta para configurar ChatSecure utilizando XMPP. El ID es similar a una dirección de correo electrónico: + Por favor, introduzca su ID de cuenta (usuario @ nombre de host) : + Por favor, introduzca o edite el nombre del servidor y número de puerto (5222 por defecto). + ChatSecure se ha configurado, ¡ahora es el momento de conectarse y empezar a conversar de forma segura y privada! + Orbot (Tor) Cancelar inicio de sesión - - + Iniciando sesión + + Hacer búsqueda SRV + Realizar una búsqueda SRV para encontrar el servidor XMPP a partir del nombre de dominio + Permitir que el usuario y la contraseña sean enviados en texto plano cuando no se utiliza cifrado + Texto plano + Verificar que el certificado es confiable + Verificación TLS + Utilizar TLS/SSL + Capa de transporte + De qué manera comienzan las conversaciones cifradas + El servidor al que conectarse, si es necesario + Servidor + Puerto TCP del servidor XMPP + Puerto de servidor + Recurso XMPP + distinguir esta conexión de otros clientes que también han iniciado sesión + Prioridad del recurso + Los mensajes a clientes con recursos múltiples activos serán enviados al recurso con la prioridad más alta + + Conversaciones + No hay conversaciones activas.\n\n¡Pulse aquí para comenzar una! + No tiene ninguna cuenta configurada.\n\n¡Pulse aquí para añadir una! Añadir contacto - Eliminar contacto - Bloquear contacto - Bloqueados - - Lista de cuentas - + El contacto \"%1$s\" se eliminará. + El contacto \"%1$s\" se bloqueará. + El contacto \"%1$s\" se desbloqueará. + Contacto \"%1$s\" añadido + Contacto \"%1$s\" eliminado + Contacto \"%1$s\" bloqueado + Contacto \"%1$s\" desbloqueado Nueva conversación - - Nueva cuenta - - Ajustes - Buscar Contactos - Iniciar conversación - - Cerrar sesión - Verficar - - - Iniciar cifrado - Finalizar cifrado - Borrar Conversación - Finalizar conversación - - - Lista de contactos - - Invitar… - - Cambiar de conversación - - Insertar emoticono - Reenviar - - Examinar huellas digitales - Su huella digital - Verificar huella digital - Verificar secreto - - MENU+ - - - - Confirmar - - ¿Quiere cerrar sesión en todos los servicios? - - "El contacto \"%1$s\" se eliminará." - - "El contacto \"%1$s\" se bloqueará." - - "El contacto \"%1$s\" se desbloqueará." - - Aceptar - - Cancelar - - Aceptar - - Cancelar - - "Se ha cerrado su cuenta de %1$s." - - "Se ha cerrado la cuenta de %1$s porque %2$s." - - - "Añadir cuenta %1$s" - - - Nombre de usuario: - - Contraseña: - - Recordar - - Iniciar sesión automáticamente. - - ¿Aún no tiene una cuenta? - - Por su seguridad, si pierde el teléfono o se lo roban, visite el sitio Web en su equipo y cambie su contraseña. - Esta opción le permite iniciar sesión automáticamente cada vez que abra la aplicación. Para desactivarla, cierre sesión y desactive la casilla de verificación \\"Acceder automáticamente\\". - - Iniciar sesión - - - - - Iniciando sesión - - - Datos de red inhabilitados - - - "Para iniciar %1$s, es necesario habilitar los datos de referencia." - - - Habilitar - - Salir - - - - "Conversaciones en curso (%1$d)" - "%1$d conectado" - + Conversaciones en curso (%1$d) + %1$d conectado Invitaciones de amigos - (\"\"Desconocido\"\") - Vacío - Ninguna conversación - - Seleccionar contactos a los que se dirige la invitación - Escriba el nombre del contacto que quiera localizar - No se ha encontrado ningún contacto. - - "Contactos bloqueados - %1$s" - - No hay ningún contacto bloqueado. - - + Contacto + escriba el nombre del contacto para comenzar una conversación + Ir + No hay ninguna conversación activa. + Añadir contacto + Dirección de correo electrónico de la persona a la que desea invitar: + Seleccionar una lista: + Escriba el nombre del contacto que quiera añadir. + Enviar invitación Perfil del contacto - Estado: - Tipo de cliente: - Equipo - Móvil - - - Conectado - - Ocupado - - Ausente - - Inactivo - - Desconectado - - Aparecer desconectado - - - - "Conversación con %1$s" - + Contactos bloqueados - %1$s + No hay ningún contacto bloqueado. + + Conversación con %1$s Yo - - Escriba el texto aquí - - "%1$s está conectado" - - "%1$s está ausente" - - "%1$s está ocupado" - - "%1$s está desconectado" - - "%1$s se ha unido a la conversación" - - "%1$s se ha desconectado" - - "'Enviado a las 'hh':'mm' 'a' el 'EEEE" - + %1$s está conectado + %1$s está ausente + %1$s está ocupado + %1$s está desconectado + %1$s se ha unido a la conversación + %1$s se ha desconectado Enviar - - No se ha podido enviar el mensaje. - - Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando se restablezca la conexión. - - "%1$s está desconectado. Los mensajes enviados se entregarán cuando %1$s vuelva a conectarse." - - "%1$s no figura en tu lista de contactos." - + Escriba el texto aquí + Reenviar + Finalizar conversación + Borrar Conversación + Insertar emoticono + Cambiar de conversación + MENU+ Seleccionar enlace - - No hay ninguna conversación activa. - - Comenzando conversación cifrada... - Deteniendo conversación cifrada... - - - Añadir contacto - - Dirección de correo electrónico de la persona a la que desea invitar: - - Seleccionar una lista: - - Escriba el nombre del contacto que quiera añadir. - - Enviar invitación - - Jabber (XMPP) - Sesión de chat en red local (Bonjour/ZeroConf) - + + %1$s le ha invitado a unirse a una conversación en grupo. + Se ha enviado la invitación a %1$s. + Aceptar + Rechazar + Invitar… + Seleccionar contactos a los que se dirige la invitación + Escriba el nombre del contacto que quiera localizar + No se ha encontrado ningún contacto. + + No + No se ha podido aprobar la suscripción de %1$s. Inténtelo de nuevo más tarde. + No se ha podido rechazar la suscripción de %1$s. Inténtelo de nuevo más tarde. + + Iniciar cifrado + Finalizar cifrado + Comenzando conversación cifrada… + Deteniendo conversación cifrada… + Sus huellas digitales + Su huella digital + Iniciar sesión + Regenerar clave + Advertencia: el cifrado no está activado en esta conversación + Esta conversación es segura, pero la identidad de los participantes NO ha sido verificada + Advertencia: el cifrado en esta conversación ha sido desactivado. + Esta conversación es segura y se encuentra verificada + Generando un nuevo par de claves OTR… + + + Examinar huellas digitales + Su huella digital + Verificar huella digital + Verificar secreto + + Autenticación + Introduzca una pregunta para enviar a su contacto junto a la respuesta que espera recibir para verificar que se trata de la persona que dice ser. + la pregunta que formular + la respuesta que se espera + Su contacto le ha autenticado. Ahora valide a su contacto mediante una pregunta. + Enviar + Cancelar + + Configuración de cuenta + + Seguridad y privacidad Cifrado y Anonimato + Cifrar Encendido/Apagado + + Interfaz de usuario + Idioma + Idiomas + Utilizar tema oscuro + Guardar conversaciones + No almacenar los mensajes en flash para evitar que puedan ser recuperados. (Advertencia: esto puede provocar la pérdida de mensajes) + Imagen de fondo + Establecer una ruta para la imagen de fondo de la aplicación + + + Otros ajustes + Conectar al inicio + Iniciar sesión automáticamente Ocultar desconectados - - Configuración de notificaciones - - Notificaciones de MI - - Notificar cuando se recibe un mensaje instantáneo en la barra de estado - Prioridad primer plano Disminuye la probabilidad de que Android reinicie la conexión. Esto añadirá una entrada en el área de notificación Intervalo de latidos Utilizar un valor más alto (en minutos) para ahorrar batería. Esto puede provocar que su proveedor cierre la conexión por inactividad + + Configuración de notificaciones + Notificaciones de MI + Notificar cuando se recibe un mensaje instantáneo en la barra de estado Vibrar - Vibrar al recibir un mensaje - Sonido - Reproducir un tono al recibir mensaje instantáneo - Seleccionar tono - - - "%1$s le ha invitado a unirse a una conversación en grupo." - - "Se ha enviado la invitación a %1$s." - - Aceptar - - Rechazar - - "%1$s le ha invitado a formar parte de su lista de contactos." - - Aceptar - - Rechazar - - "No se ha podido aprobar la suscripción de %1$s. Inténtelo de nuevo más tarde." - - "No se ha podido rechazar la suscripción de %1$s. Inténtelo de nuevo más tarde." - - - "Mensajes nuevos de %1$s" - - "%1$d conversaciones no leídas" - - "Invitación de amigo nuevo de %s" - + + + Datos de red inhabilitados + Para iniciar %1$s, es necesario habilitar los datos de referencia. + Habilitar + Salir + ¿Quiere cerrar sesión y acabar con todos los procesos? + + Mensajes nuevos de %1$s + %1$d conversaciones no leídas + Invitación de amigo nuevo de %s Invitación a conversación en grupo - - "Nueva invitación a conversación en grupo de %s" - - "Contacto \"%1$s\" añadido" - - "Contacto \"%1$s\" eliminado" - - "Contacto \"%1$s\" bloqueado" - - "Contacto \"%1$s\" desbloqueado" - - iniciar servicio de MI - Permitir que las aplicaciones inicien el servicio de MI mediante la ejecución de un intento. - - + Nueva invitación a conversación en grupo de %s + Nuevo mensaje de: + Atención - - "No ha sido posible acceder al servicio %1$s. Inténtelo de nuevo más tarde."\n"(Detalles: %2$s)" - + Código de error %1$d + No ha sido posible acceder al servicio %1$s. Inténtelo de nuevo más tarde."\n"(Detalles: %2$s) La lista no se ha añadido. - No se ha podido bloquear el contacto. - No se ha podido desbloquear el contacto. - Seleccione primero un contacto. - ¡Desconectado!\n - ¡Error del servicio! - No se ha podido cargar la lista de contactos. - No se puede establecer conexión con el servidor. Compruebe la conexión. - - "%1$s ya figura en su lista de contactos." - - "El contacto \"%1$s\" se ha bloqueado." + %1$s ya figura en su lista de contactos. + El contacto \"%1$s\" se ha bloqueado. Por favor, espera mientras se carga la lista de contactos. - Se ha producido un error de red. Se necesita una conexión WiFi. - El servidor no admite esta función. - La contraseña introducida no es válida. - Se ha producido un error en el servidor. - El servidor no admite esta función. - El servidor no está disponible en este momento. - Se ha agotado el tiempo de espera del servidor. - El servidor no admite la versión actual. - La cola de mensajes está llena. - El servidor no admite el reenvío al dominio. - El nombre de usuario introducido no se reconoce. - El usuario le ha bloqueado. - La sesión ha expirado. Inicie sesión de nuevo. - ha iniciado sesión desde otro cliente. ha iniciado sesión desde otro cliente. - No se puede leer el número de teléfono de la tarjeta SIM. Póngase en contacto con su operador para obtener ayuda. - No ha iniciado sesión. - - "Código de error %1$d" - + ChatSecure encontró un error al intentar validar su nombre de usuario o contraseña - por favor, compruebe que son correctos y vuelva a intentarlo. + ChatSecure encontró un error al generar un par de claves. + ChatSecure ha detectado un error al conectar con el servidor - por favor revise su configuración y vuelva a intentarlo. + ChatSecure encontró un error al conectar - por favor verifique su conectividad de red y vuelva a intentarlo. + ChatSecure ha perdido la conexión a la red + ChatSecure está tratando de reestablecer la conexión + No se ha introducido un nombre de dominio en su ID de cuenta, ¡vuélvalo a intentar! + Su nombre de host no contiene un .com, .net u otro valor, ¡vuélvalo a intentar! + Introduzca su contraseña: + Dado que está utilizando Tor, debe introducir el nombre de host del servidor XMPP en Configuración avanzada de la cuenta + No se ha podido enviar el mensaje. + Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando se restablezca la conexión. + %1$s está desconectado. Los mensajes enviados se entregarán cuando %1$s vuelva a conectarse. + %1$s no figura en tu lista de contactos. + Configuración de la cuenta + + Grupos + conversaciones abiertas + Salir + + + Lista de contactos + Ajustes + Lista de cuentas + Cerrar sesión + Lista de contactos + + Jabber (XMPP) + Sesión de chat en red local (Bonjour/ZeroConf) + Cuenta de Google + + + Conectado + Ocupado + Ausente + Inactivo + Desconectado + Aparecer desconectado + Feliz Triste @@ -411,7 +356,7 @@ Riendo Confundido - + Feliz Triste @@ -450,145 +395,5 @@ :-D o_O - Lista de contactos - Cifrar Encendido/Apagado - Conectar a través de Tor (Requiere la aplicación Orbot) - - Gibberbot - ¿Es la primera vez que utiliza Gibberbot? - Comenzar directamente - Comenzar - Configuración de cuenta - - Acerca de Gibberbot - Gibberbot es una aplicación de mensajería instantánea que permite que nadie pueda escuchar sus conversaciones.\n\nEsta aplicación funciona con cualquier servicio de mensajería instantánea que utilice el protocolo XMPP como Google GTalk y Jabber.org. - - ¿Es seguro? - \'Off-the-Record Messaging\' es un protocolo seguro que proporciona privacidad de forma similar a las conversaciones del mundo real aportando cifrado, autenticación, negación y confidencialidad directa.\n\nEl protocolo OTR es compatible con clientes de escritorio como Adium y Pidgin. - - ¿Es seguro? - El cifrado de conversaciones en Gibberbot solo funciona con usuarios que utilizan una aplicación compatible. \nVerifique que sus contactos utilizan Gibberbot en su dispositivo móvil o Adium/Pidgin en su escritorio. Puede ajustar de qué manera Gibberbot intenta cifrar sus conversaciones en Configuración de la cuenta. - - Configuración de contraseña - Antes de empezar por favor, elija una contraseña segura para proteger sus datos contra el acceso Gibberbot injusto. - Contraseña : - Contraseña (otra vez) : - - usuario@dominio.com - contraseña - Configuración avanzada de la cuenta - Tipo de cuenta - - Persistencia - Recordar contraseña - Almacenar contraseña en caché - No almacenar contraseña en caché - Iniciar sesión automáticamente - Conectar Gibberbot al iniciar - No conectar Gibberbot al iniciar - Iniciar sesión ahora - Iniciar sesión en la cuenta siguiente - No iniciar sesión en la cuenta siguiente - - Personal (opcional) - Cuenta Alias (su nombre) - Cómo aparece su cuenta en línea - Perfil - Breve descripción del usuario - - Forzar cifrado / rechazar texto plano - Cifrar conversaciones automáticamente cuando sea posible - Cifrar conversaciones si se solicita - Inhabilitar el cifrado en las conversaciones - - Validando credenciales ... - Generando un par de claves ... - Iniciando sesión... - - Gibberbot encontró un error al intentar validar su nombre de usuario o contraseña - por favor, compruebe que son correctos y vuelva a intentarlo. - Gibberbot encontró un error al generar un par de claves. - Gibberbot ha detectado un error al conectar con el servidor - por favor revise su configuración y vuelva a intentarlo. - Gibberbot encontró un error al conectar - por favor verifique su conectividad de red y vuelva a intentarlo. - Sus huellas digitales - Su huella digital - Iniciar sesión - Regenerar clave - Gibberbot ha perdido la conexión a la red - Gibberbot está tratando de reestablecer la conexión - Advertencia: el cifrado no está activado en esta conversación - Esta conversación es segura, pero la identidad de los participantes NO ha sido verificada - Advertencia: el cifrado en esta conversación ha sido desactivado. - Esta conversación es segura y se encuentra verificada - Asistente para cuenta - Su ID de la cuenta - Configurar el servidor - ¿Está listo? - Introduzca su ID de cuenta para configurar Gibberbot utilizando XMPP. El ID es similar a una dirección de correo electrónico: - Por favor, introduzca su ID de cuenta (usuario @ nombre de host) : - Por favor, introduzca o edite el nombre del servidor y número de puerto (5222 por defecto). - Gibberbot se ha configurado, ¡ahora es el momento de conectarse y empezar a conversar de forma segura y privada! - No se ha introducido un nombre de dominio en su ID de cuenta, ¡vuélvalo a intentar! - Su nombre de host no contiene un .com, .net u otro valor, ¡vuélvalo a intentar! - Introduzca su contraseña: - - Configuración de la cuenta - defLoc - ADVERTENCIA: Esta es una versión anticipada de Gibberbot que puede contener vulnerabilidades de seguridad o errores. - - Grupos - Generando un nuevo par de claves OTR... - Contacto - escriba el nombre del contacto para comenzar una conversación - Ir - Idioma - Idiomas - ¿Qué idioma debería utilizar InTheClear? - Hacer búsqueda SRV - Realizar una búsqueda SRV para encontrar el servidor XMPP a partir del nombre de dominio - Permitir que el usuario y la contraseña sean enviados en texto plano cuando no se utiliza cifrado - Texto plano - Verificar que el certificado es confiable - Verificación TLS - Utilizar TLS/SSL - Capa de transporte - De qué manera comienzan las conversaciones cifradas - El servidor al que conectarse, si es necesario - Servidor - Puerto TCP del servidor XMPP - Puerto de servidor - Recurso XMPP - distinguir esta conexión de otros clientes que también han iniciado sesión - Prioridad del recurso - Los mensajes a clientes con recursos múltiples activos serán enviados al recurso con la prioridad más alta - Siguiente - Atrás - Conversaciones - Nuevo mensaje de: - Autenticación - Introduzca una pregunta para enviar a su contacto junto a la respuesta que espera recibir para verificar que se trata de la persona que dice ser. - la pregunta que formular - la respuesta que se espera - Su contacto le ha autenticado. Ahora valide a su contacto mediante una pregunta. - No hay conversaciones activas.\n\n¡Pulse aquí para comenzar una! - Utilizar tema oscuro - Dado que está utilizando Tor, debe introducir el nombre de host del servidor XMPP en Configuración avanzada de la cuenta - - Conectar al inicio - Iniciar sesión automáticamente - No tiene ninguna cuenta configurada.\n\n¡Pulse aquí para añadir una! - Cuenta de Google - Guardar conversaciones - No almacenar los mensajes en flash para evitar que puedan ser recuperados. (Advertencia: esto puede provocar la pérdida de mensajes) - Imagen de fondo - Establecer una ruta para la imagen de fondo de la aplicación - Seguridad y privacidad - Interfaz de usuario - Otros ajustes - conversaciones abiertas - Orbot (Tor) - Salir - - ¿Quiere cerrar sesión y acabar con todos los procesos? + Nueva cuenta diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml new file mode 100644 index 000000000..3a12782fa --- /dev/null +++ b/res/values-eu/arrays.xml @@ -0,0 +1,9 @@ + + + + Derrigortu / Eskatu + Automatikoki saiatu + Eskatu bezala + Ezgaituta / Inoiz + + diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index dac1c0e3f..6058b1147 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -1,258 +1,457 @@ - + + + ChatSecure + ChatSecure + mezuak irakurri + +Aplikazioek BM eduki hornitzailetik irakurtzea baimentzen du. Mezua idatzi - - - - + +Aplikazioek BM eduki hornitzailean irakurtzea baimentzen du. + BM zerbitzua hasi + Aplikazioari \"intent\" bidez IM zerbitzua hastea baimentzen dio. + + Baieztatu + Ados + Utzi + Ados + Utzi + Hurrengoa + Atzera + Konektatu + + Ireki + ChatSecure blokeatuta + Pasahitza ezarri + Pasahitza baieztatu + Pasahitza + Pasahitz berria + Pasahitz berria egiaztatu + Pasahitzak ez datoz bat, saiatu berriro mesedez + Alfer nabil (Pasahitzik ez!) + + Kontu bat aukeratu + Kontu bat aukeratu + (%1$d) + %1$s kontua gehitu Honi buruz - + Saio guztiak itxi + Zerbitzu guztien saioa itxi nahi duzu? + %1$s zerbitzariak saioa bukatu du. + %1$s zerbitzariak saioa bukatu du, arrazoi honengatik: %2$s + ChatSecure erabiltzen duzun lehen aldia? + Hasteko irrikatan? + Sarrera + Kontu konfigurazioa + Pasaesaldia konfiguratu + Hasi baino lehen pasaesaldi seguru bat aukeratu mesedez, zure datuen bidegabeko atzipenak galarazteko. + Pasaesaldia: + Pasaesaldia (berriz): + Sartu pasaesaldi *berri* bat. Gutxienez legra larri bat, letra xehe bat eta zenbaki bat izan behar ditu, eta sei karaktere baino gehiagokoa izan behar du. + Zure notak pasaesaldi berriarekin zifratzen (pazientzia…) + Sartu zure pasaesaldia: + Ongi etorri! Sartu pasaesaldi sendo bat zure notak babesteko. Gutxienez letra larri bat, letra xehe bat eta zenbaki bat izan behar ditu, eta sei karaktere baino gehiagokoa izan behar du. + Zure pasaesaldia ez du luzera nahikorik + Zure pasaesaldiak ez du letra larririk + Zure pasaesaldiak ez du letra xeherik + Zure pasaesaldiak ez du zenbakirik + + ChatSecure-ri buruz + ChatSecure mobiletarako berehalako mezularitzako aplikazioa da. Segurtasun ezaugarri gehigarriak ditu eta besteek zure elkarrizketak eta komunikazioak miatzea saihesten du. + +Aplikazio honek Jabber edo XMPP protokoloa erabiltzen duten edozen berriketa zerbitzari erabil dezake, Google GTalk edo jabber.org adibidez. + + Nola da segurua? + \'OTR mezularitza\' pribatutasuna ahalbidetzeko segurtasun sistema da, mundu errealeko elkarrizketa pribatuetako ezaugarriak bilatzen dituena, besteak beste kautotzea, ukagarritasuna eta aurrerako sekretua. + +OTR-protokoloa Adium edo Pidgin ordenagailuetako berriketa bezeroekin bateragarria da. + + Nire berriketak seguruak dira? + ChatSecure-ren zifratze ezaugarriak bateragarriak diren aplikazio edo programekin bakarrik dabiltza. Zure kontaktuak mobiletan ChatSecure eta ordenagailuetan Adium edo Pidgin instalatuta dituztela ziurtatu. Ezarpenean kontrola dezakezu ChatSecure-k nola eta noiz saiatuko den zure berriketak zifratzen. + +Goazen aurrera! + + Kontu berria Kontua gehitu - Kontua editatu - Kontua ezabatu - - Saio guztiak itxi - - - - - + + Erabiltzailea@Ostalaria + Pasahitza: + Gogoratu nire pasahitza. + Saioa hasi automatikoki. + Ez duzu konturik? + Saioa hasi + Zure segurtasunerako, zure mobila galdu edo lapurtzean ordenagailuarekin webgunera joan eta aldatu zure pasahitza. + Aukera honek aplikazio hasterakoan automatikoki saioa hasten du. Aukera ezgaitzeko lehenengo saioa bukatu eta gero \"Saioa hasi automatikoki\" kontrol-laukia hustu. + Tor bidez konektatua (Orbot aplikazioa behar da) + user@domain.com + erabiltzaile berria + hornitzailea (dukgo.com, jabber.ccc.de) + pasahitza + pasahitza berretzi + Kontu ezarpen aurreratuak + Kontu mota + Kontua erregistratu + Iraunkortasuna + Pasahitza gogoratu + Pasahitza katxean + Pasahitza ez dago katxean + Saioa hasi automatikoki + ChatSecure hastean konektatu + Ez konektatu ChatSecure hastean + Saioa hasi orain + Kontu ezarpen hau erabiliz konektatu + Ez konektatu kontu ezarpen hau erabiliz + Pertsonala (aukerazkoa) + Kontuaren alias-a (zure izena) + Zure kontua linean nola ikusten den + Profila + Zuri buruzko sarrera txikia + Zifratzea derrigortu / testu soila baztertu + Aukera dagoenean berriketak automatikoki zifratu + Eskatzen denean berriketak zifratu + Berriketa zifratzea ezgaitu + Kredentzialak balioztatzen… + Gako bikotea sortzen… + Saioa hasten… + Kontu Laguntzailea + Zure kontuaren IDa + Zerbitzaria konfiguratu + Prest zaude? + ChatSecure zure XMPP berriketa zerbitzarirako konfiguratzeko zure kontuaren ID-a sartu. Eposta baten antzekoa da: + Sartu zure kontuaren IDa mesedez (erabiltzailea@ostalaria) + Mesedez sartu edo editatu zure jabber/xmpp berriketa zerbitzariko ostalari izena eta portu zenbakia (5222 defektuz). + ChatSecure konfiguratuta dago. Orain zure zerbitzura konektatzeko garaia da, salbu, seguru eta pribatuki berriketa egiteko! + Orbot (Tor) + Domeinua hautatu + Kontu berria erregistratzen… + ChatSecure martxan jartzen… Saio hasiera kantzelatu - - + Saioa hasi\u2026 + Saioa bukatzen\u2026 + + SRV bilaketa egin + DNS-tako SRV eremua eta domeinu izena erabili XMPP zerbitzaria topatzeko + Zifratu gabeko garraioa erabiltzean erabiltzaile izena eta pasahitza testu soilean bidaltzea ahalbidetu + Testu soileko kautotzea gaitu + Egiaztatu ziurtagiria fidagarria dela + TLS egiaztapena + TLS konexioa beharrezkoa + Garraioaren zifratzea + nola hasten diren berriketa zifratuak + Konektatzeko zerbitzaria, behar izatekotan + Konektatzeko zerbitzaria + XMPP zerbitzariaren TCP portua + Zerbitzari portua + XMPP Baliabidea + konexio hau izena emanda dauden beste bezeroengandik bereizteko. + XMPP baliabide lehentasuna + Baliabide anitz dituzten bezeroentzako mezuak lehentasun handiena duen baliabidera bidaliko dira. + + Elkarrizketak + Elkarrizketarik ez. + +Hemen ikutu bat hasteko! + Ez duzu konturik +konfiguratuta. + +Hemen ikutu bat gehitzeko! + Kontaktu zerrenda - %1$s Kontaktua gehitu - Kontaktua ezabatu - Kontaktua blokeatu - + Kontaktuaren ezizena Blokeatuta - - + "%1$s" kontaktua ezabatuko da. + "%1$s" kontaktua blokeatuko da. + "%1$s" kontaktua desblokeatuko da. + "%1$s" kontaktua gehituta. + "%1$s" kontaktua ezabatuta. + "%1$s" kontaktua blokeatuta. + "%1$s" kontaktua desblokeatuta. Berriketa berria - - - Ezarpenak - Kontaktuak bilatu - + Sareta erakutsi Berriketa hasi - - Saioa amaitu - - Egiaztatu - - + Profila ikusi + Gakoa egiaztatu + Abian diren berriketak (%1$d) + %1$d linean + Lagun gonbidapenak + (Ezezaguna) + Hutsik + Elkarrizketarik ez + Kontaktua + idatzi norekin egin nahi duzun berriketan + Joan + Txat aktiborik ez. + Kontaktua gehitu + Gonbidatu nahi duzun pertsonaren posta elektroniko helbidea: + Zerrenda bat hautatu: + Idatzi izen bat kontaktuetatik gehitzeko. + Gonbidapena bidali + Kontaktuaren profila + Egoera: + Bezero mota: + Ordenagailua + Mugikorra + Blokeatutako kontaktuak - %1$s + Blokeatutako kontaturik ez. + + Hasi berriketan %1$s -(r)ekin + Ni + %1$s linean dago + %1$s kanpoan dago + %1$s lanpetuta dago + %1$s lineaz kanpo dago + %1$s etorri da + %1$s joan da + Argazkia bidali + Fitxategia bidali + Audioa bidali + Argazkia atera + Fitxategi transferentzia + Transferentzia bukatuta + Transferenzia aurrera doa + Transferentzia onartu? + fitxategi hau bidali nahi dizu + Bidali + Mezua bidali + Mezu segurua bidali + Berbidali + Berriketa amaitu + Berriketa ezabatu + Txertatu aurpegiera + Txatak trukatu + Menua+ + Lotura aukeratu + + %1$s erabiltzaileak talde berriketara gonbidatu zaitu. + %1$s erabiltzaileari gonbidapena bidalita. + Onartu + Ukatu + Talde berriketa + Talde berriketa sortu edo sartu + Talde berriketara konektatzen… + Gonbidatu\u2026 + Gonbidatzeko kontaktua(k) hautatu + Idatzy kontaktua bilatzeko + Ez dago kontakturik. + +Ikutu gonbidatzeko. + Ezin da %1$s erabiltzailearen harpidetza onartu. Saiatu geroago mesedez. + Ezin da %1$s erabiltzailearen harpidetza ukatu. Saiatu geroago mesedez. + Enkriptazioa hasi Enkriptazioa gelditu - Berriketa ezabatu - Berriketa amaitu - - - Kontaktu lista - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Berriketa zifratuko saioa hasten… + Berriketa zifratuko saioa bukatzen… + Segurtazun hatzmarka + Segurtazun hatzmarka + Saioa hasi + Giltza birsortu + Zifratzea ezgaituta dago + Zifratzea gaituta (Ikutu egiaztatzeko) + Zure kontaktuak berriketa zifratua gelditu du. + Zifratuta eta egiaztatuta! + OTR gako bikote berria sortzen… + + Inportatzeko OTR gako biltegi bat topatuta. QR pasahitza orain eskaneatu nahi duzu? + Gako Sinkronizazioa gaitu + OTR gako parea ongi inportatu da + + QR eskaneatu + Zure hatz-marka + Eskuz + Galdera + Segurtasun hatzmarka (egiaztatuta) + Ziur hatzmarka hau baieztatu nahi duzula? + Hatzmarka baieztatu? + Hurruneko hatzmarka egiaztatua izan da! + Zuretzako hatzmarka + Honentzako hatzmarka: + + Kautotzea + Sartu zure kontaktura bidaliko den galdera eta dagokion erantzuna, benetan bera dela egiaztatzeko. + egingo den galdera + erantzun egokia + Zure kontaktua ondo kautotu zaitu. Orain zuk kautotu behar duzu, zure galdera propioa eginez. + Bidali + Utzi + + Dei segurua + Dei seguruak + Hemen OStel.co edo beste SIP zerbitzu segurua sartu dei integraziorako + + Kontu konfigurazioa + + Segurtasuna eta pribatutasuna + Enkriptazioa eta Anonimotasuna + Enkriptatu piztu/itzali + Pasahitzaren iraungitzea + Zifratze aplikazioa desblokeatuta mantenduko den denbora + + Erabiltzaile interfazea + Hizkuntza + Hizkuntzak + Itxura iluna erabili + Aplikazioaren itxura ilunera aldatu + Memoriako mezu biltegia bakarrik + Mezuak memoriak gordeko dira bakarrik, ez flash biltegian, mezuen eskuratzea saihesteko. (Mezuen galera ekar dezake) + Atzeko irudia + Aplikazioaren atzeko planorako irudiarentzako helbidea (\"/sdcard/foo.jpg\") zehaztu + Kontaktu sareta erakutzi + Kontaktuen zerrenda avatar-en sareta moduan erakutsi + Bai, guztia onartu + + Bestelako ezarpenak + Hasi ChatSecure automatikoki + Hasi eta berehala lehendik saioa hasitan zuten kontuekin saioa hasi + Lineaz kanpoko kontaktuak ezkutatu + Lehen planoko lehentasuna erabili + Android-ek zure konexio zerbitzua berrazieratzeko aukerak txikitu. Janikarazpen arean jakinarazpen bat jarriko du. + Taupada tartea + Balio handiagoa erabili (minututan) bateria aurrezteko. Balio handi batekin hornitzaileak konexioa itxi lezake, jarduera faltagatik. + + Jakinarazpen ezarpenak + BM ezarpenak + Jakinarazi egoera barran BM bat heltzerakoan + Bibratu + BM bat heltzerakoan ere bibratu + Soinua + BM bat heltzerakoan ere dei-tonua jo + ChatSecure-rentzat doinu berezia erabili + + Arazketa log-ak gaitu + Aplikazioaren arazketarako datuak irteera estandarrera / logcat-era bidali. + + Sareko datuak ezgaituta + +Sareko datuen konektibitatea (baita atzeko planoko datuena) beharrezkoa da aplikazioan saioa has dezazun. + + + Gaitu + Irten + Zerbitzu guztietan saioa bukatu ETA prozesu guztiak akatu nahi dituzu (irteera gogorra)? + Kontu berria sortu? + \'%1$s\' erabiltzailearentzako berriketa berria sortu? + + %1$s mezu berriak + Talde txat gonbidapena + Mezu berria(k): + ChatSecure zerbitzua hasten… + Gaituta eta desblokeatuta + + Arreta + %1$d errore kodea + Zerrenda ez da gehitu. + Kontaktua ez zegoen blokeatuta + Kontaktua ez zegoen desblokeatuta + Hautatu kontaktu bat lehenbizi, mesedez. + Deskonektatuta! + + Zerbitzu errorea! + Kontaktu zerrenda ez da kargatu. + Ezin da zerbitzarira konektatu. Egiaztatu zure konexioa. + "%1$s" dagoeneko zure kontaktu zerrendan dago. + "%1$s" kontaktua blokeatua izan da + Mesedez itxaron zure kontaktu zerrenda kargatu bitartean. + Sare akats bat gertatu da. + Konexio honetarako WiFi-a beharrezkoa da. + Zerbitzariak ez du funtzionalitate hau onartzen. + Sartu duzun pasahitza ez du balio. + Zerbitzariak akats bat aurkitu du. + Zerbitzariak ez du funtzionaltasun hau. + Zerbitzaria oran ez dago eskuragarri. + Zerbitzariak denbora muga gainditu du. + Zerbitzariak ez du egungo bertsioa onartzen. + Mezu ilara beterik dago. + Zerbitzariak ezin du domeinu horretara bideratu. + Sartu duzun erabiltzaile izena ezezeguna da. + Barkatu, erabiltzaileak blokeatu zaitu. + Saioa iraungi da, saioa hasi berriro mesedez. + beste bezero batetik saioa hasi duzu. + beste bezero batetik saioa hasi duzu. + Barkatu, SIM txartelean ezin da telefono zenbakia irakurri. Eskatu laguntza zure operadoreari. + Ez duzu saioa hasita. + ChatSecurek errorea izan du zure erabiltzaile izena edo pasahitza balioztatzean - biak egiaztatu eta berriro saiatu mesedez. + ChatSecure-k errorea izan du gako bikotea sortzean. + ChatSecure-k errorea izan du berriketa zerbitzarira konektatzean - konfigurazioa egiaztatu eta saiatu berriro mesedez. + ChatSecure-k errorea izan du konektatzean - egiaztatu sarera konektatuta zaudela eta saiatu berriro, mesedez. + ChatSecure-k sarerako konexioa galdu du + ChatSecure konexioa berrezartzen saiatzen dabil + ez duzu @ostalaria.com zatia jarri zure kontuaren ID-an. Saiatu berriz! + Zure zerbitzariaren ostalari izenak ez du .com, .net edo antzeko atzizkirik. Saiatu berriz! + Sartu zure pasahitza: + Tor erabiltzen zaudenez, XMPP \'Konektatzeko zerbitzaria\'-ren ostalari izena kontuaren ezarpen aurreratuetan sartu behar duzu. + Ezin da talde berriketara sartu + Barkatu, ezin dugu fitxategi mota hori partekatu + Fitxategiak partekatzeko zifratzea gaitu behar duzu + Berriketaren zifratzea gaitu fitxategiak partekatzeko mesedez + ChatSecure-k bere eginkizunak kentzeko eskaera egin dela ikusi du. Ziurrenik ChatSecure apurtuko da. Mesedez, kontaktuen zerrendako menuko Saio guztiak itxi elementua erabili. + Ez dago fitxategi mota honentzako aplikaziorik + Mesedez elkarrizketa segurua hasi kodeak eskaneatzen hasi baino lehen + Ez da OTR gako parea inportatu; egiaztatu fitxategia formatu eta kokapen egokian dagoela + Mesedez, kopiatu KeySync ordenagailuko aplikazioak sortuta \'otr_keystore.ofcaes\' fitxategia zure gailuaren biltegiko erro karpetara. + Mezu ezin izan da bidali. + Mezuak berkonektatzean bidaliko dira + %1$s erabiltzailea lineaz kanpo dago. Bidaltzen dituzun mezuak%1$s konektatzen denean bidaliko zaizkio. + %1$s ez dago zure kontaktu zerrendan. + Kontu ezarpenak + + KONTUZ: ChatSecure-ren bertsio hau kaleratze azkar bat da eta segurtasun zulo edo akatsak eduki ditzake. - - + Taldeak + elkarrizketa(k) ireki + Irten + Ikara + Lagunak + Kontaktuak + Zerbitzariko ziurtagiria onartu? + Hatzmarka + + + Kontaktu zerrenda + Ezarpenak + Kontuak + Saioa amaitu + Kontaktu zerrenda + + Jabber (XMPP) + Area lokala (Bonjour/ZeroConf) + Google kontua + dukgo.com + + + Linean + Lanpetuta + Kanpoan + Inaktibo + Lineaz kanpo + Lineaz kanpo agertu + + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Kontu berria diff --git a/res/values-fa/arrays.xml b/res/values-fa/arrays.xml index c56210758..5fc680b36 100644 --- a/res/values-fa/arrays.xml +++ b/res/values-fa/arrays.xml @@ -6,10 +6,4 @@ هرطور که درخواست شده غیرفعال / هرگز - - اجباری - اتوماتیک - بصورت درخواست شده - غیرفعال - diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 2679ed196..f3b6c9799 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -1,395 +1,451 @@ - + + + چت امن + چت امن + خواندن پیام های فوری - \nبه برنامه ها اجازه خواندن اطلاعات از ارایه دهنده محتوا در مسنجر را میدهد\n + +به برنامه ها اجازه خواندن اطلاعات از ارایه دهنده محتوا در مسنجر را میدهد + نوشتن پیام های فوری - \nبه برنامه ها اجازه میدهد به ارایه دهنده محتوا در مسنجر اطلاعات وارد کنند \n - - - + +به برنامه ها اجازه میدهد به ارایه دهنده محتوا در مسنجر اطلاعات وارد کنند + + شروع سرویس مسنجر + به برنامه ها اجازه میدهد که از طریق intent سرویس پیام فوری را شروع کنند. + + تایید + باشه + انصراف + باشه + انصراف + بعدی + بازگشت + اتصال + بازی + راه اندازی + + باز + چت امن قفل شده + تنظیم رمز عبور + تایید رمز عبور + رمز عبور + تایید رمز عبور جدید + رمز عبور مطابقت ندارد، لطفا دوباره سعی کنید + درشدن >> + اطلاعات سریع + ورود رمز عبور لطفا + رمز عبور لطفا … + عبارت عبور + عبارت عبور (دوباره) + یک حساب کاربری انتخاب کنید - + یک حساب کاربری انتخاب کنید + (%1$d) + اضافه کردن %1$s حساب کاربری درباره - + خروج کامل از سیستم + آیا میخواهید از همه خدمات خارج شوید؟ + شما با حساب کاربری %1$s از سیستم خارج شدید + شما از %1$s خارج شده‌اید چرا که %2$s + اولین بار با استفاده از چت امن؟ + نمیتوانید برای شروع منتظر شوید؟ + شروع کنید + راه اندازی حساب کاربری + راه اندازی گذرواژه + گذرواژه: + گذرواژه (دوباره): + در حال رمزگذاری روی متن موجود با رمز عبور جدید. صبور باشید … + کلمه عبور خود را وارد کنید: + کلمه عبور به اندازه کافی طولانی نیست + عبارت عبور شما دارای حروف بزرگ نیست + عبارت عبور شما دارای حروف کوچک نیست + عبارت عبور شما دارای اعداد نیست + + درباره چت امن + + چگونه امن است؟ + \'پیام رسانی محرمانه\' یک سیستم امنیتی است که برای بوجود آوردن حریم خصوصی، بر مبنای تقلید از ویژگیهای یک مکالمه خصوصی در دنیای حقیقی طراحی شده است، که شامل رمزگذاری، احراز هویت، قابلیت انکار و رازداری میشود. پروتکل محرمانه با کلاینتهای گفتگوی دستکتاپ از قبیل Pidgin و Adium سازگار است. + + آیا گفتگوهای من امن هستند؟ + + حساب کاربری جدید اضافه کردن حساب کاربری - ویرایش حساب کاربری - حذف حساب کاربری - - خروج کامل از سیستم - - - یک حساب کاربری انتخاب کنید - - - + حساب های موجود + + کاربر@میزبان + رمز عبور: + رمز عبور من را بخاطر بسپار. + من را بصورت خودکار وارد کن. + حساب کاربری ندارید؟ + ورود + برای امنیت خودتان، اگر گوشی شما گم یا دزدیده شده ، به وب سایت بر روی کامپیوتر خود مراجعه کنید و رمز عبور خود را تغییر دهید. + این گزینه شما را هربار بصورت خودکار وارد برنامه میکند. برای غیرفعال کردن این گزینه، از حساب کاربری خود خارج شوید و گزینه ؛بصورت خودکار من را وارد کن؛ را غیر فعال کنید + اتصال به واسطه تور (به برنامه رباط پیازی احتیاج دارد) + user@domain.com + نام کاربری جدید + ارائه دهنده خدمات (dukgo.com, jabber.ccc.de) + رمزعبور + تایید رمز عبور + تنظیمات پیشرفته حساب کاربری + نوع حساب کاربری + ثبت نام حساب کاربری + ماندگاری + رمز عبور را بخاطر بسپار + رمز عبور کش شده + رمز عبور کش نشده + ورود خودکار + بهنگام راه اندازی چت امن متصل شد + بهنگام راه اندازی چت امن متصل نشد + الان وارد شوید + پس از انجام تنظیمات، متصل شو + پس از انجام تنظیمات، متصل نشو + شخصی (انتخابی) + نام مستعار حساب کاربری (نام شما) + حساب کاربری شما روی خط چگونه دیده میشود + نمایه + شرح مختصری از خودتان + رمزگذاری اجباری / متن ساده را رد کن + هرزمان که ممکن است، گفتگو را بطور خودکار رمز گذاری کن + رمزگذاری گفتگو بر اساس درخواست + غیرفعال کردن رمزگذاری گفتگو + تایید اعتبارنامه ها… + درحال تولید یک جفت کلید… + درحال ورود… + ویزارد حساب کاربری + شناسه حساب کاربری شما + پیکربندی سرور + آماده اید؟ + لطفا حساب کاربری خود را وارد کنید (user@hostname): + لطفا عدد پورت (پیشفرض 5222 است) و نام میزبان سرور خود را وارد و یا ویرایش کنید. + اوربات(تور) + دامنه خدمات چت + ثبت نام در حساب کاربری جدید … + گرفتن رفتن … انصراف از ورود - - + امضا\u2026 + + خروج از سیستم \U2026 + + جستجوی SRV انجام بده + از DNS SRV برای پیدا کردن سرور حقیقی XMPP از نام دامنه، استفاده کن + اجازه فرستادن نام کاربری و رمز عبور با متن ساده، وقتی از حمل و نقل رمزگذاری نشده استفاده میشود + اجازه احراز هویت با متن ساده + تایید کن که گواهی مورد اطمینان است + تایید TLS + نیاز به اتصال TLS + رمزگذاری حمل و نقل + گفتگوهای رمزگذاری شده چطور شروع شده اند + سرور برای برقراری ارتباط، در شرایط نیاز + اتصال سرور + درگاه TCP برای سرور XMPP + پورت سرور + XMPP منبع + برای تشخیص این ارتباط از بقیه کلاینت هایی که وارد شده اند + XMPP اولویت منابع + پیامهای ارسالی به کلاینت هایی که بیشتر از یک منبع فعال دارند به منبعی فرستاده میشود که بالاترین اولویت را دارد + + گفت و شنود + گفتگویی وجود ندارد + +اینجا را کلیک کنید تا شروع کنید + هیچ شناسه‌ی\nتنظیم‌شده‌ای ندارید.\n\nکلیک کنید تا یکی بسازید! + لیست تماس - %1$s افزودن تماس - حذف تماس - انسداد تماس - + تماس با نام مستعار مسدود شده - - حساب های کاربری - + نام مستعار + تماس "%1$s" حذف شد. + تماس "%1$s" مسدود شد. + تماس "%1$s" دیگر مسدود نیست. + تماس "%1$s" اضافه شد. + این تماس "%1$s" حذف خواهد شد. + این تماس "%1$s" مسدود خواهد شد. + این تماس "%1$s" دیگر مسدود نخواهد بود. گفتگوی تازه - - حساب کاربری جدید - - تنظیمات - جستجوی تماس ها - + نمایش شبکه شروع گفتگو - - خروج - - تایید - - - شروع رمزگذاری - توقف رمزگذاری - پاک کردن گفتگو - پایان گفتگو - - - لیست تماس - - دعوت کن\u2026 - - تعویض گفتگو - - درج صورتک - ارسال دوباره - - اسکن اثر انگشت - اثر انگشت شما - نگارش دوباره اثر انگشت - تایید راز - - فهرست + \" - - - - تایید - - آیا میخواهید از همه خدمات خارج شوید؟ - - - - - - - - باشه - - انصراف - - باشه - - انصراف - - - - - - - - - - کاربر@میزبان - - رمز عبور: - - رمز عبور من را بخاطر بسپار. - - من را بصورت خودکار وارد کن. - - - برای امنیت خودتان، اگر گوشی شما گم یا دزدیده شده ، به وب سایت بر روی کامپیوتر خود مراجعه کنید و رمز عبور خود را تغییر دهید. - این گزینه شما را هربار بصورت خودکار وارد برنامه میکند. برای غیرفعال کردن این گزینه، از حساب کاربری خود خارج شوید و گزینه ؛بصورت خودکار من را وارد کن؛ را غیر فعال کنید - - ورود - - - - امضا\u2026\n - - - داده های پس زمینه غیر فعال شده است - - - - - - فعال - - خارج شو - - - - - - + نمایش مشخصات + بررسی تماس با + گفتگوهای درحال انجام (%1$d) + %1$d روی خط دعوتنامه های دوستی - (ناشناخته) - خالی - گفتگویی نیست - - انتخاب تماس(ها) برای دعوت - برای پیدا کردن تماس، تایپ کنید - تماسی پیدا نشد. - - - - تماس مسدودشده ای وجود ندارد. - - + تماس + برای گفتگو، نام ارتباط مورد نظر را بنویسید. + برو + گفتگوی فعالی نیست + افزودن تماس + آدرس پست الکترونیک شخصی که میخواهید دعوتش کنید: + یک لیست انتخاب کنید: + یک اسم تایپ کنید که از تماسها اضافه شود + ارسال دعوت نامه مشخصات تماس - وضعیت: - نوع کلاینت: - کامپیوتر - موبایل - - - روی خط - - مشغول - - دور - - تنبل - - آفلاین - - آفلاین به نظر برسید - - - - - + تماس های مسدود شده - %1$s + تماس مسدودشده ای وجود ندارد. + + گفتگو با %1$s من - - برای نوشتن پیام شروع به تایپ کنید - - - - - - - - - - - - - - - - + %1$s روی خط است + %1$s دور است + %1$s مشغول است + %1$s روی خط نیست + %1$s پیوست + %1$s ترک کرد + ارسال عکس + ارسال فایل + ارسال صوتی + عکس گرفتن + انتقال فایل + انتقال کامل شد + انتقال در حال پیشرفت است + پذیرفتن انتقال؟ + می خواهید فایل خود را ارسال کنید + بدون اتصال در دسترس برای ارسال سهم خود! ارسال - - این پیام نتوانست ارسال شود. - - ارتباط با سرور از دست رفت. پیام شما وقتی ارسال میشود که روی خط باشید. - - - - - + ارسال یک پیام + ارسال پیام امن + ارسال دوباره + پایان گفتگو + پاک کردن گفتگو + درج صورتک + تعویض گفتگو + فهرست + \" لینک را انتخاب کنید - - گفتگوی فعالی نیست - - شروع رمزگذاری جلسه گفتگو... - توقف رمزگذاری جلسه گفتگو... - - - افزودن تماس - - آدرس پست الکترونیک شخصی که میخواهید دعوتش کنید: - - یک لیست انتخاب کنید: - - یک اسم تایپ کنید که از تماسها اضافه شود - - ارسال دعوت نامه - - Jabber/XMPP - بنجور (زیروکانف) - + حذف + نگاه داشتن فایل + حدف ذخیره شده های برنامه چت امن + حذف اصلی؟ + نگهداری + خروجی + خروجی فایل رسانه ای؟ + این فایل چند رسانه‌ای به %1$s منتقل می‌شود. + اتمام چت؟ + پایان دادن به چت و حذف فایل ها + + %1$s از شما برای پیوستن به یک گفتگوی گروهی دعوت کرده است. + دعوت نامه فرستاده شده است٪ 1 $ ثانیه. + بپذیر + رد کن + گروه چت + ایجاد و یا پیوستن به گپ گروهی + اتصال به چت گروهی … + دعوت کن\u2026 + انتخاب تماس(ها) برای دعوت + برای پیدا کردن تماس، تایپ کنید + کانتکتی پیدا نشد.\n\nکلیک کنید تا دعوت شود. + افزودن %1$s؟ + بله + خیر + ناتوان در تصویب اشتراک از %1$sلطفن بعد از مدتی دوباره تلاش کنید. + ناتوان در رد اشتراک از %1$sلطفن بعد از مدتی دوباره تلاش کنید. + + شروع رمزگذاری + توقف رمزگذاری + شروع رمزگذاری جلسه گفتگو… + توقف رمزگذاری جلسه گفتگو… + امنیت اثر انگشت + امنیت اثر انگشت + ورود + تولید دوباره کلید + رمزنگاری خاموش است + رمزنگاری روشن است (به منظور بررسی ضربه بزنید) + رمزنگاری شده و تایید! + درحال تولید یک جفت کلید OTR… + + همگام سازی کلید فعال سازی + + اسکن QR + اثر انگشت شما + دستی + سوال + امنیت اثر انگشت ( تایید) + تایید اثر انگشت؟ + اثر انگشت از راه دور، تأیید شده است. + اثر انگشت برای شما + اثر انگشت برای + نصب اسکنر بارکد ؟ + + تصدیق + بمنظور تایید ارتباط و هویت، یک سوال و پاسخی که از ایشان انتظار دارید را وارد کنید. + سوال برای پرسیدن + پاسخ مورد انتظار + دوست شما، هویت شما را تایید کرده. حالا شما برای تایید هویت دوستتان سوال خود را بپرسید + OTR و تاییدیه + رمزگذاری چت + ارسال + انصراف + + تماس امن + صدای امن + راه اندازی حساب کاربری + + امنیت و حریم خصوصی رمزگذاری و گمنامی + رمزگذاری روشن/خاموش + اتمام مهلت رمز عبور + + لینک های قابل کلیک در تور + رابط کاربری + زبان + زبان ها + استفاده از پیش فرض سیستم + استفاده از تم تیره + ظاهر برنامه را به تیره تغییر بده + تنها پیام‌های داخل حافظه + تصویر پس زمینه + نمایش تماس با شبکه + نمایش لیست تماس به عنوان شبکه آواتار + بله، قبول همه + حذف ناامن رسانه + فروشگاه رسانه در ذخیره سازی خارجی + + ذخیره سازی خارجی گم شده + چت رسانه فروشگاه گمشده + حذف سیاهه چت + + میزان سازی دیگر + ChatSecure را خودکار اجرا کن کاربرهایی که روی خط نیستند را مخفی کن - - تنظیمات رخدادها - - آگاه سازی پیام فوری - - وقتی یک پیام فوری میرسد در نوار وضعیت آگاه سازی کن - از اولویت پیش زمینه استفاده کن احتمال قطع و وصل شدن سرویس ارتباطمان توسط اندروید را کاهش میدهد. این تنظیم یک رخداد به بخش رخدادها اضافه خواهد کرد. فاصله ضربان + + تنظیمات رخدادها + آگاه سازی پیام فوری + وقتی یک پیام فوری میرسد در نوار وضعیت آگاه سازی کن ویبره - وقتی یک پیام فوری میرسد ویبره را فعال کن - صدا - وقتی یک پیام فوری میرسد زنگ بزن - - انتخاب آهنگ زنگ - - - - - - - بپذیر - - رد کن - - - - بپذیر - - رد کن - - - - - - - - - - - - + انتخاب سفارشی آهنگ های زنگ + + فعال کردن اشکال زدایی سیاهه ها + + داده های پس زمینه غیر فعال شده است + %1$s احتیاج دارد که داده های پس زمینه فعال باشد. + فعال + خارج شو + ایجاد حساب کاربری جدید؟ + چت جدید برای شناسه‌ی \'%1$s\' می‌سازید؟ + اطلاعات گواهی نامه + گواهی نامه: + صدور توسط: + SHA1 اثر انگشت: + موضوع: + تاریخ انقضا: + پیوستن به چت روم؟ + تصویر زمینه + آیا می‌خواهید یک پس‌زمینه از گالری تصاویر انتخاب کنید؟ + انتخاب تصویر + + پیام %1$s جدید + %1$d چت نخوانده + درخواست دوستی جدید از %s + فایل جدید %1$s از %2$s دعوتنامه گفتگوی گروهی - - - - - - - - - - - - شروع سرویس مسنجر - به برنامه ها اجازه میدهد که از طریق intent سرویس پیام فوری را شروع کنند. - - + درخواست چت گروهی از سوی %s + پیام(های) تازه از طرف + شروع خدمات ChatSecure … + فعال و غیر بسته + پیام کپی شد در کلیپ بورد + توجه - - - + خطا: + خطای شماره %1$d لیست اضافه نشد. - تماس مسدود نشد. - تماس از حالت انسداد خارج نشد - لطفا ابتدا یک تماس انتخاب کنید - - قطع شد!\n - + قطع شد! + خطای سرویس! - لیست تماس بارگیری نشد. - نمیتواند به سرور متصل شود. لطفا ارتباط خود را بررسی کنید. - - - - - + "%1$s" هم اکنون در لیست تماس شما موجود است. + تماس "%1$s" مسدود شد. لطفا تا زمان بارگیری لیست تماس صبر کنید - در شبکه خطا رخ داده است. برای این ارتباط، WiFi مورد نیاز است. - سرور از این قابلیت پشتیبانی نمیکند. - رمز عبور وارد شده معتبر نیست. - سرور با یک خطا مواجه شد. - سرور از این قابلیت پشتیبانی نمیکند. - در حال حاضر سرور در دسترس نیست. - زمان ارتباط با سرور به پایان رسید. - سرور از این نسخه پشتیبانی نمیکند. - صف پیام پر است. - سرور از ارسال به دامنه پشتیبانی نمیکند. - نام کاربری که وارد کرده اید به رسمیت شناخته نشده. - با تاسف، شما توسط این کاربر مسدود شده اید. - جلسه منقضی شده، لطفا دوباره وارد شوید. - شما از کلاینت دیگری وارد شده اید. شما هم اکنون از کلاینت دیگری وارد شده اید. - با تاسف، شماره تلفن از روی سیم کارت شما خوانده نمیشود. لطفن با اوپراتور خود برای کمک تماس بگیرید. - شما درحال حاضر وارد سیستم نشده اید. - - - + شبکه آفلاین است. + ChatSecure در حال تلاش برای برقراری مجدد ارتباط است + شما برای شناسه حساب کاربری خود، قسمت @hostname.com را وارد نکرده اید. لطفن دوباره تلاش کنید! + نام میزبان شما پسوند .com یا .net یا شبیه به اینها را نداشت. لطفا دوباره تلاش کنید! + رمز عبور خود را وارد کنید: + امکان ایجاد یا پیوست به چت گروهی وجود ندارد + متاسفانه امکان به اشتراک‌گذاری این نوع فایل وجود ندارد + باید گزینه‌ی رمزگذاری را فعال کنید تا بتوانید فایل‌ها را بفرستید + خواهشمندیم که گزینه‌ی رمزگذاری چت را فعال کنید تا بتوانید فایل‌ها را بفرستید + داده‌ی ورودی غیر قابل پشتیبانی، نمی‌توان آن را به اشتراک گذاشت! + نمایشگری برای این نوع فایل وجود ندارد + این پیام نتوانست ارسال شود. + %1$sروی خط نیست. پیامی که شما ارسال کرده اید هنگامی به مقصد میرسد که %1$s روی خط بیاید. + %1$s در لیست تماس شما نیست. + کلمه عبور شما مطابقت ندارد + شماره پورت باید یک عدد باشد + خطای انتقال + امکان خواندن فایل وجود ندارد + تنظیمات حساب کاربری + + گروه ها + مکالمه باز (مکالمات باز) + خاموش و قفل + وحشت + دوستان + اطلاعات تماس + پذیرفتن گواهی نامه سرور؟ + نمایش اثر انگشت شما + در حال بارگذاری … + + درباره چت امن\nhttps://guardianproject.info/apps/chatsecure/ + + لیست تماس + تنظیمات + حساب های کاربری + خروج + لیست تماس + + Jabber/XMPP + حساب گوگل + dukgo.com + + + روی خط + مشغول + دور + تنبل + آفلاین + آفلاین به نظر برسید + خوشحال غمگین @@ -409,7 +465,7 @@ خندان گیج - + خوشحال غمگین @@ -448,112 +504,25 @@ :-D o_O - - لیست تماس - رمزگذاری روشن/خاموش - اتصال به واسطه تور (به برنامه رباط پیازی احتیاج دارد) - - نمیتوانید برای شروع منتظر شوید؟ - شروع کنید - راه اندازی حساب کاربری - - - چگونه امن است؟ - \'پیام رسانی محرمانه\' یک سیستم امنیتی است که برای بوجود آوردن حریم خصوصی، بر مبنای تقلید از ویژگیهای یک مکالمه خصوصی در دنیای حقیقی طراحی شده است، که شامل رمزگذاری، احراز هویت، قابلیت انکار و رازداری میشود. پروتکل محرمانه با کلاینتهای گفتگوی دستکتاپ از قبیل Pidgin و Adium سازگار است. - - آیا گفتگوهای من امن هستند؟ - - راه اندازی گذرواژه - گذرواژه: - گذرواژه (دوباره): - - user@domain.com - رمزعبور - تنظیمات پیشرفته حساب کاربری - نوع حساب کاربری - - ماندگاری - رمز عبور را بخاطر بسپار - رمز عبور کش شده - رمز عبور کش نشده - ورود خودکار - الان وارد شوید - پس از انجام تنظیمات، متصل شو - پس از انجام تنظیمات، متصل نشو - - شخصی (انتخابی) - نام مستعار حساب کاربری (نام شما) - حساب کاربری شما روی خط چگونه دیده میشود - نمایه - شرح مختصری از خودتان - - رمزگذاری اجباری / متن ساده را رد کن - هرزمان که ممکن است، گفتگو را بطور خودکار رمز گذاری کن - رمزگذاری گفتگو بر اساس درخواست - غیرفعال کردن رمزگذاری گفتگو - - تایید اعتبارنامه ها... - درحال تولید یک جفت کلید... - درحال ورود... - - اثر انگشت آنها - اثر انگشت شما - ورود - تولید دوباره کلید - هشدار: این گفتگو رمزگذاری نشده - این گفتگو امن است اما هویت شرکت کنندگان تایید نشده - هشدار: رمزگذاری گفتگو متوقف شده است. - این گفتگو امن و تایید شده است - ویزارد حساب کاربری - شناسه حساب کاربری شما - پیکربندی سرور - آماده اید؟ - لطفا حساب کاربری خود را وارد کنید (user@hostname): - لطفا عدد پورت (پیشفرض 5222 است) و نام میزبان سرور خود را وارد و یا ویرایش کنید. - شما برای شناسه حساب کاربری خود، قسمت @hostname.com را وارد نکرده اید. لطفن دوباره تلاش کنید! - نام میزبان شما پسوند .com یا .net یا شبیه به اینها را نداشت. لطفا دوباره تلاش کنید! - رمز عبور خود را وارد کنید: - - تنظیمات حساب کاربری - defLoc\n - - گروه ها - درحال تولید یک جفت کلید OTR... - تماس - برای گفتگو، نام ارتباط مورد نظر را بنویسید. - برو - زبان - زبان ها - InTheClear چه زبانی را باید نمایش بدهد؟ - جستجوی SRV انجام بده - از DNS SRV برای پیدا کردن سرور حقیقی XMPP از نام دامنه، استفاده کن - اجازه فرستادن نام کاربری و رمز عبور با متن ساده، وقتی از حمل و نقل رمزگذاری نشده استفاده میشود - اجازه احراز هویت با متن ساده - تایید کن که گواهی مورد اطمینان است - تایید TLS - نیاز به ارتباط TLS/SSL - رمزگذاری حمل و نقل - گفتگوهای رمزگذاری شده چطور شروع شده اند - سرور برای برقراری ارتباط، در شرایط نیاز - اتصال سرور - درگاه TCP برای سرور XMPP - پورت سرور - XMPP منبع - برای تشخیص این ارتباط از بقیه کلاینت هایی که وارد شده اند - XMPP اولویت منابع - پیامهای ارسالی به کلاینت هایی که بیشتر از یک منبع فعال دارند به منبعی فرستاده میشود که بالاترین اولویت را دارد - بعدی - بازگشت - گفت و شنود - پیام(های) تازه از طرف - تصدیق - بمنظور تایید ارتباط و هویت، یک سوال و پاسخی که از ایشان انتظار دارید را وارد کنید. - سوال برای پرسیدن - پاسخ مورد انتظار - دوست شما، هویت شما را تایید کرده. حالا شما برای تایید هویت دوستتان سوال خود را بپرسید - گفتگویی وجود ندارد\n\nاینجا را کلیک کنید تا شروع کنید - - + حساب های موجود + حساب گوگل + وای فای شبکه چت + فعال کردن وای فای چت + حساب کاربری جدید + ایجاد حساب جدید + هویت مخفی! + تولید هویت + این یک چت گروهی است + [ارسال مجدد] + [ارسال مجدد] + قادر نیست به اشتراک گذاشتن امن این فایل + نصب اوربات؟ + همیشه + شروع اوربات؟ + نام مستعار برای استفاده در اتاق + نام اتاق برای ایجاد یا ورد + سرور چت گروهی (conference.foo.com) + یک پیام رمزگذاری شده‌ی غیر قابل خواندن دریافت کردید. + نمی‌توانم پیمی که فرستادی رام رمزگشایی کنم. + < به چپ و راست بروید برای گزینه‌های بیش‌تر > diff --git a/res/values-fi-rFI/arrays.xml b/res/values-fi-rFI/arrays.xml index c757504ac..da0f83b80 100644 --- a/res/values-fi-rFI/arrays.xml +++ b/res/values-fi-rFI/arrays.xml @@ -1,2 +1,9 @@ - + + + Pakota / Vaadi + Yritä automaattisesti + Kuten pyydetty + Pois päältä / Ei koskaan + + diff --git a/res/values-fi-rFI/strings.xml b/res/values-fi-rFI/strings.xml index e9be367c5..da8e37457 100644 --- a/res/values-fi-rFI/strings.xml +++ b/res/values-fi-rFI/strings.xml @@ -1,235 +1,525 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + ChatSecure + ChatSecure + + lue pikaviestejä + +Sallii tietojen lukemisen pikaviestisovelluksista. + kirjoita pikaviestejä + +Sallii tietojen kirjoittamisen pikaviestisovelluksiin. + käynnistä pikaviestinpalvelu + Sallii sovellusten käynnistää pikaviestinpalvelu. + + Vahvista + OK + Peruuta + OK + Peruuta + Seuraava + Takaisin + Liity + Toista + Asetus + + Avaa + ChatSecure lukittu + Aseta salasana + Vahvista salasana + Salasana + Uusi salasana + Vahvista uusi salasana + Salasanat eivät täsmänneet, yritä uudelleen + Olen laiska (Ei salasanaa) + Tietokysely + Syötä salasanasi, kiitos + Salasana kiitos… + Tunnuslause + Tunnuslause (uudelleen) + + Valitse tili + Valitse tili + (%1$d) + Lisää %1$s tili + Tietoa + Kirjaudu ulos kaikista + Haluatko kirjautua ulos kaikista palveluista? + Olet kirjautunut ulos palvelusta %1$s. + Olet kirjautunut ulos palvelusta %1$s, koska %2$s + Käytätkö ChatSecurea ensimmäistä kertaa? + Olet innokas aloittamaan? + Aloita + Tilin luominen + Tunnuslauseen asettaminen + Ennen kuin aloitat, valitse tunnuslause suojaamaan sinun ChatSecure -tietojasi luvattomalta käytöltä. + Tunnuslause: + Tunnuslause (uudestaan): + Syötä *uusi* tunnuslause. Sen täytyy sisältää ainakin yksi suuraakkonen, yksi pienaakkonen, yksi numero ja sen täytyy olla pidempi kuin kuusi merkkiä. + Syötä tunnuslauseesi: + Tunnuslauseesi ei ollut riittävän pitkä + Tunnuslauseesi ei sisältänyt yhtäkään suuraakkosta + Tunnuslauseesi ei sisältänyt yhtäkään pienaakkosta + Tunnuslauseesi ei sisältänyt yhtäkään numeroa + + Tietoja ChatSecuresta + ChatSecure on matkapuhelimen pikaviestinsovellus, joka tarjoaa ylimääräisiä turvaominaisuuksia estääseen muita vakoilemasta keskustelujasi ja viestintääsi.\n\nSovellus tukee kaikkia keskustelupalveluita, jotka käyttävät Jabber- tai XMPP-protokollaa, kuten Google GTalk tai Jabber.org. + + Miten se on turvallinen? + + Ovatko keskusteluni turvallisia? + ChatSecure-keskustelun salausominaisuus toimii vain, kun keskustelukumppanillasikin on yhteensopiva sovellus tai ohjelma, joten sinun kannattaa varmistaa, että tuttavasi käyttävät ChatSecurea mobiililaitteilla ja Adiumia tai Pidginiä tietokoneellaan. Tiliasetuksissa voit hienosäätää miten ja milloin ChatSecure pyrkii salaamaan keskustelusi.\n\nAloitetaan! + + Uusi tili + Lisää tili + Muokkaa tiliä + Poista tili + Olemassa oleva tili + + Käyttäjä@Isäntä + Salasana: + Muista salasanani. + Kirjaudu sisään automaattisesti. + Eikö sinulla ole vielä tiliä? + Kirjaudu sisään + Turvallisuussyistä, jos puhelimesi katoaa tai varastetaan, mene verkkosivulle tietokoneellasi ja vaihda salasanasi. + Tämä asetus kirjaa sinut sisään automaattisesti aina kun avaat sovelluksen. Poistaaksesi asetuksen käytöstä, kirjaudu ulos ja poista valinta \"Kirjaudu sisään automaattisesti.\" + Yhdistä Tor:n kautta (Vaatii Orbot-ohjelman) + käyttäjä@verkkotunnus + uusi käyttäjänimi + palveluntarjoaja (dukgo.com, jabber.fi) + salasana + vahvista salasana + Kehittyneet tiliasetukset + Tilin luominen + Rekisteröi tili + Sinnikkyys + Muista salasana + Salasana välimuistissa + Salasana ei välimuistissa + Kirjaudu sisään automaattisesti + Yhdistä heti ChatSecuren käynnistyessä + Älä yhdistä heti ChatSecuren käynnistyessä + Kirjaudu sisään nyt + Yhdistä seuraavat tiliasetukset + Älä yhdistä seuraavia tiliasetuksia + Henkilökohtainen (valinnainen) + Tilin alias (nimesi) + Kuinka tilisi näkyy onlinena + Profiili + Pieni kuvaus itsestäsi + Pakota salaus / kieltäydy selkotekstistä + Kun mahdollista, salaa keskustelut automaattisesti + Salaa keskustelut pyyntöjen mukaisesti + Poista keskustelun salaus + Tietoja tarkistetaan… + Luodaan avainparia… + Kirjaudutaan sisään… + Tilivelho + Tilisi tunnus + Konfiguroi palvelin + Oletko valmis? + Syötä tilisi tunnus konfiguroidaksesi ChatSecure XMPP-palvelullesi. Se näyttää samanlaiselta, kuin sähköpostiosoite: + Syötä tulisi tunnus (käyttäjä@isäntänimi): + Syötä tai muuta jabber/xmpp-palvelusi isäntänimi ja porttinumero (5222 on vakio). + ChatSecure on konfiguroitu. Nyt on aika yhdistää palveluusi ja alkaa keskustella turvallisesti ja yksityisesti! + Orbot (Tor) + Valitse domain + Rekisteröidään uutta tiliä… + alustetaan ChatSecure toimintakuntoon… + Peruuta sisäänkirjautuminen + Kirjaudutaan sisään\u2026 + Kirjautuu ulos\u2026 + + Tee SRV-haku + Käytä DNS:ää löytääksesi todellisen XMPP-palvelimen toimialueen nimestä + Salli käyttäjänimen ja salasanan lähettäminen paljaana tekstinä, kun salaamaton yhteys on käytössä + Salli pelkällä tekstillä tunnistautuminen + Varmista, että sertifikaatti on luotettu + TSL-vahvistus + Vaadi TLS-yhteyttä + Liikenteen salaus + miten salatut keskustelut aloitetaan + Palvelin, johon yhdistetään tarpeen tullen + Yhdistä palvelimeen + XMPP-palvelimen TCP-portti + Palvelimen portti + XMPP-resurssi + XMPP-resurssin prioriteetti + + Keskustelut + Kontaktilista - %1$s + Lisää yhteystieto + Poista yhteystieto + Estä yhteystieto + Kontaktin lempinimi + Estetty + Lempinimi + Yhteystieto \"%1$s\" poistetaan. + Yhteystieto \"%1$s\" estetään. + Yhteystieto \"%1$s\" poistetaan estolistalta. + Yhteystieto \"%1$s\" lisätty. + Yhteystieto \"%1$s\" poistettu. + Contact \"%1$s\" estetty. + Contact \"%1$s\" poistettu estolistalta. + Uusi keskustelu + Etsi yhteystietoja + Näytä ruudukko + Aloita keskustelu + Näytä profiili + Varmista avain + Aktiiviset keskustelut (%1$d) + %1$d online + Kaverikutsut + (Tuntematon) + Tyhjä + Ei keskusteluita + Yhteystieto + Siirry + Ei aktiivisia keskusteluita + Lisää yhteystieto + Kutsuttavan henkilön sähköpostiosoite: + Valitse luettelo: + Kirjoita lisättävän kontaktin nimi + Lähetä kutsu + Yhteystietoprofiili + Tila: + Päätelaitteen tyyppi: + Tietokone + Mobiili + Estetyt yhteystiedot - %1$s + Ei estettyjä henkilöitä. + + Keskustele käyttäjän %1$s kanssa + Minä + %1$s on online + %1$s on poissa + %1$s on kiireinen + %1$s on offline + %1$s on liittynyt + %1$s on poistunut + Lähetä kuva + Lähetä tiedosto + Lähetä ääntä + Ota kuva + Tiedostojen siirto + Siirto valmis + Siirron edistyminen + Hyväksy tiedostonsiirto? + haluaa lähettää sinulle tiedoston + Lähetä + Lähetä viesti + Lähetä turvallinen viesti + Lähetä uudelleen + Lopeta keskustelu + Tyhjennä keskustelu + Lisää hymiö + Vaihda keskustelua + Menu+ + Valitse linkki + Poista + Säilytä tiedostot + Poistetaanko alkuperäinen? + Pidä + Vie + Vie mediatiedosto? + Lopetetaanko keskustelu? + Lopeta keskustelu ja poista tiedostot + + %1$s on kutsunut sinut ryhmäkeskusteluun. + Kutsu on lähetetty käyttäjälle %1$s. + Hyväksy + Hylkää + Ryhmäkeskustelu + Luo tai liity ryhmäkeskusteluu + Yhdistetään ryhmäkeskusteluun… + Kutsu\u2026 + Valitse kutsuttava(t) henkilöt + Kirjoita etsiäksesi henkilöitä + Kontakteja ei löytynyt. + +Klikkaa kutsuaksesi. + Lisää %1$s? + Kyllä + Ei + + Aloita salaaminen + Lopeta salaaminen + Aloitetaan salattua keskusteluistuntoa… + Lopetetaan salattua keskusteluistuntoa… + Turvasormenjälki + Turvasormenjälki + Kirjaudu sisään + Uudista avain + Salaus pois käytöstä + Salaus päällä (Napauta vahvistaaksesi) + Salattu ja vahvistettu! + + OTR-avaintalletuksen tuonti havaittu. Haluatko skannata QR-salasanan nyt? + Aktivoi KeySync + OTR-avainnippu tuotu onnistuneesti. + + Lue QR-koodi + Oma sormenjälkesi + Ohje + Kysymys + Sormenjälkitunnistus (Vahvistettu) + Oletko varma, että haluat vahvistaa tämän sormenjäljen? + Vahvista sormenjälki? + Etäsormenjälki on vahvistettu! + Sormenjälkesi + Sormenjälki käyttäjälle + Asenna viivakoodinlukija? + + Todennus + kysyttävä kysymys + odotettu vastaus + Keskustelun salaus + Lähetä + Peruuta + + Turvallinen puhelu + Turvallinen ääni + Anna OStel.co tai muun turvallisen SIP-palvelutilin tiedot puheluintegrointia varten + + Tiliasetukset + + Turvallisuus ja yksityisyys + Salaus ja anonymiteetti + Salaus päällä/pois + Salasanan aikakatkaisu + Aika, jonka ohjelman salauksen pitäisi pysyä lukitsematta + + Käyttöliittymä + Kieli + Kielet + Käytä järjestelmän oletusta + Käytä tummaa teemaa + Vaihda ohjelman teema tummaksi + Taustakuva + Aseta polku (\"/sdcard/foo.jpg\") sovelluksen taustakuvaan + Näytä kontaktiruudukko + Näytä kontaktilista kuvaruudukkona + Kyllä, hyväksy kaikki + Poista turvaton media + + Poista keskusteluloki + + Muita hienosäätöjä + Käynnistä ChatSecure automaattisesti + Aina käynnistä ja kirjaudu automaattisesti viimeksi sisäänkirjattuihin tileihin + Piilota poissaolevat käyttäjät + + Ilmoitusasetukset + Pikaviesti-ilmoitukset + Ilmoita tilapalkissa kun pikaviesti saapuu + Värinähälytys + Värise pikaviestin saapuessa + Ääni + Soita viestiääni pikaviestin saapuessa + Käytä muokattua soittoääntä ChatSecurelle + + Laita Debug-log päälle + Tulosta ohjelman logitiedot oletuskohteeseen/logcatiin debuggausta varten + + Taustadata poissa käytöstä + + Kirjautumiseen tarvitaan verkkoyhteys (sisältäen taustadatan). + + Ota käyttöön + Lopeta + Tahdotko kirjautua ulos kaikista palveluista JA tappaa kaikki prosessit (pakotettu lopetus)? + Luo uusi tili? + Luo uusi keskustelutili käyttäjänimellä \'%1$s\'? + Varmenteen tiedot + Varmenne: + Myöntäjä: + SHA1-sormenjälki: + Myönnetty: + Vanhenee: + Liitytäänkö chat-huoneeseen? + Ulkoinen sovellus yrittää yhdistää sinua chat-huoneeseen. Sallitko? + Valitse taustakuva + Haluatko valita taustakuvan Galleriasta? + Valitse kuva + + Uusia %1$s-viestejä + %1$d lukematonta keskustelua + Uusi ystäväpyyntö henkilöltä %s + Uusi tiedosto %1$s henkilöltä %2$s + Ryhmächatkutsu + Uusi ryhmächatkutsu henkilöltä %s + Uusia viestejä henkilöltä + Käynnistetään ChatSecure-palvelua… + Aktivoitu & lukitsematon + viesti kopioitu leikepöydälle + + Huomio + Virhe: + Virhekoodi %1$d + Palveluun %1$s ei voitu kirjautua. Yritä uudelleen myöhemmin.\n(Lisätietoja: %2$s) + Listaa ei lisätty. + Kontaktia ei estetty. + Kontaktia ei poistettu estolistalta. + Valitse ensin kontakti + Yhteys katkaistu! + + Palveluvirhe! + Kontaktilistaa ei ladattu. + Palvelimeen ei voitu yhdistää. Tarkista yhteytesi. + \"%1$s\" on jo kontaktilistallasi. + \"%1$s\" on estetty. + Odota, kontaktilistaasi ladataan. + Verkkovirhe. + WiFi-yhteyttä vaaditaan tähän. + Palvelin ei tue tätä toimintoa. + Antamasi salasana ei kelvannut. + Palvelimella tapahtui virhe. + Palvelin ei tue tätä toimintoa. + Palvelimeen ei saatu yhteyttä. + Palvelinyhteys aikakatkaistiin. + Palvelin ei tue tätä versiota. + Viestijono on täynnä. + Palvelin ei tue edelleenlähettämistä tähän osoitteeseen. + Antamaasi käyttäjänimeä ei tunnistettu. + Pahoittelut, käyttäjä on estänyt sinut. + Istunto on vanhennut, kirjaudu uudelleen. + olet kirjautunut sisään toisella ohjelmalla. + olet kirjautunut sisään toisella ohjelmalla. + Pahoittelut, puhelinnumeroa ei voitu lukea SIM-kortiltasi. Ota yhteyttä operaattoriisi avun saamiseksi. + Et ole kirjautunut sisään tällä hetkellä. + ChatSecure kohtasi virheen varmistaessaan käyttäjänimeäsi tai salasanaasi. Tarkista ne ja yritä uudelleen. + ChatSecure kohtasi virheen luodessaan avainparia. + Verkkoyhteyttä ei ole + Et syöttänyt @isäntänimi.com-osaa tunnuksestasi. Yritä uudelleen! + Palvelimesi isäntänimi ei sisältänyt .com, .net tai vastaavaa päätettä. Yritä uudelleen! + Syötä salasanasi: + Koska käytät Torria, sinun täytyy syöttää XMPP-yhteyspalvelimen isäntänimi suoraan Kehittyneisiin lisäasetuksiin. + Ryhmäkeskustelua ei voitu luoda tai siihen ei voitu liittyä + Valitan, kyseistä tiedostotyyppiä ei voi jakaa + Salauksen on oltava päällä tiedostoja jaettaessa + Laita keskustelun salaus päälle siirtääksesi tiedostoja + ChatSecure on havainnut pyynnön prosessinsa sulkemiseen. ChatSecure kaatuu todennäköisesti. Käytä Kirjaudu ulos kaikista -toimintoa tililistalta. + Kyseiselle tiedostotyypille ei ole katseluohjelmaa + Aloita turvallinen keskustelu ennen koodien skannaamista + OTR-avainnippua ei tuotu; Vahvista, että tiedosto on olemassa, oikeassa muodossa ja oikeassa sijainnissa. + Kopioi \'otr_keystore.ofcaes\' tiedosto työpöydän KeySync-työkalusta laitteesi muistin juureen. + Viestiä ei voitu lähettää. + Viestit lähetetään kun yhteys palautuu + %1$s on offline. Viestit toimitetaan, kun %1$s tulee takaisin linjalle. + %1$s ei ole kontaktilistallasi. + Salasanasi eivät täsmää + Porttinumeron täytyy olla luku + Siirtovirhe + Tiliasetukset - - + Ryhmät + avoimet keskustelut + Sammuta & lukitse + Paniikki + Kaverit + Kontaktit + Hyväksy palvelinsertifikaatti? + Sormenjälki + ladataan… + + Tietoa ChatSecuresta\nhttps://guardianproject.info/apps/chatsecure/ + + Kontaktilista + Asetukset + Tilit + Kirjaudu ulos + Yhteystietoluettelo + + Jabber (XMPP) + Google-tili + dukgo.com + + + Paikalla + Varattu + Poissa + Tyhjäkäynti + Poissa linjoilta + Esitä poissaolevaa + + + Iloinen + Surullinen + Silmänisku + Kieli ulkona + Yllättynyt + Suudelma + Huutaa + Viileä + Rahasuu + Jalka suussa + Nolostunut + Enkeli + Epävarma + Itkee + Suu supussa + Nauraa + Hämmentynyt + + + + Iloinen + Surullinen + Silmänisku + Kieli ulkona + Yllättynyt + Suudelma + Huutaa + Viileä + Rahasuu + Jalka suussa + Nolostunut + Enkeli + Epävarma + Itkee + Suu supussa + Nauraa + Hämmentynyt + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Olemassa oleva tili + Yhdistä olemassa olevaan tiliini tietyllä Jabber/XMPP-palvelimella. + Google-tili + Keskustele muille samassa WiFi-verkossa oleville – Internet-yhteyttä tai palvelinta ei tarvita! + Ota WiFi-chat käyttöön + Uusi tili + Rekisteröi uusi, ilmainen tili palveluun meidän valmiilta listalta, tai muuhun valitsemaasi palveluun. + Luo uusi tili + Tämä on ryhmäkeskustelu + [lähetetty uudelleen] + [lähetetty uudelleen] + Asennetaanko Orbot? + Aina + Käynnistetäänkö Orbot? + Orbot ei näytä olevan käynnissä. Haluatko käynnistää sen ja yhdistää Torriin? + lempinimesi tähän huoneeseen + huoneen nimi, jonka luot tai johon liityt + ryhmächatpalvelin (conference.foo.com) + Vastaanotit lukukelvottoman salatun viestin + En voinut purkaa lähettämäsi viestin salausta diff --git a/res/values-fr-rCA/arrays.xml b/res/values-fr-rCA/arrays.xml new file mode 100644 index 000000000..5ccf4897b --- /dev/null +++ b/res/values-fr-rCA/arrays.xml @@ -0,0 +1,9 @@ + + + + Forcer / Exiger + Tenter automatiquement + Tel que demandé + Désactivé / Jamais + + diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml new file mode 100644 index 000000000..bb8dfb50b --- /dev/null +++ b/res/values-fr-rCA/strings.xml @@ -0,0 +1,575 @@ + + + + ChatSecure + ChatSecure + + lire les messages instantanés + Permet aux applications de lire les données du fournisseur de contenu de MI. + écrire des messages instantanés + Permet aux applications d\u2019écrire des données vers le fournisseur de contenu de MI. + démarrer le service de MI + Permet aux applications de démarrer le service de MI par intention. + + Confirmer + OK + Annuler + OK + Annuler + Suivant + Retour + Se connecter + Lire + Configuration + + Ouvrir + ChatSecure est verrouillé + Définir un mot de passe + Confirmer le mot de passe + Mot de passe + En option, vous pouvez définir un mot de passe maître pour ChatSecure afin d\'empêcher l\u2019accès à vos contacts et à vos messages sans mot de passe : + Confirmer le nouveau mot de passe + Les mots de passe ne correspondent pas, veuillez ressayer + Ignorer >> + Demande d\'informations + Veuillez saisir le mot de passe + Mot de passe SVP… + Phrase de passe + Phrase de passe (de nouveau) + + Choisir un compte + Choisir un compte + (%1$d) + Ajouter un compte %1$s + À propos de l\u2019appli + Se déconnecter de tout + Voulez-vous vous déconnecter de tous les services ? + Vous avez été déconnecté de %1$s. + Vous avez été déconnecté de %1$s parce que %2$s + C\u2019est votre première utilisation de ChatSecure ? + Impatient de commencer ? + Commencer + Configuration des comptes + Configuration d\u2019une phrase de passe + Avant de commencer, veuillez choisir une phrase de passe sécurisé pour protéger vos données ChatSecure contre un accès non justifié. + Phrase de passe : + Phrase de passe (de nouveau) : + Saisir une *nouvelle* phrase de passe. Elle doit contenir au moins une majuscule, une minuscule, un chiffre et doit avoir plus de six caractères. + Chiffrement de vos notes existantes avec la nouvelle phrase de passe (patience…) + Saisissez votre phrase de passe : + Bienvenue ! Saisissez une phrase de passe forte pour sécuriser vos notes. Elle doit contenir au moins une majuscule, une minuscule, un chiffre et doit avoir plus de six caractères. + Votre phrase de passe n\u2019 pas assez longue + Votre phrase de passe ne contient aucune lettre majuscule + Votre phrase de passe ne contient aucune lettre minuscule + Votre phrase de passe ne contient aucun chiffre + + À propos de ChatSecure + ChatSecure est une appli mobile de messagerie instantanée fournissant des fonctions supplémentaires de sécurité qui empêchent la surveillance de vos conversations et de vos communications par d\'autres.\n\nL\u2019appli prend en charge tous service utilisant les protocoles Jabber ou XMPP tel que Google GTalk ou Jabber.org. + + Sécurisé ! Comment ? + Une messagerie confidentielle « Off-The-Record » est un système de sécurité conçu pour permettre la confidentialité en imitant les fonctions d\u2019une conversation privée dans le monde réel, incluant le chiffrement, l\'authentification, le déni et la confidentialité persistante.\n\nLe protocole OTR est compatible avec les clients de clavardage de bureau comme Adium ou Pidgin. + + Mes clavardages sont-ils sécurisés ? + La fonction de chiffrement du clavardage ChatSecure ne fonctionne qu\u2019en clavardant avec des utilisateurs d\'applis et de programmes compatibles, et ainsi vous devriez vous assurer que vos contacts utilisent ChatSecure pour appareils mobiles, et Adium ou Pidgin sur pour ordinateurs de bureau. Vous pouvez peaufinez exactement comment et quand ChatSecure essaye de chiffrer vos clavardages dans les Paramètres des comptes.\n\nCommençons ! + + Nouveau compte + Ajouter un compte + Modifier un compte + Enlever un compte + Compte existant + + utilisateur@hôte + Mot de passe : + Mémoriser mon mot de passe. + Connexion automatique + Vous n\u2019avez pas de compte ? + Connexion + Pour votre sécurité, si votre téléphone est perdu ou volé, allez sur le site Web depuis votre ordinateur et changez votre mot de passe. + Cette option vous connecte automatiquement chaque fois que vous ouvrez l\u2019application. Pour désactiver l\u2019option, déconnectez-vous, puis décochez « Connexion automatique ». + Se connecter avec Tor (nécessite l\'appli Orbot) + utilisateur@domaine + nouveau nom d\'utilisateur + fournisseur de service (dukgo.com, jabber.ccc.de) + mot de passe + confirmer le mot de passe + Paramètres avancés des comptes + Configuration des comptes + Inscrire un compte + Persistance + Mémoriser le mot de passe + Mot de passe mis en cache + Mot de passe non mis en cache + Connexion automatique + Connexion au démarrage de ChatSecure + Pas de connexion au démarrage de ChatSecure + Se connecter maintenant + Se connecter après le paramétrage des comptes + Ne pas se connecter après le paramétrage des comptes + Personnel (facultatif) + Alias du compte (votre nom) + Comment votre compte apparaît-il en ligne + Profil + Une brève présentation de vous-même + Forcer le chiffrement / refuser les textes en clair + Si possible, chiffrer automatiquement les clavardages + Chiffrer les clavardages tel que demandé + Désactiver le chiffrement du clavardage + Validation des authentifiants… + Génération de la paire de clefs… + Connexion… + Assistant de comptes + Votre ID de compte + Configurer le serveur + Êtes-vous prêt ? + Saisissez votre ID de compte afin de configurer ChatSecure pour votre service de clavardage XMPP. Il ressemble à une adresse courriel : + Veuillez saisir votre ID de compte (utilisateur@ nomd\u2019hôte) : + Veuillez saisir ou modifier le nom d\'hôte de votre serveur jabber/xmpp et le numéro de port (5222 par défaut). + ChatSecure a été configuré, et il est maintenant temps de vous connecter à votre service et de commencer à clavarder de façon sécurisée et confidentielle ! + Orbot (Tor) + Domaine de service de clavardage + Inscription du nouveau compte… + on se lance… + Annuler la connexion + Connexion… + Déconnexion\u2026 + + Rechercher l\'enregistrement de service + Utiliser l\'enregistrement de service du DNS pour trouver le serveur XMPP du nom de domaine + Autoriser l\'envoi en clair du nom d\'utilisateur et du mot de passe lors de l\'utilisation d\'un transport non chiffré + Autoriser l\'authentification en clair + Vérifier que le certificat est de confiance + Vérification TLS + Exige une connexion TLS + Chiffrement du transport + comment les clavardages chiffrés sont-ils démarrés + Le serveur auquel se connecter, si nécessaire + Se connecter au serveur + Port TCP pour le serveur XMPP + Port du serveur + Ressource XMPP + pour distinguer cette connexion d\'autres clients aussi connectés + Priorité des ressources XMPP + Les messages vers les clients ayant plusieurs ressources actives seront envoyés à la ressource avec la plus haute priorité + + Conversations + Aucune conversation.\n\nToquer ici pour en commencer une ! + Vous n\'avez aucun\ncompte configuré.\n\nToquez ici pour en ajouter un ! + Liste des contacts - %1$s + Ajouter un contact + Supprimer un contact + Bloquer un contact + Pseudonyme du contact + Bloqué + Pseudonyme + Le contact « %1$s » sera supprimé. + Le contact « %1$s » sera bloqué. + Le contact « %1$s » sera débloqué. + Le contact « %1$s » a été ajouté. + Le contact « %1$s » a été supprimé. + Le contact « %1$s » a été bloqué. + Le contact « %1$s » a été débloqué. + Nouveau clavardage + Rechercher les contacts + Montrer la grille + Commencer le clavardage + Afficher le profil + Vérifier le contact + Clavardages en cours (%1$d) + %1$d en ligne + Invitations des amis + (Inconnu) + Vide + Aucune conversation ouverte\n\nToquer pour en commencer une ! + Contact + tapez le nom d\'un contact avec qui clavarder + Allez + Aucun clavardage actif. + Ajouter un contact + Nom d\'utilisateur ou JabberID de la personne à ajouter : + Compte auquel ajouter : + Saisir un nom à ajouter à partir des contacts. + Envoyer une invitation + Profil du contact + État : + Type de client : + Ordinateur + Appareil + Contacts bloqués - %1$s + Aucun contact bloqué. + + Clavarder avec %1$s + Moi + %1$s est en ligne + %1$s est absent + %1$s est occupé + %1$s est hors ligne + %1$s s\'est joint + %1$s est parti + Envoyer une photo + Envoyer un fichier + Envoyer de l\'audio + Prendre une photo + Transfert de fichiers + Transfert terminé + Transfert en cours + Accepter le transfert ? + souhaite vous envoyer le fichier + Aucune connexion disponible pour envoyer votre partage ! + Envoyer + Envoyer un message + Envoyer un message sécurisé + Renvoyer + Terminer le clavardage + Effacer le clavardage + Insérer une frimousse + Changer de clavardage + Menu+ + Choisir un lien + Supprimer + Conserver les fichiers + Supprimer le stockage sécurisé de la session de clavardage ? + Tous les fichiers téléversés et téléchargés de la session seront supprimés irrémédiablement. Avertissement : cette opération ne peut pas être annulée ! + Supprimer l\'original ? + Ce fichier sera copié dans le stockage sécurisé avant d\'être envoyé. Voudriez-vous supprimer le fichier original du stockage non sécurisé de l\'appareil ? + Conserver + Exporter + Exporter le fichier multimédia ? + Ce fichier multimédia sera exporté vers %1$s + Mettre fin au clavardage ? + Tous les éléments multimédias sécurisés de cette session seront supprimés. Exportez un élément multimédia par un long clic sur l\'icône de l\'imagette. + Mettre fin au clavardage et supprimer les fichiers + + %1$s vous a invité à rejoindre un clavardage de groupe. + Une invitation a été envoyé à %1$s. + Accepter + Refuser + Clavardage de groupe + Créer ou rejoindre un clavardage de groupe + Connexion au clavardage de groupe… + Inviter\u2026 + Choisir le/les contact(s) à inviter + Saisir le nom du contact à trouver + Aucun contact trouvé.\n\Toquer pour inviter. + Ajouter %1$s? + Oui + Non + Impossible d\'approuver l\'abonnement de %1$s. Veuillez ressayer ultérieurement. + Impossible de refuser l\'abonnement de %1$s. Veuillez ressayer ultérieurement. + + Démarrer le chiffrement + Arrêter le chiffrement + Démarrage d\'une session de clavardage chiffrée… + Arrêt de la session de clavardage chiffrée… + Empreinte de sécurité + Empreinte de sécurité + Connexion + Régénérer la clef + Le chiffrement est désactivé + Le chiffrement est activité (toquer pour vérifier) + Votre contact a arrêté le clavardage chiffré. + Chiffré et vérifié ! + Génération d\'une nouvelle paire de clefs OTR… + + Nous avons détecté un magasin de clefs OTR à importer. Voulez-vous balayer le mot de passe QR maintenant ? + Activer KeySync + Le trousseau OTR a été importé avec succès + + Balayer le QR + Votre empreinte + Manuel + Question + Empreinte de sécurité (vérifiée) + Êtes-vous sûr de vouloir confirmer cette empreinte ? + Vérifier l\'empreinte ? + L\'empreinte distante a été vérifiée ! + Votre empreinte + Empreinte pour + Installer le lecteur de codes à barres + Le lecteur de codes à barres est nécessaire à cette application. Voudriez-vous l\'installer ? + + Authentification + Saisissez une question a envoyer à votre contact ainsi que la réponse que vous attendez de lui afin de vérifier qu\'il est bien celui qu\'il prétend être. + la question à poser + la réponse attendue + Votre contact vous a authentifié avec succès. Authentifiez-le maintenant en posant votre propre question. + Vérification OTR par Q&R + Chiffrement du clavardage + Envoyer + Annuler + + Appel sécurisé + Appel vocal sécurisé + Saisissez ici votre compte OStel.co ou autre service SIP sécurisé pour l\'intégration des appels + + Configuration du compte + + Sécurité et confidentialité + Chiffrement et anonymat + Chiffrement marche/arrêt + Délai d\'expiration du mot de passe + Temps durant lequel le chiffrement devrait dire Déverrouillé + + Liens cliquables sur Tor + Pour les comptes utilisant Tor, rend les liens cliquables dans le clavardage (AVERTISSEMENT, confidentialité possiblement exposée!) + Interface utilisateur + Langue + Langues + Utiliser les valeurs par défaut du système + Utiliser le thème Sombre + Changer le thème de l\'appli en sombre + Stockage des messages en mémoire seulement + Ne stocker les messages qu\'en mémoire, et non sur les stockages flash, afin de se défendre contre l\'extraction des messages (peut entraîner la perte de messages). + Image d\'arrière-plan + Définir le chemin (« /sdcard/foo.jpg ») vers un arrière-plan pour l\'appli + Montrer la grille des contacts + Afficher la liste des contacts en grille d\'avatars + Oui, tout accepter + Supprimer le fichier multimédia non sécurisé + Lors du partage d\'une photo ou d\'un fichier, le supprimer automatiquement du stockage non sécurisé original après l\'importation + Stocker le média en externe + Les fichiers multimédias des sessions de clavardage sont stockés dans un conteneur chiffré qui peut être stocké en interne ou en externe. + + Stockage externe manquant + Les journaux de clavardage sont stockés sur une carte SD, mais aucune n\'est présente. Veuillez insérer la bonne carte SD, ou supprimez le journal de clavardage existant et relancez ChatSecure. + Le stockage de médias est manquant + Les journaux de clavardage sont stockés sur la carte SD, mais le fichier est absent de la carte SD actuelle. Veuillez insérer la bonne carte SD, ou supprimez le journal de clavardage existant et relancez ChatSecure. + Supprimer le journal de clavardage + + Autres réglages + Démarrer ChatSecure automatiquement + Toujours démarrer et se reconnecter automatiquement aux comptes connectés auparavant + Masquer les contacts hors ligne + Utiliser la priorité d\'avant-plan + Réduire le risque qu\'Android réinitialise notre service de connexion. Ceci placera une notification dans la zone de notification. + Pulsation + Utilisez une valeur plus haute (en minutes) pour préserver la batterie. Une valeur haute pourrait entraîner la fermeture de votre connexion par le fournisseur pour cause d\'inactivité. + + Paramètres de notification + Notifications MI + Notification d\'un message dans la barre d\'état + Vibrer + Vibrer aussi à la réception d\'un message + Son + Jouer aussi une sonnerie à la réception d\'un message + Choisir une sonnerie personnalisée + + Activer les journaux de débogage + Extraire le journal de données de l\'appli vers la sortie standard / logcat pour le débogage + + Données réseau désactivées + La connectivité des données réseau (incluant les données d\'arrière-plan) sont exigées pour que l\'appli se connecte. + Activer + Quitter + Voulez-vous vous déconnecter de tous les services ET tuer tous les processus (sortie dure) ? + Créer un nouveau compte ? + Créer un nouveau compte de clavardage pour « %1$s » ? + Infos du certificat + Certificat : + Délivré par : + Empreinte SHA1 : + Délivré le : + Expire le : + Rejoindre ce clavardoir ? + Un appli externe tente de vous connecter à un clavardoir. Le permettre ? + Choisir un arrière-plan + Voulez-vous choisir une image d\'arrière-plan de la galerie ? + Choisir une image + + Nouveaux messages %1$s + %1$d clavardage non lus + Nouvelle invitation d\'amitié de %s + Nouveau fichier %1$s de %2$s + Invitation à un clavardage de groupe + Nouvelle invitation de clavardage de groupe de %s + Nouveau(x) message(s) de + Démarrage du service ChatSecure… + Activé & déverrouillé + message copié vers le presse-papiers + + Attention + Erreur : + Code d\'erreur %1$d + Impossible de se connecter au service %1$s. Veuillez ressayer ultérieurement.\n"(Détails : %2$s) + La liste n\'a pas été ajoutée. + Le contact n\'a pas été bloqué. + Le contact n\'a pas été débloqué. + Veuillez d\'abord choisir un contact. + Déconnecté !\n + Erreur du service ! + La liste de contacts n\'a pas pu être chargée. + Impossible de se connecter au serveur. Veuillez vérifier votre connexion. + « %1$s » existe déjà dans votre liste de contacts. + Le contact « %1$s » a été bloqué. + Veuillez patienter pendant le chargement de votre liste de contacts. + Une erreur réseau s\'est produite. + Le Wi-Fi est exigé pour cette connexion. + Le serveur ne prend pas en charge cette fonction. + Le mot de passe saisi n\'est pas valide. + Une erreur du serveur est survenue. + Le serveur ne prend pas en charge cette fonction. + Le serveur n\'est pas disponible actuellement. + Le délai du serveur est écoulé. + Le serveur ne prend pas en charge la version actuelle. + La file d\'attente des messages est pleine. + Le serveur ne prend pas en charge la redirection vers le domaine. + Le nom d\'utilisateur que vous avez saisi n\'est pas reconnu. + Désolé, vous êtes bloqué par cet utilisateur . + La session est expiré, veuillez vous reconnecter. + vous vous êtes connecté depuis un autre client. + vous vous êtes connecté depuis un autre client. + Désolé, le numéro de téléphone ne peut pas être lu de votre carte SIM. Veuillez contacter votre opérateur pour de l\'aide. + Vous n\'êtes actuellement pas connecté. + ChatSecure a rencontré une erreur lors de la validation du nom d\'utilisateur ou du mot de passe - veuillez les vérifier et ressayer. + ChatSecure a rencontré une erreur lors de la génération d\'une paire de clefs. + ChatSecure a rencontré une erreur lors de la connexion au serveur de clavardage - veuillez vérifier votre configuration et ressayer. + ChatSecure a rencontré une erreur lors de la connexion - veuillez revérifier votre connectivité réseau et ressayer. + Le réseau est hors ligne + ChatSecure tente de rétablir une connexion + Vous n\'avez pas saisi la partie @nomdedomaine.com pour votre ID de compte. Ressayer ! + Le nom d\'hôte de votre serveur n\'avait pas de suffixe .com ou similaire. Ressayer ! + Saisissez votre mot de passe : + Comme vous utilisez Tor, vous devez saisir le nom d\'hôte XMPP du serveur de connexion directement dans les paramètres avancés du compte + AVERTISSEMENT : ce service utilise un certificat dont la cryptographie est FAIBLE. Veuillez dire à l\'administrateur de le mettre à niveau. + Le certificat fourni ne correspond pas au certificat attendu : + Impossible de créer ou de rejoindre un clavardage de groupe + Désolé, nous ne pouvons pas partager ce type de fichier + Vous devez activer le chiffrement pour partager des fichiers + Veuillez activer le chiffrement du clavardage pour partager des fichiers + Données entrantes non prises en charge, partage impossible ! + ChatSecure a détecté qu\'une requête à été faite pour retirer sa tâche. ChatSecure va sûrement planter. Veuillez plutôt utiliser l\'élément de menu Se déconnecter de tout de l\'écran de liste des comptes. + Il n\'y a aucun visualiseur disponible pour ce format de fichier + Veuillez démarrer une conversation sécurisée avant de balayer des codes + Le trousseau OTR n\'a pas été importé ; veuillez vérifier que le fichier existe bien au format et à l\'endroit appropriés + Veuillez copier le fichier « otr_keystore.ofcases » de l\'outil KeySync de votre ordinateur de bureau dans le répertoire racine du stockage de votre appareil. + Impossible d\'envoyer ce message. + Vous êtes déconnecté. Les messages seront transmis après votre reconnexion. + %1$s est hors ligne. Les messages que vous envoyez seront livrés quand %1$s sera en ligne. + %1$s n\'est pas dans votre liste de contacts. + Vos mots de passe ne correspondent pas + La priorité doit être un chiffre entre [0 … 127] + Le numéro de port doit être un numéro + Erreur de transfert + Impossible de lire le fichier vers le stockage + Erreur majeure : impossible de déverrouiller ou de charger la base de données de l\'appli. Veuillez réinstaller l\'appli ou effacer les données. + Aucune appli installée pour gérer ce lien! + Paramètres des comptes + + Groupes + conversation(s) en cours + Éteindre & verrouiller + Panique + Amis + Contacts + Accepter le certificat du serveur ? + Afficher votre empreinte + chargement… + + À propos de ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Liste de contacts + Paramètres + Gérer les comptes + Déconnexion + Liste de contacts + + Jabber (XMPP) + Zone local (Bonjour/ZéroConf) + Compte Google + dukgo.com + + + En ligne + Occupé + Absent + Inactif + Hors ligne + Apparaître hors ligne + + + Joyeux + Triste + Clin d\'œil + Langue tirée + Surpris + Bisous + Cri + Cool + Acte = parole + Pieds dans l\'plat + Embarrassé + Ange + Indécis + En pleurs + Bouche cousue + Rire + Confus + + + + Joyeux + Triste + Clin d\'œil + Langue tirée + Surpris + Bisous + Cri + Cool + Acte = parole + Pieds dans l\'plat + Embarrassé + Ange + Indécis + En pleurs + Bouche cousue + Rire + Confus + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Compte existant + Se connecter à mon compte existant sur un serveur Jabber / XMPP spécifique. + Compte Google + Clavardez avec d\'autres utilisateurs Google en utilisant votre compte Google. + Clavardage Wi-Fi Mesh + Clavardez avec d\'autres sur le même réseau local ou mesh - aucun Internet ni serveur exigé ! + Activer le clavardage Wi-Fi + Nouveau compte + Inscrivez-vous à un nouveau compte gratuit sur un service de notre liste intégrée, ou sur celui de votre choix. + Créer un nouveau compte + Identité secrète ! + Créez un compte de clavardage « jetable » et anonyme en un simple clic (Orbot : Tor pour Android est exigé) + Générer une identité + Ceci est un clavardage de groupe + [renvoyé] + [renvoyé] + Le partage sécurisé de ce fichier est impossible + Installer Orbot ? + Orbot doit être installé et relayer activement le trafic. Voulez-vous l\'installer? + Toujours + Démarrer Orbot ? + Orbot ne semble pas tourner. Voulez-vous le démarrer et vous connecter à Tor ? + pseudonyme à utiliser dans le clavardoir + nom du clavardoir à créer ou à rejoindre + serveur de clavardage de groupe (conference.foo.com) + Votre magasin de clefs est corrompu. Veuillez réinstaller ChatSecure ou « effacer les données » de l\'appli. + Vous avez reçu un message chiffré illisible + Je n\'ai pas pu déchiffrer le message que vous avez envoyé + < Glisser gauche et droite pour plus d\'options > + diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index 39e5db00d..bd23dfd6d 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -6,64 +6,4 @@ Sur demande Désactivé / Jamais - - forcer - auto - sur demande - desactiver - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 5c3a2f171..9451bc0fc 100755 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -1,400 +1,504 @@ - + + + ChatSecure + ChatSecure + lire les messages instantanés Permet aux applications de lire les données du fournisseur de contenu de messagerie instantanée. écrire des messages instantanés - \nPermet aux applications d\'écrire des données au fournisseur de contenu de messagerie instantanée. - - ChatSecure - - + +Permet aux applications d\'écrire des données au fournisseur de contenu de messagerie instantanée. + démarrer le service de messagerie + Permet aux applications de lancer le service de messagerie sur demande. + + Confirmer + OK + Annuler + OK + Annuler + Suivant + Retour + Connectez-vous + Lire + Configuration + + Ouvert + ChatSecure vérouillé + Choisir un mot de passe + Confirmez le mot de passe + mot de passe + Nouveau mot de passe + Confirmer le nouveau mot de passe + Les mots de passe ne correspondent pas, veuillez essayer à nouveau + J\'ai la flemme (PAS de mot de passe!) + Demande d\'informations + Entrez le mot de passe svp + Mot de passe svp… + Phrase de passe + Phrase de passe (à nouveau) + Sélectionner un compte - - A propos - + Sélectionner un compte + (%1$d) + Ajouter un compte %1$s + À propos + Tout déconnecter + Voulez-vous déconnecter tous les services ? + Vous avez été déconnecté de %1$s. + Vous avez été déconnecté de %1$s parce que %2$s + Première utilisation de ChatSecure ? + Prêt à commencer ? + A propos de ChatSecure + Configuration du compte + Configuration d\'une phrase de passe + Avant de commencer, veuillez choisir un mot de passe robuste pour protéger vos données des accès illégitimes. + Phrase de passe : + Phrase de passe (à nouveau) : + Entrez une *nouvelle* phrase de passe. Elle doit contenir au moins une lettre majuscule, une minuscule et un chiffre, et être plus longue que six caractères. + Chiffrement de vos notes existantes avec la nouvelle phrase de passe (patience …) + Entrez votre phrase de passe : + Bienvenue ! Entrez une phrase de passe forte pour sécuriser vos notes. Elle doit contenir au moins une lettre majuscule, une minuscule et un chiffre, et être plus longue que six caractères. + Votre phrase de passe est trop courte + Votre phrase de passe ne contient aucune lettre majuscule + Votre phrase de passe ne contient aucune lettre minuscule + Votre phrase de passe ne contient aucun chiffre + + A propos de ChatSecure + ChatSecure est une application mobile de messagerie instantanée qui fournit des fonctionnalités pour éviter que l\'on espionne vos conversations et communications. + +L\'application supporte tous les services fonctionnant avec les protocoles Jabber ou XMPP comme Google GTalk ou Jabber.org. + + Pourquoi est-ce sécurisé ? + \'Off-the-Record Messaging\' est un système de sécurité conçu pour préserver la vie privée en imitant les principes que l\'on rencontre habituellement dans le monde réel lors d\'une conversation privée, en incluant le chiffrement, l\'authentification et la confidentialité persistante. + +Le protocole OTR est compatible avec les clients de messagerie comme Adium ou Pidgin. + + Mes conversations sont-elles sécurisées ? + La fonctionnalité de chiffrement de ChatSecure ne fonctionne que lorsque la personne avec laquelle vous discutez utilise un programme compatible. Vous devriez donc vous assurer que vos contacts utilisent soit ChatSecure sur un appareil mobile, soit Adium ou Pidgin sur un ordinateur. Vous pouvez par ailleurs configurer comment et quand ChatSecure essaye de chiffrer vos conversations dans les Paramètres du compte. + +Allons-y ! + + Nouveau compte Ajouter un compte - Modifier le compte - Supprimer le compte - - Tout déconnecter - - - Sélectionner un compte - - - + Compte existant + + Nom d\'utilisateur : + Mot de passe : + Mémoriser mon mot de passe + Me connecter automatiquement + Vous ne possédez pas de compte ? + Se connecter + Pour votre sécurité, si votre téléphone est perdu ou volé, allez sur le site Web depuis votre ordinateur et changez votre mot de passe. + Cette option vous connecte automatiquement au lancement de l\'application. Pour désactiver l\'option, déconnectez-vous, puis décochez la case \\"Connexion automatique\\". + Connectez-vous via Tor (Nécessite l\'application Orbot) + utilisateur@domaine.fr + nouveau nom d\'utilisateur + fournisseur de service (dukgo.com, jabber.ccc.de) + mot de passe + confirmez le mot de passe + Paramètres avancés du compte + Type de compte + Nouveau compte + Persistance + Retenir votre mot de passe + Mot de passe mis en cache + Mot de passe non mis en cache + Connexion automatique + Se connecter au démarrage de ChatSecure + Ne pas se connecter au démarrage de ChatSecure + Se connecter maintenant + Se connecter avec les paramètres du compte + Ne pas se connecter avec les paramètres du compte + Personnel (facultatif) + Nom du compte (votre nom) + Comment votre compte apparaît en ligne + Profil + Une brève présentation de vous-même + Forcer le chiffrement / Refuser les communications en clair + Lorsque cela est possible, chiffrer automatiquement les conversations + Chiffrer les conversations sur demande + Désactiver le chiffrement des conversations + Validation des certificats … + Génération de la paire de clés … + Connexion … + Account Wizard + ID de votre compte + Configurer le serveur + Êtes-vous prêt ? + Introduisez votre ID de compte pour configurer ChatSecure au service de discussion XMPP. Il ressemble à une adresse e-mail : + Veuillez entrer l\'identifiant de votre compte (user @ hostname) : + Veuillez entrer ou modifier le nom de votre serveur jabber/xmpp et le numéro de port associé (5222 par défaut). + ChatSecure est configuré, il est donc temps de se connecter au service, et de commencer à discuter, de façon sécurisée et privée ! + Orbot (Tor) + Sélectionnez un domaine + Enregistrement du nouveau compte… + Démarrage de ChatSecure… Annuler la connexion - - + Connexion… + Déconnexion\u2026 + + Vérifier le SRV + Utiliser le Serveur DNS pour trouver le serveur XMPP depuis un nom de domaine + Autoriser l\'envoi en clair du nom d\'utilisateur et du mot de passe lorsque la connexion n\'est pas chiffrée + Autoriser l\'identification en clair + Vérifie que le certificat est de confiance + Vérification TLS + Nécessite une connexion TLS/SSL + Chiffrement + comment les conversations chiffrées sont démarrées + Le serveur auquel se connecter, si nécessaire + Connection au serveur + Port TCP du serveur XMPP + Port du Serveur + Ressource XMPP + pour distinguer cette connexion de celle des autres clients connectés + Priorité de la ressource XMPP + Les messages aux clients ayant plusieurs ressources actives seront envoyés à la ressource qui a la priorité la plus haute + + Conversations + Pas de conversation. + +Tapez ici pour commencer à discuter ! + Vous n\'avez pas de +comptes configurés. + +Appuyez ici pour en ajouter un ! + Liste de contacts - %1$s Ajouter un contact - Supprimer le contact - Bloquer le contact - + Surnom du contact Bloqué - - Comptes - + Surnom + Le contact \"%1$s\" sera supprimé. + Le contact \"%1$s\" sera bloqué. + Le contact \"%1$s\" sera débloqué. + Contact \"%1$s\" ajouté. + Contact \"%1$s\" supprimé. + Contact \"%1$s\" bloqué. + Contact \"%1$s\" débloqué. Nouvelle conversation - - Nouveau compte - - Paramètres - Rechercher des contacts - + Montrer la grille Commencer la conversation - - Se déconnecter - Afficher le profil - - - Chiffrer - Arrêter le chiffrement - Effacer la conversation - Terminer la conversation - Appel sécurisé - - - Liste de contacts - - Inviter... - - Changer de conversation - - Insérer une émoticône - Renvoyer - - Scan d\'empreinte - Votre empreinte - Vérifier les empreintes - Vérifier l\'identité - - Menu+ - - - - Confirmer - - Voulez-vous vous déconnecter tous les services ? - - - - - - - - OK - - Annuler - - OK - - Annuler - - - - - - - - - - Nom d\'utilisateur : - - Mot de passe : - - Mémoriser mon mot de passe - - Me connecter automatiquement - - Vous ne possédez pas de compte ? - - Pour votre sécurité, si votre téléphone est perdu ou volé, allez sur le site Web sur votre ordinateur et changez votre mot de passe. - Cette option vous connecte automatiquement au lancement de l\'application. Pour désactiver l\'option, déconnectez-vous, puis décochez la case \"Connexion automatique\". - - Se connecter - - - Démarrage de ChatSecure... - - Connexion... - - - Données d\'arrière-plan désactivées - - - - - - Activer - - Quitter - - - - - - + Vérifier la clé + Conversations en cours (%1$d) + %1$d en ligne Inviter des amis - (\"\"Inconnu\"\") - Vide - Aucune conversation - - Sélectionnez les contacts à inviter - Saisissez le nom du contact à rechercher - Aucun contact trouvé. - - - - Aucun contact bloqué. - - + Contact + Tappez le nom d\'un contact avec qui discuter + Go + Aucune conversation active. + Ajouter un contact + Adresse e-mail de la personne que vous souhaitez inviter : + Sélectionnez une liste : + Saisissez un nom à ajouter à partir de vos contacts + Envoyer une invitation Profil du contact - État : - Type de client : - Ordinateur - Portable - - - En ligne - - Occupé - - Absent - - Inactif - - Hors ligne - - Apparaître hors ligne - - - - - + Contacts bloqués : %1$s + Aucun contact bloqué. + + Chatter avec %1$s Moi - - Saisissez votre message - - - - - - - - - - - - - - - - + %1$s est en ligne + %1$s est absent + %1$s est occupé + %1$s est hors ligne + %1$s a rejoint la conversation + %1$s a quitté la conversation + Envoyer une photo + Envoyez un fichier + Envoyer du son + Prendre une photo + Transfert de fichier + Transfert terminé + Transfert en cours + Accepter le transfert ? + souhaite vous envoyer le fichier + Aucune connexion disponible pour partager ! Envoyer - - Ce message n\'a pas pu être envoyé. - - Connexion au serveur interrompue. Les messages seront transmis lorsque vous serez de nouveau connecté. - - - - - + Saisissez votre message + Envoyer un message sécurisé + Renvoyer + Terminer la conversation + Effacer la conversation + Insérer une émoticône + Changer de conversation + Menu+ Sélectionner le lien - - Aucune conversation active. - - Démarrage de la conversation chiffrée... - Arrêt du chiffrement de la conversation... - - - Ajouter un contact - - Adresse e-mail de la personne que vous souhaitez inviter : - - Sélectionnez une liste : - - Saisissez un nom à ajouter à partir de vos contacts - - Envoyer une invitation - - Jabber (XMPP) - Chat réseau local (Bonjour/ZeroConf) - + Supprimer + Garder les fichiers + Supprimer les données securisées de la session de chat ? + Toutes les données envoyées et reçues seront définitivement supprimées. Cette opération ne pourra pas être annulée ! + Supprimer l’original ? + Ce fichier va être copié dans l\'espace sécurisé de stockage avant d\'être envoyé. Voulez vous supprimer le fichier original de l\'espace non sécurisé du téléphone ? + Garder + Déplacer + Déplacer le fichier multimédia ? + Ce fichier multimédia va être déplacer vers %1$s + Quitter le chat ? + Tous les médias sécurisés de cette session vont être supprimés. Pour garder un fichier, restez appuyé sur son icône + Terminer la conversation et supprimer les fichiers + + %1$s vous a invité à rejoindre un chat en groupe. + L\'invitation a été envoyé à %1$s. + Accepter + Refuser + Discussion de groupe + Créer ou rejoindre une discussion de groupe + Connexion au chat de groupe… + Inviter… + Sélectionnez les contacts à inviter + Saisissez le nom du contact à rechercher + Aucun contact trouvé.⏎ +⏎ +Appuyez pour inviter. + Ajouter %1$s? + Oui + Non + Impossible d\'approuver l\'inscription de %1$s. Veuillez réessayer ultérieurement. + Impossible de refuser l\'inscription de %1$s. Veuillez réessayer ultérieurement. + + Chiffrer + Arrêter le chiffrement + Démarrage de la conversation chiffrée… + Arrêt du chiffrement de la conversation… + Sécurité de l\'empreinte + Sécurité de l\'empreinte + Se connecter + Regénérer les clés + Le chiffrement n\'est pas activé + Le chiffrement est activité (Appuyez pour vérifier) + Votre contact a arrêté le chat chiffré. + Chiffré et vérifié ! + Génération d\'une nouvelle paire de clés OTR… + + Nous avons détecté une base de stockage de clés OTR à importer. Voulez-vous scanner le mot de passe QR maintenant? + Activer KeySync + Les clés OTR ont été importées avec succès + + Scan QR + Votre empreinte + Manuel + Question + Sécurité de l\'empreinte (Vérifiée) + Êtes-vous sûr que vous voulez confirmer cette empreinte ? + Vérifier l\'empreinte ? + L\'empreinte de la clé a été vérifiée ! + Votre empreinte + Empreinte pour + Installer Barcode Scanner? + Cette application nécéssite Barcode Scanner. Voulez-vous l\'installer ? + + Authentification + Entrez une question à destination de vos contacts ainsi que la réponse que vous espérez d\'eux afin de vérifier qu\'ils sont bien ceux qu\'ils prétendent être. + la question à poser + la réponse attendue + Votre contact vous a authentifié avec succès. Authentifiez-le maintenant en posant votre propre question. + Vérification Q&R OTR + Chiffrage de la conversation + Envoyer + Annuler + + Appel sécurisé + Appel vocal sécurisé + Entrez votre information de compte OStel.co ou autre fournisseur SIP sécurisé pour effectuer l\'intégration des appels + Configuration du compte + + Sécurité et Vie privée Chiffrement et Anonymat + Chiffrement On/Off + Mot de passe expiré + Temps pendant lequel le chiffrement reste dévérouillé + + Liens cliquables sur Tor + Pour les comptes utilisant Tor, rendre les liens cliquables dans les conversations (ATTENTION, risque d\'atteinte à la confidentialité!) + Interface Utilisateur + Langue + Langues + Utiliser les paramètres systèmes par défaut + Utiliser le thème Sombre + Changer le thème de l\'application en sombre + Enregistrement des messages en mémoire vive uniquement + N\'enregistre les messages qu\'en mémoire vive et non sur la mémoire flash afin de prévenir leur récupération. (Peut causer la perte de messages) + Image d\'arrière-plan + Entrez le chemin (\"/sdcard/foo.jpg\") d\'un arrière plan pour l\'application + Montrer la grille des contacts + Afficher la liste de contact en grille + Oui, accepter tout + Supprimer les média non sécurisés + Après avoir partagé une photo ou un fichier, supprimer automatiquement l\'original du média de stockage + Enregistrer les média sur le support de stockage externe + Les fichiers multimédia des conversations sont stockés dans un conteneur chiffré, enregistré sur le support interne ou externe de l\'appareil. + + Support de stockage externe manquant + Les enregistrements de vos conversations sont stockés sur le support carte SD. Néanmoins, aucune carte SD n\'est présente. Veuillez insérez le support de stockage ou supprimer les enregistrements existants et lancer à nouveau ChatSecure. + Stockage des fichiers multimédia manquant + Les enregistrements de vos conversations sont stockés sur le support carte SD. Néanmoins, le fichier n\'est pas présent sur ce support. Veuillez insérez le support de stockage ou supprimer les enregistrements existants et lancer à nouveau ChatSecure. + Supprimer les enregistrements des conversations + + Autres réglages + Démarrer ChatSecure automatiquement + Toujours démarrer et se reconnecter automatiquement aux comptes Masquer les contacts hors ligne - - Paramètres de notification - - Notifications de message - - Signaler la réception d\'un message dans la barre d\'état - Notification par vibration Réduit la chance qu\'Android réinitialise le service de connexion. Une notification apparaîtra dans la zone de notification. Pulsation Utilisez une valeur plus grande (en minutes) pour préserver la batterie. Une valeur haute peut cependant vous faire perdre la connexion pour cause d\'inactivité. + + Paramètres de notification + Notifications de message + Signaler la réception d\'un message dans la barre d\'état Vibreur - - Vibre également à la réception d\'un message - + Vibrer également à la réception d\'un message Son - Déclencher la sonnerie lors de la réception d\'un message - - Sélectionner une sonnerie - - - - - - - Accepter - - Refuser - - - - Accepter - - Refuser - - - - - - - - - - - - + Utiliser une sonnerie personnalisée + + Activer journal de déboguage + Afficher journal de l\'application sur la sortie standard / logcat pour déboguage + + Données d\'arrière-plan désactivées + Les données d\'arrière-plan sont requises pour l\'activation de %1$s. + Activer + Quitter + Voulez-vous vous déconnecter de tous les services ET tuer tous les processus (hard exit) ? + Créer un nouveau compte ? + Créer un nouveau compte de chat pour \'%1$s\'? + Informations de certificats + Certificat : + Délivré par : + Empreinte SHA1 : + Délivré le : + Expire le : + Joindre le salon de discussion ? + Une application externe tente de vous connecter au salon. Autoriser ? + Choisir un fond d\'écran + Voulez-vous sélectionner un fond d\'écran depuis la Gallerie ? + Sélectionner une image + + Nouveaux messages de %1$s + %1$d messages non lus + Nouvelle demande d\'ami de %s + Nouveau fichier %1$s de %2$s Invitation à un chat en groupe - - - - - - - - - - - - démarrer le service de messagerie - Permet aux applications de lancer le service de messagerie sur demande. - - + Nouvelle invitation de conversation de groupe de %s + Nouveau(x) message(s) de + Lancement du service ChatSecure… + Activé & déverrouillé + message copié dans le presse papier + Avertissement - - - + Erreur : + Code d\'erreur %1$d + Connexion au service %1$s impossible. Veuillez réessayer ultérieurement."\n"(Détails : %2$s) La liste n\'a pas été ajoutée. - Le contact n\'a pas été bloqué. - Le contact n\'a pas été débloqué. - Veuillez d\'abord sélectionner un contact. - \"Déconnecté !\"\n - Erreur du service ! - La liste de contacts n\'a pas pu être chargée. - Connexion au serveur impossible. Veuillez vérifier votre connexion. - - - - - + %1$s existe déjà dans votre liste de contacts. + Le contact \"%1$s\" a été bloqué. Veuillez patienter pendant le chargement de votre liste de contacts. - Une erreur réseau s\'est produite. Le WiFi est requis pour cette connexion. - Le serveur ne prend pas en charge cette fonctionnalité. - Le mot de passe saisi n\'est pas correct. - Le serveur a rencontré une erreur. - Le serveur ne prend pas en charge cette fonctionnalité. - Le serveur n\'est pas disponible actuellement. - Le délai d\'inactivité du serveur est écoulé. - Le serveur ne prend pas en charge la version actuelle. - La file d\'attente des messages est pleine. - Le serveur ne prend pas en charge la redirection vers ce domaine. - Le nom d\'utilisateur que vous avez saisi n\'est pas reconnu. - Désolé, l\'utilisateur vous a bloqué. - La session a expiré. Veuillez vous reconnecter. - vous vous êtes connecté depuis un autre client. vous êtes déjà connecté depuis un autre client. - Impossible de lire le numéro de téléphone de votre carte SIM. Veuillez contacter votre opérateur pour obtenir de l\'aide. - Vous n\'êtes actuellement pas connecté. - - - + Erreur lors de la validation du nom d\'utilisateur ou mot de passe - veuillez les vérifier et recommencer + Erreur lors de la création des clés publique/privée + Erreur lors de la connexion au serveur de messages - veuillez valider votre configuration et essayer à nouveau. + Erreur lors de la connexion - veuillez vérifier que vous avez accès au réseau et essayer à nouveau. + ChatSecure a perdu la connexion au réseau + ChatSecure tente de rétablir une connexion + Vous n\'avez pas renseigné la partie @nomdedomaine.fr pour votre compte. Veuillez réessayer ! + Le nom de domaine renseigné n\'a pas de suffixe de type .com, .fr ou similaire. Veuillez réessayer ! + Entrez votre mot de passe : + Si vous utilisez Tor, vous devez renseigner le nom de domaine XMPP du Serveur de connexion directement dans les Paramètres Avancés du compte + ATTENTION : Ce service utilise un certificat dont le chiffrement est FAIBLE. Veuillez demander à l\'administrateur une mise à niveau. + Le certificat fourni ne correspond pas à celui vérifié (pinning) : + Impossible de créer ou de rejoindre une discussion de groupe + Désolé, vous ne pouvez pas partager ce type de fichier + Vous devez lancer le chiffrement pour partager des fichiers + Veuillez lancer le chiffrement pour partager des fichiers + Données d\'entrée non supportées. Ne peuvent être partagées ! + ChatSecure a détecté qu\'une requête à été faite pour le retirer des tâches actives. Il va sûrement planter. Utilisez plutôt le bouton \"Tout déconnecter\" dans l\'écran des comptes. + Aucune visionneuse disponible pour ce format de fichier + Veuillez démarrer une conversation sécurisée avant de scanner des codes + Les clés OTR n\'ont pas été importées; veuillez vérifier que le fichier existe dans un format et un endroit appropriés + Veuillez copier le fichier \'otr_keystore.ofcases\' de l\'outil KeySync dans le répertoire racine de votre terminal + Ce message n\'a pas pu être envoyé. + Les messages seront transmis lorsque vous serez reconnecté + %1$s est hors ligne. Les messages que vous lui envoyez lui seront remis lorsque %1$s se reconnectera. + %1$s n\'est pas dans votre liste de contacts. + Vos mots de passe ne correspondent pas + La priorité doit être un nombre compris entre [0 … 127] + Le numéro de Port doit être un nombre + Erreur de transfert + Fichier illisible + Erreur Majeure : impossible de déverrouiller ou charger la base de données de l\'application. Veuillez réinstaller l\'application ou nettoyer ses données. + Pas d\'application installée qui supporte ce lien ! + Paramètres du compte + + Groupes + conversation(s) en cours + Quitter + Panique + Amis + Contacts + Accepter le certificat serveur ? + Empreinte + chargement … + + A propos de ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Liste de contacts + Paramètres + Comptes + Se déconnecter + Liste de contacts + + Jabber (XMPP) + Chat réseau local (Bonjour/ZeroConf) + Compte Google + dukgo.com + + + En ligne + Occupé + Absent + Inactif + Hors ligne + Apparaître hors ligne + Joyeux Triste @@ -414,7 +518,7 @@ Rire Confus - + Joyeux Triste @@ -447,179 +551,39 @@ :-! :-[ O:-) - :-\ + :-\\ :\'( :-X :-D o_O - Liste de contacts - Chiffrement On/Off - Connectez-vous via Tor (Nécessite l\'application Orbot) - - ChatSecure - Première utilisation de ChatSecure ? - Prêt à commencer ? - A propos de Gibberbot - Configuration du compte - - A propos de ChatSecure - ChatSecure est une application mobile de messagerie instantanée qui fournit des fonctionnalités pour éviter que l\'on espionne vos conversations et communications.\n\nL\'application supporte tous les services fonctionnant avec les protocoles Jabber ou XMPP comme Google GTalk ou Jabber.org. - - Comment est-il sécurisé? - \'Off-the-Record Messaging\' est un système de sécurité conçu pour préserver la vie privée en imitant les principes que l\'on rencontre habituellement dans le monde réel lors d\'une conversation privée, en incluant le chiffrement, l\'authentification et la confidentialité persistante. \n\nLe protocole OTR est compatible avec les clients de messagerie comme Adium ou Pidgin. - - Mes conversations sont-elles sécurisées ? - La fonctionnalité de chiffrement de Gibberbot ne fonctionne que lorsque la personne avec laquelle vous discutez utilise un programme compatible. Vous devriez donc vous assurer que vos contacts utilisent soit Gibberbot sur un appareil mobile, soit Adium ou Pidgin sur un ordinateur. Vous pouvez par ailleurs configurer comment et quand Gibberbot essaye de chiffrer vos conversations dans les Paramètres du compte.\n\nAllons-y ! - - Configuration d\'une phrase de passe - Avant de commencer, veuillez choisir un mot de passe sécurisé pour protéger vos données ChatSecure contre un accès non autorisé. - Phrase de passe : - Phrase de passe (à nouveau) : - - utilisateur@domaine.fr - nouveau nom d\'utilisateur - fournisseur de service (dukgo.com, jabber.ccc.de) - mot de passe - confirmez le mot de passe - Paramètres avancés du compte - Type de compte - Nouveau compte - - Persistance - Retenir votre mot de passe - Mot de passe mis en cache - Mot de passe non mis en cache - Connexion automatique - Se connecter au démarrage de ChatSecure - Ne pas se connecter au démarrage de ChatSecure - Se connecter maintenant - Se connecter avec les paramètres du compte - Ne pas se connecter avec les paramètres du compte - - Personnel (facultatif) - Nom du compte (votre nom) - Comment votre compte apparaît en ligne - Profil - Une brève présentation de vous-même - - Forcer le chiffrement / Refuser les communications en clair - Lorsque cela est possible, chiffrer automatiquement les conversations - Chiffrer les conversations sur demande - Désactiver le chiffrement des conversations - - Validation des certificats ... - Génération de la paire de clés ... - Connexion ... - - Erreur lors de la validation du nom d\'utilisateur ou mot de passe - veuillez les vérifier et recommencer - Erreur lors de la création des clés publique/privée - Erreur lors de la connexion au serveur de messages - veuillez valider votre configuration et essayer à nouveau. - Erreur lors de la connexion - veuillez vérifier que vous avez accès au réseau et essayer à nouveau. - Leurs empreintes - Votre empreinte - Se connecter - Regénérer les clés - ChatSecure a perdu la connexion au réseau - ChatSecure tente de rétablir une connexion - Attention : Cette conversation n\'est pas chiffrée - Cette conversation est sécurisée mais l\'identité des participants n\'a pas été vérifiée - Attention : le chiffrement de la conversation a été arrêté. - Cette conversation est sécurisée et vérifiée - Account Wizard - ID de votre compte - Configurer le serveur - Êtes-vous prêt ? - Introduisez votre ID de compte pour configurer ChatSecure au service de discussion XMPP. Il ressemble à une adresse e-mail: - Veuillez entrer l\'identifiant de votre compte (user @ hostname) : - Veuillez entrer ou modifier le nom de votre serveur jabber/xmpp et le numéro de port associé (5222 par défaut). - ChatSecure est configuré, il est donc temps de se connecter au service, et de commencer à discuter, de façon sécurisée et privée! - Vous n\'avez pas renseigné la partie @nomdedomaine.fr pour votre compte. Veuillez réessayer ! - Le nom de domaine renseigné n\'a pas de suffixe de type .com, .fr ou similaire. Veuillez réessayer ! - Entrez votre mot de passe : - - Paramètres du compte - defLoc - ATTENTION: ceci est une édition anticipée de ChatSecure qui contient peut-être des failles de sécurité ou des bogues. - - Groupes - Génération d\'une nouvelle paire de clés OTR... - Contact - Tappez le nom d\'un contact avec qui discuter - Go - Langue - Langues - Quelle langue InTheClear doit afficher ? - Vérifier le SRV - Utiliser le Serveur DNS pour trouver le serveur XMPP depuis un nom de domaine - Autoriser l\'envoi en clair du nom d\'utilisateur et du mot de passe lorsque la connexion n\'est pas chiffrée - Autoriser l\'identification en clair - Vérifie que le certificat est de confiance - Vérification TLS - Nécessite une connexion TLS/SSL - Chiffrement - comment les conversations chiffrées sont démarrées - Le serveur auquel se connecter, si nécessaire - Connection au serveur - Port TCP du serveur XMPP - Port du Serveur - Ressource XMPP - pour distinguer cette connexion de celle des autres clients connectés - Priorité de la ressource XMPP - Les messages aux clients ayant plusieurs ressources actives seront envoyés à la ressource qui a la priorité la plus haute - Suivant - Retour - Conversations - Nouveau(x) message(s) de - Authentification - Entrez un question à destination de vos contacts ainsi que la réponse que vous espérez d\'eux afin de vérifier qu\'ils sont bien ceux qu\'ils prétendent être. - la question à poser - la réponse attendue - Votre contact vous a authentifié avec succès. Authentifiez-le maintenant en posant votre propre question. - Pas de conversation.\n\nTapez ici pour commencer à discuter ! - Utiliser le thème Sombre - Si vous utilisez Tor, vous devez renseigner le nom de domaine XMPP du Serveur de connexion directement dans les Paramètres Avancés du compte - - Démarrer ChatSecure automatiquement - Toujours démarrer et se reconnecter automatiquement aux comptes - Vous n\'avez pas de compte configuré\n\nTapez ici pour en ajouter un ! - Compte Google - Enregistrement des messages en mémoire vive uniquement - N\'enregistre les messages qu\'en mémoire vive et non sur la mémoire flash afin de prévenir leur récupération. (Peut causer la perte de messages) - Image d\'arrière-plan - Entrez le chemin (\"/sdcard/foo.jpg\") d\'un arrière plan pour l\'application - Sécurité et Vie privée - Interface Utilisateur - Autres réglages - conversation(s) en cours - Orbot (Tor) - Quitter - - Voulez-vous vous déconnecter de tous les services ET tuer tous les processus (hard exit) ? - Entrez une *nouvelle* phrase secrète. Elle doit contenir au moins une lettre majuscule, une minuscule et un chiffre, et être plus longue que six caractères. - Chiffrement de vos notes existantes avec le nouveau mot de passe (patience. ..) - Entrez votre phrase secrète: - Bienvenue! Entrez une phrase secrète forte pour sécuriser vos notes. Elle doit contenir au moins une lettre majuscule, une minuscule et un chiffre, et être plus longue que six caractères. - Votre phrase secrète est trop courte - Votre phrase secrète ne contient aucune lettre majuscule - Votre phrase secrète ne contient aucune lettre minuscule - Votre phrase secrète ne contient aucun chiffre - Ouvert - ChatSecure vérouillé - Créer une phrase secrète - Entrez votre phrase secrète - Entrez une nouvelle phrase secrète - Confirmez la phrase secrète - Les phrases secrètes ne correspondent pas, veuillez essayer à nouveau - Appel vocal sécurisé - Entrez votre information de compte OStel.co ou autre fournisseur SIP sécurisé pour effectuer l\'intégration des appels - dukgo.com - Nous avons détecté une base de stockage de clés OTR à importer. Voulez-vous scanner le mot de passe QR maintenant? - Importer la base de stockage de clés OTR - Les clés OTR ont été importées avec succès - Les clés OTR n\'ont pas été importées; veuillez vérifier que le fichier existe dans un format et un endroit appropriés - Mot de passe expiré - Temps pendant lequel le chiffrement reste dévérouillé + Compte existant + Se connecter à un compte existant sur un serveur Jabber / XMPP spécifique + Compte Google + Discuter avec des utilisateurs Google grâce à votre compte Google existant. + Conversation par réseau WiFi (mesh) + Discuter avec des personnes présentes sur le même réseau WiFi - pas d\'accès internet ou de serveur requis ! + Activer les conversations par WiFi + Nouveau compte + Créer un nouveau compte, gratuit, sur un service de notre liste prédéfinie, ou un de votre choix. + Créer un nouveau compte + Identité secrète ! + Créer un compte temporaire anonyme en un seul clic (requiert Orbot : Tor pour Android) + Générer une identité + Ceci est une conversation de groupe + [renvoyé] + [renvoyé] + Impossible de partager de façon sûre ce fichier + Installer Orbot ? + Vous devez avoir Orbot installé et activé pour rediriger le trafic (proxy). Voulez-vous l\'installer ? + Toujours + Lancer Orbot ? + Orbot ne semble pas lancé. Souhaitez vous le lancer et vous connecter à Tor ? + surnom à utiliser dans la salle + Nom du salon à créer ou rejoindre + Serveur de conversation groupée (conference.foo.com) + Votre coffre de clés (keystore) est corrompu. Veuillez réinstaller ou nettoyer les données de l\'application + Vous avez reçu un message chiffré illisible + Je n\'ai pas pu déchiffrer le message que vous avez envoyé + < glisser à gauche ou droite pour plus d\'options > diff --git a/res/values-gl/arrays.xml b/res/values-gl/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-gl/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml new file mode 100644 index 000000000..22ef85d34 --- /dev/null +++ b/res/values-gl/strings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-he b/res/values-he new file mode 120000 index 000000000..6fe8b4958 --- /dev/null +++ b/res/values-he @@ -0,0 +1 @@ +values-iw \ No newline at end of file diff --git a/res/values-he/strings.xml b/res/values-he/strings.xml deleted file mode 100644 index a3870aee3..000000000 --- a/res/values-he/strings.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - אודות - - הוספת חשבון - - ערוך חשבון - - מחק חשבון - - - - - - - - - - - - - חשבונות - - - - - - - - - - - - - - - - - - - - - - - - - - - - ביטול - - - - - - - User@Host - - סיסמה - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - פתק קצר עליך - - - - - - - - diff --git a/res/values-hi/arrays.xml b/res/values-hi/arrays.xml new file mode 100644 index 000000000..cafee18d3 --- /dev/null +++ b/res/values-hi/arrays.xml @@ -0,0 +1,9 @@ + + + + बल / आवश्यकता + स्वचालित रूप से प्रयास + के रूप में अनुरोध + निष्क्रिय/ कभी नहीँ + + diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml new file mode 100644 index 000000000..50476e2c0 --- /dev/null +++ b/res/values-hi/strings.xml @@ -0,0 +1,178 @@ + + + + चैटसिक्योर + चैटसिक्योर + + + ठीक + रद्द करें + ठीक + रद्द करें + अगला + पीछे + खेलें + + खोलें + चैटसिक्योर लॉक किया गया है + कूटशब्द + कृपया कूटशब्द दाखिल करे + कृपया कूटशब्द दाखिल करें… + कूटवाक्यांश + कूटवाक्यांश (फिर से) + + खाता चुनें + खाता चुनें + (%1$d) + %1$s खाता जोड़ें + परिचय + खाता व्यवस्थित + कूटवाक्यांश व्यवस्थित + कूटवाक्यांश: + कूटवाक्यांश (फिर से): + कूटवाक्यांश राखिल करें: + आपका कूटवाक्यांश में कोई अंक नहीं थे + + + यह सुरक्षित कैसा है? + + + नया खाता + खाता जोड़ें + खाता मिटाएँ + मौजूदा खाता + + कूटशब्द: + मेरा कूटशब्द याद रखें। + नया उपयोक्तानाम + कूटशब्द + कूटशब्द सम्प्रेष्ट करें + उन्नत खाता विन्यास + खाता व्यवस्थित + कूटशब्द याद रखें + क्या आप तैयार हैं? + ऑरबोट (टोर) + + + संपर्क जोड़ें + संपर्क मिठाएँ + संपर्क खोजें + संपर्क + चलें + संपर्क जोड़ें + + भेजें + + + गोपन चालु करें + गोपन बंद करें + सुरक्षा फिंगरप्रिंट + सुरक्षा उंगलीछाप + गोपन बंद है + + + आपका उंगलीछाप + सुरक्षा उंगलीछाप (सत्यापित) + उंगलीछाप सत्यापित करें? + सुदूरवर्ती उंगलीछाप सत्यापित किया गया है + आपके लिए उंगलीछाप + उंगलीछाप + + भेजें + रद्द करें + + + खाता व्यवस्थित + + + भाषा + भाषा + + + आवाज़ + + + नया खाता बनाएँ ? + प्रमाणपत्र जानकारी + प्रमाणपत्र: + + + ध्यान + त्रुटि: + त्रुटि कोड %1$d + कृपया पहले संपर्क चुनें + कूटशब्द दाखिल करें + संदेश नहीं भेजा गया + खाता विन्यास + + + समूह + बंद और लॉक करो + खलबली + दोस्तों + संपर्क + आपका उंगलीछाप प्रदर्शित करें + लोड कर रहा है… + + + संपर्क सूची + विकल्प + संपर्क सूची + + Jabber (XMPP) + इलाका (Bonjour/ZeroConf) + गूगल खाता + dukgo.com + + + व्यस्त + ऑफलाइन + ऑफलाइन प्रकट करें + + + ख़ुशी + Sad + Winking + Tongue sticking out + Surprised + Kissing + Yelling + Cool + Money mouth + Foot in mouth + Embarrassed + Angel + Undecided + Crying + Lips are sealed + हँसी + Confused + + + + ख़ुशी + Sad + Winking + Tongue sticking out + Surprised + Kissing + Yelling + Cool + Money mouth + Foot in mouth + Embarrassed + Angel + Undecided + Crying + Lips are sealed + हँसी + Confused + + मौजूदा खाता + गूगल खाता + नया खाता + नया खाता बनाएँ + ऑरबोट इंस्टॉल करें? + हर समय + ऑरबोट चालू करें? + diff --git a/res/values-hu/arrays.xml b/res/values-hu/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-hu/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml new file mode 100644 index 000000000..da240e5c9 --- /dev/null +++ b/res/values-hu/strings.xml @@ -0,0 +1,575 @@ + + + + ChatSecure + ChatSecure + + azonnali üzenetek olvasása + Engedélyezze az alkalmazásoknak, hogy adatot olvassanak az IM tartalomszolgáltatótól. + azonnali üzenetek írása + Engedélyezze az alkalmazásoknak, hogy adatot írjanak az IM tartalomszolgáltatónak. + IM szolgáltatás indítása + Engedélyezze az alkalmazásoknak, hogy szükség esetén IM szolgáltatást indítsanak. + + Megerősít + OK + Mégse + OK + Mégse + Következő + Vissza + Csatlakozás + Lejátszás + Telepítés + + Megnyitás + ChatSecure Zárolva + Jelszó Beállítása + Jelszó Megerősítése + Jelszó + Lehetőséged van mesterjelszót beállítani, ami nélkül a ChatSecure nem tud hozzáférni a kapcsolataidhoz és az üzeneteidhez: + Új Jelszó Megerősítése + A jelszó nem egyezik, próbáld újra + Kihagyás >> + Felugró Ablak + Kérem adj meg egy jelszót + Jelszót kérem… + Jelmondat + Jelmondat (újra) + + Felhasználói fiók kiválasztása + Felhasználói fiók kiválasztása + (%1$d) + Fiók %1$s hozzáadása + Rólunk + Kijelentkezés mindenből + Minden szolgáltatásból ki akarsz jelentkezni? + Ki vagy jelentkezve ebből: %1$s. + Ki vagy jelentkezve ebből: %1$s, mert %2$s + Először használod a ChatSecure-t? + Alig várod, hogy elkezdd? + Kezdés + Fiók Beállítása + Jelmondat Beállítása + Mielőtt elkezdenéd, kérlek válassz egy biztonságos jelmondatot, hogy megvédd a ChatSecure adataidat az illetéktelen hozzáféréstől. + Jelmondat: + Jelmondat (újra): + Adj meg egy új jelszót! A jelszónak tartalmaznia kell legalább egy nagy-, egy kisbetűt, és egy számjegyet, valamint legalább hat karakter hosszúnak kell lennie. + Meglévő jegyzeteid titkosítása az új jelmondattal (türelem…) + Jelszó megadása: + Üdvözlünk! Adj meg egy nehezen megfejthető jelszót a jegyzeteid titkosításához! A jelszónak tartalmaznia kell legalább egy nagy-, egy kisbetűt, és egy számjegyet, valamint legalább hat karakter hosszúnak kell lennie. + Az álatlad megadott jelszó nem elég hosszú + Az általad megadott jelszó nem tartalmaz nagybetűt + Az általad megadott jelszó nem tartalmaz kisbetűt + Az általad megadott jelszó nem tartalmaz számjegyet + + A ChatSecure-ról + A ChatSecure egy mobil azonnali üzenetküldő alkalmazás, ami extra biztonságot nyújt az ellen, hogy mások kémkedni tudjanak a beszélgetéseidről.\n\nAz alkalmazás támogat minden chat szolgáltatást, ami a Jabber vagy XMPP protokollt használja, mint a Google GTalk vagy a Jabber.org. + + Hogyan biztonságos? + Az \'Off-the-Record Üzenetváltás\' egy biztonságos rendszer, ami arra lett tervezve, hogy engedélyezze az adatvédelmet úgy, hogy lemásolja a való élet privát beszélgetéseinek jellemzőit, beleértve a Titkosítást, Hitelesítést, Megtagadhatóságot és a Továbbítási biztonságot.\n\nAz OTR-protokoll kompatibilis a számítógépes chat kliensekkel, mint az Adium vagy a Pidgin. + + Biztonságosak a Chatjeim? + A ChatSecure chat titkosítás funkciója csak akkor működik, ha olyan személlyel váltasz üzenetet, aki kompatibilis alkalmazást vagy programot használ, szóval győződj meg róla, hogy a kapcsolataid a ChatSecure-t használják mobilon és az Adium-ot vagy Pidgin-t használják számítógépen. A Fiókbeállításokban finomhangolhatod, hogy hogyan és mikor titkosítsa a ChatSecure a chatjeidet.\n\nKezdjük! + + Új Fiók + Fiók Hozzáadása + Fiók Szerkesztése + Fiók Eltávolítása + Létező Fiók + + Felhasználó@Kiszolgáló + Jelszó: + Jelszó megjegyzése. + Automatikus bejelentkezés. + Nincs fiókod? + Bejelentkezés + A biztonságod érdekében, ha a telefonod elveszik vagy ellopják, lépj be a weboldalra számítógépen, és változtasd meg a jelszavad. + Ez az opció automatikusan bejelentkeztet, amikor megnyitod az alkalmazást. A letiltáshoz jelentkezz ki és töröld az \"Automatikus bejelentkezés\" megjelölést. + Csatlakozás Tor-ral (Orbot alkalmazás szükséges) + felhasználó@domain + új felhasználónév + szolgáltató (dukgo.com, jabber.ccc.de) + jelszó + jelszó megerősítése + Haladó Fiókbeállítások + Fiók Beállítása + Fiók Regisztrálása + Perzisztencia + Jelszó Megjegyzése + Jelszó tárolva + Jelszó nincs tárolva + Automatikus Bejelentkezés + Csatlakozás a ChatSecure-hoz indításkor + Ne csatlakozzon a ChatSecure-hoz indításhoz + Bejelentkezés Most + Kapcsolódás a következő fiók beállításához + Ne kapcsolódjon a következő fiók beállításához + Személyes (választható) + Fiók Álnév (a te neved) + Hogyan jelenjen meg az elérhető fiókod + Profil + Rövid szöveg magadról + Erőltetett titkosítás / sima szöveg visszautasítás + Chat automatikus titkosítása, ha lehetséges + Titkosított chat kérése + Chat titkosítás kikapcsolása + Követelmények érvényesítése… + Kulcspár generálása… + Bejelentkezés… + Fiók Varázsló + Fiókazonosító + Szerver Beállítása + Készen Állsz? + Add meg a fiókazonosítódat, hogy a ChatSecure-t beállítsd az XMPP chat szolgáltatásához. Úgy néz ki mint egy email cím: + Kérem add meg a Fiókazonosítódat (felhasználó@kiszolgáló): + Kérem add meg, vagy szerkeszd a jabber/xmpp chat szerver kiszolgálójának nevét és port számát (5222 az alapértelmezett) + A ChatSecure beállítva, itt az idő, hogy csatlakozz a szolgáltatásához, és biztonságosan és titkosítva chatelj! + Orbot (Tor) + Chat szolgáltatás domain + Új fiók regisztrálása… + Haladás… + Bejelentkezés megszakítása + Bejelentkezés\u2026 + Kijelentkezés\u2026 + + SRV Lookup + DNS SRV használata, hogy megtalálja a jelenlegi XMPP szervert a domain névből + Engedélyezi a felhasználónév és jelszó elküldését sima szövegként, amikor titkosítatlan forgalmat használ + Sima Szöveg Hitelesítésének Engedélyezése + Megerősítés, hogy a tanúsítvány megbízható + TLS ellenőrzés + Megkövetelt TLS Kapcsolat + Forgalom Titkosítás + hogyan kezdődtek a titkosított chatek + A szerver, amihez kapcsolódik, ha szükséges + Szerver Csatlakoztatás + TCP Port az XMPP Szerverhez + Szerver Port + XMPP Forrás + megkülönböztetni ezt a kapcsolatot a többi klienstől, amin be vannak jelentkezve + XMPP Forrás Prioritás + Több aktív forrással rendelkező kliensek üzenetei a legmagasabb prioritású forrásnak lesznek elküldve + + Beszélgetések + Nincsenek beszélgetések.\n\nÉrintsd meg itt a kezdéshez! + Nincsenek\nfiókjaid beállítva.\n\nÉrintsd meg itt a hozzáadáshoz! + Kapcsolat Lista - %1$s + Kapcsolat Hozzáadása + Kapcsolat Törlése + Kapcsolat Blokkolása + Kapcsolat nicknév + Blokkolva + Nicknév + A kapcsolat "%1$s" törölve lesz. + A kapcsolat "%1$s" blokkolva lesz. + A kapcsolat "%1$s" fel lesz oldva. + Kapcsolat "%1$s" hozzáadva. + Kapcsolat "%1$s" törölve. + Kapcsolat "%1$s" blokkolva. + Kapcsolat "%1$s" feloldva. + Új Chat + Kapcsolatok Keresése + Rács Megjelenítése + Chat indítása + Profil Megtekintése + Kapcsolat Megerősítése + Folyamatban lévő chatek (%1$d) + %1$d online + Ismerős meghívások + (Ismeretlen) + Üres + Nincsenek beszélgetések megnyitva\n\nÉrintsd meg chatelés kezdéséhez! + Kapcsolat + írd be a kapcsolat nevét a chateléshez + Indít + Nincsenek aktív chatek. + Kapcsolat hozzáadása + Hozzáadni kívánt személy felhasználóneve vagy JabberID-je: + Hozzáadni kívánt fiók: + Írj be egy nevet a Kapcsolatokból történő hozzáadáshoz. + Meghívó Küldése + Kapcsolat profilja + Státusz: + Kliens típusa: + Számítógép + Mobil + Blokkolt kapcsolatok - %1$s + Nincsenek blokkolt kapcsolatok. + + Chatelés vele: %1$s + Én + %1$s elérhető + %1$s távol van + %1$s elfoglalt + %1$s nem elérhető + %1$s csatlakozott + %1$s elhagyta a beszélgetést + Fotó Küldése + Fájl küldése + Hang Küldése + Kép Készítése + Fájl Küldése + Elküldve + Küldés folyamatban + Küldés fogadása? + fájlt akar küldeni + Nincs elérhető kapcsolat a megosztásod elküldéséhez! + Küldés + Üzenet küldése + Biztonságos üzenet küldése + Újraküldés + Üzenetváltás befejezése + Chat törlése + Smiley beszúrása + Chatek Váltása + Menü+ + Link választása + Törlés + Fájlok Megtartása + Chat Folyamat Törlése A Biztonságos Tárhelyről? + Minden folyamat fel- és letöltött fájlja véglegesen törölve lesz. Vigyázat: a folyamat nem vonható vissza! + Eredeti Törlése? + Ez a fájl át lesz másolva a biztonságos tárhelyre, mielőtt elküldöd. Szeretnéd törölni az eredeti fájlt az eszköz titkosítatlan tárhelyéről? + Megtart + Exportálás + Média Fájl Exportálása? + Ez a média fájl exportálva lett ide: %1$s + Chat befejezése? + Ebből a folyamatból minden biztonságos médiaelem törölve lesz. Médiaelem exportálásához tartsd hosszan az ujjad az ikonon. + Chat Befejezése és Fájlok Törlése + + %1$s meghívott egy csoportos chatbe. + Meghívó elküldve neki: %1$s. + Elfogad + Elutasít + Csoportos Chat + Létrehoz vagy Csatlakozik Csoportos Chathez + Kapcsolódás csoportos chathez… + Meghívás\u2026 + Meghívandó fiók(ok) kiválasztása + Gépelj kapcsolat kereséséhez + Nem talált kapcsolat.\n\nÉrintés a meghíváshoz. + Hozzáadod %1$s? + Igen + Nem + Nem lehet jóváhagyni a feliratkozást tőle: %1$s. Kérem próbáld később. + Nem lehet megtagadni a feliratkozást tőle: %1$s. Kérem próbáld később. + + Titkosítás Indítása + Titkosítás Megállítása + Titkosított chat folyamat indítása… + Titkosított chat folyamat megállítása… + Biztonságos Ujjlenyomat + Biztonságos Ujjlenyomat + Bejelentkezés + Kulcs Újragenerálás + Titkosítás kikapcsolva + Titkosítás bekapcsolva (Érintsd meg a megerősítéshez) + A kapcsolatod befejezte a titkosított chatet. + Titkosítva és megerősítve! + Új OTR kulcspár generálása… + + OTR kulcstartó érzékelve importálásra. Szeretnéd beolvasni a QR jelszót most? + KeySync Aktiválása + Sikeresen importált OTR kulcskarika + + QR beolvasás + A Te Ujjlenyomatod + Kézi + Kérdés + Biztonságos Ujjlenyomat (Megerősítve) + Biztos vagy benne, hogy megerősíted ezt az ujjlenyomatot? + Ujjlenyomat Megerősítése? + A távoli ujjlenyomat megerősítve! + Ujjlenyomat neked + Ujjlenyomat neki: + Barcode Scanner Telepítése? + Az alkalmazásnak szüksége van a Barcode Scannerre. Telepíted most? + + Azonosítás + Írj be egy kérdést, ami el lesz küldve a kapcsolatnak, és a választ, amit vár tőlük, ezzel megerősíthetik, hogy valóban azok, akiknek állítják magukat. + a feltett kérdés + a várt válasz + A kapcsolatod sikeresen hitelesített téged. Most rajtad a sor, hogy hitelesítsd őt egy saját kérdéssel. + OTR K&V megerősítés + Chat Titkosítás + Küldés + Mégse + + Biztonságos Hívás + Biztonságos Hang + Add meg az OStel.co vagy másik biztonságos SIP szolgáltatás fiókodat a hívás integráláshoz + + Fiók Beállítása + + Biztonság és Adatvédelem + Titkosítás és Anonimitás + Titkosítás Be/Ki + Jelszó lejárati idő + Az az idő, amíg az alkalmazás titkosítás feloldott állapotot jelez + + Kattintható linkek Tor-ban + A fiókoknak, amik Tor-t használnak, engedélyezze a linkek kattinthatóságát a chatben (VIGYÁZAT, lehetséges adatszivárgás!) + Felhasználói Felület + Nyelv + Nyelvek + Alapértelmezett nyelv + Sötét Téma Használata + Alkalmazás témájának cseréje sötétre + Üzenet Tárolása Csak A Memóriában + Üzenetek tárolása csak a memóriában, nem a tárhelyen, hogy megvédje az üzeneteket a kibontástól. (Üzenetvesztést eredményezhet) + Háttérkép + (\"/sdcard/foo.jpg\") útvonal beállítása az alkalmazás háttérképéhez + Kapcsolati Rács Megjelenítése + Kapcsolatlista megjelenítése avatar rácsként + Igen, Mindet Elfogadom + Nem Biztonságos Média Törlése + Fotó vagy fájl megosztása után automatikusan törölje az eredetit a nem biztonságos tárhelyről miután importálta + Média Tárolása Memóriakártyán + Médiafájlok a chat folyamatokból tárolva vannak egy biztonságos konténerben, amiket tárolni lehet a belső vagy a külső tárhelyen + + Memóriakártya Hiányzik + A chat naplók tárolva vannak az SD kártyán, de jelenleg nincs SD kártya. Kérem helyezd be a megfelelő SD kártyát, vagy töröld a létező chat naplót és indítsd újra a ChatSecure-t. + Chat Média Tároló Hiányzik + A chat naplók SD kártyán vannak tárolva, de a fájl hiányzik a jelenlegi SD kártyáról. Kérem helyezd be a megfelelő SD kártyát, vagy töröld a meglévő chat naplót és indítsd újra a ChatSecure-t. + Chat Napló Törlése + + Egyéb Finomhangolás + ChatSecure Indítása Automatikusan + Mindig indítsa és automatikusan jelentkezzen be az előzőleg naplózott fiókba + Nem elérhető kapcsolatok elrejtése + Előtér prioritás használata + Csökkentse az esélyét annak, hogy az Android újraindítsa a kapcsolati szolgáltatást. Ez elhelyez egy értesítést az értesítési sávban. + Életjel ciklus + Magasabb érték használata (percekben) energiatakarékosság miatt. Magasabb értéknél a szolgáltató megszakíthatja a kapcsolatot az inaktivitás miatt. + + Értesítések beállítása + IM értesítések + Értesítési sáv jelzés, amikor IM érkezik + Rezgés + Rezegjen is IM érkezésénél + Hang + Hangjelzés is IM érkezésénél + Egyéni Csengőhang Kiválasztása + + Hibakeresési Napló Engedélyezése + Kimeneti alkalmazásnapló adat a standard kimenetre / logcat hibakereséshez + + Mobiladatok letiltva + A hálózati adatkapcsolat (adatcsere a háttérben is) szükséges az alkalmazásnak a bejelentkezéshez. + Engedélyez + Kilépés + Ki szeretnél jelentkezni az összes szolgáltatásból ÉS kilőni a folyamatokat (kényszerbezárás)? + Új fiók létrehozása? + Új chat fiók létrehozása a felhasználónévhez \'%1$s\'? + Tanúsítvány Info + Tanúsítvány: + Kiadva általa: + SHA1 Ujjlenyomat: + Kiadva: + Lejár: + Csatlakozás Chat Szobához? + Egy külső alkalmazás megpróbál csatlakoztatni egy chatszobába. Engedélyezed? + Háttér Választása + Akar háttérképet választani a Galériából? + Kép Választása + + Új %1$s üzenetek + %1$d olvasatlan üzenet + Új meghívás ismerőstől: %s + Új fájl %1$s tőle: %2$s + Csoportos chat meghívás + Új csoportos chat meghívás tőle: %s + Új üzenet(ek) tőle: + ChatSecure szolgáltatás indítása… + Aktiválva & feloldva + üzenet másolva a vágólapra + + Figyelem + Hiba: + Hibakód %1$d + Nem lehet bejelentkezni a %1$s szolgáltatásba. Kérem próbáld később.\n(Detail: %2$s) + A lista nincs hozzáadva. + Kapcsolat nincs blokkolva. + Kapcsolat nincs feloldva. + Kérem először válassz egy kapcsolatot. + Lecsatlakozva!\n + Szolgáltatás hiba! + A kapcsolatlista nem töltődött be. + Nem lehet csatlakozni a szerverhez. Kérem ellenőrizd a kapcsolatot. + "%1$s" már létezik a Kapcsolatlistádban. + Kapcsolat "%1$s" blokkolva. + Kérem várj, amíg a Kapcsolatlista betöltődik. + Hálózati hiba történt. + WiFi szükséges ehhez a kapcsolathoz. + A szerver nem támogatja ezt a funkciót. + A megadott jelszó nem megfelelő. + A szerver hibába ütközött. + A szerver nem támogatja ezt a funkciót. + A szerver jelenleg nem elérhető. + Időtúllépés történt a szerveren. + A szerver nem támogatja a jelenlegi verziót. + Az üzenetsor megtelt. + A szerver nem támogatja a továbbítást a domainhez. + A megadott felhasználónév nem felismerhető. + Sajnáljuk, a felhasználó által blokkolva van. + A folyamat lejárt, kérem jelentkezz be újra. + be vagy jelentkezve egy másik kliensen. + be vagy jelentkezve egy másik kliensben. + Sajnáljuk, a telefonszámot nem lehet beolvasni a SIM kártyáról. A segítségért kérem vedd fel a kapcsolatot a szolgáltatóddal. + Jelenleg nincs bejelentkezve. + A ChatSecure hibába ütközött, amikor érvényesítette a felhasználóneved vagy jelszavad - kérem ellenőrizd őket és próbáld újra. + A ChatSecure hibába ütközött, amikor generálta a kulcspárokat. + A ChatSecure hibába ütközött, amikor megpróbált csatlakozni a chat szerverhez - kérem ellenőrizd a beállításokat, majd próbáld újra. + A ChatSecure hibába ütközött, amikor megpróbált csatlakozni - kérem ellenőrizd duplán a hálózati kapcsolatot, majd próbáld újra. + Hálózat nem elérhető + A ChatSecure megpróbálja újrastabilizálni a kapcsolatot + Nem adtál meg @kiszolgáló.com részt a Fiókazonosítóhoz. Próbáld újra! + A szerver neve nem tartalmaz .com, .net vagy hasonló függeléket. Próbáld újra! + Jelszó megadása: + Mióta Tor-t használsz, azóta az XMPP \'Connect Server\' kiszolgálót közvetlenül a Haladó Fiókbeállításokban kell megadni. + VIGYÁZAT: Ez a szolgáltatás gyenge kriptográfiával ellátott tanúsítványt használ. Kérem értesítsd a rendszergazdát a fejlesztésről. + A Közölt Tanúsítvány Nem Egyezik A Letűzött Tanúsítvánnyal: + Nem lehet létrehozni vagy csatlakozni csoportos chathez + Sajnos nem tudjuk megosztani ezt a fájltípust + Engedélyezned kell a titkosítást a fájlok megosztásához + Kérem engedélyezd a chat titkosítást a fájlmegosztáshoz + Nem támogatott beérkező adat, nem lehet megosztani! + A ChatSecure érzékelte, hogy egy kérés el szeretné távolítani a folyamatot. A ChatSecure valószínűleg összeomlik. Kérem ehelyett használd a Kijelentkezés mindenből menüt a fióklista képernyőn. + Nincs nézegető ehhez a fájlformátumhoz + Kérem indíts egy biztonságos beszélgetést, mielőtt kódot olvasol be + OTR kulcskarika nincs importálva; Kérem ellenőrizd, hogy a fájl létezik megfelelő kiterjesztésben és helyen + Kérem másold az \'otr_keystore.ofcaes\' fájlt az számítógép KeySync eszközből a készülék főkönyvtárába. + Ezt az üzenetet nem lehet elküldeni. + Le vagy csatlakozva. Az üzenetek akkor lesznek elküldve, amikor újrakapcsolódsz. + %1$s nem elérhető. Az elküldött üzenetek kézbesítve lesznek, amikor %1$s elérhető lesz. + %1$s nincs a Kapcsolatlistában. + A jelszavaid nem egyeznek + A prioritásnak [0 .. 127] közötti számnak kell lennie + A port számnak számnak kell lennie + Átviteli Hiba + Nem lehet a fájlt a tárhelyre írni + Jelentős Hiba: Nem lehet feloldani vagy betölteni az alkalmazás adatbázist. Kérem telepítsd újra az alkalmazást, vagy töröld az adatokat. + Nincs alkalmazás telepítve, amely kezelni tudná a hivatkozást! + Fiók Beállításai + + Csoportok + beszélgetés(ek) megnyitása + Kikapcsolás & Zárolás + Pánik + Haverok + Kapcsolatok + Elfogadod A Szerver Tanúsítványát? + Ujjlenyomat Megjelenítése + betöltés… + + ChatSecure-ról\nhttps://guardianproject.info/apps/chatsecure/ + + Kapcsolat Lista + Beállítások + Fiókok Kezelése + Kijelentkezés + Kapcsolatlista + + Jabber (XMPP) + Helyi (Bonjour/ZeroConf) + Google Fiók + dukgo.com + + + Elérhető + Elfoglalt + Távol + Tétlen + Nem elérhető + Láthatatlan + + + Boldog + Szomorú + Kacsint + Kiöltött nyelv + Meglepett + Csók + Meglepett + Menő + Mérges + Fáradt + Ártatlan + Angyal + Hmmm + Sír + Lakat a száján + Nevetés + Zavart + + + + Boldog + Szomorú + Kacsint + Kiöltött nyelv + Meglepett + Csók + Meglepett + Menő + Mérges + Fáradt + Ártatlan + Angyal + Hmmm + Sír + Lakat a száján + Nevetés + Zavart + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Létező Fiók + Kapcsolódás a meglévő fiókomhoz egy egyéni Jabber / XMPP szerveren. + Google Fiók + Chatelés más Google felhasználókkal meglévő Google fiók használatával. + WiFi Hálózat Chat + Chatelés másokkal ugyanazon a WiFin vagy hálózaton - nincs szükség internetre vagy szerverre! + WiFi Chat Engedélyezése + Új Fiók + Új, ingyenes fiók regisztrálása bármelyik szolgáltatásra a beépített listából, vagy bármely választottra. + Új Fiók Létrehozása + Titkos Személyazonosság! + Névtelen, eldobható \"önmegsemmisítő\" chat fiók létrehozása egy egyszerű érintéssel (Orbot: Tor for Android szükséges) + Személyazonosság Generálása + Ez egy csoportos chat + [újraküldve] + [újraküldve] + Nem lehetséges biztonságosan megosztani ezt a fájlt + Orbot Telepítése? + Az Orbot-nak telepítve és aktiválva kell lennie, hogy proxy forgalom haladhasson át rajta. Szeretnéd telepíteni? + Mindig + Orbot Indítása? + Úgy tűnik, hogy az Orbot nem fut. Szeretnéd elindítani és csatlakozni a Tor-hoz? + szobában használt nicknév + szoba neve az elkészítéshez vagy csatlakozáshoz\" + csoport chat szerver (conference.foo.com) + A kulcstartód sérült. Kérem telepítsd újra a ChatSecure-t vagy válaszd az \'Adatok törlése\'-t az alkalmazásnál + Kaptál egy olvashatatlan titkosított üzenetet + Nem tudom feloldani az elküldött üzenetet + < lapozz balra vagy jobbra a további lehetőségekhez > + diff --git a/res/values-id b/res/values-id new file mode 120000 index 000000000..9ea8dda4b --- /dev/null +++ b/res/values-id @@ -0,0 +1 @@ +values-in \ No newline at end of file diff --git a/res/values-id/arrays.xml b/res/values-id/arrays.xml deleted file mode 100644 index 4450f6878..000000000 --- a/res/values-id/arrays.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - Harus - Otomatis - Ketika Diminta - Non Aktif - - - paksa - otomatis - diminta - non-aktif - - - Default - Arab - Farsi - Cina - Jepang - Korea - Denmark - Jerman - Inggris - Spanyol - Esperanto - Perancis - Itali - Hungaria - Belanda - Norwegia - Portugis - Rusia - Slowakia - Swedia - Tibet - Vietnam - Turki - Ceko - Yunani - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - - diff --git a/res/values-id/strings.xml b/res/values-id/strings.xml deleted file mode 100644 index fe068fb52..000000000 --- a/res/values-id/strings.xml +++ /dev/null @@ -1,316 +0,0 @@ - - - baca pesan instan - tulis pesan instan - - - - - - - - - - - - - - - - - - - Terblokir - - - Percakapan Baru - - - Pengaturan - - Cari Kontak - - Mulai percakapan - - Keluar - - Periksa Ulang - - - Mulai Enkripsi - Hentikan Enkripsi - Hapus Percakapan - Hentikan percakapan - - - Daftar kontak - - Undang\u2026 - - Pindah percakapan - - Masukkan smiley - - Pindai Sidik Jari - Sidik Jari Anda - - - - - Pastikan - - Apakah anda ingin keluar dari semua layanan? - - - - - OK - - Batal - - OK - - Batal - - - - - - - - Kata Sandi: - - Ingat kata sandi saya. - - Masukkan saya secara otomatis. - - Tidak punya akun? - - Untuk keamanan anda, jika telepon anda hilang atau dicuri, segera akses laman di komputer anda dan rubah kata kunci. - Pilihan ini akan memasukkan anda secara otomatis tiap kali anda membuka aplikasi ini. Untuk menon-aktifkan pilihan ini, keluar, lalu hapus contrengan \"Masukkan saya secara otomatis\". - - Masuk - - - - Proses masuk - - - - - - - Aktifkan - - Keluar - - - - - Undangan rekan - - (Tidak dikenal) - - Kosong - - Tidak ada percakapan - - Pilih kontak yang akan diundang - Ketik untuk mencari kontak - Kontak tidak ditemukan. - - - Tidak ada kontak yang diblokir. - - - Profil kontak - - Status: - - Tipe klien: - - Komputer - - Telepon genggam - - - Online - - Sibuk - - Tidak didepan komputer - - Menganggur - - Offline - - Tampil seperti offline - - - - - Saya - - Ketik untuk bercakap - - - - - - - - - - Kirim - - Pesan ini tidak dapat terkirim. - - Koneksi ke server terputus. Pesan akan dikirim jika tersambung. - - - - Pilih tautan - - Tidak ada percakapan aktif. - - - - Tambahkan kontak - - Alamat email orang yang akan anda undang: - - Pilih daftar: - - Ketik nama untuk menambahkan dari kontak. - - Kirim undangan - - - Pengaturan Akun - Enkripsi dan Anonimitas - Sembunyikan kontak yang offline - - Pengaturan pemberitahuan - - Pemberitahuan IM - - Tampilkan pesan di status bar jika menerima IM - - Getar - - Juga getarkan jika menerima IM - - Suara - - Juga mainkan nada dering jika menerima pesan instan - - Pilih nada dering - - - - - Terima - - Tolak - - - Terima - - Tolak - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values-in/arrays.xml b/res/values-in/arrays.xml new file mode 100644 index 000000000..0a1e2df2d --- /dev/null +++ b/res/values-in/arrays.xml @@ -0,0 +1,9 @@ + + + + Harus / Wajib + Coba Otomatis + Sesuai Permintaan + Non Aktif / Tidak Pernah + + diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml new file mode 100644 index 000000000..b33f2ccb2 --- /dev/null +++ b/res/values-in/strings.xml @@ -0,0 +1,491 @@ + + + + ChatSecure + ChatSecure + + baca pesan instan + + Perbolehkan aplikasi untuk membaca data dari penyedia konten IM. + + tulis pesan instan + + Perbolehkan aplikasi untuk menulis data ke penyedia konten IM. + + mulai layanan IM + Perbolehkan aplikasi untuk memulai layanan IM menggunakan intent. + + Penegasan + OK + Batal + OK + Batal + Berikutnya + Kembali + Hubungkan + + Buka + ChatSecure Terkunci + Setel Password + Tegaskan Password + Password + Password Baru + Tegaskan Password Baru + Password tidak cocok, silahkan coba lagi + Saya Merasa Malas (Tanpa Password!) + + Pilih sebuah akun + Pilih sebuah akun + (%1$d) + Tambah akun %1$s + Tentang + Sign out semua + Apakah anda ingin melakukan sign out untuk semua layanan? + Anda telah melakukan Sign Out dari %1$s. + Anda telah melakukan Sign Out dari %1$s karena %2$s + Pertama kali menggunakan ChatSecure? + Tidak sabar untuk memulai? + Memulai + Mempersiapkan Akun + Mempersiapkan Passphrase + Sebelum anda mulai, silahkan pilih passphrase yang aman untuk menjaga data ChatSecure anda dari akses yang tidak berhak. + Passphrase: + Passphrase (lagi): + Masukkan sebuah passphrase yang *baru*. Passphrase tersebut harus memiliki paling sedikit satu huruf besar, satu huruf kecil dan satu angka, juga minimal memiliki 7 karakter. + Mengenkripsi catatan anda yang ada dengan passphrase yang baru (harap sabar…) + Masukkan Passphrase anda: + Selamat Datang! Masukkna sebuah passphrase yang kuat untuk mengamankan catatan anda. Passphrase tersebut harus memiliki paling sedikit satu huruf besar, satu huruf kecil dan satu angka, juga lebih panjang dari 6 karakter. + Passphrase anda tidak cukup panjang + Passphrase anda tidak memiliki minimal sebuah huruf besar + Passphrase anda tidak memiliki minimal sebuah huruf kecil + Passphrase anda tidak memiliki minimal sebuah angka + + Tentang ChatSecure + ChatSecure adalah aplikasi mobile perpesanan instan yang menyediakan fitur keamanan ekstra untuk mencegah orang lain mengintip percakapan dan komunikasi anda. + +Aplikasi ini mendukung setiap layanan percakapan yang menggunakan protokol Jabber atau XMPP, seperti Google GTalk atau Jabber.org. + + Bagaimana hal tersebut aman? + \'Off-the-Record Messaging\' adalah sistem keamanan yang dirancang untuk memungkinkan privasi dengan meniru karakter dari percakapan pribadi di dunia nyata, termasuk Penyandian, Otentikasi, Penyangkalan, dan Penerusan Kerahasiaan. + +OTR-protocol kompatibel dengan klien percakapan desktop seperti Adium atau Pidgin. + + Apakah percakapan saya aman? + Fitur enkripsi ChatSecure hanya berfungsi ketika menggunakan aplikasi yang kompatibel, jadi anda sebaiknya memastikan kontak anda menggunakan ChatSecure untuk mobile dan Adium atau Pidgin untuk desktop. Anda dapat menyesuaikan dengan tepat bagaimana dan kapan ChatSecure mencoba untuk mengenkripsi percakapan anda di Pengaturan Akun. + +Mari kita mulai! + + Akun Baru + Tambah akun + Edit akun + Hapus akun + + User@Host + Password: + Ingat kata sandi saya + Buat saya otomatis Sign In. + Tidak punya akun? + Masuk + Untuk keamanan anda, jika telepon anda hilang atau dicuri, masuk ke situs web pada komputer anda dan ubah password anda. + Pilihan ini membuat anda otomatis Sign In setiap membuka aplikasi ini. Untuk menonaktifkan opsi ini, lakukan Sign Out, kemudian hapus check box \"Buat saya otomatis Sign In\" + Hubungkan melalui Tor (memerlukan aplikasi Orbot) + user@domain.com + username baru + penyedia layanan (dukgo.com, jabber.ccc.de) + password + penegasan password + Pengaturan Akun Lanjutan + Tipe Akun + Pendaftaran Akun + Penyimpanan + Ingat Password + Password disimpan + Password tidak disimpan + Otomatis Sign In + Hubungkan ke ChatSecure ketika start-up + Jangan hubungkan ke ChatSecure ketika start-up + Sign In Sekarang + Hubungkan setelah mempersiapkan akun + Jangan hubungkan setelah mempersiapkan akun + Personal (opsional) + Nama lain Akun (nama anda) + Bagaimana akun anda terlihat online + Profil + Uraian singkat mengenai diri anda + Wajibkan enkripsi / tolak teks biasa + Jika memungkinkan, otomatis enkripsi percakapan + Enkripsi percakapan ketika diminta + Non aktifkan enkripsi percakapan + Memeriksa pengenal… + Membangkitkan keypair… + Melakukan Sign In… + Wizard Akun + ID Akun Anda + Konfigurasi Server + Anda Siap? + Masukkan ID akun anda untuk mengkonfigurasi ChatSecure pada layanan percakapan XMPP anda. ID tersebut terlihat seperti alamat email: + Silahkan masukkan ID akun anda (user@hostname): + Silahkan masukkan atau edit hostname dan port number (default adalah 5222) server percakapan jabber/xmpp anda. + ChatSecure telah dikonfigurasi, dan sekarang waktunya untuk terhubung ke layanan anda, dan memulai percakapan yang aman, terjamin secara privat! + Orbot (Tor) + Pilih sebuah domain + Mendaftarkan akun baru… + membuat ChatSecure akan… + Batalkan sign in + Melakukan Sign In\u2026 + Melakukan Sign Out\u2026 + + Lakukan SRV Lookup + Gunakan DNS SRV untuk mencari server XMPP sebenarnya dari nama domain + Izinkan username dan password dikirim sebagai teks biasa ketika menggunakan transport yang tidak dienkripsi + Izinkan Otentifikasi dengan Teks Biasa + Memastikan certificate dipercaya + Verifikasi TLS + Memerlukan Koneksi TLS + Enkripsi Transport + bagaimana enkripsi percakapan dimulai + Server yang ingin dihubungi, jika diperlukan + Menghubungi Server + Port TCP untuk Server XMPP + Port Server + Sumberdaya XMPP + untuk membedakan koneksi ini dari klien lain yang juga sedang Log In + Prioritas Sumberdaya XMPP + Pesan ke klien dengan beberapa sumberdaya aktif akan dikirim ke sumberdaya yang memiliki prioritas tertinggi + + Percakapan + Tidak ada percakapan. + +Sentuh disini untuk memulai! + Anda tidak memiliki +akun yang dikonfigurasi. + +Ketuk di sini untuk menambah! + Daftar Kontak - %1$s + Tambah kontak + Hapus kontak + Blokir kontak + Diblokir + Kontak "%1$s" akan dihapus. + Kontak "%1$s" akan diblokir. + Kontak "%1$s", blokir akan dibuka. + Kontak "%1$s" ditambahkan. + Kontak "%1$s" dihapus. + Kontak "%1$s" diblokir. + Kontak "%1$s" batal diblokir. + Percakapan Baru + Cari Kontak + Tampilkan Grid + Mulai Percakapan + Lihat Profil + Sahkan Key + Percakapan sedang berlangsung (%1$d) + %1$d online + Undangan pertemanan + (Tidak Diketahui) + Kosong + Tidak terdapat percakapan + Kontak + ketika nama kontak dengan siapa anda ingin mengobrol + Mulai + Tidak ada percakapan yang aktif. + Tambah kontak + Alamat email orang yang ingin anda undang: + Pilih dari daftar: + Ketik nama untuk ditambahkan dari Kontak. + Kirim undangan + Profil kontak + Status: + Jenis klien: + Komputer + Telepon genggam + Daftar kontak diblokir - %1$s + Tidak ada kontak yang diblokir + + Mengobrol dengan %1$s + Saya + %1$s sedang online + %1$s sedang tidak di tempat + %1$s sedang sibuk + %1$s sedang offline + %1$s telah bergabung + %1$s telah keluar + Kirim Foto + Kirim File + Kirim Audio + Ambil Gambar + Transfer File + Transfer Selesai + Transfer dalam proses + Terima transfer? + ingin mengirim anda file + Kirim + Kirim sebuah pesan + Kirim pesan aman + Kirim ulang + Sudahi percakapan + Hapus Percakapan + Sisipkan smiley + Pindah percakapan + Menu+ + Pilih tautan + + %1$s mengundang anda untuk bergabung di percakapan grup. + Undangan telah dikirim kepada %1$s. + Setuju + Tolak + Percakapan Grup + Buat atau Bergabung ke Percakapan Grup + Menghubungi percakapan grup… + Mengundang\u2026 + Pilih kontak untuk diundang + Ketik untuk mencari kontak + Kontak tidak ditemukan. + +Sentuh untuk mengundang. + Tidak dapat menyetujui langganan dari %1$s. Silahkan coba lagi. + Tidak dapat menolak langganan dari %1$s. Silahkan coba lagi. + + Mulai Enkripsi + Hentikan Enkripsi + Memulai enkripsi sesi percakapan… + Menghentikan enkripsi sesi percakapan… + Fingerprint Keamanan + Fingerprint Keamanan + Sign In + Regen Key + Enkripsi mati + Enkripsi hidup (Sentuh untuk verifikasi) + Kontak anda telah menghentikan percakapan terenkripsi. + Terenkripsi dan disahkan! + Membangkitkan OTR keypair yang baru … + + Kami mendeteksi sebuah OTR keystore yang bisa diimpor. Apakah anda mau memindai password QR sekarang? + Aktifkan KeySync + Berhasil mengimpor OTR keyring + + Pindai QR + Fingerprint Anda + Panduan + Pertanyaan + Fingerprint Keamanan (Disahkan) + Apakah anda yakin untuk menegaskan fingerprint ini? + Periksa Fingerprint? + Remote Fingerprint sudah disahkan! + Fingerprint untuk anda + Fingerprint untuk + + Otentifikasi + Masukkan pertanyaan yang akan dikirim ke kontak, dan jawaban yang anda harapkan akan diberikan oleh mereka, untuk memastikan bahwa mereka adalah benar sesuai pengakuannya. + pertanyaan yang akan ditanyakan + jawaban yang diharapkan + Kontak anda berhasil mengotentikasi anda. Sekarang otentikasi kontak anda dengan menanyakan pertanyaan anda. + Kirim + Batal + + Panggilan Aman + Secure Voice + Masukkan OStel.co atau akun layanan secure SIP anda yang lain disini untuk integrasi panggilan + + Mempersiapkan Akun + + Keamanan dan Privasi + Enkripsi dan Anonimitas + Enkripsi Hidup/Mati + Password Timeout + Waktu yang diperlukan aplikasi untuk tetap tidak terkunci + + Antarmuka Pengguna + Bahasa + Bahasa - bahasa + Gunakan Tema Gelap + Ubah Tema Aplikasi ke Gelap + Hanya Gunakan Penyimpanan Pesan In-Memory + Simpan pesan hanya di memory, bukan di penyimpanan flash, untuk mencegah pesan diekstrak. (Dapat menyebabkan hilangnya pesan) + Gambar Latar Belakang + Atur path (\"/sdcard/foo.jpg\") ke wallpaper gambar latar belakang di aplikasi + Tampilkan Grid Kontak + Tampilkan daftar kontak sebagai grid avatar + Ya, Setujui Semua + + Penyesuaian Lain + Mulai ChatSecure Otomatis + Selalu mulai dan otomatis lakukan Sign In ke akun yang sebelumnya telah Log In + Sembunyikan kontak offline + Gunakan prioritas latar depan + Mengurangi kemungkinan Android untuk memulai kembali layanan koneksi. Ini akan menempatkan sebuah ikon pada notification area. + Rentang waktu Heartbeat + Gunakan nilai yang lebih besar (dalam menit) untuk menghemat baterai. Nilai yang besar dapat menyebabkan penyedia layanan menutup koneksi karena tidak ada aktifitas. + + Pengaturan pemberitahuan + Notifikasi IM + Tandai di status bar ketika ada IM + Getar + Getar ketika ada IM + Suara + Bunyikan ringtone ketika ada IM + Gunakan nada dering ChatSecure yang dibuat sendiri + + + Data Jaringan non aktif + + Koneksi data jaringan (termasuk background data) diperlukan aplikasi untuk login. + + Izinkan + Berhenti + Apakah anda ingin melakukan Sign Out semua layanan dan menghentikan semua proses (hard exit)? + Buat akun baru? + Buar sebuah akun percakapan baru untuk username \'%1$s\'? + + Terdapat %1$s pesan baru + Undangan percakapan grup + Pesan baru dari + Memulai layanan ChatSecure… + Diaktifkan & tak terkunci + + Perhatian + Kode kesalahan %1$d + Daftar tidak ditambahkan. + Kontak tidak diblokir. + Kontak tidak dibatalkan blokir. + Silahkan pilih sebuah kontak terlebih dahulu. + Tidak terhubung! + + Kesalahan layanan! + Daftar kontak tidak dimuat. + Tidak dapat menghubungi server. Silahkan cek koneksi anda. + "%1$s" sudah terdapat di daftar Kontak anda. + Kontak "%1$s" telah diblokir. + Silahkan tunggu sementara daftar Kontak anda dimuat. + Terjadi kesalahan jaringan. + Koneksi ini memerlukan WiFi. + Server tidak mendukung fungsi ini. + Password yang anda masukkan tidak sah. + Terjadi kesalahan pada server. + Server tidak mendukung fungsi ini. + Server sedang tidak tersedia. + Koneksi server timed out. + Server tidak mendukung versi ini. + Antrian pesan penuh. + Server tidak mendukung penerusan ke domain. + Username yang anda masukkan tidak dikenali. + Maaf, anda diblokir oleh pengguna. + Sesi sudah berakhir, silahkan lakukan Sign In lagi. + anda telah Sign In dari klien lain. + anda telah Sign In dari klien lain. + Maaf, nomor telepon tidak dapat dibaca dari kartu SIM anda. Silahkan hubungi operator anda untuk bantuan. + Saat ini anda tidak Sign In. + ChatSecure mengalami masalah ketika memastikan username atau password anda - silahkan cek dan coba lagi. + ChatSecure mengalami masalah ketika membangkitkan keypair. + ChatSecure mengalami masalah ketika menghubungi server percakapan - silahkan cek konfigurasi dan coba lagi. + ChatSecure mengalami masalah ketika menghubungi - silahkan cek koneksi jaringan anda dan coba lagi. + ChatSecure kehilangan koneksi ke jaringan + ChatSecure mencoba koneksi ulang + Anda tidak memasukkan bagian @hostname.com dari ID akun anda. Silahkan coba lagi! + Hostname server anda tidak memiliki .com, .net atau akhiran yang serupa. Silahkan coba lagi! + Masukkan password anda: + Karena anda menggunakan Tor, anda harus mengisi hostname \'Koneksi Server\' XMPP pada Pengaturan Akun Lanjutan + Tidak dapat membuat atau bergabung ke percakapan grup + Maaf kami tidak dapat berbagi menggunakan tipe file seperti itu + Anda harus mengaktifkan enkripsi untuk berbagi file + Silahkan akifkan enkripsi percakapan untuk berbagi file + ChatSecure mendeteksi sebuah permintaan untuk melepas task. ChatSecure cenderung akan crash. Disarankan menggunakan menu Sign out semua dari layar daftar akun. + OTR keyring not imported; Silahkan cek jika file ada di lokasi dan format yang tepat + Silahkan salin file \'otr_keystore.ofcaes\' dari desktop dengan alat KeySync ke root directory penyimpanan + Pesan ini tidak dapat dikirim. + Pesan akan dikirim ketika terhubung kembali + %1$s sedang offline. Pesan yang anda kirim akan diserahkan ketika %1$s sudah online. + %1$s tidak terdapat di daftar Kontak anda. + Pengaturan Akun + + PERINGATAN: Ini adalah rilis awal dari ChatSecure yang mungkin masih memiliki lubang keamanan dan bug. + + Grup + buka percakapan + Keluar + Panik + Teman - teman + Kontak + Terima Certificate Server? + Fingerprint + + + Daftar Kontak + Pengaturan + Akun - Akun + Keluar + Daftar kontak + + Jabber (XMPP) + Local Area (Bonjour/ZeroConf) + Akun Google + dukgo.com + + + Online + Sibuk + Tidak di tempat + Diam + Offline + Tampak offline + + + Gembira + Sedih + Berkedip + Menjulurkan lidah + Terkejut + Mencium + Teriak + Jaim + Mulut uang + Kaki di muut + Malu + Malaikat + Bimbang + Menangis + Tutup mulut + Tertawa + Bingung + + + + Gembira + Sedih + Berkedip + Menjulurkan lidah + Terkejut + Mencium + Teriak + Jaim + Mulut uang + Kaki di muut + Malu + Malaikat + Bimbang + Menangis + Tutup mulut + Tertawa + Bingung + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Akun Baru + diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml index d7e8e5284..f7301fe14 100644 --- a/res/values-it/arrays.xml +++ b/res/values-it/arrays.xml @@ -2,68 +2,8 @@ Forza / Richiedi - Tenta Automaticamente - Come richiesto - Disabilitato / Mai - - - forzato - auto - richiesto - disabilitato - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el + Tenta automaticamente + Se richiesta + Disabilitata / Mai diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index eab917581..ac70aa967 100755 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1,400 +1,505 @@ - + + + ChatSecure + ChatSecure + leggere i messaggi istantanei Consente alle applicazioni di leggere i dati dal fornitore di contenuti IM. scrivere messaggi istantanei Consente alle applicazioni di scrivere i dati al fornitore del contenuto IM. - - ChatSecure - - - Chat - selezionare un account - - Riguardo a - - Aggiungi account - - Modifica account - - Rimuovere l\'account - - Esci da tutte le - - - Chat - selezionare un account - - - - Annulla accesso - - - Aggiungi contatto - - Elimina contatto - - Blocca contatto - - Bloccati - - Elenco account - - Nuova Chat - - Nuovo Account - - Impostazioni - - Cerca Contatti - - Inizia conversazione - - Disconnetti - - Visualizza profilo - - - Avvio di crittografia - Stop di crittografia - Clear Chat - Termina conversazione - Chiamata Sicura - - - Elenco contatti - - Invita... - - Cambia conversazione - - Inserisci emoticon - Reinvia - - Scansione delle impronte digitali - La propria impronta digitale - Verifica di impronte digitali - Verifica il Segreto - - Menu+ - - - + avviare servizio IM + Consente l\'avvio del servizio IM tramite intent. + Conferma - - Vuoi uscire tutti i servizi? - - - - - - - OK - Annulla - OK - Annulla - - - - - - - - - - Nome utente: - + Successivo + Precedente + Connetti + Riproduci + Impostazioni + + Aperto + ChatSecure Bloccato + Imposta Password + Conferma Password + Password + È possibile impostare una password principale per ChatSecure in modo da impedire l\'accesso ai tuoi contatti e mesaggi senza di essa: + Conferma Nuova Password + La password non corrisponde, per favore riprova + Salta >> + Prompt di informazione + Inserire la Password + Inserire la password… + Passphrase + Passphrase (di nuovo) + + Seleziona un account + Seleziona un account + (%1$d) + Aggiungi account %1$s + Informazioni + Esci da tutte le + Vuoi uscire da tutti i servizi? + Utente disconnesso da %1$s. + Utente disconnesso da %1$s. Causa: %2$s. + Prima volta con ChatSecure? + Non vedi l\'ora di iniziare? + Comincia con ChatSecure + Impostazioni Account + Imposta Passphrase + Prima di iniziare si prega di scegliere un codice segreto sicuro per proteggere i dati da accessi inappropriati a ChatSecure. + Passphrase: + Passphrase (di nuovo) : + Inserisci un *nuovo* codice segreto. Deve contenere almeno una lettera maiuscola, una minuscola e un numero, e deve essere più lunga di sei caratteri. + Crittografando le tue note esistenti con il nuovo codice segreto (pazienta…) + Inserisci il tuo codice segreto: + Benvenuto! Inserisci un codice segreto robusto per mettere in sicurezza le tue note. Deve contenere almeno una lettera maiuscola, una minuscola e un numero, e deve essere più lungo di sei caratteri. + Il tuo codice segreto non era lungo abbastanza + Il tuo codice segreto non conteneva alcuna lettera maiuscola + Il tuo codice segreto non conteneva alcuna lettera minuscola + Il tuo copdice segreto non conteneva alcun numero + + Informazioni su ChatSecure + ChatSecure è un applicazione mobile di messaggistica istantanea che fornisce funzionalità di sicurezza aggiuntive che impediscono ad altri di curiosare sulle vostre conversazioni e comunicazioni. + +L\'applicazione supporta qualsiasi servizio chat che utilizza il protocollo Jabber o XMPP, come Google GTalk o Jabber.org. + + Quanto è sicuro? + \'Off-the-Record Messaging\' è un sistema di sicurezza progettato per consentire la privacy imitando le caratteristiche di una conversazione privata, nel mondo reale, includendo Crittografia, Autenticazione, Negabilità e Segretezza di Inoltro. + +Il protocollo OTR è compatibile con client di chat per computer desktop come Adium o Pidgin. + + Le mie chat sono sicure? + La funzione di crittografia di ChatSecure funziona solo quando si chatta con altri utenti che utilizzano un programma o app compatibile, quindi dovresti assicurarti che i tuoi contatti stiano utilizzando ChatSecure per dispositivi mobili e Adium o Pidgin per computer desktop. Puoi mettere a punto esattamente quando e come ChatSecure deve cercare di crittografare le chat in Impostazioni Account + +Iniziamo! + + Nuovo Account + Aggiungi account + Modifica account + Rimuovi account + Account Esistente + + Indirizzo utente: Password: - Memorizza la password. - Accesso automatico - Non hai un account? - + Accedi Per la tua sicurezza, se il telefono viene perso o rubato, visitare il sito Web sul proprio computer e cambiare la password. Questa opzione ti consente di accedere automaticamente ogni volta che apri l\'applicazione. Per disattivare l\'opzione, disconnettiti e deseleziona la casella \"Accesso automatico\". - - Accedi - - - avviando ChatSecure... - - Accesso... - - - Dati in background disattivati - - - - - - Attiva - - Esci - - - - - - - Inviti amici - + Connessione via Tor (Richiede app Orbot) + utente@dominio.it + nuovo nome utente + fornitore servizio (dukgo.com, jabber.ccc.de) + password + conferma password + Impostazioni Account Avanzate + Tipologia Account + Registra Account + Persistenza + Ricorda la password + Password memorizzata nella cache + Password non memorizzata nella cache + Accesso automatico + Connetti all\'avvio di ChatSecure + Non connettere all\'avvio di ChatSecure + Entra adesso + Collegare seguenti impostazioni account + Don\'t collegare seguente impostazione dell\'account + Personale (opzionale) + Account Alias (il tuo nome) + Come il vostro account viene visualizzato on line + Profilo + Un breve testo su di te + Forza crittografia / rifiuta testo in chiaro + Quando possibile, crittografa le chat automaticamente + Cifra chat come richiesto + Disabilita la crittografia della chat + Convalida delle credenziali… + Generazione coppia di chiavi… + In connessione… + Configurazione Guidata Account + Il tuo account ID + Configura server + Sei pronto? + Inserisci il tuo ID per configurare ChatSecure per utilizzare il tuo servizio chat XMPP. Appare come un indirizzo e-mail: + Inserisci il tuo account ID (user@hostname) : + Si prega di inserire o modificare i tuoi jabber/xmpp chat hostname del server e il numero di porta (5222 è l\'impostazione predefinita). + ChatSecure è stato configurato, ed ora è il momento di connettersi al servizio, e iniziare a chattare in modo sicuro, protetto e riservato! + Orbot (Tor) + Scegli un dominio + Registrazione nuovo account… + ChatSecure in avvio… + Annulla accesso + Accesso in corso… + Disconnessione in corso… + + Fare Lookup SRV + Utilizzare DNS SRV per trovare il server XMPP dal nome di dominio + Permette che nome utente e password siano inviati in chiaro quando si usa un protocollo di trasporto non criptato + Permettere Autenticazione in Chiaro + Verificare che il certificato sia attendibile + Verifica TLS + Richiedi connessione TLS + Crittografia Protocollo di Trasporto + come le chat crittografate vengono avviate + Il server al quale connettersi, se necessario + Server di connessione + Porta TCP per Server XMPP + Porta del Server + Risorsa XMPP + per distinguere questa connessione dagli altri client che sono connessi + Priorità XMPP Resource + Messaggi verso client con risorse multiple attive verranno inviati alla risorsa con priorità più alta. + + Conversazioni + Nessuna conversazione. + +Tocca qui per iniziarne una! + Nessun +account configurato. + +Tocca qui per aggiungerne uno! + Lista Contatti - %1$s + Aggiungi contatto + Elimina contatto + Blocca contatto + Rinomina contatto + Bloccati + Nickname + Il contatto \"%1$s\" sarà eliminato. + Il contatto \"%1$s\" sarà bloccato. + Il contatto \"%1$s\" sarà sbloccato. + Contatto \"%1$s\" aggiunto. + Contatto \"%1$s\" eliminato. + Contatto \"%1$s\" bloccato. + Contatto \"%1$s\" sbloccato. + Nuova Chat + Cerca Contatti + Mostra Griglia + Inizia conversazione + Visualizza Profilo + Verifica Contatto + Conversazioni in corso (%1$d) + %1$d online + Invita amici (\"\"Sconosciuto\"\") - Vuoto - - Nessuna conversazione - - Seleziona contatti da invitare - Digita per trovare contatti - Nessun contatto trovato. - - - - Nessun contatto bloccato. - - + Nessuna conversazione\n\n +Tocca qui per cominciare! + Contatto + inserisci il nome del contatto con cui chattare + Vai + Nessuna conversazione attiva. + Aggiungi contatto + Indirizzo email della persona da invitare: + Selezionare un elenco: + Digita un nome da aggiungere dai contatti. + Invia invito Profilo contatto - Stato: - Tipo client: - Computer - Portatile - - - Online - - Occupato - - Assente - - Inattivo - - Offline - - Invisibile - - - - - + Contatti bloccati - %1$s + Nessun contatto bloccato. + + Conversazione con %1$s Io - - Digita qui per scrivere - - - - - - - - - - - - - - - - + %1$s è online + %1$s non è al computer + %1$s è occupato + %1$s è offline + %1$s è entrato + %1$s è uscito + Invia Immagine + Invia File + Invia Audio + Scatta una Foto + Trasferimento File + Trasferimento Completato + Trasferimento in corso + Accettare il trasferimento? + vuole inviarti il file + Nessuna connessione disponibile per inviare la tua condivisione! Invia - - Impossibile inviare il messaggio. - - Connessione al server persa. I messaggi saranno inviati quando verrà ristabilita. - - - - - + Invia un messaggio + Invia messaggio sicuro + Reinvia + Termina conversazione + Cancella lo storico + Inserisci emoticon + Cambia conversazione + Menu+ Seleziona link - - Nessuna conversazione attiva. - - Inizio chat crittografata... - Fine chat crittografata... - - - Aggiungi contatto - - Indirizzo email della persona da invitare: - - Selezionare un elenco: - - Digita un nome da aggiungere dai contatti. - - Invia invito - - Jabber (XMPP) - Chat di Rete Locale (Bonjour/ZeroConf) - + Elimina + Mantieni File + Cancellare la sessione di chat memorizzata in modo sicuro? + Tutti i file ricevuti ed inviati saranno irreversibilmente cancellati. Attenzione: questa opreazione non può essere annullata + Eliminare l\'originale? + Il file verrà copiato nella memoria sicura prima di essre inviato. Cancellare il file originale presente nella memoria non sicura? + Mantieni + Esporta + Esporta Media File? + Questo file multimediale verrà esportato in %1$s + Terminare la conversazione? + Tutti i contenuti multimediali della sessione saranno cancellati. Esportare un contenuto tenendo premuto sulla sua icona di anteprima + Chiudi Chat ed Elimina File + + %1$s ti ha invitato a unirti a una conversazione di gruppo. + L\'invito è stato inviato a %1$s. + Accetto + Rifiuto + Chat di Gruppo + Crea o entra in una chat di gruppo + Connessione alla chat di gruppo… + Invita… + Selezionare i contatti da invitare + Digita per trovare contatti + Nessun contatto trovato. + +Tocca per invitare qualcuno. + Aggiungere %1$s? + + No + Impossibile approvare l\'iscrizione di %1$s. Riprova più tardi. + Impossibile rifiutare l\'iscrizione di %1$s. Riprova più tardi. + + Avvia Crittografia + Ferma Crittografia + Inizio chat crittografata… + Fine chat crittografata… + Sicurezza Impronta Digitale + Sicurezza Impronta Digitale + Entra + Rigenera chiave + Crittografia disabilitata + Crittografia abilitata (Tocca per verificare) + Il tuo interlocutore ha disabilitato la chat criptata. + Crittografata e verificata! + Generazione di una nuova coppia di chiavi OTR… + + Portachiavi OTR rilevato. Eseguire la scansione del codice QR contente la password? + Attiva KeySync + Keyring OTR importato con successo + + Scansiona QR + La propria impronta digitale + Manuale + Domanda + Sicurezza Impronta Digitale (Verificata) + Sei sicuro di voler confermare questa impronta digitale? + Verificare Impronta Digitale? + L\'impronta digitale remota è stata verificata! + Impronta digitale per te + Impronta digitale per + Installare Barcode Scanner? + Questa applicazione richiede Barcode Scanner. Vuoi installarlo? + + Autenticazione + Inserisci una domanda da inviare ai propri contatti e la risposta che ci si attende da loro, in modo da verificare che solo siano veramente chi dicano di essere. + la domanda da porre + la risposta prevista + Il tuo contatto ti ha autenticato correttamente. Ora autentica tu il tuo contatto porgendogli la tua domanda. + Verifica OTR Q&A + Crittografia chat + Invia + Annulla + + Chiamata Sicura + Voce Sicura + Inserisci qui il tuo account OStel.co o un altro servizio SIP sicuro per la integrazione delle chiamate + Impostazione account + + Sicurezza e Privacy Crittografia e Anonimato - Nascondi contatti offline - - Impostazioni notifiche - - Notifiche chat - - Notifica in barra di stato all\'arrivo di un messaggio - + Cifra On/Off + Timeout della Password + Quantità di tempo che la criptografia della app deve rimanere sbloccata. + + Collegamenti a Tor cliccabili + Per gli account che usano Tor, rende i collegamenti nelle chat cliccabili (ATTENZIONE, possibile perdita di privacy!) + Interfaccia Utente + Lingua + Lingue + Predefinito di sistema + Usa il Tema Scuro + Passa al tema scuro dell\'app + Memorizzazione messaggi solo In-Memory + Memorizza i messaggi solo in memoria, non sulla flash, per difendersi contro la loro estrapolazione. (Potrebbe causarne la perdita) + Immagine di sfondo + Imposta il percorso (\"/sdcard/foo.jpg\") ad un\'immagine di sfondo per l\'app + Mostra Griglia Contatti + Mostra la lista contatti come griglia di avatar + Sì, Accetta Tutti + Elimina contenuti non protetti + I file condivisi in modo sicuro saranno automaticamente cancellati dalla posizione originale non sicura. + Memorizzare i contenuti sulla memoria esterna + I contenuti multimediali della chat sono memorizzati in un contenitore crittografato che può essere contenuto nella memoria interna o esterna. + + Memoria esterna non presente + La cronologia è memorizzata su una memoria esterna, ma nessuna memoria esterna è presente. Inserire la memoria esterna corretta, o cancellare la cronologia e riavviare ChatSecure. + Memoria Chat Multimediale non presente + Lo storico delle conversazioni sono memorizzate sulla scheda SD, ma il file correlato non si trova sulla scheda SD attualmente inserita. Si prega di sostituire la scheda con quella corretta o procedere alla cancellazione dello storico e riaprire ChatSecure. + Cancella lo storico + + Altre Impostazioni + Avviare ChatSecure automaticamente + Avviare ed accedere automaticamente a tutti gli account attivi nella precedente sessione + Nascondi i contatti non in linea Usa la priorità del primo piano - Diminuisce la probabilità che Android riavvii il nostro servizio di connessione. Questo aggiungerà una notifica nell\'area notifiche. + Diminuisce la probabilità che Android riavvii il servizio di connessione. Comporta l\'aggiunta di una voce nell\'area notifiche. Intervallo di Heartbeat - Usa un avalore più alto (in minuti) per risparmiare batteria. Un valore alto può causare la chiusura della connessione da parte del tuo provider per inattività. + Usa un valore più alto (in minuti) per risparmiare la batteria. Un valore alto può causare la chiusura della connessione da parte del tuo provider per inattività. + + Impostazioni notifiche + Notifica messaggi + Notifica nella barra di stato quando arriva un messaggio Vibrazione - - Vibra anche all\'arrivo di msg chat - - Allarme - - Usa anche suoneria all\'arrivo di msg chat - - Seleziona suoneria - - - - - - - Accetto - - Rifiuto - - - - Accetto - - Rifiuto - - - - - - - - - - - - + Vibra quando arriva un messaggio + Suoni + Suona quando arriva un messaggio + Seleziona una suoneria personalizzata + + Attiva Registri di Debug + Stampa i registri dell\'app sullo standard out / usare logcat per il debug + + Dati in background non attivi + %1$s richiede l\'attivazione dei dati in background. + Attiva + Esci + Uscire e terminare tutti i processi (uscita forzata)? + Creare un nuovo account? + Creare un nuovo account per l\'utente \'%1$s\'? + Info Certificato + Certificato: + Verificato da: + Impronta SHA1: + Verificato: + Scadenza: + Entrare nella stanza? + Un\'app esterna sta cercando di connettersi alla tua stanza. Consentire? + Seleziona Sfondo + Selezionare un\'immagine di sfondo dalla Galleria? + Seleziona Immagine + + Nuovi messaggi %1$s + %1$d chat non lette + Nuova richiesta di amicizia da %s + Nuovo file %1$s da %2$s Invito a conversazione di gruppo - - - - - - - - - - - - avviare servizio IM - Consente l\'avvio del servizio IM tramite intent. - - + Invito ad una chat di gruppo da %s + Nuovo messaggio(i) da + Avviamento del servizio ChatSecure… + Attivato & sbloccato + messaggio copiato negli appunti + Attenzione - - - - L\'elenco non è stato aggiunto. - + Errore: + Codice errore %1$d + Impossibile accedere al servizio %1$s. Riprova più tardi."\n"(Dettagli: %2$s) + La lista non è stata aggiunta. Il contatto non è stato bloccato. - Il contatto non è stato sbloccato. - - Seleziona prima un contatto. - + Selezionare prima un contatto. \"Disconnesso!\"\n - - Errore del servizio. - - Elenco contatti non caricato. - + Errore del servizio! + La lista contatti non è stata caricata. Impossibile collegarsi al server. Controllare la connessione. - - - - - - Attendere il caricamento dell\'elenco contatti. - + %1$s è già presente nell\'elenco contatti. + Il contatto \"%1$s\" è stato bloccato. + Attendere il caricamento della lista contatti. Errore di rete. - Il WiFi é necessario per questa connessione. - + Il WiFi è necessario per questa connessione. Il server non supporta questa funzionalità. - La password inserita non è valida. - - Errore del server. - + Errore del Server. Il server non supporta questa funzionalità. - - Server al momento non disponibile. - + Il server è temporaneamente non disponibile. Timeout del server. - Il server non supporta la versione corrente. - La coda messaggi è piena. - Il server non supporta l\'inoltro al dominio. - - Nome utente inserito non riconosciuto. - - Spiacenti. Sei stato bloccato dall\'utente. - - Sessione scaduta. Accedi di nuovo. - + Il nome utente inserito non è riconosciuto. + Spiacenti. Sei bloccato dall\'utente. + La sessione è scaduta, rieffettuare l\'accesso. accesso effettuato da un altro client accesso effettuato da un altro client - Spiacenti. Impossibile leggere il numero di telefono sulla scheda SIM. Contattare l\'operatore. - Accesso non effettuato. - - - + ChatSecure ha incontrato un errore durante la convalidazione del nome utente o della password - si prega di verificare e riprovare. + ChatSecure ha incontrato un errore durante la generazione di una coppia di chiavi. + ChatSecure ha incontrato un errore durante la connessione al server di chat - si prega di controllare la configurazione e riprovare. + ChatSecure è verificato un errore durante la connessione - si prega di ricontrollare i connettività di rete e riprovare. + ChatSecure ha perso una connessione alla rete + ChatSecure sta tentando di ristabilire il collegamento + Non è stata inserita la parte @hostname.com per il tuo account ID. Riprova! + L\'hostname del server fornito non ha un suffisso .com, .net o simili. Riprovare! + Inserire la password: + Dato che si sta utilizzando Tor, è necessario inserire l\'hostname \'Server di Connessione\' XMPP direttamente nelle Impostazioni Account Avanzate + ATTENZIONE: Questo serviziò utilizza un certificato con una crittografia DEBOLE. Farlo presente all\'amministratore. + Il certificato fornito non coincide con il certificato segnato: + Impossibile creare o unirsi alla chat di gruppo + Non e\' possibile condividere file di questo formato + Abilitare la crittografia per trasferire i file + Abilitare la crittografia della chat per trasferire i file + Impossibile condividere il contenuto: formato non supportato + ChatSecure ha rilevato una richiesta di arresto del proprio processo. +ChatSecure terminerebbe in modo anomalo. Preferire l\'utilizzo della voce di Disconnessione presente nella lista degli account. + Nessun visualizzatore disponibile per questo formato + Avviare una conversazione sicura prima di acquisire dei codici + Keyring OTR non importato. Controlla che il file sia nel formato e nel luogo corretto + Copia il file \'otr_keystore.ofcaes\' fornito dall\'applicazione desktop KeySync nella radice della memoria del dispositivo. + Impossibile inviare il messaggio. + Connessione assente. I messaggi saranno inviati quando verrà ristabilita la connessione + %1$s è offline. I messaggi inviati saranno consegnati non appena %1$s torna online. + %1$s non presente nell\'elenco contatti. + Le password non coincidono + La priorità dev\'essere un numero compreso nell\'intervallo [0 .. 127] + Il numero di porta dev\'essere un numero + Errore nel trasferimento + Impossibile leggere i file + Errore critico: Impossibile sbloccare o caricare i dati. Reinstallare l\'app o cancellare i dati. + Nessuna app installata che possa gestire questo collegamento! + Impostazioni Account + + Gruppi + conversazioni aperte + Esci e Blocca + Panico + Amici + Contatti + Accettare il certificato del server? + Impronta digitale + caricamento… + + Informazioni su ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Lista di contatti + Impostazioni + Gestisci gli Account + Disconnetti + Lista contatti + + Jabber (XMPP) + Rete locale (Bonjour/ZeroConf) + Google Account + dukgo.com + + + In linea + Occupato + Assente + Inattivo + Non in linea + Risulta non in linea + Felice Triste @@ -414,7 +519,7 @@ Ridente Confuso - + Felice Triste @@ -447,177 +552,40 @@ :-! :-[ O:-) - :-\ + :-\\ :\'( :-X :-D o_O - Lista di contatti - Cifra On/Off - Connessione via Tor (Richiede app Orbot) - - ChatSecure - Prima volta con ChatSecure? - Can\'t l\'ora di cominciare? - A proposito di Gibberbot - Impostazione account - - A proposito di ChatSecure - ChatSecure è un applicazione mobile di messaggistica istantanea che fornisce funzionalità di sicurezza aggiuntive che impediscono ad altri di curiosare sulle vostre conversazioni e comunicazioni.\n\nL\'applicazione supporta qualsiasi servizio chat che utilizza il protocollo Jabber o XMPP, come Google GTalk o Jabber.org. - - Quanto è sicuro? - \'Off-the-Record Messaging\' è un sistema di sicurezza progettato per consentire la privacy imitando le caratteristiche di una conversazione privata, nel mondo reale, includendo Crittografia, Autenticazione, Negabilità e Segretezza di Inoltro. \n\nIl protocollo OTR è compatibile con client di chat per computer desktop come Adium o Pidgin. - - Le mie chat sono sicure? - La funzione di crittografia di ChatSecure funziona solo quando si chatta con altri utenti che utilizzano un programma o app compatibile, quindi dovresti assicurarti che i tuoi contatti stiano utilizzando ChatSecure per dispositivi mobili e Adium o Pidgin per computer desktop. Puoi mettere a punto esattamente quando e come ChatSecure deve cercare di crittografare le chat in Impostazioni Account\n\nIniziamo! - - Codice segreto di Setup - Prima di iniziare si prega di scegliere un codice segreto sicuro per proteggere i dati da accessi inappropriati a ChatSecure. - Codice segreto: - Codice segreto (di nuovo) : - - utente@dominio.it - nuovo nome utente - fornitore servizio (dukgo.com, jabber.ccc.de) - password - conferma password - Impostazioni Account Avanzate - Tipologia Account - Registra Account - - Persistenza - Ricorda la password - Password memorizzata nella cache - La password non memorizzati nella cache - Accesso automatico - Connetti all\'avvio di ChatSecure - Non connettere all\'avvio di ChatSecure - Entra adesso - Collegare seguenti impostazioni account - Don\'t collegare seguente impostazione dell\'account - - Personale (opzionale) - Account Alias (il tuo nome) - Come il vostro account viene visualizzato on line - Profilo - Un breve testo su di te - - Forza crittografia / rifiuta testo in chiaro - Quando possibile, crittografa le chat automaticamente - Cifra chat come richiesto - Disabilita la crittografia della chat - - Convalida delle credenziali... - Generazione coppia di chiavi... - Connettendo... - - ChatSecure ha incontrato un errore durante la convalida del nome utente o della password - si prega di verificare e riprovare. - ChatSecure ha incontrato un errore durante la generazione di una coppia di chiavi. - ChatSecure è verificato un errore durante la connessione al server di chat - si prega di controllare la configurazione e riprovare. - ChatSecure è verificato un errore durante la connessione - si prega di ricontrollare i connettività di rete e riprovare. - Le loro impronte digitali - La propria impronta digitale - Entra - Rigenera chiave - ChatSecure ha perso una connessione alla rete - ChatSecure sta tentando di restablish una connessione - Attenzione: Questa chat non è criptata - Questa chat è sicura, ma l\'identità dei partecipanti NON sono state verificate - Attenzione: la criptazione della chat è stata arrestata. - Questa chat è assicurata e verificata - Configurazione Guidata Account - Il tuo account ID - Configura server - Are You Ready? - Inserisci il tuo ID per configurare ChatSecure per utilizzare il tuo servizio chat XMPP. Appare come un indirizzo e-mail: - Inserisci il tuo ID account (user @ hostname) : - Si prega di inserire o modificare i tuoi jabber/xmpp chat hostname del server e il numero di porta (5222 è l\'impostazione predefinita). - ChatSecure è stato configurato, ed ora è il momento di connettersi al servizio, e iniziare a chattare in modo sicuro, sicuro e riservato! - Non hai inserito la parte @hostname.com per il tuo account ID. Prova ancora! - L\'hostname del tuo server non ha un suffisso .com, .net o simili. Riprova! - Inserisci il tuo password: - - Impostazioni Account - defLoc - ATTENZIONE: Questa versione prematura di ChatSecure che può contenere falle o bug di sicurezza. - - Gruppi - Generazione di una nuova coppia di chiavi OTR... - Contatti - inserisci il nome del contatto con cui chattare - Vai - Lingua - Lingue - Che linguaggio dovrebbe essere visualizzato da InTheClear? - Fare Lookup SRV - Utilizzare DNS SRV per trovare il server XMPP dal nome di dominio - Permettere che nome utente e password siano inviati in chiaro quando si usa un trasporto non criptato - Permettere Autenticazione in Chiaro - Verificare che il certificato sia attendibile - Verifica TLS - Richiedi connessione TSL/SSL - Crittografia Protocollo di Trasporto - come le chat crittografate vengono avviate - Il server al quale connettersi, se necessario - Connettere al Server - Porta TCP per Server XMPP - Porta del Server - Risorsa XMPP - per distinguere questa connessione dagli altri client che sono connessi - Priorità XMPP Resource - Messaggi verso client con risorse multiple attive verranno inviati alla risorsa con priorità più alta. - Successivo - Precedente - Conversazioni - Nuovo messaggio(i) da - Autenticazione - Inserisci una domanda da inviare ai propri contatti e la risposta che ci si attende da loro, in modo da verificare che solo siano veramente chi dicano di essere. - la domanda da porre - la risposta prevista - Il tuo contatto ti ha autenticato correttamente. Ora autentica tu il tuo contatto porgendogli la tua domanda. - Nessuna conversazione.\n\nTocca qui per iniziarne una! - Usa il Dark Theme - Dato che stai usando Tor, devi inserire il nome host \'Server di Connessione\' XMPP direttamente nelle Impostazioni Account Avanzate - - Avvia ChatSecure automaticamente - Avvia ed entra automaticamente negli account connessi in precedenza sempre - Non hai configurato alcun account.\n\nTocca qui per aggiungerne uno! - Google Account - Memorizzazione messaggi solo In-Memory - Memorizza i messaggi solo in memoria, non sulla flash, per difendersi contro la loro estrapolazione. (Potrebbe causarne la perdita) - Immagine di sfondo - Imposta il percorso (\"/sdcard/foo.jpg\") ad un\'immagine di sfondo per l\'app - Sicurezza e Privacy - Interfaccia Utente - Altre Impostazioni - conversazione(i) aperta(e) - Orbot (Tor) - Esci - - Vuoi uscire E uccidere tutti i processi (hard exit)? - Inserisci un *nuovo* codice segreto. Deve contenere almeno una lettera maiuscola, una minuscola e un numero, e deve essere più lunga di sei caratteri. - Crittografando le tue note esistenti con il nuovo codice segreto (pazienta...) - Inserisci il tuo codice segreto: - Benvenuto! Inserisci un codice segreto robusto per mettere in sicurezza le tue note. Deve contenere almeno una lettera maiuscola, una minuscola e un numero, e deve essere più lungo di sei caratteri. - Il tuo codice segreto non era lungo abbastanza - Il tuo codice segreto non conteneva alcuna lettera maiuscola - Il tuo codice segreto non conteneva alcuna lettera minuscola - Il tuo copdice segreto non conteneva alcun numero - Aperto - ChatSecure Bloccato - Crea codice segreto - Inserisci codice segreto - Inserisci nuovo codice segreto - Conferma nuovo codice segreto - I Codici segreti non combaciano, prova di nuovo - Inserisci qui il tuo account OStel.co o un altro servizio SIP sicuro per la integrazione delle chiamate - dukgo.com - Abbiamo rilevato un contenitore di chiave OTR da importare. Vuoi scansionare la password QR adesso? - Importa contenitore di chiave OTR - Keyring OTR importato con successo - Keyring OTR non importato. Controlla che il file sia nel luogo e nel formato corretti - Quantità di tempo che la criptografia della app deve rimanere sbloccata. + Account Esistente + Connettersi ad un proprio account esistente su un server Jabber / XMPP. + Account Google + Chatta con altri utenti Google utilizzando il proprio account Google esistente. + Chat WiFi Mesh + Chatta con chi si trova nella stessa rete WiFi o mesh - non sono richiesti internet o un server! + Abilita Chat WiFi + Nuovo Account + Registra gratuitamente un nuovo account su uno dei server proposti o quale si preferisca. + Crea un nuovo account + Identità Segreta! + Crea un account anonimo usa e getta in un singolo tocco (richiede Orbot: Tor per Android) + Genera Identità + Questa è una chat di gruppo + [rinviato] + [rinviato] + Impossibile condividere questo file in modo sicuro + Installare Orbot? + Orbot dev\'essere installato e attivato per gestire il traffico. +Vuoi installarlo? + Sempre + Avviare Orbot? + Orbot non risulta essere attivo. Avviarlo e collegarsi a Tor? + nickname da usare nella stanza + nome della stanza da creare o in cui entrare + server della chat di gruppo (conferenza.foo.com) + Il tuo portachiavi è corrotto. Reinstalla ChatSecure o cancella i dati dell\'app + Hai ricevuto un messaggio criptato illeggibile + Impossibile decriptare il messaggio inviato + < trascina a sinistra per più opzioni > diff --git a/res/values-iw/arrays.xml b/res/values-iw/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-iw/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml new file mode 100644 index 000000000..bed0c6cc4 --- /dev/null +++ b/res/values-iw/strings.xml @@ -0,0 +1,46 @@ + + + + + + ביטול + ביטול + + + אודות + + + + + + User@Host + סיסמה + פתק קצר עליך + + + + + + + + + ביטול + + + + + + + + + + + + + + + + + + + diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml index 531f3c3d9..92a8606c3 100644 --- a/res/values-ja/arrays.xml +++ b/res/values-ja/arrays.xml @@ -6,64 +6,4 @@ 要求されたときだけ 無効にする/絶対に有効にしない - - 強制 - 自動 - 要求 - 無効 - - - デフォルト - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 86ccb394a..fbad2d5cf 100755 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -1,383 +1,493 @@ - + + + ChatSecure + ChatSecure + インスタントメッセージを読む - \nアプリケーションがIMのコンテンツ・プロバイダーからデータを読み取ることを許可する。 + +アプリケーションがIMのコンテンツ・プロバイダーからデータを読み取ることを許可する。 インスタントメッセージを作成 - \nアプリケーションがIMのコンテンツ・プロバイダーにデータを書き込むことを許可する。 - - - - チャット - 選択してアカウント - - - アカウントを追加 - - アカウントを編集 - - アカウントを削除 - - すべてサインアウト - - - チャット - 選択してアカウント - - - - ログインをキャンセル - - - 連絡先を追加 - - 連絡先を削除 - - 連絡先をブロック - - ブロック - - チャットアカウント一覧 - - - - 設定 - - 連絡先を検索 - - チャットを始める - - ログアウト - - プロフィールを表示 - - - 暗号化を開始 - 暗号化を停止 - チャット履歴を消去 - チャット終了 - - - 連絡先リスト - - 招待... - - チャットを切り替え - - 絵文字を挿入 - - フィンガープリントをスキャン - 自分のフィンガープリント - 確認指紋 - - Menu+ - - - + +アプリケーションがIMのコンテンツ・プロバイダーにデータを書き込むことを許可する。 + IM/チャットの起動 + アプリケーションがintent経由でIMサービスを開始することを許可します。 + 確認 - - すべてのサービスからログアウトしますか? - - - - - - - OK - キャンセル - OK - キャンセル - - - - - - - - - - ユーザー名: - + 次へ + 戻る + 接続 + 再生 + セットアップ + + 開く + ChatSecure はロックされました + パスワードを設定 + パスワードを確認 + パスワード + パスワードなしに連絡先やメッセージへアクセスすることを防止するために、オプションで ChatSecure にマスターパスワードを設定することができます: + 新しいパスワードを確認 + パスワードが一致しません、再度入力してください + スキップ >> + 情報プロンプト + パスワードを入力してください + パスワードを入力してください… + パスフレーズ + パスフレーズ (再び) + + アカウントを選択してください + アカウントを選択してください + (%1$d) + %1$sアカウント + アプリについて + すべてサインアウト + すべてのサービスからログアウトしますか? + 既に%1$sをログアウトしています。 + %1$s をログアウトしています。理由 %2$s + ChatSecure を使うのは初めてですか? + 開始するまで待てませんか? + Gibberbotについて + アカウント設定 + パスフレーズの設定 + 不正なアクセスから ChatSecure のデータを保護するため、始める前に安全なパスフレーズを選択してください。 + パスフレーズ: + パスフレーズ(再入力): + *新しい* パスフレーズを入力してください。これは、少なくとも1つの大文字、1つの小文字、および1つの数字が含まれており、6文字以上でなければなりません。 + 新しいパスフレーズを使用して、既存のメモを暗号化しています (お待ちください…) + パスフレーズを入力してください: + +ようこそ! あなたのメモを保護するために強力なパスフレーズを入力してください。これは、少なくとも1つの大文字、1つの小文字、および1つの数字が含まれており、6文字以上でなければなりません。 + パスフレーズの長さが足りません + パスフレーズに大文字が含まれていません + パスフレーズに小文字が含まれていません + パスフレーズに数字が含まれていません + + ChatSecure について + ChatSecureは、あなたの会話や通信が他人からのぞき見されるのを防止するため、追加のセキュリティ機能を提供する、モバイル インスタント メッセージング アプリです。\n\nこのアプリは、Google GTalk や、Jabber.org などのJabber または XMPP プロトコルを使用するすべてのチャットサービスをサポートしています。 + + どう安全なんですか? + \'オフ ザ レコード/OTR メッセージング\'は、暗号化、認証、否認および前方秘匿性を含む、現実世界でのプライベートな会話の特性を模倣することで、プライバシーを有効にするために設計されたセキュリティシステムです。\n\nOTR プロトコルは、AdiumやPidginのようなデスクトップ チャット クライアントに対応しています。 + + 私のチャットは安全ですか? + 互換性のあるアプリケーションやプログラムを使用して他の人とメッセージを送受信する時のみ、ChatSecureのチャット暗号化機能は動作するため、あなたの連絡先が、モバイル用のChatSecureか、デスクトップ用のAdiumやPidginを使用していることを確認する必要があります。ChatSecureがチャットをいつ、どのように暗号化しようとするか、アカウント設定で確実に調整することができます。\n\nでは始めましょう! + + 新しいアカウント + アカウントを追加 + アカウントを編集 + アカウントを削除 + 既存のアカウント + + ユーザー@ホスト パスワード - パスワードを保存する - 自動ログイン - アカウントがない場合 - + ログイン セキュリティ保護のため、お使いの携帯電話を紛失したり盗まれた場合は、コンピュータ上のWebサイトにアクセスし、パスワードを変更してください。 このオプションをONにするとこのアプリケーションを開くたびに自動的にこのアカウントでログインします。このオプションをOFFにするにはログアウトしてから「自動ログイン」をOFFにしてください。 - - ログイン - - - - ログインしています... - - - バックグラウンドデータが無効です - - - - - - 有効にする - - 終了 - - - - - - + Tor経由で接続します (Orbotアプリが必要です) + ユーザー@ドメイン + 新しいユーザー名 + サービス プロバイダー (dukgo.com, jabber.ccc.de) + パスワード + パスワードを確認 + アカウントの詳細設定 + アカウント設定 + アカウントを登録 + 永続性 + パスワードを記憶する + パスワードはキャッシュされました + パスワードがキャッシュされていません + 自動サインイン + ChatSecureの起動時に接続する + ChatSecureの起動時に接続しない + 今すぐログイン + アカウント設定後に自動接続 + アカウント設定後に自動接続しない + パーソナル(オプション) + アカウント名(あなたのニックネーム) + アカウントをオンラインで表示する方法 + プロフィール + 簡単な自己紹介 + 暗号化されていないテキストを強制的に暗号化、または拒否する + 可能な場合は、自動的にチャットを暗号化 + 要求した時に、チャットを暗号化 + チャットの暗号化を無効にする + 信用証明書を照合中… + 鍵ペア生成中… + ログイン中.. + アカウントウィザード + アカウントIDを + サーバーの構成 + 準備ができている? + XMPPチャットサービス用にChatSecureを設定するには、アカウントIDを入力してください。メールアドレスなどです: + :アカウントID(ユーザ@ホスト名)を入力してください + (5222がデフォルトです)を入力するか、あなたのjabber/xmppを編集するチャットサーバーのホスト名とポート番号をしてください。 + ChatSecureが設定されました。これで、サービスに接続し、安全に、秘密を守ってチャットを開始することができます! + Orbot (Tor) + チャット サービス ドメイン + 新しいアカウントを登録しています… + 予定を取得しています… + ログインをキャンセル + ログインしています… + ログアウトしています\u2026 + + SRV ルックアップ + ドメイン名から実際の XMPP サーバーを検索するために DNS SRV を使用する + 暗号化されていない転送を使用する際に、ユーザー名とパスワードがプレーンテキストで送信されるのを許可する + プレーンテキストの認証を許可する + 証明書が信頼済であることを検証する + TLS 検証 + TLS 接続が必要 + 転送の暗号化 + 暗号化されたチャットの開始方法 + 必要であれば、サーバーの接続先 + 接続サーバー + XMPPサーバーのTCPポート + サーバー ポート + XMPP リソース + ログインしている他のクライアントからこの接続を区別するため + XMPP リソース優先度 + 複数のアクティブなリソースを持つクライアントへのメッセージは、最も高い優先度のリソースに送信されます + + 会話 + 会話はありません。\n\n開始するにはここをタップしてください! + アカウントが設定されていません。\n\n追加するにはここをタップしてください! + 連絡先リスト - %1$s + 連絡先を追加 + 連絡先を削除 + 連絡先をブロック + 連絡先ニックネーム + ブロック + ニックネーム + 連絡先「%1$s」を削除します。 + 連絡先「%1$s」をブロックします。 + 連絡先「%1$s」のブロックを解除します。 + %1$s」を追加しました。 + %1$s」を削除しました。 + %1$s」をブロックしました。 + %1$s」のブロックを解除しました。 + 新しいチャット + 連絡先を検索 + グリッドを表示 + チャットを始める + プロフィールを表示 + 連絡先を検証 + チャット中(%1$d + オンライン%1$d 友達を招待 - (\"\"不明\"\") - 未登録 - チャットなし - - 招待する連絡先を選択してください - 入力して、連絡先を検索 - 連絡先がありません。 - - - - ブロック中のユーザーはいません。 - - + 連絡先 + チャットする連絡先の名前を入力してください + 移動 + アクティブなチャットはありません。 + 連絡先を追加 + 追加するユーザー名またはJabberID: + リストを選択: + 連絡先から追加する名前を入力してください。 + 招待状を送る 連絡先プロフィール - ステータス: - クライアントタイプ: - パソコン - 携帯 - - - オンライン - - 取り込み中 - - 退席中 - - アイドル状態 - - オフライン - - オフラインとして表示 - - - - - + ブロックしている連絡先-%1$s + ブロック中のユーザーはいません。 + + %1$sとチャット 自分 - - メッセージを入力 - - - - - - - - - - - - - - - - + %1$sさんはオンラインです + %1$sさんは退席中です + %1$sさんは取り込み中です + %1$sさんはオフラインです + %1$sさんが参加しました + %1$sさんが退出しました + 写真を送信 + ファイルを送信 + オーディオを送信 + 写真を撮影 + ファイル転送 + 転送が完了しました + 転送中です + 転送を承認しますか? + ファイルを送信したい + 共有を送信するために利用可能な接続がありません! 送信 - - メッセージを送信できませんでした。 - - サーバーとの接続が失われました。オンラインになり次第メッセージを送信します。 - - - - - + メッセージを送信 + 安全にメッセージを送信 + 再送信 + チャット終了 + チャット履歴を消去 + 絵文字を挿入 + チャットを切り替え + Menu+ リンクを選択 - - アクティブなチャットはありません。 - - - - 連絡先を追加 - - 招待する人のメールアドレス: - - リストを選択: - - 連絡先から追加する名前を入力してください。 - - 招待状を送る - - + 削除 + ファイルを保持 + チャットセッションのセキュリティで保護されたストレージを削除しますか? + すべてのセッションのアップロードとダウンロードファイルが完全に削除されます。警告: この操作は元に戻すことはできません! + 下のファイルを削除しますか? + このファイルは、送信される前に保護されたストレージにコピーされます。デバイスのセキュリティで保護されていないストレージから、元のファイルを削除しますか? + 保持 + エクスポート + メディアファイルをエクスポートしますか? + このメディアファイルは %1$s にエクスポートされます + チャットを終了しますか? + このセッションのすべての保護されたメディアアイテムは削除されます。サムネイルアイコン上で長押しして、メディアアイテムをエクスポートしてください。 + チャット終了およびファイル削除 + + %1$sさんからグループチャットに招待されました。 + 招待状を%1$sさんに送りました。 + 承認 + 拒否 + グループチャット + グループチャットを作成または参加 + グループチャットに接続しています… + 招待… + 招待する連絡先を選択してください + 入力して、連絡先を検索 + 連絡先が見つかりません。\n\n招待するにはタップしてください。 + %1$s を追加しますか? + はい + いいえ + %1$sさんからの登録依頼を承認できませんでした。しばらくしてからもう一度試してください。 + %1$sさんからの登録依頼を拒否できませんでした。しばらくしてからもう一度試してください。 + + 暗号化を開始 + 暗号化を停止 + 暗号化されたチャットセッションを開始しています… + 暗号化されたチャットセッションを停止しています… + セキュリティ指紋 + セキュリティ指紋 + サインイン + 鍵の再生成 + 暗号化は off です + 暗号化は on です (タップで確認) + 連絡先が暗号化されたチャットを停止しました。 + 暗号化と検証をしました! + 新しい OTR キーペアを生成しています… + + インポートするOTRキーストアを検出しました。今すぐQRパスワードをスキャンしますか? + キー同期を有効にする + OTRキーリングをインポートしました + + QRをスキャン + 自分のフィンガープリント + 手動 + 質問 + セキュリティ フィンガープリント (検証済) + このフィンガープリントを確認しますか? + フィンガープリントを検証しますか? + リモートのフィンガープリントを検証しました! + あなたへのフィンガープリント + へのフィンガープリント + バーコードスキャナーをインストールしますか? + このアプリケーションはバーコードスキャナーが必要です。インストールしますか? + + 認証 + 彼らが本人であることを検証するために、連絡先に送信する質問、およびあなたが期待する回答を入力してください。 + たずねる質問 + 期待する回答 + 連絡先はあなたを認証しました。あなた自身の質問をして、連絡先を認証してください。 + OTR Q&A 検証 + チャット暗号化 + 送信 + キャンセル + + 安全な呼び出し + 安全な音声 + 呼び出し統合のために、OStel.coや他の安全なSIPサービスアカウントを入力してください + アカウントを設定する + + セキュリティおよびプライバシー 暗号化と匿名性 + 暗号化On/Off + パスワード タイムアウト + アプリの暗号化がロック解除されるべきまでの時間 + + Tor でクリック可能なリンク + Tor を使用するアカウントで、チャット内のリンクをクリック可能にします (警告、プライバシーが漏れる可能性があります!) + ユーザーインターフェイス + 言語 + 言語 + システムデフォルトを使用 + ダーク テーマを使用 + アプリのテーマをダークに変更します + インメモリ メッセージ ストレージのみ + メッセージの抽出から保護するために、フラッシュストレージではなく、メモリ内にメッセージを格納します。 (メッセージを損失することがあります) + 背景画像 + アプリの背景画像壁紙のパスを設定してください (\"/sdcard/foo.jpg\") + 連絡先グリッドを表示 + アバターグリッドとして連絡先リストを表示します + はい、すべて承認 + 安全でないメディアを削除 + 写真やファイルを共有した後、インポート後、元の、安全でないストレージから自動的に削除します + 外部ストレージにメディアを保存 + チャットセッションのメディアファイルは、暗号化されたコンテナに格納して、内部または外部のストレージに保存できます。 + + 外部ストレージが見つかりません + チャットログはSDカードに保存されましたが、SDカードが存在しません。正しいSDカードを挿入するか、既存のチャットログを削除してもう一度ChatSecureを起動してください。 + 格納したチャットメディアが見つかりません + チャットログはSDカードに保存されましたが、ファイルが現在のSDカードで見つかりません。正しいSDカードを挿入するか、既存のチャットログを削除してもう一度ChatSecureを起動してください。 + チャットログを削除 + + その他のチューニング + ChatSecure を自動的に開始する + 常に起動し、以前ログインしたアカウントに自動的にログインします オフラインの連絡先を表示しない - + フォアグラウンド優先度を使用 + Androidが接続サービスを再起動する可能性を減らします。これは通知領域に通知が配置されます。 + ハートビート間隔 + バッテリーを節約するにはより高い値(分単位)を使用してください。高い値にすると、プロバイダーが非アクティブな接続を閉じる可能性があります。 + 通知設定 - チャットの通知 - チャット受信: ステータスバーで通知 - バイブレーション - チャット受信: バイブレーションON - - チャット受信: 着信音ON - - 着信音を選択 - - - - - - - 承認 - - 拒否 - - - - 承認 - - 拒否 - - - - - - - - - - - - + カスタムの着信音を選択 + + デバッグログを有効にする + デバッグのために標準出力 / logcat にアプリ ログ データを出力します + + バックグラウンドデータが無効です + %1$sではバックグラウンドデータを有効にする必要があります。 + 有効にする + 終了 + すべてのサービスをログアウトし、すべてのプロセスを終了しますか (ハード終了)? + 新しいアカウントを作成しますか? + ユーザー名 \'%1$s\' の新しいチャット アカウントを作成しますか? + 証明書情報 + 証明書: + 発行者: + SHA1 フィンガープリント: + 発行: + 期限: + チャットルームに参加しますか? + 外部アプリが、チャットルームに接続しようとしています。許可しますか? + 背景を選択 + ギャラリーから背景画像を選択しますか? + 画像を選択 + + 新しい%1$sメッセージが届きました + %1$d 未読のチャット + %s から新しい友達の招待 + %2$s から、新しいファイル %1$s グループチャットの招待 - - - - - - - - - - - - IM/チャットの起動 - アプリケーションがintent経由でIMサービスを開始することを許可します。 - - + %s から新しいグループチャットの招待 + 新しいメッセージ。送信者 + ChatSecure サービスを開始しています… + 有効化 & ロック解除しました + メッセージをクリップボードにコピーしました + 注意 - - - + エラー: + エラーコード%1$d + %1$sサービスにログインできませんでした。しばらくしてからやり直してください。"\n"(詳細:%2$s リストを追加できませんでした。 - 連絡先をブロックできませんでした。 - 連絡先のブロックを解除できませんでした。 - まず連絡先を選択してください。 - \"切断されました。\"\n - サービスエラー - 連絡先リストを読み込めませんでした。 - サーバーに接続できません。接続を確認してください。 - - - - - + %1$sさんは既に連絡先リストに登録されています。 + %1$s」はブロックされています。 連絡先リストを読み込んでいます。しばらくお待ちください。 - ネットワークエラーが発生しました。 - + この接続にはWiFiが必要です この機能はサーバーでサポートされていません。 - 入力されたパスワードは無効です。 - サーバーでエラーが発生しました。 - この機能はサーバーでサポートされていません。 - 現在サーバーを使用できません。 - サーバーがタイムアウトしました。 - 現在のバージョンはサーバーでサポートされていません。 - メッセージキューがいっぱいです。 - このドメインへの転送はサーバーでサポートされていません。 - 入力されたユーザー名を認識できません。 - ユーザーによってブロックされています。 - セッションは期限切れになりました。もう一度ログインしてください。 - 別のクライアントからログインしています。 別のクライアントから既にログインしています。 - SIMカードから電話番号を読み取れませんでした。通信事業者にお問い合わせください。 - 現在ログインしていません。 - - - + ユーザー名とパスワードを検証中に ChatSecure でエラーが発生しました - 確認して、再度実行してください。 + 鍵ペアの生成中に ChatSecure でエラーが発生しました。 + チャットサーバに接続中 ChatSecure でエラーが発生しました - 設定を確認して、再度実行してください。 + 接続中に ChatSecure でエラーが発生しました - ネットワーク接続を再度確認して、実行してください。 + ネットワークはオフラインです + ChatSecure は接続を再確立しています + アカウントIDに @ホスト名.com の部分を入力していません。再度入力してください! + サーバーのホスト名に .com, .net のような接尾語がありません。再度入力してください! + あなたのpassword:を入力してください + Tor を使用しているので、アカウントの詳細設定に直接 XMPP \'接続サーバー\' のホスト名を入力する必要があります + 警告: このサービスは、弱い暗号の証明書を使用しています。アップグレードするには、管理者に連絡してください。 + 提供された証明書は、ピンニングされた証明書と一致しません: + グループチャットを作成または参加することができません + 申し訳ありません、そのファイルタイプは共有できません + ファイルを共有するには暗号化を有効にする必要があります + ファイルを共有するためチャット暗号化を有効にしてください + 取得したデータはサポートされていません、共有できません! + ChatSecureは、そのタスクを削除する要求が行われたことを検出しました。 ChatSecureはおそらくクラッシュします。代わりに、アカウント一覧画面からすべてサインアウトのメニュー項目をご利用下さい。 + このファイル形式に使用可能なビューアーがありません + コードをスキャンする前に、安全な会話を開始してください + OTR キーリングがインポートされていません; ファイルが適切な形式で、適切な場所に存在することを確認してください + デスクトップ KeySync ツールから、お使いのデバイスのストレージのルートディレクトリに、\'otr_keystore.ofcaes\' ファイルをコピーしてください + メッセージを送信できませんでした。 + 切断されています。再接続時にメッセージが送信されます。 + %1$sさんはオフラインです。%1$sさんがオンラインになったら、メッセージを送信します。 + %1$sさんは連絡先リストに登録されていません。 + パスワードが一致しません + 優先度は [0 .. 127] の範囲の数字でなければなりません + ポート番号は数字でなければなりません + 転送エラー + ストレージのファイルを読み込むことができません + メジャーエラー: ロック解除やアプリのデータベースをロードすることができません。アプリを再インストールするかデータをクリアデータしてください。 + リンクを処理するアプリがインストールされていません! + アカウント設定 + + グループ + 会話を開く + シャットダウン & ロック + パニック + 仲間 + 連絡先 + サーバー証明書を受け付けますか? + あなたのフィンガープリントを表示 + ロード中… + + ChatSecure について\nhttps://guardianproject.info/apps/chatsecure/ + + 連絡先リスト + 設定 + チャットアカウント一覧 + ログアウト + 連絡先リスト + + Jabber (XMPP) + ローカル (Bonjour/ZeroConf) + Google アカウント + dukgo.com + + + オンライン + 取り込み中 + 退席中 + アイドル状態 + オフライン + オフラインとして表示 + 幸せ 悲しい @@ -397,7 +507,7 @@ 笑う 混乱 - + 幸せ 悲しい @@ -417,69 +527,52 @@ 笑う 混乱 - お問い合わせ一覧 - 暗号化On/Off - Tor経由で接続します (Orbotアプリが必要です) - - 開始するまで待てませんか? - Gibberbotについて - アカウント設定 - - - どう安全なんですか? - - 私のチャットは安全ですか? - - パスフレーズの設定 - パスフレーズ: - パスフレーズ(再入力): - - パスワード - アカウントの詳細設定 - - 永続性 - パスワードを記憶する - パスワードはキャッシュされました - パスワードがキャッシュされていません - 自動サインイン - 今すぐログイン - アカウント設定後に自動接続 - アカウント設定後に自動接続しない - - パーソナル(オプション) - アカウント名(あなたのニックネーム) - アカウントをオンラインで表示する方法 - プロフィール - 簡単な自己紹介 - - 暗号化されていないテキストを強制的に暗号化、または拒否する - 可能な場合は、自動的にチャットを暗号化 - 要求した時に、チャットを暗号化 - チャットの暗号化を無効にする - - 信用証明書を照合中... - 鍵ペア生成中... - ログイン中.. - - 相手のフィンガープリント - あなたのフィンガープリント - サインイン - 鍵の再生成 - 警告:このチャットは暗号化されていません - このチャットは安全なものですが、通信相手のID照合が済んでいません。 - 警告:チャット暗号化が停止されています。 - このチャットの安全は確保されており、照合済みです。 - アカウントウィザード - アカウントIDを - サーバーの構成 - 準備ができている? - :アカウントID(ユーザ@ホスト名)を入力してください - (5222がデフォルトです)を入力するか、あなたのjabber/xmppを編集するチャットサーバーのホスト名とポート番号をしてください。 - あなたのpassword:を入力してください - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + 既存のアカウント + 指定したJabber/ XMPPサーバーの自分の既存のアカウントに接続します。 + Google アカウント + 既存のGoogleアカウントを使用して、他のGoogleユーザーとチャットします。 + WiFi メッシュ チャット + 同じローカルWiFiネットワークやメッシュ上の他の人とチャットします - インターネットやサーバは必要ありません! + WiFi チャットを有効にする + 新しいアカウント + ビルトインの、またはあなたが任意に選んだからリストから、新しい、無料のアカウントをサービスに登録してください。 + 新しいアカウントを作成 + 秘密のID! + 1回簡単にタップして、匿名で、使い捨ての \"バーナー\" チャットアカウントを作成してください (Orbot が必要です: Tor for Android) + IDを生成 + これはグループチャットです + [再送] + [再送] + このファイルを安全に共有することができません + Orbotをインストールしますか? + あなたはOrbotをインストールし、それを介してプロキシ転送を有効化している必要があります。インストールしますか? + いつも + Orbotを開始しますか? + Orbotが実行されていないようです。起動して、Torに接続しますか? + ルームで使用するニックネーム + 作成または参加するルームの名前\" + グループ チャット サーバー (conference.foo.com) + キーストアが破損しています。ChatSecureを再インストールするか、アプリの \'データをクリア\' してください + 受け取った暗号化されたメッセージは読めません + あなたが送信したメッセージを解読することができませんでした + < 左や右にスワイプで追加のオプション > diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index b883f62ab..b871af27b 100755 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -1,385 +1,253 @@ - + + + 쪽지 읽기 - \n앱이 쪽지 컨텐트 프로바이더의 데이터를 읽기 가능하게 합니다. + +앱이 쪽지 컨텐트 프로바이더의 데이터를 읽기 가능하게 합니다. 쪽지 쓰기 - \n앱이 쪽지 컨텐트 프로바이더의 데이터를 쓰기 가능하게 합니다. - - - - 채팅 - 선택 계정 - - 이 앱에 대하여 - - 계정 추가 - - 계정 수정 - - 계정 삭제 - - 모두 로그아웃 - - - 채팅 - 선택 계정 - - - - 로그인 취소 - - - 연락처 추가 - - 연락처 삭제 - - 연락처 차단 - - 차단 목록 - - 계정 목록 - - 새 채팅 창 - - - 설정 - - 주소록 검색 - - 채팅 시작 - - 로그아웃 - - 프로필 보기 - - - 암호화 시작 - 암호화 중단 - 채팅 로그 지우기 - 채팅 종료 - - - 연락처 목록 - - 초대\u2026 - - 채팅 전환 - - 이모티콘 삽입 - - 지문 스캔 - 내 지문 - 지문 확인 - - Menu+ - - - + +앱이 쪽지 컨텐트 프로바이더의 데이터를 쓰기 가능하게 합니다. + 메신저 서비스 시작 + 응용프로그램이 인텐트를 통해 메신저 서비스를 시작할 수 있도록 합니다. + 확인 - - 모든 서비스에서 로그아웃하시겠습니까? - - - - - - - 확인 - 취소 - 확인 - 취소 - - - - - - - - - + 다음 + + + 채팅 - 선택 계정 + 채팅 - 선택 계정 + %1$s 계정 추가 + 이 앱에 대하여 + 모두 로그아웃 + 모든 서비스에서 로그아웃하시겠습니까? + %1$s에서 로그아웃되었습니다. + 건너뛰기 + 소개 + 계정 설정 + 암호 설정 + 암호: + 암호 (다시): + + + 얼마나 안전한가요? + \'Off - 더 - 레코드 Messaging\'암호화, 인증, 진술 거부란 및 앞으로 Secrecy.\n\nThe OTR - 프로토콜을 포함하여 현실 세계에 사적인 대화의 특성을 흉내낸하여 개인 정보를 사용하도록 설계된 보안 시스템이다 데스크톱 클라이언트 채팅과 호환됩니다 Adium을 등또는 피진. + + 채팅은 안전한가요? + + 계정 추가 + 계정 수정 + 계정 삭제 + 사용자 이름: - 비밀번호: - 내 비밀번호 저장 - 자동으로 로그인 - 계정이 없으세요? - + 로그인 휴대전화를 분실하거나 도난 당하셨으면 보안을 위해 다른 컴퓨터에서 웹 사이트로 접속하시어 비밀번호를 변경해 주십시오. 이 옵션을 선택하면 앱이 시작할 때마다 자동으로 로그인됩니다. 중지하시려면 로그아웃한 다음 \'자동으로 로그인\' 확인란을 선택 취소하세요. - - 로그인 - - - + 토르로 연결 (Orbot 앱 필요) + user@domain.com + 암호 + 고급 계정 설정 + 계정 설정 + 지속 설정 + 비밀번호 기억 + 비밀번호 캐시 + 비밀번호 캐시하지 않음 + 자동으로 로그인 + 로그인 + 다음 계정 설정으로 접속 + 다음 계정 설정으로 접속하지 않음 + 개인 (옵션) + 계정 별칭 (사용자 이름) + 계정을 온라인에서 보기 + 프로필 + 자신에 관한 간략한 정보 + 암호화 강제 및 일반 텍스트 거부 + 가능하면 자동으로 채팅 암호화하기 + 요청하면 채팅 암호화하기 + 채팅 암호화 안 하기 + 신분증 확인 중··· + Keypair 생성 중··· + 로그인 중··· + 계정 마법사 + 계정 아이디 + 서버 설정 + 준비되셨나요? + 계정 아이디를 입력하십시오 (예: user@hostname): + Jabber/xmpp 채팅 서버 호스트 이름과 포트 번호를 입력해주십시오 (기본값 5222). + 로그인 취소 로그인 중\u2026 - - - 백그라운드 데이터 사용 안함 - - - - - - 사용 - - 종료 - - - - - - + + 서버 포트 + + 연락처 추가 + 연락처 삭제 + 연락처 차단 + 차단 목록 + 연락처 \'%1$s\'이(가) 삭제됩니다. + 연락처 \'%1$s\'이(가) 차단됩니다. + 연락처 \'%1$s\'이(가) 차단 해제됩니다. + 연락처 \'%1$s\'이(가) 추가되었습니다. + 연락처 \'%1$s\'이(가) 삭제되었습니다. + 연락처 \'%1$s\'이(가) 차단되었습니다. + 연락처 \'%1$s\'이(가) 차단 해제되었습니다. + 새 채팅 창 + 주소록 검색 + 채팅 시작 + 진행 중인 채팅(%1$d개) + 온라인 접속자 %1$d 친구 초대 - (\"\"알 수 없음\"\") - 비어 있음 - 대화 내용 없음 - - 초대할 연락처 선택 - 입력하여 연락처 찾기 - 주소록이 비었습니다. - - - - 차단된 주소가 없습니다. - - + 연락처 + 채팅하시려는 연락처의 이름을 넣어보세요 + 검색 + 진행 중인 채팅이 없습니다. + 연락처 추가 + 초대할 사람의 이메일 주소: + 목록 선택: + 주소록에서 추가할 이름을 입력하세요. + 초대장 보내기 연락처 프로필 - 상태: - 클라이언트 유형: - 컴퓨터 - 모바일 - - - 온라인 - - 다른 용무 중 - - 자리 비움 - - 대기 - - 오프라인 - - 오프라인으로 표시 - - - - - + 차단된 주소록 - %1$s + 차단된 주소가 없습니다. + + %1$s님과 채팅 - - 메시지를 입력하세요. - - - - - - - - - - - - - - - - + %1$s님이 온라인 상태입니다. + %1$s님이 자리비움 상태입니다. + %1$s님이 다른 용무 중입니다. + %1$s님은 오프라인 상태입니다. + %1$s님이 채팅에 참여했습니다. + %1$s님이 채팅에서 나갔습니다. 전송 - - 메시지를 보내지 못했습니다. - - 서버와 연결이 끊어졌습니다. 메시지는 온라인 상태일 때 전송됩니다. - - - - - + 채팅 종료 + 채팅 로그 지우기 + 이모티콘 삽입 + 채팅 전환 + Menu+ 링크 선택 - - 진행 중인 채팅이 없습니다. - - - - 연락처 추가 - - 초대할 사람의 이메일 주소: - - 목록 선택: - - 주소록에서 추가할 이름을 입력하세요. - - 초대장 보내기 - - + + %1$s님이 그룹 채팅에 초대하셨습니다. + %1$s님에게 초대장을 보냈습니다. + 수락 + 거부 + 초대\u2026 + 초대할 연락처 선택 + 입력하여 연락처 찾기 + %1$s님의 구독을 승인할 수 없습니다. 잠시 후에 다시 시도해 주세요. + %1$s님의 구독을 거부할 수 없습니다. 잠시 후에 다시 시도해 주세요. + + 암호화 시작 + 암호화 중단 + 로그인 + 키 다시 생성 + 새 OTR keypair 생성하기··· + + + 내 지문 + + 전송 + 취소 + + 계정 설정 + 암호화 및 익명 + 암호화 On/Off + + 언어 + 언어 + 오프라인 주소록 숨기기 - + 알림 설정 - 쪽지 알림 - 쪽지가 오면 상태 표시줄에 알림 - 진동 - 쪽지 오면 진동 - 사운드 - 쪽지 오면 벨소리 재생 - - 벨소리 선택 - - - - - - - 수락 - - 거부 - - - - 수락 - - 거부 - - - - - - - - - - - - + + + 백그라운드 데이터 사용 안함 + %1$s을(를) 사용하려면 백그라운드 데이터를 사용하도록 설정해야 합니다. + 사용 + 종료 + + 새 메시지 %1$s 그룹 채팅 초대 - - - - - - - - - - - - 메신저 서비스 시작 - 응용프로그램이 인텐트를 통해 메신저 서비스를 시작할 수 있도록 합니다. - - + 주의 - - - + 오류 코드 %1$d + %1$s 서비스에 로그인할 수 없습니다. 잠시 후에 다시 시도해 주세요."\n"(세부정보: %2$s) 목록이 추가되지 않았습니다. - 연락처를 차단하지 못 했습니다. - 연락처를 차단 해제하지 못 했습니다. - 먼저 연락처를 선택하세요. - - \"연결 끊김\"\n - + \"연결 끊김\" + 서비스 오류 - 연락처 목록을 로드하지 못했습니다. - 서버에 연결할 수 없습니다. 연결을 확인하세요. - - - - - + %1$s님이 이미 연락처 목록에 있습니다. + 연락처 \'%1$s\'은(는) 차단되었습니다. 연락처 목록이 로드되는 동안 잠시 기다려 주세요. - 네트워크 오류가 발생했습니다. - 서버에서 이 기능을 지원하지 않습니다. - 입력한 비밀번호가 올바르지 않습니다. - 서버에서 오류가 발생했습니다. - 서버에서 이 기능을 지원하지 않습니다. - 현재 서버를 사용할 수 없습니다. - 서버가 제한 시간을 초과했습니다. - 서버에서 현재 버전을 지원하지 않습니다. - 메시지 대기열이 가득 찼습니다. - 서버에서 도메인으로 전달하는 기능을 지원하지 않습니다. - 입력한 사용자 이름을 인식할 수 없습니다. - 죄송합니다. 상대방이 차단했습니다. - 세션이 만료되었습니다. 다시 로그인하세요. - 다른 클라이언트에서 로그인했습니다. 이미 다른 클라이언트로부터 로그인했습니다. - 죄송합니다. SIM 카드에서 전화번호를 읽을 수 없습니다. 이동통신사에 문의하세요. - 현재 로그인되어 있지 않습니다. - - - + 계정 이름에 @hostname.com 부분을 넣지 않으셨습니다. 다시 시도해 주십시오. + 서버 호스트 이름에 .com, .net같은 최상위 도메인을 넣지 않으셨습니다. 다시 시도해 주십시오. + 비밀번호: + 메시지를 보내지 못했습니다. + %1$s님이 오프라인 상태입니다. %1$s님이 온라인 상태가 되면 보낸 메시지가 전달됩니다. + %1$s이(가) 연락처 목록에 없습니다. + 계정 설정 + + 그룹 + + + 연락처 목록 + 설정 + 계정 목록 + 로그아웃 + 연락처 목록 + + + + 온라인 + 다른 용무 중 + 자리 비움 + 대기 + 오프라인 + 오프라인으로 표시 + 기쁨 슬픔 @@ -399,7 +267,7 @@ 웃음 혼란 - + 기쁨 슬픔 @@ -438,81 +306,4 @@ :-D o_O - 연락처 목록 - 암호화 On/Off - 토르로 연결 (Orbot 앱 필요) - - 건너뛰기 - 소개 - 계정 설정 - - - 얼마나 안전한가요? - - 채팅은 안전한가요? - - 암호 설정 - 암호: - 암호 (다시): - - user@domain.com - 암호 - 고급 계정 설정 - - 지속 설정 - 비밀번호 기억 - 비밀번호 캐시 - 비밀번호 캐시하지 않음 - 자동으로 로그인 - 로그인 - 다음 계정 설정으로 접속 - 다음 계정 설정으로 접속하지 않음 - - 개인 (옵션) - 계정 별칭 (사용자 이름) - 계정을 온라인에서 보기 - 프로필 - 자신에 관한 간략한 정보 - - 암호화 강제 및 일반 텍스트 거부 - 가능하면 자동으로 채팅 암호화하기 - 요청하면 채팅 암호화하기 - 채팅 암호화 안 하기 - - 신분증 확인 중··· - Keypair 생성 중··· - 로그인 중··· - - 상대방 지문 - 내 지문 - 로그인 - 키 다시 생성 - 주의: 이 채팅은 암호화되지 않습니다 - 이 채팅은 안전하지만 참가자의 신원이 확인되지 않았습니다! - 주의: 채팅 암호화가 중지되었습니다. - 이 채팅은 안전하고 검즌되었습니다 - 계정 마법사 - 계정 아이디 - 서버 설정 - 준비되셨나요? - 계정 아이디를 입력하십시오 (예: user@hostname): - Jabber/xmpp 채팅 서버 호스트 이름과 포트 번호를 입력해주십시오 (기본값 5222). - 계정 이름에 @hostname.com 부분을 넣지 않으셨습니다. 다시 시도해 주십시오. - 서버 호스트 이름에 .com, .net같은 최상위 도메인을 넣지 않으셨습니다. 다시 시도해 주십시오. - 비밀번호: - - 계정 설정 - - 그룹 - 새 OTR keypair 생성하기··· - 연락처 - 채팅하시려는 연락처의 이름을 넣어보세요 - 검색 - 언어 - 언어 - InTheClear에 나올 언어를 선택하십시오. - - diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml new file mode 100644 index 000000000..e4cece1ee --- /dev/null +++ b/res/values-land/dimens.xml @@ -0,0 +1,31 @@ + + + + + 20dp + 8dp + + 8dp + 8dp + 16dp + 32dp + 200dp + 15dp + + 148dip + 148dip + + \ No newline at end of file diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml new file mode 100644 index 000000000..d42219c62 --- /dev/null +++ b/res/values-mk/strings.xml @@ -0,0 +1,256 @@ + + + + ChatSecure + ChatSecure + + + Потврди + Во ред + Откажи + Во ред + Откажи + Понатаму + Назад + Поврзи + Играј + Уредување + + Отвори + Потврди ја лозинката + Лозинка + Потврди ја новата лозинка + Неточна лозинка, ве молиме пробајте повторно. + Прескокни >> + Ве молиме внесете ја лозинката + Ве молиме лозинката + Лозинка + Лозинка (повторно) + + Избери сметка + Избери сметка + (%1$d) + Додај %1$s сметка + Повеќе + Одјави ги сите + Се одјавивте од %1$s. + Се одјавивте од %1$s бидејќи %2$s + За прв пат го користете ChatSecure? + Поставување на сметка + Поставување на лозинка + Пред да започнете, Ве молиме изберете безбедна лозинка за заштита на вашите ChatSecure податоци од неовластен пристап. + Лозинка: + Лозинка (повторете ја): + Внесете *нова* лозинка. Таа мора да содржи најмалку една голема буква, една мала буква и еден број, и да биде подолга од шест знаци. + Дешифрирање на Вашите постоечки белешки со нова лозинка (ве молиме почекајте…) + Внесете ја Вашата лозинка: + Добредојдовте! Внесете силна лозинка за да ги вашите белешки. Таа мора да создржи една голема буква, една мала буква и еден број, и да биде поголема од шест знаци. + Вашата лозинка не е доволно долго + Вашата лозинка не содржи големи букви + Вашата лозинка не содржи мали букви + Вашата лозинка не содржи бројки + + За ChatSecure + + + Дали моите разговори се осигурани? + + Нова сметка + Додај сметка + Измени сметка + Избриши сметка + Постоечка сметка + + Лозинка: + Запамети ја мојата лозинка. + Најави ме автоматски. + Најави се + Поврзи ме преку Tor (потребна е Orbot апликацијата) + Ново корисничко име + Испорачувач на услуги (dukgo.com, jabber.ccc.de) + Лозинка + Потврди ја лозинката + Поставување на сметка + Регистрирај сметка + Запамети ја лозинката + Автоматско најавување + Најави се сега + Лични информации (опционално) + Сметка Alias (вашето име) + Профил + Најавување … + Дали сте спремни? + Orbot (Tor) + Регистрирање на нова сметка… + Откажи најавување + Најавување\u2026 + Одјавување\u2026 + + Потребно е TLS поврзување + Поврзување со серверот + TCP порти за XMPP сервер + + Разговори + Нема разговори.\n\nДопри овде за нов разговор! + Контакти - %1$s + Додај контакт + Избриши контакт + Блокирај контакт + Прекар на контактот + Блокирано + Прекар + Контактот "%1$s" ќе биде избришан. + Контактот "%1$s" ќе биде блокиран. + Контактот "%1$s" ќе биде одблокиран. + Контактот "%1$s" е додаден. + Контактот "%1$s" е избришан. + Контактот "%1$s" е блокиран. + Контактот "%1$s" е одблокиран. + Нов разговор + Барај контакти + Прикажи мрежа + Започни разговор + Види профил + Тековни разговори (%1$d) + Покани пријател + (Непознат) + Празно + Нема сè уште разговори. \n\nДопрете за да започнете да разговарате! + Контакт + Внесете го името на контактот со кого сакате да разговарате. + Ајде + Нема активни разговори. + Додај контакт + Додај сметка на: + Испрати покана + Статус: + Вид на клиент: + Компјутер + Мобилен + Блокирани контакти - %1$s + Нема блокирани контакти. + + Разговарај со %1$s + Јас + %1$s е вклучен + %1$s не е тука + %1$s е зафатен/а + %1$s е исклучен + %1$s се придружи + %1$s го напушти разговорот + Испрати слика + Испрати датотека + Испрати звук + пренос на датотека + Испраќањето е завршено + Испраќањето е во тек + Прифати го преносот? + сака да ви испрати датотека + Не сте поврзани за да ја испратите вашата порака! + Испрати + Испрати порака + Испрати безбедна порака + Испрати повторно + Крај на разговорот + Избриши го разговорот + Мени+ + Избери врска + Избриши + Чувај датотеки + Избриши го оригиналот? + Чувај + Извези + Крај на разговорот? + Крај на разговорот и избриши датотеки? + + %1$s Ве покани да се придружите во групниот разговор. + Поканата е испратена на %1$s. + Прифати + Одби + Групен разговор + Создај или придружи се на групен разговор + Поврзување на групен разговор… + Покани\u2026 + Избери контакт(и) за да го/и поканиш + Додај %1$s? + Да + Не + + Безбедносен отпечаток на прст + Безбедносен отпечаток на прст + Најави се + Обнови го клучот + Шифрирањето е исклучено + Шифрирањето е уклучено (допри за потврда) + Шифрирано и потврдено! + + + Вашиот отпечаток од прст + Мануелно + Прашање + Дали сте сигурни дека сакате да се потврди овој отпечаток од прст? + Потврди го отпечатокот од прст? + Отпечаток од прст за + + Очекуваниот одговор + Испрати + Откажи + + + Поставување на сметка + + + Јазик + Јазици + Користи темна позадина + Да, прифати ги сите + + + + Нагодувања за известување + Звук + + Овозможи листа на грешки + + Овозможи + Создади нова сметка? + Избери слика + + Нови %1$s пораки + %1$d непрочитани разговори + Нова покана за пријателство од %s + Нова датотека %1$s од %2$s + Нова/и порака/и од + + Грешка: + Контактот не е блокиран. + Контактот не е одблокиран. + Ве молиме прво изберето го контактот. + Не може да се поврзе на серверот. Ве молиме проверете ја вашата врска. + Контактот "%1$s" е блокиран. + Внесената лозинка не е точна. + Серверот не ја поддржува моменталната верзија. + Се најавивте од друг клиент. + Се најавивте од друг клиент. + Во моментот не сте најавени. + Внесете ја вашата лозинка: + Оваа порака не може да се испрати. + Вашата лозинка е неточна + + Контакти + Вчитување… + + За ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Нагодувања + Одјави се + + Google сметка + + + + + Постоечка сметка + Google сметка + Нова сметка + diff --git a/res/values-ml/arrays.xml b/res/values-ml/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-ml/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml new file mode 100644 index 000000000..22ef85d34 --- /dev/null +++ b/res/values-ml/strings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-mr/arrays.xml b/res/values-mr/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-mr/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml new file mode 100644 index 000000000..92c1b723e --- /dev/null +++ b/res/values-mr/strings.xml @@ -0,0 +1,87 @@ + + + + चॅटसिक्यॉर + चॅटसिक्यॉर + + + नक्की करा + ठीक आहे + रद्द करा + ठीक आहे + रद्द करा + पुढे + मागे + स्थापना + + उघडा + परवलीचा शब्द + + + + + + + + + नवीन गप्पा + संपर्क शोधा + रिकामे + संपर्क + संपर्क जोडा + संगणक + भ्रमणध्वनी + + मी + %1$s ऑनलाइन आहेत + %1$s बाहेर गेले आहेत + %1$s व्यस्त आहेत + %1$s ऑफलाइन आहेत + %1$s आले आहेत + %1$s सोडून गेले + पुन्हा पाठवा + गप्पा थांबवा + गप्पा पुसा + ठेवा + निर्यात करा + + स्वीकार करा + नकार द्या + सामूहिक गप्पा + सामूहिक गप्पा सुरू करा किंवा जोडा + सामूहिक गप्पांशी जोडत आहोत… + आमंत्रित करण्याच्या संपर्कांना निवडा + संपर्क शोधण्यासाठी टंका + + एन्क्रिप्शन सुरू करा + एन्क्रिप्शन बंद करा + एन्क्रिप्टेड गप्पा सुरू करत आहोत… + एन्क्रिप्टेड गप्पा बंद करत आहोत… + सुरक्षितता फिंगरप्रिंट + सुरक्षितता फिंगरप्रिंट + प्रवेश करा + किल्ली पुन्हा बनवा + एन्क्रिप्शन बंद आहे + + + + रद्द करा + + + + + + + + + + + + + + + + + + + diff --git a/res/values-my/arrays.xml b/res/values-my/arrays.xml index ed740bfdd..b05d6d33b 100644 --- a/res/values-my/arrays.xml +++ b/res/values-my/arrays.xml @@ -6,48 +6,4 @@ တောင်းဆိုထားသည့်အတိုင်း ပိတ်ထားသော / မည့်သည့်အခါမျှ - - အတင်းအကျပ် - အလိုအလျောက် - တောင်းဆိုထားသော - ပိတ်ထားသော - - - နဂိုအတိုင်း - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - ဒတ်ချ် - အင်္ဂလိပ် - စပိန် - ပြင်သစ် - အီတာလျံ - နယ်သာလန် - Pyccĸий - တိဘက် - ဗီယက်နမ် - Tϋrkçe - Čeština - - - xx - ar - fa - zh-rCN - ja - ko - de - en - es_ES - fr - it - nl - ru - bo - vi - tr - cs - diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index e3a8407d8..aba6f31f3 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -1,361 +1,280 @@ - + + + စာတိုများ ဖတ်ရန် - \n Allows applications to read data from the IM content provider.\n + + Allows applications to read data from the IM content provider. + စာတိုများ ရေးရန် - \n Allows applications to write data to the IM content provider.\n - - - - အကောင့်တစ်ခုရွေးပါ - - အကြောင်း - - အကောင့်ပေါင်းထည့် - - အကောင့်ပြင်ဆင် - - အကောင့်ကိုဖယ်ပစ် - - အားလံုးကိုထွက် - - - အကောင့်တစ်ခုရွေးပါ - - - - ဝင်ရောက်နေခြင်းပယ်ဖျက် - - - အဆက်အသွယ်ပေါင်းထည့် - - အဆက်အသွယ်ပယ်ဖျက် - - အဆက်အသွယ်အားပိတ်ထား - - ပိတ်ထားသော - - အကောင့်များ - - စကားဝိုင်းအသစ် - - အကောင့်အသစ် - - ဆက်တင် - - အဆက်အသွယ်ရှာဖွေ - - စကားဝိုင်းစ - - ထွက်ခွာ - - အတည်ပြု - - - စာဝှက်စနစ်စတင် - စာဝှက်စနစ်ရပ်ဆိုင်း - စကားဝိုင်းရှင်း - စကားဝိုင်းဆုံး - - - အဆက်အသွယ်စာရင်း - - ဖိတ်ခေါ် \u2026 - - စကားဝိုင်းပြောင်း - - မျက်နှာပြောင် ထည့်မည် - ထပ်ပို့ - - လက်ဗွေအားစိစစ် - သင့်လက်ဗွေ - လက်ဗွေအားအတည်ပြု - လျို့ဝှက်ချက်အတည်ပြု - - မီနူး+ - - - + + Allows applications to write data to the IM content provider. + + IM ဝန်ဆောင်မှုစတင် + အက်ပလီကေးရှင်းများကို intent သုံး၍ IM ဆားဗစ် စရန် ခွင့်ပြုပါ။ + အတည်ပြုချက် - - ဝန်ဆောင်မှုအားလံုးမှထွက်ခွာမည်လား - - - - အိုကေ - ပယ်ဖျက် - အိုကေ - ပယ်ဖျက် - - - - - - + ရှေ့သို့ + နောက်သို့ + + + အကောင့်တစ်ခုရွေးပါ + အကောင့်တစ်ခုရွေးပါ + အကြောင်း + အားလံုးကိုထွက် + ဝန်ဆောင်မှုအားလံုးမှထွက်ခွာမည်လား + စသုံးဖို့ မစောင့်နိုင်တော့ဘူးလား + စသုံးရန် + အကောင့်ပြင်ဆင်မှု + စကားဝှက် သတ်မှတ်ရန် + စကားဝှက် - + စကားဝှက် (တစ်ဖန်) - + + + ဘယ်လို လုံခြုံတာလဲ? + \'Off-the-Record Messaging\' is a security system designed to enable privacy by mimicking the characteristics of a private conversation in the real world, including Encryption, Authentication, Deniability and Forward Secrecy. + +The OTR-protocol is compatible with desktop chat clients such as Adium or Pidgin. + + ကျွန်တော့်စကားဝိုင်းတွေ လုံခြုံရဲ့လား? + + အကောင့်အသစ် + အကောင့်ပေါင်းထည့် + အကောင့်ပြင်ဆင် + အကောင့်ကိုဖယ်ပစ် + User@Host - စကားဝှက်: - စကားဝှက်အားသိမ်းဆည်းထား - အလိုအလျောက်ဝင် - အကောင့် မရှိဘူးလား? - + ဝင်ရောက် လုံခြုံရေးအတွက် အသင့်၏ ဖုန်း ပျောက်ဆုံးခိုးယူခံရပါက အသင့် ကွန်ပျူတာဝဘ်ဆိုက်မှ တစ်ဆင့် အသင့် စကားဝှက်ကို ပြောင်းလဲပါ။ ဤအချက်သည် အက်ပလီကေးရှင်ကို ဖွင့်လိုက်တိုင်း အသင့်ကို အလိုလို ဝင်စေပါသည်။ ဤအချက်ကို ပိတ်ရန် ပြန်ထွက်၍ \"အလိုအလျောက် ဝင်\" အမှန်ခြစ်ကွက်ကို အမှန်ခြစ်ဖယ်ပေးပါ။ - - ဝင်ရောက် - - - + Tor ကို သုံး၍ ချိတ်ဆက်ပါ (Orbot app လိုအပ်ပါသည်) + user@domain.com + စကားဝှက် + အကောင့် ဆက်တင်များ + အကောင့်အမျိုးအစား + စွဲမြဲခြင်း + စကားဝှက် မှတ်ထားပါ + စကားဝှက် ခေတ္တသိမ်းထားသည် + စကားဝှက် ခေတ္တသိမ်းထားခြင်းမရှိပါ။ + အလိုအလျောက် ဝင်ပါ + ယခု ဝင်ရန် + အကောင့်ပြင်ဆင်သတ်မှတ်ပြီး ဆက်သွယ်ပါ + အကောင့်ပြင်ဆင်သတ်မှတ်ပြီး မဆက်သွယ်ပါနှင့် + ကိုယ်ရေးကိုယ်တာ (အရေးမကြီး) + အကောင့် အမည်ကွဲ (အသင့် နာမည်) + အသင့်အကောင်း အွန်လိုင်းတွင် ပေါ်နေပုံ + ပရိုဖိုင် + မိမိအကြောင်း တစ်စေ့တစ်စောင်း + အတင်းဝှက် / ရိုးရိုးစာသား ငြင်း + ဖြစ်နိုင်လျှင် စကားဝိုင်းများကို အလိုအလျှောက်ဝှက်ပါ + တောင်းဆိုသည့်အတိုင်း စာကားဝိုင်းများကို ဝှက် + စကားဝိုင်းဝှက်ခြင်းကို ပိတ်ပါ + အချက်အလက်များကို စစ်ဆေးအတည်ပြုနေပါတယ်… + ကီးတွဲများ ဖန်တီနေပါတယ်… + ဝင်နေသည်… + အကောင့်ဆိုင်ရာ + အသင့် အကောင့် အိုင်ဒီ + ဆာဗာ သတ်မှတ်ရန် + အသင့်ဖြစ်ပြီလား + အသင့်၏ အကောင့်အိုင်ဒီကို ရိုက်ပါ (user@hostname) - + အသင့်၏ jabber/xmpp စကားဝိုင်းဆာဗာ အိမ်ရှင်စက်အမည်နှင့် port နံပါတ် (ပုံမှန် ၅၂၂၂၂) ကို ပြင်ဆင်ရေးသားပါ။ + Orbot (Tor) + ဝင်ရောက်နေခြင်းပယ်ဖျက် ဝင်နေတယ် \u2026 - - - ကွန်ယက်အချက်အလက်ပိတ်ထား - - - \n အက်ပလီကေးရှင်မှ ဝင်သုံးရန် ကွန်ရက်ဒေတာ ချိတ်ဆက်မှု (နောက်ကွယ် ဒေတာများ အပါအဝင်) ကို လိုအပ်ပါတယ်။ \n - - - ခွင့်ပြု - - ထွက်ခွာ - - - - + + SRV ရှာဖွေမှု ပြုလုပ်ပါ + ဒိုမိန်းနာမည်မှ XMPP ဆာဗာကို ရှာရန် DNS SRV ကို သုံးပါ + ဝှက်ထားခြင်းမရှိသော ပေးပို့မှုတွင် ယူဇာအမည်နှင့် စကားဝှက်ကို ရိုးရိုးစာသားအနေဖြင့် ပေးပို့ခြင်း ခွင့်ပြုပါ + Plain Text စိစစ်စစ်ဆေးမှု ခွင့်ပြုပါ + ဤ certificate သည် ယုံကြည်စိတ်ချရကြောင်း အတည်ပြုပါ + TLS စစ်ဆေးခြင်း + Transport Encryption + ဝှက်ထားသည့် စကားဝိုင်းများ စတင်ပုံ + လိုအပ်လျှင် ချိတ်ဆက်ရမည့် ဆာဗာ + ဆာဗာသို့ ချိတ်ဆက်ခြင်း + XMPP ဆာဗာအတွက် TCP Port + ဆာဗာ Port + XMPP Resource + ဤကော်နက်ရှင်ကို အခြားဝင်ထားသော ကလိုင်းယင့်များထံမှ ခွဲခြားရန် + XMPP Resource Priority + Messages to clients with multiple active resources will be sent to the resource with the highest priority + + စကားဝိုင်းများ + စကားဝိုင်း မရှိပါ။ + +အသစ်စရန် ဒီမှာ ထိနှိပ်ပါ။ + အဆက်အသွယ်ပေါင်းထည့် + အဆက်အသွယ်ပယ်ဖျက် + အဆက်အသွယ်အားပိတ်ထား + ပိတ်ထားသော + စကားဝိုင်းအသစ် + အဆက်အသွယ်ရှာဖွေ + စကားဝိုင်းစ မိတ်ဖွဲ့ ကမ်းလှမ်မ်းချက်များ - ( - ဘာမှမရှိ - စကားစမြည်ပြောထားသည်များမရှိ - - ဖိတ်ခေါ်ရမည့် အဆက်အသွယ်များ ရွေးပါ - အဆက်အသွယ်အားရှာရန်ရိုက်ပါ - အဆက်အသွယ်တစ်ခုမှမတွေ့ - - - ပိတ်ထားသော အဆက်အသွယ် မရှိ - - + အဆက်အသွယ် + စကားပြောဆိုမည့် အဆက်အသွယ်၏ အမည်ရိုက်ရန် + သွားရန် + ပြောနေတဲ့ စကားဝိုင်း မရှိ + အဆက်အသွယ်ပေါင်းထည့် + အသင့်ဖိတ်ခေါ်လို့သည့် လူ၏ အီးမေးလ်လိပ်စာ - + စာရင်းရွေးချယ်: + အဆက်အသွယ်ထဲမှပေါင်းထည့်ရန်အမည်ကိုရိုက်ပါ. + ဖိတ်ခေါ်ချက်ပို့ အဆက်အသွယ်ပရိုဖိုင် - အခြေအနေ: - ကလိုင်းယင့် အမျိုးအစား: - ကွန်ပျူတာ - မိုဘိုင်း - - - လိုင်းပေါ်တွင်ရှိ - - အလုပ်ရှုပ်နေ - - အဝေးတွင် - - အားနေ - - လိုင်းပေါ်တွင်မရှိ - - အောဖ့်လိုင်းဟု ပြပါ - - - - + ပိတ်ထားသော အဆက်အသွယ် မရှိ + ကျွန်ုပ် - - ရေးဖွဲ့ရန်ရိုက်ပါ - - - - - - - - - ပို့ - - ဤအကြောင်းကြားချက်အားမပို့နိုင် - - ဆာဗာနှင့်ချိတ်ဆက်မှုပြတ်တောက်၊ လိုင်းပေါ်ရောက်ပါက အကြောင်းကြားချက်အား ပို့မည် - - - + ထပ်ပို့ + စကားဝိုင်းဆုံး + စကားဝိုင်းရှင်း + မျက်နှာပြောင် ထည့်မည် + စကားဝိုင်းပြောင်း + မီနူး+ လင့်ရွေးပါ - - ပြောနေတဲ့ စကားဝိုင်း မရှိ - - ဝှက်ထားသည့် စကားဝိုင်းဆက်ရှင် စနေပါသည်... - ဝှက်ထားသည့် စကားဝိုင်းဆက်ရှင် ရပ်နေပါသည်... - - - အဆက်အသွယ်ပေါင်းထည့် - - အသင့်ဖိတ်ခေါ်လို့သည့် လူ၏ အီးမေးလ်လိပ်စာ - - - စာရင်းရွေးချယ်: - - အဆက်အသွယ်ထဲမှပေါင်းထည့်ရန်အမည်ကိုရိုက်ပါ. - - ဖိတ်ခေါ်ချက်ပို့ - - Jabber (XMPP) - လိုကယ်ကွန်ရန် စကားဝိုင်း (Bonjour/ZeroConf) - + + လက်ခံ + ငြင်းဆို + ဖိတ်ခေါ် \u2026 + ဖိတ်ခေါ်ရမည့် အဆက်အသွယ်များ ရွေးပါ + အဆက်အသွယ်အားရှာရန်ရိုက်ပါ + + စာဝှက်စနစ်စတင် + စာဝှက်စနစ်ရပ်ဆိုင်း + ဝှက်ထားသည့် စကားဝိုင်းဆက်ရှင် စနေပါသည်… + ဝှက်ထားသည့် စကားဝိုင်းဆက်ရှင် ရပ်နေပါသည်… + ဝင် + ကီး ပြန်ဖန်တီးရန် + OTR ကီးတွဲသစ် ဖန်တီးနေပါတယ်… + + + သင့်လက်ဗွေ + + စိစစ်စစ်ဆေးခြင်း + အသင့်အဆက်အသွယ်ကို အတည်ပြုစစ်ဆေးရန် မေးရမည့် မေးခွန်းနှင့် သူတို့ ဖြေရမည့် အဖြေကို ရိုက်ပါ။ + မေးရမည့် မေးခွန်း + မျှော်လင့်ထားသော အဖြေ + အသင့် အဆက်အသွယကို အောင်မြင်စွာ စစ်ဆေးပြီးပါပြီ။ ယခု ၎င်းကို အတည်ပြုရန် မေးခွန်းမေးပါ။ + ပို့ + ပယ်ဖျက် + + အကောင့်ပြင်ဆင်မှု + + Security and Privacy Encryption and Anonymity + ဝှက်ခြင်း အဖွင့်/အပိတ် + + User Interface + ဘာသာစကား + ဘာသာစကားများ + အရောင်ရင့်ရင့် အဆင်အယင်သုံးပါ + စာများကို မမ်မိုရီအတွင်းသာ သိမ်းရန် + Only store messages in memory, not on flash storage, to defend against messages being extracted. (May cause lost messages) + နောက်ခံပုံ + အက်ပလီကေးရှင်း၏ နောက်ခံပုံလမ်းကြောင်းကို (\"/sdcard/foo.jpg\") သတ်မှတ်ရန် + + အခြား + နဂိုဝင်သုံးဖူးသည့် အကောင့်များသို့ အလိုအလျှောက် ဝင်ရန် လိုင်းပေါ်တွင်မရှိသောအဆက်သွယ်များအားဖျောက် - - အကြောင်းကြားချက်ဆင်တင် - - IM အကြာင်းကြားချက်များ - - စာသစ်ရောက်လျှင် အခြေအနေပြဘားတွင်း အကြောင်းကြားပါ။ - Use foreground priority အန်းဒရိုက်မှ ဒီကော်နက်ရှင်ဆားဗစ်ကို ပြန်စနိုင်ခြင်းကို လျော့ချပါ။ အချက်ပြဧရိယာတွင် အချက်ပြမှု ပေါ်နေပါလိမ့်မည်။ နှလုံးခုန်နူန်း ဘက်ထရီစား သက်သာစေရန် မိနစ်များများ သုံးပါ။ + + အကြောင်းကြားချက်ဆင်တင် + IM အကြာင်းကြားချက်များ + စာသစ်ရောက်လျှင် အခြေအနေပြဘားတွင်း အကြောင်းကြားပါ။ တုန်ခါ - စာရောက်လာလျှင် တုန်ပေးပါ - အသံ - စာရောက်လာလျှင် ringtone ဖွင့်ပါ - - ringtone ရွေးရန် - - - - - လက်ခံ - - ငြင်းဆို - - - လက်ခံ - - ငြင်းဆို - - - - - - - + + + ကွန်ယက်အချက်အလက်ပိတ်ထား + + အက်ပလီကေးရှင်မှ ဝင်သုံးရန် ကွန်ရက်ဒေတာ ချိတ်ဆက်မှု (နောက်ကွယ် ဒေတာများ အပါအဝင်) ကို လိုအပ်ပါတယ်။ + + ခွင့်ပြု + ထွက်ခွာ + ဆားဗစ်အားလုံးမှ ထွက်၍ ပရောဆက်များအားလုံးကို ပိတ်မည်လား? + အုပ်စုလိုက် စကားဝိုင်း တောင်းဆိုခြင်း - - - - - - - IM ဝန်ဆောင်မှုစတင် - အက်ပလီကေးရှင်းများကို intent သုံး၍ IM ဆားဗစ် စရန် ခွင့်ပြုပါ။ - - + စာအသစ်၊ ပို့သူမှာ + အာရုံစိုက်ရန် - - စာရင်းအားမပေါင်းထည့်လိုက်ပါ - အဆက်အသွယ်ကို ပိတ်ဆို့ထားခြင်း မရှိပါ။ - အဆက်အသွယ်ကို ပိတ်ဆို့မှုဖွင့်ထားခြင်း မရှိပါ။ - ကျေးဇူးပြုပြီး ပထမဦးစွာ အဆက်အသွယ်ကိုရွေးချယ်ပါ - ကော်နက်ရှင် ကျသွားပြီ - ဝန်ဆောင်မှုအမှား! - အဆက်အသွယ်စာရင်း တင်ယူထားခြင်း မရှိပါ။ - ဆာဗာသို့ ချိတ်ဆက်၍ မရပါ။ ကျေးဇူးပြုပြီး ကောနက်ရှင် စစ်ကြည့်ပါ။ - - - အဆက်အသွယ်စာရင်းအားဆွဲတင်နေစဉ် ကျေးဇူးပြု၍ ခေတ္တစောင့်ပါ - ကွန်ယက်အမှားပေါ်ပေါက် ဤကော်နက်ရှင်အတွက် ဝိုင်ဖိုင် လိုပါတယ်။ - ဆာဗာက ဤဖန်ရှင်အား ထောက်ပံနိုင်ခြင်း မရှိပါ - သင်ရိုက်ထည့်သောစကားဝှက်အားလက်မခံနိင် - ဆာဗာမှ အမှားနှင့်ကြံုတွေ့ - ဆာဗာက ဤဖန်ရှင်အား ထောက်ပံနိုင်ခြင်း မရှိပါ - လက်ရှိတွင်ဆာဗာအားမရရှိနိုင် - ဆာဗာမှ အကြောင်းမပြန်ပါ - လက်ရှိမူကွဲအားဆာဗာမှထောက်ပံ့ခြင်းမပြု - စာသားအတန်း ပြည့်နေပြီ - ဆာဗာက ဒိုမိန်းသို့ ဆင့်ကမ်းပို့ခြင်း ခွင့်မပြုပါ - ရိုက်လိုက်သည့် အကောင့်အမည်ကို မသိရှိပါ - အသင့်ကို တစ်ဖက်ယူဇာမှ ပိတ်ထားပါတယ်။ - ဆက်ရှင်သက်တမ်းကုန်ဆံုး၊ ကျေးဇူးပြု၍နောက်တစ်ကြိမ်ဝင်ရောက်ပါ - အခြားကလိုင်းယင့်မှ ဝင်ထားခြင်း ဖြစ်သည်။ အခြားကလိုင်းယင့်မှ ဝင်ထားခြင်း ဖြစ်သည်။ - ဆင်းမ်ကတ်မှ အသင့်ဖုန်းနံပါတ် ဖတ်၍ မရပါ။ အကူအညီအတွက် အော်ပရေတာကို ဆက်သွယ်ပါရန်။ - သင်သည် လက်ရှိတွင် မဝင်ရသေးပါ - - + အသင့်အကောင့်အိုင်ဒီ ရိုက်ထားသည်တွင် @hostname.com မပါရှိပါ။ ထပ်မံကြိုးစားကြည့်ပါ။ + အသင့်ဆာဗာတင် .com, .net (သို့) အခြား အလားတူ နောက်ဆက်တွဲများ မပါရှိပါ။ ထပ်မံကြိုးစားကြည့်ပါ။ + စကားဝှက် ရိုက်ပါ - + Tor ကို အသုံးပြုမည် ဖြစ်၍ XMPP \'ဆာဗာ ချိတ်ဆက်မှု\' အိမ်ရှင်စက်အမည်ကို အကောင့်ဆက်တင်ထဲတွင် တိုက်ရိုက် ရိုက်ထည့်ရမည် + ဤအကြောင်းကြားချက်အားမပို့နိုင် + အကောင့်ဆက်တင် + + + အဖွဲ့များ + ဖွင့်ထားသော စကားဝိုင်းများ + ထွက်ရန် + + + အဆက်အသွယ်စာရင်း + ဆက်တင် + အကောင့်များ + ထွက်ခွာ + အဆက်အသွယ်စာရင်း + + Jabber (XMPP) + ဂူးဂဲ အကောင့် + + + လိုင်းပေါ်တွင်ရှိ + အလုပ်ရှုပ်နေ + အဝေးတွင် + အားနေ + လိုင်းပေါ်တွင်မရှိ + အောဖ့်လိုင်းဟု ပြပါ + ပျော်နေ ဝမ်းနည်း @@ -375,7 +294,7 @@ ရယ်နေ စိတ်ရှုပ်နေ - + ပျော်နေ ဝမ်းနည်း @@ -414,127 +333,5 @@ :-D o_O - အဆက်အသွယ်စာရင်း - ဝှက်ခြင်း အဖွင့်/အပိတ် - Tor ကို သုံး၍ ချိတ်ဆက်ပါ (Orbot app လိုအပ်ပါသည်) - - စသုံးဖို့ မစောင့်နိုင်တော့ဘူးလား - စသုံးရန် - အကောင့်ပြင်ဆင်မှု - - - ဘယ်လို လုံခြုံတာလဲ? - \'Off-the-Record Messaging\' is a security system designed to enable privacy by mimicking the characteristics of a private conversation in the real world, including Encryption, Authentication, Deniability and Forward Secrecy.\n\nThe OTR-protocol is compatible with desktop chat clients such as Adium or Pidgin. - - ကျွန်တော့်စကားဝိုင်းတွေ လုံခြုံရဲ့လား? - - စကားဝှက် သတ်မှတ်ရန် - စကားဝှက် - - စကားဝှက် (တစ်ဖန်) - - - user@domain.com - စကားဝှက် - အကောင့် ဆက်တင်များ - အကောင့်အမျိုးအစား - - စွဲမြဲခြင်း - စကားဝှက် မှတ်ထားပါ - စကားဝှက် ခေတ္တသိမ်းထားသည် - စကားဝှက် ခေတ္တသိမ်းထားခြင်းမရှိပါ။ - အလိုအလျောက် ဝင်ပါ - ယခု ဝင်ရန် - အကောင့်ပြင်ဆင်သတ်မှတ်ပြီး ဆက်သွယ်ပါ - အကောင့်ပြင်ဆင်သတ်မှတ်ပြီး မဆက်သွယ်ပါနှင့် - - ကိုယ်ရေးကိုယ်တာ (အရေးမကြီး) - အကောင့် အမည်ကွဲ (အသင့် နာမည်) - အသင့်အကောင်း အွန်လိုင်းတွင် ပေါ်နေပုံ - ပရိုဖိုင် - မိမိအကြောင်း တစ်စေ့တစ်စောင်း - - အတင်းဝှက် / ရိုးရိုးစာသား ငြင်း - ဖြစ်နိုင်လျှင် စကားဝိုင်းများကို အလိုအလျှောက်ဝှက်ပါ - တောင်းဆိုသည့်အတိုင်း စာကားဝိုင်းများကို ဝှက် - စကားဝိုင်းဝှက်ခြင်းကို ပိတ်ပါ - - အချက်အလက်များကို စစ်ဆေးအတည်ပြုနေပါတယ်... - ကီးတွဲများ ဖန်တီနေပါတယ်... - ဝင်နေသည်... - - ၎င်းတို့ လက်ဗွေရာ - သင့်လက်ဗွေ - ဝင် - ကီး ပြန်ဖန်တီးရန် - သတိပေးချက်၊ ဤစကားဝိုင်းကို ဝှက်ထားခြင်းမရှိပါ - ဤစကားဝိုင်းသည် လုံခြုံစိတ်ချရသော်လည်း စကားဝိုင်းတွင် ပါဝင်သောသူများကို အတည်ပြုပြီး မဟုတ်ပါ။ - သတိပေးချက် - စကားဝိုင်းဝှက်ခြင်း ရပ်လိုက်ပါပြီ - ဤစကားဝိုင်းသည် အတည်ပြုပြီး လုံခြုံစိတ်ချရသည်။ - အကောင့်ဆိုင်ရာ - အသင့် အကောင့် အိုင်ဒီ - ဆာဗာ သတ်မှတ်ရန် - အသင့်ဖြစ်ပြီလား - အသင့်၏ အကောင့်အိုင်ဒီကို ရိုက်ပါ (user@hostname) - - အသင့်၏ jabber/xmpp စကားဝိုင်းဆာဗာ အိမ်ရှင်စက်အမည်နှင့် port နံပါတ် (ပုံမှန် ၅၂၂၂၂) ကို ပြင်ဆင်ရေးသားပါ။ - အသင့်အကောင့်အိုင်ဒီ ရိုက်ထားသည်တွင် @hostname.com မပါရှိပါ။ ထပ်မံကြိုးစားကြည့်ပါ။ - အသင့်ဆာဗာတင် .com, .net (သို့) အခြား အလားတူ နောက်ဆက်တွဲများ မပါရှိပါ။ ထပ်မံကြိုးစားကြည့်ပါ။ - စကားဝှက် ရိုက်ပါ - - - အကောင့်ဆက်တင် - defLoc - - အဖွဲ့များ - OTR ကီးတွဲသစ် ဖန်တီးနေပါတယ်... - အဆက်အသွယ် - စကားပြောဆိုမည့် အဆက်အသွယ်၏ အမည်ရိုက်ရန် - သွားရန် - ဘာသာစကား - ဘာသာစကားများ - ဘယ်ဘာသာစကားကို InTheClear မှာ ပြရမလဲ - SRV ရှာဖွေမှု ပြုလုပ်ပါ - ဒိုမိန်းနာမည်မှ XMPP ဆာဗာကို ရှာရန် DNS SRV ကို သုံးပါ - ဝှက်ထားခြင်းမရှိသော ပေးပို့မှုတွင် ယူဇာအမည်နှင့် စကားဝှက်ကို ရိုးရိုးစာသားအနေဖြင့် ပေးပို့ခြင်း ခွင့်ပြုပါ - Plain Text စိစစ်စစ်ဆေးမှု ခွင့်ပြုပါ - ဤ certificate သည် ယုံကြည်စိတ်ချရကြောင်း အတည်ပြုပါ - TLS စစ်ဆေးခြင်း - TLS/SSL ကော်နက်ရှင် သုံးမည် - Transport Encryption - ဝှက်ထားသည့် စကားဝိုင်းများ စတင်ပုံ - လိုအပ်လျှင် ချိတ်ဆက်ရမည့် ဆာဗာ - ဆာဗာသို့ ချိတ်ဆက်ခြင်း - XMPP ဆာဗာအတွက် TCP Port - ဆာဗာ Port - XMPP Resource - ဤကော်နက်ရှင်ကို အခြားဝင်ထားသော ကလိုင်းယင့်များထံမှ ခွဲခြားရန် - XMPP Resource Priority - Messages to clients with multiple active resources will be sent to the resource with the highest priority - ရှေ့သို့ - နောက်သို့ - စကားဝိုင်းများ - စာအသစ်၊ ပို့သူမှာ - စိစစ်စစ်ဆေးခြင်း - အသင့်အဆက်အသွယ်ကို အတည်ပြုစစ်ဆေးရန် မေးရမည့် မေးခွန်းနှင့် သူတို့ ဖြေရမည့် အဖြေကို ရိုက်ပါ။ - မေးရမည့် မေးခွန်း - မျှော်လင့်ထားသော အဖြေ - အသင့် အဆက်အသွယကို အောင်မြင်စွာ စစ်ဆေးပြီးပါပြီ။ ယခု ၎င်းကို အတည်ပြုရန် မေးခွန်းမေးပါ။ - စကားဝိုင်း မရှိပါ။ \n\nအသစ်စရန် ဒီမှာ ထိနှိပ်ပါ။ - အရောင်ရင့်ရင့် အဆင်အယင်သုံးပါ - Tor ကို အသုံးပြုမည် ဖြစ်၍ XMPP \'ဆာဗာ ချိတ်ဆက်မှု\' အိမ်ရှင်စက်အမည်ကို အကောင့်ဆက်တင်ထဲတွင် တိုက်ရိုက် ရိုက်ထည့်ရမည် - - နဂိုဝင်သုံးဖူးသည့် အကောင့်များသို့ အလိုအလျှောက် ဝင်ရန် - ပြင်ဆင်ထားသည့် အကောင့်များ မရှိပါ။ အကောင့်သစ်တစ်ခုထည့် ဤနေရာကို ထိနှိပ်ပါ။ - ဂူးဂဲ အကောင့် - စာများကို မမ်မိုရီအတွင်းသာ သိမ်းရန် - Only store messages in memory, not on flash storage, to defend against messages being extracted. (May cause lost messages) - နောက်ခံပုံ - အက်ပလီကေးရှင်း၏ နောက်ခံပုံလမ်းကြောင်းကို (\"/sdcard/foo.jpg\") သတ်မှတ်ရန် - Security and Privacy - User Interface - အခြား - ဖွင့်ထားသော စကားဝိုင်းများ - Orbot (Tor) - ထွက်ရန် - - ဆားဗစ်အားလုံးမှ ထွက်၍ ပရောဆက်များအားလုံးကို ပိတ်မည်လား? + အကောင့်အသစ် diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml index 80207943a..25a62cb9c 100644 --- a/res/values-nb/arrays.xml +++ b/res/values-nb/arrays.xml @@ -1,69 +1,9 @@ - Tvinge / Kreve + Tving / Krev Automatisk forsøk Som forespurt Deaktivert / Aldri - - tvinge - automatisk - på forespørsel - deaktivert - - - Standard - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norsk Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index d0db17fc4..5b6f6197a 100755 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -1,397 +1,509 @@ - + + + ChatSecure + ChatSecure + les meldinger - \n Gir applikasjoner lov til å lese data fra meldingsleverandøren.\n + + Gir applikasjoner lov til å lese data fra meldingsleverandøren. + skriv meldinger - \n Gir applikasjoner lov til å skrive til meldingsleverandøren.\n - - ChatSecure - - + + Gir applikasjoner lov til å skrive til meldingsleverandøren. + + start lynmeldingstjeneste + Tillater applikasjoner å starte lynmeldingstjenesten. + + Bekreft + OK + Avbryt + OK + Avbryt + Neste + Tilbake + Koble til + Spill av + Oppsett + + Åpne + ChatSecure låst + Sett passord + Bekreft passord + Passord + Nytt passord + Bekreft nytt passord + Passord samsvarte ikke med fasit, vennligst prøv igjen + Latmannsvariant (Inget passord!) + Informasjonsforespørsel + Skriv inn ditt passord + Passord takk… + Passord. + Passord (igjen) + Velg en konto - + Velg en konto + (%1$d) + Legg til %1$s-konto Om - + Logg av alle + Vil du logge av alle tjenestene? + De er blitt logget av %1$s. + Du er blitt logget av %1$s fordi %2$s + Førstegangsbruker av ChatSecure? + Kan du ikke vente med å starte? + Start + Kontoinnstillinger + Passordinnstillinger + Før du begynner, vennligst velg et sikkert passord for å beskytte ChatSecure data mot uønsket tilgang. + Passord. + Passord (igjen): + Opprett et *nytt* passord. Det må inneholde tall, minst en stor og liten bokstav og være lengre enn seks tegn. + Krypterer dine eksisterende notater med det nye passordet (vær tålmodig…) + Oppgi passord ditt: + Velkommen! Opprett et sterkt passord for å sikre notatene dine. Det må inneholde tall, minst en stor og liten bokstav og være lengre enn seks tegn. + Passordet ditt var ikke langt nok + Passordet ditt inneholdt ingen store bokstaver + Passordet ditt inneholdt ingen små bokstaver + Passordet ditt inneholdt ingen tall + + Om ChatSecure + ChatSecure er en mobil chat-app som tilbyr ekstra sikkerhetsfunksjoner som forhindrer andre i å spionere på kommunikasjonen og samtalene dine. + +Appen støtter alle chat-tjenester som bruker Jabber- eller XMPP-protokollen, som f.eks Google GTalk eller Jabber.org. + + Hvordan er det sikkert? + \'Off-the-Record chat\' er et sikkert system designet for å fremme personvern ved å imitere karakteristikkene til en privat samtale i den virkelige verden, inkludert kryptering, autentisering, benektelse og Perfect Forward Secrecy. + +OTR-protokollen er kompatibel med chatklienter til datamaskiner som f.eks Adium eller Pidgin. + + Er samtalene mine sikre? + ChatSecures krypterte chatfunksjon fungerer kun når du sender meldinger til andre som bruker en kompatibel app eller program, så du bør sørge for at kontaktene dine bruker mobilversjonen av ChatSecure og Adium eller Pidgin på datamaskinen. Du kan finjustere nøyaktig når og hvordan ChatSecure forsøker å kryptere samtalene dine i Kontoinnstillinger. + +La oss begynne! + + Ny konto Legg til konto - Endre konto - Fjern konto - - Logg av alle - - - Velg en konto - - - + Eksisterende konto + + Brukernavn: + Passord: + Husk passord. + Logg på automatisk. + Mangler du konto? + Logg på + For din sikkerhet, hvis telefonen din mistes eller stjeles, oppsøk nettsiden på datamaskinen din og forandre passordet ditt. + Dette valget logger deg automatisk på hver gang du åpner applikasjonen. For å avmerke valget, logg av og fjern så haken ved «Logg på automatisk». + Tilkobling via Tor (Krever programmet Orbot) + user@domain.com + nytt brukernavn + tjenestetilbyder (dukgo.com, jabber.ccc.de) + passord + bekreft passord + Avanserte kontoinnstillinger + Kontotype + Registrer konto + Vedvarende lagring + Husk Passord + Passord lagret + Passord ikke lagret + Logg inn automatisk + Koble til ChatSecure ved oppstart + Ikke koble til ChatSecure ved oppstart + Logg inn nå + Koble til følgende konto-oppsett + Ikke koble til følgende konto-oppsett + Personlig data (frivillig) + Kontoalias (navnet ditt) + Hvordan kontoen min fremstår på nett + Profil + Kortfattet om deg selv + Krev kryptering / avvis klartekst + Om mulig, kryptér chat automatisk + Krypter chatten som forespurt + Deaktiver chatkryptering + Validerer legitimasjon… + Genererer nøkkelpar… + Logger inn… + Kontoveiviser + Din Konto-ID + Konfigurér Server + Er du klar? + Oppgi konto-IDen din for å konfigurere ChatSecure med din XMPP chat-tjeneste. Den ser ut som en e-postadresse: + Vennligst skriv inn din konto-ID (bruker@vertsnavn): + Vennligst skriv inn eller rediger JABBER-/XMPPchatserver vertsnavnet ditt og portnummer (5222 er standard). + ChatSecure ble konfigurert og det er nå tid for å koble til tjenesten og begynne å chatte trygt, sikkert og privat! + Orbot (Tor) + Velg domene + Registrer ny konto… + starter opp ChatSecure… Avbryt pålogging - - + Logger på… + Logger ut\u2026 + + Utfør SRV-sjekk + Bruk DNS SRV for å finne aktuell XMPP-serveren for domenenavnet + Tillat at brukernavnet og passordet blir sendt som klartekst under bruk av ukryptert transport + Tillat autentisering i klartekst + Verifiser at sertifikatet er til å stole på + TLS verifisering + Krev TLS-tilkobling + Transport-kryptering + hvordan krypterte chatter startes + Serveren som skal tilkobles, etter behov + Tilkoblingsserver + XMPP-servers TCP-port + Serverport + XMPP-ressurs + for å skille denne tilkoblingen fra andre klienter som også er logget inn + XMPP-ressursprioritering + Meldinger til klienter med flere aktive resursser vil bli sendt til den resurssen med høyest prioritet + + Samtaler + Ingen samtaler. + +Klikk her for å starte en! + Du har ingen +kontoer tilknyttet. + +Trykk her for å legge til én! + Kontaktliste - %1$s Legg til kontakt - Fjern kontakt - Blokker kontakt - + Kontakt-kallenavn Blokkert - - Kontoliste - + Kallenavn + Kontakten "%1$s" vil bli slettet. + Kontakten «%1$s» vil bli blokkert. + Kontakten «%1$s» vil bli avblokkert. + Kontakten «%1$s» ble lagt til. + Kontakten «%1$s» ble slettet. + Kontakten «%1$s» ble blokkert. + Kontakten «%1$s» ble avblokkert. Ny Chat - - Ny konto - - Innstillinger - Søk Kontakter - + Vis rutenett Start samtale - - Logg av - Vis profil - - - Start Kryptering - Stopp Kryptering - Tøm Chat - Avslutt samtale - Sikker samtale - - - Kontaktliste - - Inviter… - - Bytt mellom samtaler - - Sett inn smilefjes - Send igjen - - Scan Fingeravtrykk - Ditt Fingeravtrykk - Verifiser fingeravtrykk - Verifiser hemmelighet - - Meny+ - - - - Bekreft - - Vil du logge av alle tjenestene? - - - - - - - OK - - Avbryt - - OK - - Avbryt - - - - - - - - - - Brukernavn: - - Passord: - - Husk passord. - - Logg på automatisk. - - Mangler du konto? - - For din sikkerhet, hvis telefonen din mistes eller stjeles, oppsøk nettsiden på datamaskinen din og forandre passordet ditt. - Dette valget logger deg automatisk på hver gang du åpner applikasjonen. For å avmerke valget, logg av og fjern så haken ved «Logg på automatisk». - - Logg på - - - starter opp ChatSecure... - - Logger på… - - - Bakgrunnsdata deaktivert - - - - - - Aktiver - - Avslutt - - - - - - + Kontroller nøkkel + Pågående samtaler (%1$d) + %1$d tilgjengelig Venneinvitasjoner - (\"\"Ukjent\"\") - Tom - Ingen samtaler - - Velg kontakt(er) som skal inviteres - Skriv for å finne kontakter - Fant ingen kontakter. - - - - Ingen blokkerte kontakter. - - + Kontakt + oppgi navnet på kontakten du ønsker å chatte med + + Ingen aktive samtaler. + Legg til kontakt + E-postadresse til den du ønsker å invitere: + Velg en liste: + Skriv inn et navn som skal legges til fra kontaktene. + Send invitasjon Kontaktprofil - Status: - Klienttype: - Datamaskin - Mobil - - - Tilgjengelig - - Opptatt - - Borte - - Inaktiv - - Frakoblet - - Usynlig - - - - + Blokkerte kontakter - %1$s + Ingen blokkerte kontakter. + + Sludre med %1$s Meg - - Skriv her - - \n\n - - - - - - - - - - - - - - + + + + %1$s er borte + %1$s er opptatt + %1$s er frakoblet + %1$s har blitt med i samtalen + %1$s har forlatt samtalen + Send bilde + Send fil + Send lyd + Ta bilde + Filoverføring + Overføring ferdig + Overføring på gang + Godta overføring? + ønsker å sende deg filen + Ingen tilkobling tilgjengelig for forsendelse av dine delte filer! Send - - Kunne ikke sende meldingen. - - Mistet tilkoblingen til tjeneren. Meldingen vil bli sendt ved tilkobling. - - - - - + Send en melding + Send sikret melding + Send igjen + Avslutt samtale + Tøm Chat + Sett inn smilefjes + Bytt mellom samtaler + Meny+ Velg kobling - - Ingen aktive samtaler. - - Starter kryptert chat... - Stopper kryptert chat... - - - Legg til kontakt - - E-postadresse til den du ønsker å invitere: - - Velg en liste: - - Skriv inn et navn som skal legges til fra kontaktene. - - Send invitasjon - - Jabber (XMPP) - Lokal nettverkschat (Bonjour/ZeroConf) - + Slett + Behold filer + Slett sludreøkt fra sikret lagringsmedie? + Opplastede og nedlastede filer tilknyttet denne sesjonen vil bli slettet for godt. Advarsel: Dette kan ikke omgjøres! + Slett original? + Denne filen vil bli kopiert til sikret lagringsmedia før den sendes. Ønsker du å slette orginalfilen fra enhetens usikrede lagringsmedia? + Behold + Eksport + Eksporter mediafil? + Denne mediafilen vil bli flyttet til %1$s + Avslutt samtale? + Alle sikrede medieelementer fra denne økta vil bli slettet. Eksporter et medie-element ved å klikke lenge på miniatyrbilde-ikonet. + Avslutt samtale og slett filer + + %1$s har invitert deg til en gruppesamtale. + Invitasjonen er blitt sendt til %1$s. + Godta + Avslå + Gruppesludder + Start eller ta del i gruppesludder + Kolber til gruppesludder… + Inviter… + Velg kontakt(er) som skal inviteres + Skriv for å finne kontakter + Finner ingen kontakter. + +Trykk for å invitere. + Legg til %1$s? + Ja + Nei + Kunne ikke godta abonnement fra %1$s. Prøv igjen senere. + Kunne ikke avslå abonnement fra %1$s. Prøv igjen senere. + + Start Kryptering + Stopp Kryptering + Starter kryptert chat… + Stopper kryptert chat… + Fingeravtrykk for sikkerhet + Fingeravtrykk for sikkerhet + Logg inn + Lag ny nøkkel + Kryptering av + Kryptering på (trykk for å kontrollere) + Din samtalepartner har stoppet krypteringen på sludringen. + Kryptert og bekreftet! + Genererer nytt OTR nøkkelpar… + + Vi har funnet et OTR-nøkkelknippe som kan importeres. Vil du skanne inn QR-passordet nå? + Aktiver KeySync + Import av OTR-nøkkelring fullført + + Skann QR-kode + Ditt Fingeravtrykk + Manuelt + Spørsmål + Fingeravtrykk for sikkerhet (kontrollert) + Er du sikker på at du vil bekrefte dette fingeravtrykket? + Kontroller fingeravtrykk? + Eksternt fingeravtrykk har blitt bekreftet! + Fingeravtrykk av deg + Fingeravtrykk for + Installer strekkodeskanner? + Dette programmet krever strekkodeskanner. Vil du installere det? + + Autentisering + Angi et spørsmål som sendes kontakten din, og svaret du forventer at de oppgir, for å verifisere at du faktisk snakker med den de gir seg ut for være. + spørsmålet som skal stilles + svaret som forventes + Kontakten din har verifisert deg. Verifiser nå din kontakt ved å stille ditt eget spørsmål. + OTR Q&A bekreftelse + Samtalekryptering + Send + Avbryt + + Sikker samtale + Secure Voice + Oppgi kontoinformasjon for OStel.co eller en annen sikker SIP-tjeneste for samtaleintegrering + Kontoinnstilling + + Sikkerhet og Personvern Kryptering og anonymitet + Krypering På/Av + Tidsavbrudd for passord + Hvor lenge opplåsning av krypteringen skal oppgis + + Klikkbare lenker på Tor + For kontoer som bruker Tor, gjør lenker i sludresamtaler klikkbare (ADVARSEL, potensielt personverns-brudd!) + Brukergrensesnitt + Språk + Språk + Bruk systemets forvalg + Bruk mørkt tema + Bytt til mørkt fargetema + Kun lagre meldinger i minnet + Kun lagre meldinger i minnet, ikke flashlagring, for å beskytte mot at noen får tilgang til eller kan hente ut meldingene. (Kan forårsake tapte meldinger) + Bakgrunnsbilde + Angi plasseringen (\"/sdcard/foo.jpg\") til et bakgrunnsbilde til appen + Vis kontakt-rutenett + Vis kontaktliste som kollasj av kontaktbilder + Ja, godta alle + Slett usikrede mediefiler + Etter deling av filer, det være seg photo eller annet, automatisk slett den fra original usikret lagringsplass etter import. + Lagre mediefiler på eksternt lagringsmedium + Mediefiler fra sludreøkter blir lagret i en kryptert beholder som kan lagres på internt eller eksternt lagringsmedium. + + Eksternt lagringsmedium mangler + Dine sludrelogger lagres på et SD-kort, men det ingen SD-kort er er finne. Sett inn rett SD-kort, eller slett eksisterende sludrelogg og kjør ChatSecure på nytt. + Medialagring for sludder mangler + Dine sludrelogger lagres på et SD-kort, men filen mangler på gjeldende SD-kort. Sett inn rett SD-kort, eller slett eksisterende sludrelogg og kjør ChatSecure på nytt. + Slett sludrelogg + + Andre justeringer + Start ChatSecure automatisk + Alltid start og automatisk logg inn på sist innloggede kontoer. Skjul avloggede kontakter - - Varslingsinnstillinger - - Varsel om lynmeldinger - - Varsle i statusveltet når det kommer lynmeldinger - Bruk forgrunnsprioritet Reduser sjansen for at Android starter tilkoblingstjenesten vår på nytt. Dette vil gi deg et varsel i varslingsvinduet. Pulsintervall Bruk en høyere verdi (i minutter) for å spare strøm. En høyere verdi kan gjøre at leverandøren avslutter tilkoblingen din grunnet inaktivitet. + + Varslingsinnstillinger + Varsel om lynmeldinger + Varsle i statusveltet når det kommer lynmeldinger Vibrer - Vibrer når det kommer lynmeldinger - Lyd - Spill ringetone når det kommer lynmeldinger - - Velg ringetone - - - - - - - Godta - - Avslå - - - - Godta - - Avslå - - - - - - - - - - - - + Bruk ChatSecure spesial-ringetone + + Skru på loggføring av feilsøkingsinformasjon + Skriv programloggdata til standard utdata / logcat for feilsøking + + Bakgrunnsdata deaktivert + %1$s trenger at bakgrunnsdata er aktivert. + Aktiver + Avslutt + Vil du logge ut av alle tjenester OG avslutte alle prosessene (hard exit)? + Opprett ny konto? + Opprett ny sludre-konto for brukernavnet \'%1$s\'? + Sertifikatsinfo + Sertifikat: + Utstedt av: + SHA1-fingeravtrykk: + Utstedt: + Utløper: + Ta del i samtalerom + Et eksternt program forsøker å koble deg til en sludrekanal. Tillat? + Velg bakgrunnsbilde + Vil du velge et bakgrunnsbilde fra galleriet? + Velg bilde + + Nye %1$s-meldinger + %1$d uleste sludresamtaler + Ny venneinvitasjon fra %s + Ny fil %1$s fra %2$s Invitasjon til gruppesamtale - - - - - - - - - - - - start lynmeldingstjeneste - Tillater applikasjoner å starte lynmeldingstjenesten. - - + Ny gruppesludrings-invitasjon fra %s + Ny melding(er) fra + Starter ChatSecure tjenesten… + Aktivert & låst opp + melding kopiert til utklippstavle + NB - - + Feil: + Feilkode %1$d + Kunne ikke logge på %1$s. Prøv igjen senere."\n"(Detaljer: %2$s) Listen ble ikke lagt til. - Kontakten ble ikke blokkert. - Kontakten ble ikke avblokkert. - Velg en kontakt først. - \"Frakoblet!\"\n - Tjenestefeil! - Kontaktlisten ble ikke lastet. - Kan ikke koble til tjeneren. Sjekk tilkoblingen. - - - - - + %1$s finnes allerede i kontaktlisten. + Kontakten «%1$s» er nå blokkert. Vent mens kontaktlisten lastes. - Det oppsto en nettfeil. - WiFi kreves for denne tilkoblingen. - + Wi-Fi kreves for denne tilkoblingen. Tjeneren støtter ikke denne funksjonen. - Passordet er ikke gyldig. - Det oppsto en feil på tjeneren. - Tjeneren støtter ikke denne funksjonen. - Tjeneren er utilgjengelig. - Det oppsto et tidsavbrudd på tjeneren. - Tjeneren støtter ikke denne versjonen. - Meldingskøen er full. - Tjeneren støtter ikke videresending til domenet. - Brukernavnet du skrev inn ble ikke gjenkjent. - Beklager, du er blokkert av brukeren. - Sesjonen har gått ut, logg på på nytt. - du er pålogget med en annen klient. du allerede er pålogget med en annen klient. - Beklager, kan ikke lese telefonnummeret fra SIM-kortet. Kontakt operatøren for hjelp. - Du er ikke pålogget. - - - + ChatSecure støtte på en feil under verifisering av brukernavnet og passordet ditt - vennligst sjekk dem og prøv igjen. + ChatSecure støtte på en feil under opprettelse av et nøkkelpar. + ChatSecure støtte på en feil under tilkobling til chatserveren - vennligst sjekk konfigurasjonen og prøv igjen. + ChatSecure støtte på en feil under tilkobling - vennligst dobbeltsjekk nettverksforbindelsen din og prøv igjen. + ChatSecure har mistet forbindelsen til nettverket + ChatSecure prøver å gjenopprette en tilkobling + Du har ikke skrevet inn @vertsnavn.com delen av konto-ID\'en din. Prøv igjen! + Din servers vertsnavn hadde ikke en .com, .net eller lignende endelse. Prøv igjen! + Skriv inn passordet ditt: + Siden du bruker Tor, må du angi vertsnavnet til XMPP-\'Tilkoblingsserver\' direkte i Avanserte kontoinnstilinger + Advarsel: Tjenesten bruker et sertifikat med SVAK kryptografisk integritet. Be din administrator om å oppgradere. + Medfølgende sertifikat samsvarer ikke med betrodd sertifikat: + Klarte ikke å starte eller ta del i gruppesludder + Beklager, vi kan ikke dele den filtypen + Du må skru på kryptering for å dele filer + Vennligst skru på kryptering av sludring for å dele filer + Ustøttet innkommende data, kan ikke dele! + Chatsecure har registrert en forespørsel om å fjerne prosessen som kjører den. ChatSecure vil etter alt å dømme kræsje. Vennligst bruk menyvalget Logg Ut Alle fra kontolisten istedenfor. + Det er inget tilknyttet program tilgjengelig for lesing av dette filformatet + Vennligst start en sikker sludderinstans før du skanner koder + OTR-nøkkelring ble ikke importert; Vennligst sjekk at filen har riktig format og plassering + Vennligst kopier \'otr_keystore.ofcaes\' -filen med skrivebordsverktøyet KeySync til rotmappen av din enhets lagringsmedie + Kunne ikke sende meldingen. + Meldinger vil bli sendt når forbindelsen gjenopprettes + %1$s er frakoblet. Meldinger du sender vil bli levert når %1$s kobler til. + %1$s er ikke i kontaktlisten. + Passord samsvarer ikke + Prioritet må settes som et nummer fra [0 .. 127] + Port må oppgis som nummer + Overføringsfeil + Kunne ikke skrive fil til lagringsmedie + Kjempefeil: Kunne ikke låse opp eller laste inn programdatabase. Installer programmet på ny eller tøm data. + Har ingen programmer som kan håndtere den lenken! + Kontoinnstillinger + + Grupper + åpne samtale(r) + Avslutt + Panikk + Venner + Kontakter + Godta tjener-sertifikat? + Fingeravtrykk + laster… + + Om Chatsecure\nhttps://guardianproject.info/apps/chatsecure/ + + Kontaktliste + Innstillinger + Kontoliste + Logg av + Kontaktliste + + Jabber (XMPP) + Lokalt område (Bonjour/ZeroConf) + Google-konto + dukgo.com + + + Tilgjengelig + Opptatt + Borte + Inaktiv + Frakoblet + Usynlig + Glad Trist @@ -411,7 +523,7 @@ Ler Forvirret - + Glad Trist @@ -450,171 +562,33 @@ :-D o_O - Kontaktliste - Krypering På/Av - Tilkobling via Tor (Krever programmet Orbot) - - ChatSecure - Førstegangsbruker av ChatSecure? - Kan du ikke vente med å starte? - Start - Kontoinnstillinger - - Om ChatSecure - ChatSecure er en mobil chat-app som tilbyr ekstra sikkerhetsfunksjoner som forhindrer andre i å spionere på kommunikasjonen og samtalene dine.\n\nAppen støtter alle chat-tjenester som bruker Jabber- eller XMPP-protokollen, som f.eks Google GTalk eller Jabber.org. - - Hvordan er det sikkert? - \'Off-the-Record chat\' er et sikkert system designet for å fremme personvern ved å imitere karakteristikkene til en privat samtale i den virkelige verden, inkludert kryptering, autentisering, benektelse og Perfect Forward Secrecy.\n\nOTR-protokollen er kompatibel med chatklienter til datamaskiner som f.eks Adium eller Pidgin. - - Er samtalene mine sikre? - ChatSecures krypterte chatfunksjon fungerer kun når du sender meldinger til andre som bruker en kompatibel app eller program, så du bør sørge for at kontaktene dine bruker mobilversjonen av ChatSecure og Adium eller Pidgin på datamaskinen. Du kan finjustere nøyaktig når og hvordan ChatSecure forsøker å kryptere samtalene dine i Kontoinnstillinger.\n\nLa oss begynne! - - Passordinnstillinger - Før du begynner, vennligst velg et sikkert passord for å beskytte ChatSecure data mot uønsket tilgang. - Passord. - Passord (igjen): - - user@domain.com - nytt brukernavn - tjenestetilbyder (dukgo.com, jabber.ccc.de) - passord - bekreft passord - Avanserte kontoinnstillinger - Kontotype - Registrer konto - - Vedvarende lagring - Husk Passord - Passord lagret - Passord ikke lagret - Logg inn automatisk - Koble til ChatSecure ved oppstart - Ikke koble til ChatSecure ved oppstart - Logg inn nå - Koble til følgende konto-oppsett - Ikke koble til følgende konto-oppsett - - Personlig data (frivillig) - Kontoalias (navnet ditt) - Hvordan kontoen min fremstår på nett - Profil - Kortfattet om deg selv - - Krev kryptering / avvis klartekst - Om mulig, kryptér chat automatisk - Krypter chatten som forespurt - Deaktiver chatkryptering - - Validerer legitimasjon... - Genererer nøkkelpar... - Logger inn... - - ChatSecure støtte på en feil under verifisering av brukernavnet og passordet ditt - vennligst sjekk dem og prøv igjen. - ChatSecure støtte på en feil under opprettelse av et nøkkelpar. - ChatSecure støtte på en feil under tilkobling til chatserveren - vennligst sjekk konfigurasjonen og prøv igjen. - ChatSecure støtte på en feil under tilkobling - vennligst dobbeltsjekk nettverksforbindelsen din og prøv igjen. - Deres fingeravtrykk - Ditt fingeravtrykk - Logg inn - Lag ny nøkkel - ChatSecure har mistet forbindelsen til nettverket - ChatSecure prøver å gjenopprette en tilkobling - Advarsel: Denne chatten er IKKE kryptert - Denne chatten er sikker, men identiteten til brukerne har IKKE blitt verifisert. - Advarsel: chattkrypteringen er stoppet. - Denne chatten er sikret og verifisert - Kontoveiviser - Din Konto-ID - Konfigurér Server - Er du klar? - Oppgi konto-IDen din for å konfigurere ChatSecure med din XMPP chat-tjeneste. Den ser ut som en e-postadresse: - Vennligst skriv inn din konto-ID (bruker@vertsnavn): - Vennligst skriv inn eller rediger JABBER-/XMPPchatserver vertsnavnet ditt og portnummer (5222 er standard). - ChatSecure ble konfigurert og det er nå tid for å koble til tjenesten og begynne å chatte trygt, sikkert og privat! - Du har ikke skrevet inn @vertsnavn.com delen av konto-ID\'en din. Prøv igjen! - Din servers vertsnavn hadde ikke en .com, .net eller lignende endelse. Prøv igjen! - Skriv inn passordet ditt: - - Kontoinnstillinger - defLoc - ADVARSEL: Dette er en tidlig utgave av ChatSecure som fortsatt kan inneholde sikkerhetshull og feil. - - Grupper - Genererer nytt OTR nøkkelpar... - Kontakt - oppgi navnet på kontakten du ønsker å chatte med - - Språk - Språk - Hvilket språk skal InTheClear vise? - Utfør SRV-sjekk - Bruk DNS SRV for å finne aktuell XMPP-serveren for domenenavnet - Tillat at brukernavnet og passordet blir sendt som klartekst under bruk av ukryptert transport - Tillat autentisering i klartekst - Verifiser at sertifikatet er til å stole på - TLS verifisering - Krev TLS/SSL tilkobling - Transport-kryptering - hvordan krypterte chatter startes - Serveren som skal tilkobles, etter behov - Tilkoblingsserver - XMPP-servers TCP-port - Serverport - XMPP-ressurs - for å skille denne tilkoblingen fra andre klienter som også er logget inn - XMPP-ressursprioritering - Meldinger til klienter med flere aktive resursser vil bli sendt til den resurssen med høyest prioritet - Neste - Tilbake - Samtaler - Ny melding(er) fra - Autentisering - Angi et spørsmål som sendes kontakten din, og svaret du forventer at de oppgir, for å verifisere at du faktisk snakker med den de gir seg ut for være. - spørsmålet som skal stilles - svaret som forventes - Kontakten din har verifisert deg. Verifiser nå din kontakt ved å stille ditt eget spørsmål. - Ingen samtaler.\n\nKlikk her for å starte en! - Bruk mørkt tema - Siden du bruker Tor, må du angi vertsnavnet til XMPP-\'Tilkoblingsserver\' direkte i Avanserte kontoinnstilinger - - Start ChatSecure automatisk - Alltid start og automatisk logg inn på sist innloggede kontoer. - Du har ingen konfigurerte kontoer.\n\nKlikk her for å opprette en! - Google-konto - Kun lagre meldinger i minnet - Kun lagre meldinger i minnet, ikke flashlagring, for å beskytte mot at noen får tilgang til eller kan hente ut meldingene. (Kan forårsake tapte meldinger) - Bakgrunnsbilde - Angi plasseringen (\"/sdcard/foo.jpg\") til et bakgrunnsbilde til appen - Sikkerhet og Personvern - Brukergrensesnitt - Andre justeringer - åpne samtale(r) - Orbot (Tor) - Avslutt - - Vil du logge ut av alle tjenester OG avslutte alle prosessene (hard exit)? - Opprett et *nytt* passord. Det må inneholde tall, minst en stor og liten bokstav og være lengre enn seks tegn. - Krypterer dine eksisterende notater med det nye passordet (vær tålmodig...) - Oppgi passord ditt: - Velkommen! Opprett et sterkt passord for å sikre notatene dine. Det må inneholde tall, minst en stor og liten bokstav og være lengre enn seks tegn. - Passordet ditt var ikke langt nok - Passordet ditt inneholdt ingen store bokstaver - Passordet ditt inneholdt ingen små bokstaver - Passordet ditt inneholdt ingen tall - Åpne - ChatSecure låst - Opprett passord - Oppgi passord - Oppgi nytt passord - Bekreft nytt passord - Passordene samsvarer ikke, vennligst prøv igjen - Secure Voice - Oppgi kontoinformasjon for OStel.co eller en annen sikker SIP-tjeneste for samtaleintegrering - dukgo.com - Vi har oppdaget lagrede OTR-nøkler som kan importeres. Vil du scanne QR-passordet nå? - Importer lagrede OTR-nøkler - Import av OTR-nøkkelring fullført - OTR-nøkkelring ble ikke importert; Vennligst sjekk at filen har riktig format og plassering + Eksisterende konto + Koble til min eksisterende konto på en gitt Jabber/XMPP-tjener + Google-konto + Sludre med andre Google-brukere med din eksisterende Google-konto. + Wi-Fi-mesh-sludrerom + Sludre med andre Wi-Fi eller mesh-nettverk - inget Internett eller tjener påkrevd! + Skru på Wi-Fi-samtaler + Ny konto + Registrer en ny, gratis konto på en tjeneste fra vår innebygde liste, eller fritt etter eget ønske. + Opprett ny konto + Hemmelig identitet! + Opprett en anonym engangskonto med ett enkelt tastetrykk (krever Orbot: Tor for Android) + Opprett identitet + Dette er en gruppesamtale + [gjensendt] + [gjensendt] + Kunne ikke dele denne filen på en sikker måte + Installer Orbot? + Du må ha Orbot installert og aktivert for å mellomtjene trafikk gjennom det. Vil du installere nå? + Alltid + Start Orbot? + Det later til at Orbot ikke kjører. Vil du starte det opp og koble til Tor? + kallenavn for bruk i rom + navn på rom som skal opprettes eller tas del i + tjener for gruppesamtaler (konferanse.noe.org) + Ditt nøkkelvelv er skadet. Installer ChatSecure på ny, eller \'tøm data\' i programmet + Du har mottat en uleselig, kryptert melding. + Jeg kunne ikke dekryptere meldingen du sendte + < sveip til venstre og høyre for flere valg > diff --git a/res/values-nl-rBE/arrays.xml b/res/values-nl-rBE/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-nl-rBE/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-nl-rBE/strings.xml b/res/values-nl-rBE/strings.xml new file mode 100644 index 000000000..22ef85d34 --- /dev/null +++ b/res/values-nl-rBE/strings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml index 3a06eb34e..0523a8f6b 100644 --- a/res/values-nl/arrays.xml +++ b/res/values-nl/arrays.xml @@ -6,64 +6,4 @@ Op verzoek Uitgeschakeld / nooit - - forceer - automatisch - op verzoek - uitgeschakeld - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index a3f315fcc..884e22680 100755 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -1,401 +1,494 @@ - + + + ChatSecure + ChatSecure + lees chatberichten - ⏎\n Staat applicaties toe om data te lezen van de IM content provider.\n + ⏎ + Staat applicaties toe om data te lezen van de IM content provider. + schrijf chatberichten - \n Staat applicaties om data te schrijven naar de IM content provider.\n - - ChatSecure - - + + Staat applicaties om data te schrijven naar de IM content provider. + + chatservice starten + Toepassingen toestaan om chatservice te starten zoals bedoeld. + + Bevestigen + OK + Annuleren + OK + Annuleren + Verder + Terug + Verbind + Spelen + Setup + + Openen + ChatSecure vergrendeld + Wachtwoord instellen + Bevestig wachtwoord + Wachtwoord + Nieuw wachtwoord + Bevestig nieuw wachtwoord + Wachtwoorden kwamen niet overeen, probeer opnieuw + Ik voel me lui (Geen Paswoord!) + Informatie prompt + Voer wachtwoord in + Geef je wachtwoord in… + Geheime vraag + Geheime vraag (opnieuw) + Chat - Selecteer een account - + Chat - Selecteer een account + (%1$d) + %1$s-account toevoegen Over - + Log alles uit + Wil je uitloggen bij alle servers? + U bent afgemeld bij %1$s. + U bent afgemeld bij %1$s vanwege %2$s + Eerste keer dat u met ChatSecure werkt? + Kunt u niet wachten om te beginnen? + Aan de slag + Account Instellen + Toegangscode Instellen + Stel voordat u begint een wachtwoord in om jouw ChatSecure data te beschermen. + Toegangscode: + Toegangscode (opnieuw): + Vul een nieuw wachtwoord in. Het moet ten minste één hoofdletter, één gewone letter en één nummer bevatten. Het wachtwoord moet meer dan zes karakters hebben. + Jouw bestaande berichten worden met het nieuwe wachtwoord versleuteld (geduld…) + Voer wachtwoord in: + Welkom! Vul een sterk wachtwoord in om uw berichten te beveiligen. Het moet ten minste één hoofdletter, één gewone letter en één nummer bevatten. Het wachtwoord moet meer dan zes karakters hebben. + Uw wachtwoord was niet lang genoeg + Uw wachtwoord bevatte geen hoofdletters + Uw wachtwoord bevatte geen kleine letters + Uw wachtwoord bevatte geen cijfers + + Over ChatSecure + ChatSecure is een mobile chat applicatie met extra beveiligingsonderdelen. Deze zorgen ervoor dat anderen jouw communicatie niet kan lezen. + +De applicatie ondersteund alle chat diensten die het Jabber of XMPP protocol gebruiken, zoals Google GTalk of Jabber.org. + + Waarom is het veilig? + \'Off-the-Record Messaging\' is een beveiligingssysteem ontwikkeld om privacy te beschermen door het nabootsen van privé gesprekken in de echte wereld, met Versleuteling, Authentificatie, Mogelijke ontkenning en Forward Secrecy. + +Het OTR-protocol is compatibel met desktop chatcliënten als Adium of Pidgin. + + Zijn mijn chats veilig? + ChatSecure\'s chat versleuteling werkt alleen wanneer de ander een compatibele app of programma gebruik. Zorg er dus voor dat je zeker weet dat contacten ChatSecure op hun mobiel of Adium of Pidgin op hun desktop gebruiken. Je kunt veranderen hoe ChatSecure verbindingen probeert te versleutelen bij Account Instellingen.⏎ +⏎ +Aan de slag! + + Nieuwe Account Account toevoegen - Bewerk account - Verwijder account - - Log alles uit - - - Chat - Selecteer een account - - - - + Bestaand account + + Gebruikersnaam: + Wachtwoord: + Mijn wachtwoord onthouden. + Automatisch aanmelden. + Heeft u geen account? + Aanmelden + Ga voor uw eigen veiligheid, als uw telefoon kwijt of gestolen is, naar de Website op uw computer en verander uw wachtwoord. + Met deze optie wordt u automatisch aangemeld wanneer u deze toepassing opent. Als u deze optie wilt uitschakelen, verwijdert u het vinkje uit het selectievakje \'Automatisch aanmelden\'. + Verbind via Tor (Vereist de Orbot app) + gebruiker@domein.com + nieuwe gebruikersnaam + service provider (dukgo.com, jabber.ccc.de) + wachtwoord + wachtwoord bevestigen + Geavanceerde Account Instellingen + Account Type + Registreer Account + Volhouden + Wachtwoord Onthouden + Wachtwoord in cache + Wachtwoord niet in cache + Automatisch Inloggen + Verbinden bij opstarten ChatSecure + Niet verbinden bij opstarten ChatSecure + Nu inloggen + Verbind deze ingestelde account + Verbind niet deze ingestelde account + Persoonlijk (optioneel) + Account Alias (uw naam) + Hoe uw account online getoond wordt + Profiel + Wat korte info over jezelf + Forceer versleuteling / weiger platte tekst + Wanneer mogelijk, automatisch chats versleutelen + Versleutel chats op verzoek + Chat versleuteling uitschakelen + Referenties aan het valideren… + Sleutelpaar aan het genereren… + In aan het loggen… + Account Wizard + Uw Gebruikersnaam + Server Instellen + Bent u klaar? + Voer uw gebruikersnaam in om ChatSecure in te stellen voor uw XMPP chatservice. Het lijkt op een e-mail adres: + Voer alstublieft uw gebruikersnaam in (gebruiker@domein): + Voer in of bewerk alstublieft uw jabber/xmpp chatserver hostnaam en poortnummer (standaard is dit 5222). + ChatSecure is nu ingesteld. Het is tijd om verbinding te maken met uw chatservice om versleuteld, veilig en privé te chatten! + Orbot (Tor) + Kies een domein + Registreer een nieuwe account… + ChatSecure aan het initialiseren… Aanmelding annuleren - - + Aanmelden… + Logt uit\u2026 + + Check SRV + Gebruik DNS SRV om de werlijke XMPP server van de domeinnaam te vinden + Toestaan om de gebruikersnaam en wachtwoord als platte tekst te versturen bij een onversleutelde verbinding + Sta platte tekst authentificatie toe + Verifieer of het certificaat vertrouwd is + TLS Verificatie + Vereis TLS Connectie + Transport Versleuteling + Hoe de versleuteling van de chat te starten. + De server om mee te verbinding, indien nodig + Server Adres + TCP Poort voor XMPP Server + Server Poort + XMPP Middel + om deze verbinding te onderscheiden van andere ingelogde cliënten + XMPP Middel Prioriteit + Berichten naar cliënten met meerdere actieve XMPP middelen worden gestuurd naar het middel met de hoogste prioriteit. + + Conversaties + Nog geen conversaties. + +Klik hier om te beginnen! + Je hebt geen +accounten ingesteld. + +Klik hier om er één toe te voegen! + Contact Lijst - %1$s Contact toevoegen - Contact verwijderen - Contact blokkeren - + Contact bijnaam Geblokkeerd - - Accountlijst - + Gebruikersnaam + Contact \'%1$s\' wordt verwijderd. + Contact \'%1$s\' wordt geblokkeerd. + Contact \'%1$s\' wordt gedeblokkeerd. + Contact \' %1$s\' is toegevoegd. + Contact \'%1$s\' is verwijderd. + Contact \'%1$s\' is geblokkeerd. + Contact \'%1$s\' is gedeblokkeerd. Nieuwe Chat - - Nieuwe Account - - Instellingen - Zoek Contacten - + Toon Raster Chat starten - - Afmelden - - Profiel weergeven - - - Start Versleuteling - Stop Versleuteling - Chat Leegmaken - Chat beëindigen - Secure Call - - - Lijst met contacten - - Uitnodigen... - - Schakelen tussen chats - - Smiley invoegen - Opnieuw versturen - - Scan Vingerafdruk - Uw Vingerafdruk - Verifieer Vingerafdruk - Verifieer Geheim - - Menu+ - - - - Bevestigen - - Wil je uitloggen bij alle servers? - - - - - - - - OK - - Annuleren - - OK - - Annuleren - - - - - - - - - - Gebruikersnaam: - - Wachtwoord: - - Mijn wachtwoord onthouden. - - Automatisch aanmelden. - - Heeft u geen account? - - Ga voor uw eigen veiligheid, als uw telefoon kwijt of gestolen is, naar de Website op uw computer en verander uw wachtwoord. - Met deze optie wordt u automatisch aangemeld wanneer u deze toepassing opent. Als u deze optie wilt uitschakelen, verwijdert u het vinkje uit het selectievakje \'Automatisch aanmelden\'. - - Aanmelden - - - ChatSecure aan het initialiseren... - - Aanmelden... - - - Achtergrondgegevens uitgeschakeld - - - - - - Inschakelen - - Sluiten - - - - - - + Bekijk profiel + Verifieer sleutel + Actieve chats (%1$d) + %1$d online Uitnodigingen voor vriendschap - (\"\"Onbekend\"\") - Leeg - Geen gesprekken - - Contacten selecteren om uit te nodigen - Typ om een contact te vinden - Kan geen contacten vinden. - - - - Geen geblokkeerde contacten. - - + Contact + typ de naam van een contact om mee te chatten + Ok + Geen actieve chats. + Contact toevoegen + E-mailadres van persoon die u wilt uitnodigen: + Een lijst kiezen: + Typ een naam om toe te voegen vanuit \'Contacten\'. + Uitnodiging verzenden Profiel van contact - Status: - Type client: - Computer - Mobiel - - - Online - - Bezet - - Afwezig - - Inactief - - Offline - - Offline weergeven - - - - - + Geblokkeerde contacten - %1$s + Geen geblokkeerde contacten. + + Chatten met %1$s Ik - - Typ om een bericht te schrijven - - - - - - - - - - - - - - - - + %1$s is online + %1$s is afwezig + %1$s is bezet + %1$s is offline + %1$s is online gekomen + %1$s is weggegaan + Verzend foto + Verzend bestand + Verzend audio + Neem foto + Bestandsoverdracht + Overdracht voltooid + Overdracht bezig + Aanvaard overdracht? + wil je een bestand versturen Verzenden - - Dit bericht kan niet worden verzonden. - - Verbinding met de server is verbroken. Berichten worden verzonden wanneer u online bent. - - - - - + Verstuur een bericht + Stuur beveiligd bericht + Opnieuw versturen + Chat beëindigen + Chat Leegmaken + Smiley invoegen + Schakelen tussen chats + Menu+ Link selecteren - - Geen actieve chats. - - Versleutelde chat sessie aan het starten... - Versleutelde chat sessie aan het stoppen... - - - Contact toevoegen - - E-mailadres van persoon die u wilt uitnodigen: - - Een lijst kiezen: - - Typ een naam om toe te voegen vanuit \'Contacten\'. - - Uitnodiging verzenden - - Jabber/XMPP - Lokaal Serverloos XMPP - + Verwijderen + Bestanden behouden + Verwijder Chat Sessie Beveiligde Opslag? + Al de sessie\'s up- en downloadbestanden zullen permanent verwijderd worden. Waarschuwing: deze actie kan niet ongedaan worden! + Verwijder origineel + Dit bestand zal in de beveiligde opslag gekopiëerd worden voor het verzenden. Wil je het originele bestand uit de onbeveiligde opslag van het apparaat wissen? + Behouden + Exporteren + Exporteer media bestand + Dit media bestand zal worden geëxporteerd naar %1$s + Chat beëindigen + Alle veilige media items van deze sessie zullen verwijderd worden. Exporteer een media item door lang te tikken op het miniatuur icoon. + Beëindig chat en verwijder bestanden + + %1$s heeft u uitgenodigd deel te nemen aan een groepchat + Er is een uitnodiging verzonden naar %1$s. + Accepteren + Afwijzen + Groepchat + Creëer of treed toe tot een GroepsChat + Connecteerd naar group chat… + Uitnodigen… + Contacten selecteren om uit te nodigen + Typ om een contact te vinden + Geen contacten gevonden. + +Klik hier om uit te nodigen. + Kan het abonnement van %1$s niet goedkeuren. Probeer het later opnieuw. + Kan het abonnement van %1$s niet afwijzen. Probeer het later opnieuw. + + Start Versleuteling + Stop Versleuteling + Versleutelde chat sessie aan het starten… + Versleutelde chat sessie aan het stoppen… + Beveiligingsvingerafdruk. + Beveiligingsvingerafdruk. + Log In + Hergen Sleutel + Encryptie staat uit + Encryptie staat aan (klik om te bevestigen) + Je contact heeft de beveiligde chat beëindigd. + Geëncrypteerd en geverifieerd! + Nieuw OTR sleutelpaar aan het genereren… + + We hebben een OTR keystore gevonden om te importeren. Wil je het QR paswoord meteen inscannen? + Activeer KeySync + OTR-sleutelring succesvol geïmporteerd + + Scan QR + Uw Vingerafdruk + Manueel + Vraag + Beveiligingsvingerafdruk (Geverifieerd) + Ben je zeker dat je deze vingerprint wil bevestigen? + Vingerafdruk verifiëren? + De externe vingerafdruk werd geverifieerd! + Vingerafdruk voor jou + Vingerafdruk voor + Barcode Scanner installeren? + Deze applicatie vereist Barcode Scanner. Will je dit installeren? + + Authentificatie + Voer een vraag in om naar de ander te sturen en het antwoord wat je verwacht te krijgen, zodat je de identiteit van de ander kan verifieren. + de vraag om te stellen + het verwachte antwoord + Uw contact heeft u succesvol geauthentificeerd. Authentificeer nu uw contact door uw eigen vraag te stellen. + OTR Vraag & Antwoord verificatie + Chat Encryptie + Verzenden + Annuleren + + Secure Call + Secure Voice + Gebruik uw OStel.co of andere beveiligde SIP service account voor telefoon integratie. + Account Instellingen + + Beveiliging en Privacy Versleuteling en Anonimiteit + Versleutel On/Off + Wachtwoord-timeout + Hoe lang moet app-versleuteling ontsleuteld blijven + + Gebruikersinterface + Taal + Talen + Gebruik systeem standaard + Gebruik Donker Thema + Verander de app vormgeving naar donker + Uitsluitend Berichten Opslag in het Geheugen + Berichten alleen opslaan in het geheugen, niet in de flashopslag, om zo extractie van berichten te kunnen voorkomen. (Leidt mogelijk tot verlies van berichten) + Achtergrondafbeelding + Stel het pad (\"/sdcard/foo.jpg\") in voor een achtergrondafbeelding voor de app + Toon Contact Raster + Toon contactlijst als avatar raster + Ja, aanvaard alle + Verwijder onveilige media + Verwijder automatisch een foto of bestand van de originele onveilige opslag na het importeren voor het delen. + + Overige Tuning + Start ChatSecure Automatisch + Altijd starten en automatisch inloggen op eerder ingelogde accounts Offline contacten verbergen - - Instellingen voor meldingen - - Meldingen voor chatberichten - - Melding op statusbalk weergeven wanneer een chatbericht wordt ontvangen - Gebruik voorgrond prioriteit Verminder de kans dat Android onze verbindingsservice herstart. Dit zal een melding in de notificatiebalk plaatsen. Hartslag interval Gebruik een hogere waarde (in minuten) om uw batterij te sparen. Een hogere waarde kan veroorzaken dat de provider de connectie sluit vanwege inactiviteit. + + Instellingen voor meldingen + Meldingen voor chatberichten + Melding op statusbalk weergeven wanneer een chatbericht wordt ontvangen Trillen - Ook trillen wanneer een chatbericht wordt ontvangen - Geluid - Ook beltoon afspelen wanneer een chatbericht wordt ontvangen - - Beltoon selecteren - - - - - - - Accepteren - - Afwijzen - - - - Accepteren - - Afwijzen - - - - - - - - - - - - + Gebruik de custom ChatSecure ringtone. + + Zet Debug Logs aan + Output app log data naar standard out / logcat voor debuggen + + Achtergrondgegevens uitgeschakeld + %1$s heeft achtergrondgegevens nodig om te worden ingeschakeld. + Inschakelen + Sluiten + Wilt u uitloggen bij alle services EN alle processen stopzetten (ruwe stopzetting)? + Nieuwe account aanmaken? + Nieuwe chat account aanmaken voor gebruikersnaam \'%1$s\'? + Certificaat info + Certificaat: + Uitgegeven door: + SHA1 vingerafdruk: + Uitgegeven + Verstrijkt: + Deelnemen aan groepsgesprek? + Een externe app probeert je te connecteren met een groepsgesprek. Toelaten? + Achtergrond kiezen + Wil je een achtergrond afbeelding selecteren uit de galerij? + Selecteer foto + + Nieuwe berichten van %1$s + %1$d ongelezen chats + Nieuw vriendschapsverzoek van %s + Nieuw bestand %1$s van %2$s Uitnodiging voor groepchat - - - - - - - - - - - - chatservice starten - Toepassingen toestaan om chatservice te starten zoals bedoeld. - - + Nieuwe uitnodiging voor groepchat van %s + Nieuw(e) bericht(en) van + De ChatSecure service wordt gestart… + Geactiveerd & ontgrendeld + bericht gekopieerd naar het klembord + Aandacht vereist - - - + Fout: + Foutcode %1$d + Kan niet aanmelden bij de %1$s-service. Probeer het later opnieuw."\n"(Details: %2$s) De lijst is niet toegevoegd. - Contact is niet geblokkeerd. - Contact is niet gedeblokkeerd. - Selecteer eerst een contact. - \"Verbinding verbroken!\"\n - Servicefout. - De lijst met contacten is niet geladen. - Kan geen verbinding maken met de server. Controleer uw verbinding. - - - - - + %1$s staat al in uw lijst met contacten. + Contact \'%1$s\' is geblokkeerd. Uw lijst met contacten wordt geladen. - Er is een netwerkfout opgetreden. WiFi is nodig voor deze verbinding. - Deze functionaliteit wordt niet ondersteund door de server. - Het opgegeven wachtwoord is ongeldig. - Er is een fout aangetroffen op de server. - Deze functionaliteit wordt niet ondersteund door de server. - De server is momenteel niet beschikbaar. - Er is een time-out opgetreden op de server. - De huidige versie wordt niet ondersteund door de server. - De wachtrij voor berichten is vol. - Omleiden naar het domein wordt niet ondersteund door de server. - De opgegeven gebruikersnaam wordt niet herkend. - U bent geblokkeerd door deze gebruiker. - De sessie is verlopen. Meld u opnieuw aan. - u bent aangemeld via een andere client. u bent al aangemeld via een andere client. - Het telefoonnummer kan niet worden gelezen vanaf uw SIM-kaart. Neem contact op met uw operator voor ondersteuning. - U bent momenteel niet aangemeld. - - - + ChatSecure trof een fout aan bij het valideren van uw gebruikersnaam en wachtwoord - controleert u ze alstublieft en probeer opnieuw. + ChatSecure trof een fout aan tijdens het genereren van een sleutelpaar. + ChatSecure trof een fout aan tijdens het verbinding maken met de chatserver - controleer alstublieft uw instellingen en probeer opnieuw. + ChatSecure trof een fout aan tijdens het verbinding maken - controleert u alstublieft uw netwerkverbinding en probeer opnieuw. + ChatSecure heeft de verbinding met het netwerk verloren + ChatSecure is aan het proberen de verbinding te herstellen + U voerde geen @hostnaam.com gedeelte in bij uw gebruikersnaam. Probeer opnieuw! + Uw server hostnaam bevatte geen .com, .net of een vergelijkbare appendix. Probeer opnieuw! + Voer uw wachtwoord in: + Omdat u Tor gebruikt, moet u de XMPP \'Server Adres\' hostnaam direct invoeren bij Geavanceerde Account Instellingen + WAARSCHUWING: Deze service gebruikt een certificaat met een ZWAKKE versleutelmethode. Vraagt u alstublieft aan de beheerder om up te graden. + Verstrekt Certificaat Komt Niet Overeen Met VASTGEPIND Certificaat: + Kan geen groepschat starten of toetreden. + Sorry, we kunnen dit bestandstype niet delen + Je moet encryptie inschakelen om bestanden te delen + Gelieve chat encryptie aan te zetten om bestanden te delen + ChatSecure detecteerde een aanzoek om zijn taak te verwijderen. +ChatSecure zal waarschijnlijk crashen. Gebruik in de plaats a.u.b. het \"Log alle uit\" menu item van het account lijst venster. + Er is geen viewer beschikbaar voor dit bestandsformaat + Gelieve een beveiligde conversatie te beginnen alvorens codes te scannen + OTR-sleutelopslag niet geïmporteerd. Controleer of het bestand bestaat en in het juiste formaat en op de juiste plek is. + Kopieer a.u.b. het \'otr_keystore.ofcaes\' bestand van de desktop KeySync tool naar de root directory van je opslagruimte. + Dit bericht kan niet worden verzonden. + Berichten zullen na herverbinding verstuurd worden. + %1$s is offline. Berichten die u verzendt, worden afgeleverd wanneer %1$s online komt. + %1$s staat niet in uw lijst met contacten + Je wachtwoorden komen niet overeen + Prioriteit moet een getal zijn tussen [0 .. 127] + De poort moet een getal zijn + Overdrachtsfout + Kon het bestand niet lezen + Grote Fout: Kan de app database niet laden of ontgrendelen. Gelive de app opnieuw te installeren of de data te wissen. + Account Instellingen + + LET OP: Dit is een prille versie van ChatSecure die beveiligingslekken of fouten kan bevatten. + + Groepen + open conversatie(s) + Uitschakelen + Paniek + Vrienden + Contacten + Aanvaard Server Certificaat? + Vingerafdruk + Laden… + + Over ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Contact Lijst + Instellingen + Accountlijst + Afmelden + Lijst met contacten + + Jabber/XMPP + Locale Zone (Bonjour/ZeroConf) + Google Account + dukgo.com + + + Online + Bezet + Afwezig + Inactief + Offline + Offline weergeven + Blij Bedroefd @@ -415,7 +508,7 @@ Lachend Verward - + Blij Bedroefd @@ -448,180 +541,36 @@ :-! :-[ O:-) - :-\ + :-\\ :\'( :-X :-D o_O - - Contact Lijst - Versleutel On/Off - Verbind via Tor (Vereist de Orbot app) - - ChatSecure - Eerste keer dat u met ChatSecure werkt? - Kunt u niet wachten om te beginnen? - Aan de slag - Account Instellen - - Over ChatSecure - ChatSecure is een mobile chat applicatie met extra beveiligingsonderdelen. Deze zorgen ervoor dat anderen jouw communicatie niet kan lezen.\n\nDe applicatie ondersteund alle chat diensten die het Jabber of XMPP protocol gebruiken, zoals Google GTalk of Jabber.org. - - Waarom is het veilig? - \'Off-the-Record Messaging\' is een beveiligingssysteem ontwikkeld om privacy te beschermen door het nabootsen van privé gesprekken in de echte wereld, met Versleuteling, Authentificatie, Mogelijke ontkenning en Forward Secrecy.\n\nHet OTR-protocol is compatibel met desktop chatcliënten als Adium of Pidgin. - - Zijn mijn chats veilig? - Gibberbot\'s chat versleuteling werkt alleen wanneer de ander een compatibele app of programma gebruik. Zorg er dus voor dat je zeker weet dat contacten Gibberbot op hun mobiel of Adium of Pidgin op hun desktop gebruiken. Je kunt veranderen hoe Gibberbot verbindingen probeert te versleutelen bij Account Instellingen.⏎\n⏎\nAan de slag! - - Toegangscode Instellen - Stel voordat u begint een wachtwoord in om jouw ChatSecure data te beschermen. - Toegangscode: - Toegangscode (opnieuw): - - gebruiker@domein.com - nieuwe gebruikersnaam - service provider (dukgo.com, jabber.ccc.de) - wachtwoord - wachtwoord bevestigen - Geavanceerde Account Instellingen - Account Type - Registreer Account - - Volhouden - Wachtwoord Onthouden - Wachtwoord in cache - Wachtwoord niet in cache - Automatisch Inloggen - Verbinden bij opstarten Gibberbot - Niet verbinden bij opstarten Gibberbot - Nu inloggen - Verbind deze ingestelde account - Verbind niet deze ingestelde account - - Persoonlijk (optioneel) - Account Alias (uw naam) - Hoe uw account online getoond wordt - Profiel - Wat korte info over jezelf - - Forceer versleuteling / weiger platte tekst - Wanneer mogelijk, automatisch chats versleutelen - Versleutel chats op verzoek - Chat versleuteling uitschakelen - - Referenties aan het valideren... - Sleutelpaar aan het genereren... - In aan het loggen... - - Gibberbot trof een fout aan bij het valideren van uw gebruikersnaam en wachtwoord - controleert u ze alstublieft en probeer opnieuw. - Gibberbot trof een fout aan tijdens het genereren van een sleutelpaar. - Gibberbot trof een fout aan tijdens het verbinding maken met de chatserver - controleer alstublieft uw instellingen en probeer opnieuw. - Gibberbot trof een fout aan tijdens het verbinding maken - controleert u alstublieft uw netwerkverbinding en probeer opnieuw. - Hun Vingerafdruk - Uw Vingerafdruk - Log In - Hergen Sleutel - Gibberbot heeft de verbinding met het netwerk verloren - Gibberbot is aan het proberen de verbinding te herstellen - Let op: Deze chat is NIET versleuteld - Deze chat is veilig, maar de identiteiten van de deelnemers zijn NIET geverifieerd - Let op: chat versleuteling is geëindigd - Deze chat is veilig en geverifieerd - Account Wizard - Uw Gebruikersnaam - Server Instellen - Bent u klaar? - Voer uw gebruikersnaam in om Gibberbot in te stellen voor uw XMPP chatservice. Het lijkt op een e-mail adres: - Voer alstublieft uw gebruikersnaam in (gebruiker@domein): - Voer in of bewerk alstublieft uw jabber/xmpp chatserver hostnaam en poortnummer (standaard is dit 5222). - Gibberbot is nu ingesteld. Het is tijd om verbinding te maken met uw chatservice om versleuteld, veilig en privé te chatten! - U voerde geen @hostnaam.com gedeelte in bij uw gebruikersnaam. Probeer opnieuw! - Uw server hostnaam bevatte geen .com, .net of een vergelijkbare appendix. Probeer opnieuw! - Voer uw wachtwoord in: - - Account Instellingen - defLoc - LET OP: Dit is een prille versie van Gibberbot die beveiligingslekken of fouten kan bevatten. - - Groepen - Nieuw OTR sleutelpaar aan het genereren... - Contact - typ de naam van een contact om mee te chatten - Ok - Taal - Talen - Welke taal moet InTheClear tonen? - Check SRV - Gebruik DNS SRV om de werlijke XMPP server van de domeinnaam te vinden - Toestaan om de gebruikersnaam en wachtwoord als platte tekst te versturen bij een onversleutelde verbinding - Sta platte tekst authentificatie toe - Verifieer of het certificaat vertrouwd is - TLS Verificatie - Vereis TLS/SSL verbinding - Transport Versleuteling - Hoe de versleuteling van de chat te starten. - De server om mee te verbinding, indien nodig - Server Adres - TCP Poort voor XMPP Server - Server Poort - XMPP Middel - om deze verbinding te onderscheiden van andere ingelogde cliënten - XMPP Middel Prioriteit - Berichten naar cliënten met meerdere actieve XMPP middelen worden gestuurd naar het middel met de hoogste prioriteit. - Verder - Terug - Conversaties - Nieuw(e) bericht(en) van - Authentificatie - Voer een vraag in om naar de ander te sturen en het antwoord wat je verwacht te krijgen, zodat je de identiteit van de ander kan verifieren. - de vraag om te stellen - het verwachte antwoord - Uw contact heeft u succesvol geauthentificeerd. Authentificeer nu uw contact door uw eigen vraag te stellen. - Nog geen conversaties.\n\nKlik hier om te beginnen! - Gebruik Donker Thema - Omdat u Tor gebruikt, moet u de XMPP \'Server Adres\' hostnaam direct invoeren bij Geavanceerde Account Instellingen - - Start Gibberbot Automatisch - Altijd starten en automatisch inloggen op eerder ingelogde accounts - U heeft geen accounts ingesteld.\n\nKlik hier om een nieuwe toe te voegen! - Google Account - Uitsluitend Berichten Opslag in het Geheugen - Berichten alleen opslaan in het geheugen, niet in de flashopslag, om zo extractie van berichten te kunnen voorkomen. (Leidt mogelijk tot verlies van berichten) - Achtergrondafbeelding - Stel het pad (\"/sdcard/foo.jpg\") in voor een achtergrondafbeelding voor de app - Beveiliging en Privacy - Gebruikersinterface - Overige Tuning - open conversatie(s) - Orbot (Tor) - Uitschakelen - - Wilt u uitloggen bij alle services EN alle processen stopzetten (ruwe stopzetting)? - Vul een nieuw wachtwoord in. Het moet ten minste één hoofdletter, één gewone letter en één nummer bevatten. Het wachtwoord moet meer dan zes karakters hebben. - Jouw bestaande berichten worden met het nieuwe wachtwoord versleuteld (geduld...) - Voer wachtwoord in: - Welkom! Vul een sterk wachtwoord in om uw berichten te beveiligen. Het moet ten minste één hoofdletter, één gewone letter en één nummer bevatten. Het wachtwoord moet meer dan zes karakters hebben. - Uw wachtwoord was niet lang genoeg - Uw wachtwoord bevatte geen hoofdletters - Uw wachtwoord bevatte geen kleine letters - Uw wachtwoord bevatte geen cijfers - Openen - ChatSecure vergrendeld - Maak wachtwoord - Voer wachtwoord in - Voer nieuw wachtwoord in - Bevestig nieuw wachtwoord - Wachtwoorden kwamen niet overeen, probeer opnieuw - Secure Voice - Gebruik uw OStel.co of andere beveiligde SIP service account voor telefoon integratie. - dukgo.com - We hebben een OTR-sleutelopslag gevonden. Wil je nu het QR-wachtwoord scannen om deze te importeren? - Importeer OTR-sleutels - OTR-sleutelring succesvol geïmporteerd - OTR-sleutelopslag niet geïmporteerd. Controleer of het bestand bestaat en in het juiste formaat en op de juiste plek is. - Wachtwoord-timeout - Hoe lang moet app-versleuteling ontsleuteld blijven + Bestaand account + Verbinding maken met een bestaand account op een specifieke Jabber / XMPP server. + Google Account + WiFi Mesh Chat + Chat met anderen op hetzelfde lokale WiFi netwerk of mesh - geen internet of server nodig! + WiFi Chat inschakelen + Nieuwe Account + Registreer een nieuwe, gratis account bij een service van onze ingebouwde lijst, of naar eigen keuze. + Maak account aan + Geheime identiteit! + Creëer een anonieme, wegwerp chat-account in één simpele tik (vereist Orbot: Tor voor Android) + Maak identiteit + Dit is een groepsgesprek + [opnieuw verzonden] + [opnieuw verzonden] + Niet mogelijk dit bestand veilig te delen + Installeer Orbot? + Altijd + Start Orbot? + Orbot lijkt niet te worden uitgevoerd. Wilt u het opstarten en verbinding maken met Tor? + Alias in het groepsgesprek + naam van het te starten of toe te treden groepsgesprek + groepschat server (conference.foo.com) + Je sleutelopslag is corrupt. Gelieve ChatSecure opnieuw te installeren of \'wis de data\' van de app. + Je hebt een onleesbaar geëncrypteerd bericht ontvangen + Ik kon je verstuurde bericht niet decrypteren diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml index 252ac7019..568986232 100644 --- a/res/values-pl/arrays.xml +++ b/res/values-pl/arrays.xml @@ -6,64 +6,4 @@ Jeśli zażądane Wyłączone / Nigdy - - wymuszone - automatyczne - wymagane - nieaktywne - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 64ad23a8d..746ee60ab 100755 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -1,400 +1,498 @@ - + + + ChatSecure + ChatSecure + czytaj wiadomości - \nZezwala aplikacji na odczyt danych od dostawcy zawartości czatu.\n + +Zezwala aplikacji na odczyt danych od dostawcy zawartości czatu. + twórz wiadomości - \nZezwala aplikacji na przesyłanie danych do dostawcy zawartości czatu.\n - - Bezpieczny Komunikator = ChatSecure - - - Wybierz konto - - O Gibberbot - - Dodaj kont - - Edytuj konto - - Usuń konto - - Wyloguj całość - - - Wybierz konto - - - - Anuluj logowanie - - - Dodaj kontakt - - Usuń kontakt - - Zablokuj kontakt - - Zablokowano - - Lista kont - - Nowy czat - - Nowe konto - - Ustawienia - - Przeglądaj kontakty - - Rozpocznij czat - - Wyloguj - - Przeglądaj profil - - - Rozpocznij szyfrowanie - Zakończ szyfrowanie - Wyczyść czat - Zakończ czat - Bezpieczna rozmowa głosowa - - - Lista kontaktów - - Zaproś… - - Przełącz czaty - - Wstaw emotikonę - Wyślij ponowniw - - Skanuj odcisk palca - Twój odcisk palca - Potwierdź odcisk palca - Potwierdź Sekret - - Menu+ - - - + +Zezwala aplikacji na przesyłanie danych do dostawcy zawartości czatu. + + uruchom usługę czatu + Umożliwia aplikacjom uruchamianie czatu na żądanie. + Potwierdź - - Czy chcesz wylogować się z wszystkich usług ? - - - - - - - OK - Anuluj - OK - Anuluj - - - - - - - - - + Dalej + Cofnij + Połącz + Play + Ustawienia + + Otwórz + Bezpieczny Komunikator = ChatSecure Zablokowany + Ustaw Hasło + Potwierdź hasło + Hasło + Nowe Hasło + Potwierdź Nowe Hasło + Hasła nie zgadzają się, proszę spróbuj ponownie + Jestem leniwy (Bez hasła!) + Prośba o informacje + Prosimy o wpisanie hasła + Hasło… + Hasło + Hasło (ponownie) + + Wybierz konto + Wybierz konto + (%1$d) + Dodaj konto %1$s + O ChatSecure + Wyloguj całość + Czy chcesz wylogować się ze wszystkich usług ? + Nastąpiło wylogowanie z konta %1$s. + Zostałeś wylogowany z %1$s ponieważ %2$s + Pierwszy raz używasz ChatSecure ? + Nie możesz doczekać się żeby zacząć ? :) + Zaczynij tutaj + Tworzenie konta + Ustawienie wyrażenia hasłowego + Zanim zaczniemy najpierw wybierz bezpieczne hasło, aby chronić dane ChatSecure przed niechcianym dostępem z zewnątrz. + Wyrażenie hasłowe: + Wyrażenie hasłowe (powtórz): + Proszę wpisać *nowe* wyrażenie hasłowe. Wyrażenie musi zawierać przynajmniej dużą i małą literę oraz numer. Wyrażenie musi składać się również przynajmniej z 6 znaków. + Szyfrowanie aktualnych notatek z użyciem nowego wyrażenia hasłowego (cierpliwości…) + Wprowadź twoje wyrażenie hasłowe: + Witamy! Wprowadź mocne wyrażenie hasłowe aby zabezpiecznyć twoje rozmowy. Wyrażenie musi zawierać przynajmniej dużą i małą literę oraz numer. Wyrażenie musi składać się również przynajmniej z 6 znaków. + Twoje wyrażenie hasłowe nie jest wystarczająco długie (minimum 6 znaków) + Twoje wyrażenie hasłowe nie zawiera żadnej Dużej litery a powinno + Twoje wyrażenie hasłowe nie zawiera żadnej małej litery a powinno + Twoje wyrażenie hasłowe nie zawiera żadnej cyfry a powinno + + Na temat ChatSecure + ChatSecure jest mobilną aplikacją czatu zapewniającą bezpieczeństwo, uniemożliwiając innym osobom podglądanie Twoich rozmów i korespondencji. + +Aplikacja wspiera każdy serwis komunikacyjny, który używa protokołów Jabber lub XMPP, na przykład Google GTalk lub Jabber.org. + + Jak to jest zabezpieczone ? + OTR, skrót od ang. \"Off-the-Record Messaging\", to system zabezpieczenia na celu umożliwienia prywatności poprzez naśladowanie charakterystyki prywatnej rozmowy w rzeczywistym świecie, w tym szyfrowanie, uwierzytelnianie, zaprzeczenie i \"forward secrecy\" . + +Protokół OTR jest zgodny z klientami czatu stacjonarnymi takie jak Adium lub Pidgin. + + Czy moje rozmowy są zabezpieczone? + Szyfrowanie rozmów w ChatSecure działa tylko wtedy kiedy obaj użytkownicy używają kompatybilnych aplikacji, więc upewnij się, że Twoi znajomi korzystają z ChatSecure na swoich telefonach komórkowych, Adium lub Pidgin w przypadku korzystania z komputera. Pamiętaj aby ustawić jak i kiedy ChatSecure będzie szyfrował Twoje rozmowy w Ustawieniach Konta. + +No to zaczynamy! + + Nowe konto + Dodaj konto + Edytuj konto + Usuń konto + Istniejące Konto + Nazwa użytkownika (User@Host): - Hasło: - Pamiętaj moje hasło. - Zaloguj mnie automatycznie. - Nie masz konta? - - Dla twojego bezpieczeństwa, gdy stracisz lub zgubisz telefon, zmień hasło przez stronę internetową. - Ta opcja powoduje automatyczne logowanie użytkownika przy każdym uruchomieniu tej aplikacji. Aby wyłączyć tę opcję, wyloguj się, a następnie usuń zaznaczenie pola wyboru „Zaloguj automatycznie”. - Zaloguj - - - Włączam Bezpiecznego Komunikatora = ChatSecure... - + Dla twojego bezpieczeństwa, gdy stracisz lub zgubisz telefon, zmień hasło przez stronę internetową. + Ta opcja powoduje automatyczne logowanie użytkownika przy każdym uruchomieniu tej aplikacji. Aby wyłączyć tę opcję, wyloguj się, a następnie odznacz pole wyboru „Zaloguj automatycznie”. + Połącz wykorzytując Tor (Wymaga aplikacji Orbot) + użytkownik@domena.com + nowy użytkownik + dostawca serwisu (dukgo.com, jabber.ccc.de) + hasło + potwierdź hasło + Zaawansowane ustawienia konta + Rodzaj konta + Zarejestruj konto + Trwałość + Zapamiętaj hasło + Hasło zbuforowane + Hasło niezbuforowane + Automatycznie zaloguj + Połącz ChatSecure przy starcie + Nie łącz ChatSecure przy starcie + Zaloguj teraz + Podłącz następujące ustawienia konta + Nie podłącz następujące ustawienia konta + Prywatny (opcja) + Alias konta (twoja imię) + Jak wygląda twoje konto w sieci + Profil + Krótka informacja o tobie + Wymuś szyforwanie / odmawiaj używania zwykłego tekstu + Kiedy możliwe, szyfruj rozmowy automatycznie + Szyfruj rozmowy jak zażądane + Wyłącz szyfrowanie rozmów + Trwa weryfikacja poświadczeń… + Generowanie klucza… + Trwa logowanie… + Czarodziej Konta + ID twojego konta + Skonfiguruj serwer + Czy jesteś gotów? + Wpisz ID swojego konta by skonfigurować ChatSecure dla Twojego serwisu czata XMPP. Wygląda jak adres email: + Wprowadź ID twojego konta (użytkownik@nazwa_hosta): + Wprowadź lub edytuj nazwę hosta twojego serwera rozmów jabber/xmpp oraz numer portu (5222 jest domyślny). + ChatSecure został skonfigurowany i tym samym przyszedł czas na połączenie się z Twoim serwisem i rozpoczęciem bezpiecznych, prywatnych rozmów! + Orbot (Tor) + Wybierz domenę + Rejestrowanie nowego konta… + Włączam ChatSecure… + Anuluj logowanie Trwa logowanie… - - - Dane w tle są wyłączone - - - - - - Włącz - - Zamknij - - - - - - + Wylogowywanie\u2026 + + Wyszukaj SRV + Użyj DNS SRV aby znaleźć aktualny serwer XMPP korzystając z nazwy domeny + Zezwól aby dane użytkownika i hasło zostały wysłane w postaci tekstu czyli nie były zaszyfrowane. Odradzamy + Zezwól autoryzację z użyciem niezaszyfrowanego tekstu + Zweryfikuj czy cerytfikat jest zaufany + Weryfikacja TLS + Wymaga połączenia TLS + Transportuj Szyfrowanie + jak zaszyfrowane rozmowy są rozpoczynane + Serwer do ustanowienia połącznia gdy nastąpi taka potrzeba + Połącz Serwer + Port TCP dla serwera XMPP + Port Serwera + Źródło XMPP + aby rozróżnić to połączenie od innych klientów też zalogownych + Priorytet Zasób XMPP + Wiadomości wysłane do klientów zawierających wielu aktywnych zasób zostaną wysłane do zasobu z najwyższym priorytetem + + Rozmowy + Brak trwających rozmów + +Kliknij tu aby rozpocząć rozmowę! + Nie masz +skonfigurowanego konta + +Kliknij aby dodać konto. + Lista Kontaktów - %1$s + Dodaj kontakt + Usuń kontakt + Zablokuj kontakt + Pseudonim kontaktowy + Zablokowano + Pseudonim: + Kontakt „%1$s” zostanie usunięty. + Kontakt „%1$s” zostanie zablokowany. + Kontakt „%1$s” zostanie odblokowany. + Dodano kontakt „%1$s”. + Usunięto kontakt „%1$s”. + Zablokowano kontakt „%1$s”. + Odblokowano kontakt „%1$s”. + Nowy czat + Przeglądaj kontakty + Pokaż Kartę + Rozpocznij czat + Wyświetl Profil + Zweryfikuj klucz + Aktywne czaty (%1$d) + %1$d online Zaproszenia - (\"\"Nieznany\"\") - Pusty - Brak rozmów - - Wybierz kontakty do zaproszenia - Wpisz, aby znaleźć kontakt - Nie znaleziono kontaktów. - - - - Brak zablokowanych kontaktów. - - + Kontakt + Wpisz osobę z którą chcesz rozpocząć rozmowę + Idź + Brak aktywnych czatów. + Dodaj kontakt + Adres e-mail zapraszanej osoby: + Wybierz listę + Wpisz imię, aby dodać do Kontaktów. + Wyślij zaproszenie Profil kontaktu - Status: - Typ klienta: - Komputer - Przenośny - - - Online - - Zajęty - - Z dala od komputera - - Bezczynność - - Offline - - Pokazuj jako offline - - - - - + Zablokowane kontakty - %1$s + Brak zablokowanych kontaktów. + + Czat z użytkownikiem %1$s Ja - - Wpisz, aby utworzyć - - - - - - - - - - - - - - - - + Użytkownik %1$s jest dostępny + Użytkownik %1$s jest niedostępny + Użytkownik %1$s jest zajęty + Użytkownik %1$s jest niedostępny + Użytkownik %1$s dołączył do czatu + Użytkownik %1$s opuścił czat + Wyślij Zdjęcie + Wyślij plik + Wyślij Audio + Zrób Zdjęcie + Przekaz plików + Przekaz zakończony + Przekaz w toku + Akceptować przekaz? + chce Ci wysłać plik + Brak połączenia, aby wysłać Wyślij - - Nie można wysłać wiadomości. - - Utracono połączenie z serwerem. Wiadomości zostaną wysłane po przejściu w tryb online. - - - - - + Wyślij wiadomość + Wyślij bezpieczną wiadomość + Wyślij ponowniw + Zakończ czat + Wyczyść czat + Wstaw emotikonę + Przełącz czaty + Menu+ Wybierz łącze - - Brak aktywnych czatów. - - Trwa rozpoczynanie szyfrowanej rozmowy... - Trwa przerywanie szyfrowaniej rozmowy... - - - Dodaj kontakt - - Adres e-mail zapraszanej osoby: - - Wybierz listę - - Wpisz imię, aby dodać do Kontaktów. - - Wyślij zaproszenie - - Jabber (XMPP) - Rozmowy w sieci lokalnej (Bonjour/ZeroConf) - + Usuń + Trzymaj pliki + Usunąć zabezpieczoną pamięć sesji czatu? + Wszystkie wysyłane i pobrane pliki sesji zostaną trwale usunięte. Ostrzeżenie: ta czynność nie może zostać cofnięta! + Usunąć oryginał? + Plik zostanie skopiowany do zabezpieczonej pamięci przed wysłaniem. Czy chcesz usunąć oryginalny plik z niezabezpieczonej pamięci urządzenia? + Trzymaj + Eksport + Eksportować plik mediów? + Ten plik został wyeksportowany do %1$s + Zakończyć czat? + Wszystkie zabezpieczone elementy multimedialne tej sesji zostaną usunięte. Eksportuj elementy multimedialne za pomocą długiego kliknięcia na ikonę miniatur. + Zakończ czat i usuń pliki + + %1$s przesyła zaproszenie do czatu grupowego. + Wysłano zaproszenie do użytkownika %1$s. + Zaakceptuj + Odmów + Rozmowa grupowa + Stwórz albo dołącz do Rozmow grupowej + Podłączanie do grupy czat… + Zaproś… + Wybierz kontakty do zaproszenia + Wpisz, aby znaleźć kontakt + Nie znaleziono kotaktów + +Kliknij aby zaprosić. + Nie można zatwierdzić subskrypcji od użytkownika %1$s. Spróbuj ponownie później. + Nie można odmówić subskrypcji od %1$s. Spróbuj ponownie później. + + Rozpocznij szyfrowanie + Zakończ szyfrowanie + Trwa rozpoczynanie szyfrowanej rozmowy… + Trwa przerywanie szyfrowaniej rozmowy… + Klucz bezpieczeństwa + Klucz bezpieczeństwa + Zaloguj + Regenerowanie klucza + Szyfrowanie jest wyłączone. + Szyfrowanie jest włączone (Kliknij aby zweryfikować) + Twój kontakt zakończył szyfrowaną rozmowę. + Zaszyfrownano i zweryfikowano! + Generowanie nowego klucza OTR… + + Dostrzegliśmy zbiór kluczy OTR do importowania. Chcesz teraz zeskanować hasło QR? + Aktywuj KeySync + Pomyślnie zaimportowany Zbiór Kluczy OTR + + Skanuj QR + Twój odcisk palca + Manual + Pytanie + Mapowanie Bezpieczeństwa (Zweryfikowane) + Czy jesteś pewien, że chcesz potwierdzić ten klucz? + Potwierdzić klucz? + Zdalny klucz został zweryfikowany! + Klucz dla Ciebie + Klucz dla + Zainstalować Skaner Barkodów? + Ta aplikacja wymaga Skanera Barkodów. Czy chcesz ją zainstalować? + + Autentykacja + Wpisz pytanie aby wysłać do tego kontaktu i odpowiedź jakiej oczekujesz, w celu zweryfikowania toższamości osoby + pytanie, które chcesz zadać + oczekiwana odpowiedź + Twoj kontakt zweryfikował twoją tożsamość. Teraz ty zweryfikuj osobę po drugiej stronie zadając swoje pytanie. + OTR Weryfikacja Q&A + Szyfrowanie Czata + Wyślij + Anuluj + + Bezpieczna rozmowa głosowa + Bezpieczny Głos + Proszę wpisać twoje konto OStel.co lub inne konto serwisu zabezpieczone SIP tutaj dla integracji rozmowy + Tworzenie konta + + Zabezpiecznia i prywatność Szyfrowanie i Anonimowość + Włączo/Wyłącz szyfrowanie + Hasło wygasło po przekroczeniu czasu + Jak długo szyfrowanie aplikacji powinno zostać odblokowane. + + Klikalne linki w Torze + W przypadku kont używających Tor\'a, linki w czatach zostaną klikalne (Uwaga, możliwe zagrożenie prywatności!) + Interfejs uzytkownika + Język + Języki + Użyj domyślnej systemu + Użyj ciemnej skórki + Zmień motyw aplikacji na ciemny + Przetrzymywanie wiadomości jedynie w pamięci + Zapisuj wiadomości w pamięci, nie na pamięci flash, aby chronić przed wykradnięciem. (Może spowodować utratę wiadomości) + Obrazek tła + Wskaż scieżkę (\"/sdcard/foo.jpg\") dla obrazu tła w aplikacji + Pokaż Kartę Kontaktów + Pokaż listę kontaktów jako kartę avatarów + Tak, akceptuj wszystkie + Usuń nieszyfrowane media + Po udostępnieniu zdjęcia lub pliku, automatycznie usuń je z oryginalnej, niezabezpieczonej pamięci po imporcie + + Inne Dostrajanie + Automatycznie włącz Bezpieczny komunikator = ChatSecure + Zawsze loguj automatycznie do poprzednio używanego konta lub kont Ukryj kontakty offline - - Ustawienia powiadomień - - Powiadamiaj o czacie - - Sygnalizuj nową wiadomość czatu na pasku stanu - Użyj wysoki priorytet Zmniejsz prawdopodobieństwo, że Android zresetuje twoje poączenie z serwisem. To zostanie wyświetlone w polu powiadamiania Interwał pulsu serca Użyj większej wartości (w minutach) aby oszczędzać baterię. Wyższa wartość może spowodować, że provider zakończy twoje połączenie ze względu na nieaktywność. + + Ustawienia powiadomień + Powiadamiaj o czacie + Sygnalizuj nową wiadomość czatu na pasku stanu Wibracje - Sygnalizuj wiadomość czatu wibracją - Dźwięk - Sygnalizuj wiadomość czatu dzwonkiem - - Wybierz dzwonek - - - - - - - Zaakceptuj - - Odmów - - - - Zaakceptuj - - Odmów - - - - - - - - - - - - + Używaj domyślnego dzwonka ChatSecure + + Włącz Logi Debugowania + Dane wyjściowe logu aplikacji na standardowe wyjście / LogCat do debugowania + + Dane w tle są wyłączone + Usługa %1$s wymaga, aby włączone były dane w tle. + Włącz + Zamknij + Czy chcesz wylogować się z serwisu i zakończyć wszystkie procesy (tzw. twarde wyłącznie) ? + Utwórz nowe konto? + Utwórz nowe konto czat dla użytkownika \'%1$s\'? + Info Certyfikatu + Certyfikat: + Wydany przez: + SHA1 Fingerprint: + Wydany: + Wygaśnie: + Dołączyć do czata? + Zewnętrzna aplikacja próbuje połączyć Cię do czatu. Pozwolić? + Wybierz tło + Czy chcesz wybrać obraz tła z galerii? + Wybierz zdjęcie + + Nowe wiadomości %1$s + %1$d nieprzeczytanych czatów + Nowe zaproszenie do znajomych od %s + Nowy plik %1$s od %2$s Zaproszenie do czatu grupowego - - - - - - - - - - - - uruchom usługę czatu - Umożliwia aplikacjom uruchamianie czatu na żądanie. - - + Nowe zaproszenie do grupowego czata od %s + Nowa/e wiadomość/i od + Uruchamianie serwisu ChatSecure… + Aktywowany & odblokowany + wiadomość skopiowana do schowka + Uwaga - - - + Błąd: + Kod błędu %1$d + Nie można zalogować do usługi %1$s. Spróbuj ponownie później."\n"(Szczegóły: %2$s) Nie dodano listy. - Nie zablokowano kontaktu. - Nie odblokowano kontaktu. - Należy wybrać kontakt. - \"Rozłączono!\"\n - Błąd usługi! - Nie załadowano listy kontaktów. - Nie można się połączyć z serwerem. Sprawdź połączenie. - - - - - + Użytkownik %1$s już znajduje się na Twojej liście kontaktów. + Zablokowano kontakt „%1$s”. Czekaj, trwa ładowanie listy kontaktów. - Wystąpił błąd sieciowy. WiFi jest wymagane dla tego połącznia - Serwer nie obsługuje tej funkcji. - Wprowadzono nieprawidłowe hasło. - Błąd serwera. - Serwer nie obsługuje tej funkcji. - Serwer jest aktualnie niedostępny. - Przekroczono limit czasu serwera. - Serwer nie obsługuje bieżącej wersji. - Kolejka wiadomości jest pełna. - Serwer nie obsługuje przekazywania do domeny. - Nie rozpoznano wprowadzonej nazwy użytkownika. - Niestety, ten użytkownik Cię blokuje. - Sesja wygasła, zaloguj się ponownie. - zalogowania z innego klienta. użytkownik już jest zalogowany z innego klienta. - Niestety, nie można odczytać numeru telefonu z karty SIM. Skontaktuj się z operatorem, aby uzyskać pomoc. - Użytkownik nie jest aktualnie zalogowany. - - - + ChatSecure napotkał błąd w trakcie logowania - sprawdź ponownie nazwę użytkownika i hasło + ChatSecure napotkał błąd w trakcie generowania pary kluczy. + ChatSecure napotkał błąd w trakcie łączenia się z serwerem czatu - sprawdź ustawienia i spróbuj ponownie. + ChatSecure napotkał błąd w trakcie łączenia się - sprawdź swoje połączenie sieciowe i spróbuj ponownie. + ChatSecure utracił połączenie z siecią + ChatSecure próbuje nawiązać ponowne połączenie z siecią + Nie wprowadziłeś części @nazwa-hosta.com dla ID twojego konta. Spróbuj ponownie! + Twój hostname (nazwa hostu) serwera nie ma .com, .net lub podobnego załącznika. Spróbuj ponownie! + Wpisz swoje hasło: + Używasz Tor więc podaj XMPP \'Connect Server\' hostname bezpośrednio w Zaawansowanych ustawieniach konta + UWAGA: Ten serwis używa certyfikatu ze słabym szyfrowaniem. Należy poinformować administratora o aktualizacji. + Dostarczony certyfikat nie odpowiada zaufanemu certyfikatu. + Nie zdolny do stworzenia lub dołaćzenia do rozmowy grupowej + Niestety nie możemy udostępnić tego typu plik + Musisz włączyć szyfrowanie do udostępniania plików + Musisz włączyć szyfrowanie czata aby udostępniać pliki + Nieobsługiwane dane przychodzące, nie może się nimi podzielić! + ChatSecure wykrył prośbę na zakończenie jego działania . Najprawdopodobniej dojdzie to niekontrolowanego zamknięcia ChatSecure. Proszę użyć opcji Wyloguj Całość z menu na Liście Kont. + Nie ma odpowiedniego programu dla tego typu formatu pliku + Proszę rozpocznij bezpieczną rozmowę przed skanowaniem kodów + Zbiór Kluczy OTR nie został zaimportowany; Proszę sprawdzić, czy plik istnieje w właściwym formacie i lokalizacji + Prosimy o skopiowanie pliku \'otr_keystore.ofcaes\' z pulpitu narzędzia KeySync do głównego katalogu na Twoim dysku. + Nie można wysłać wiadomości. + Wiadomości będą wysłane przy ponowym połączeniu. + Użytkownik %1$s jest offline. Wysyłane wiadomości zostaną dostarczone, gdy %1$s będzie znowu online. + Użytkownik %1$s nie jest na Twojej liście kontaktów. + Twoje hasła nie zgadzają się + Priorytet musi być liczbą pomiędzy [0 .. 127] + Numer portu musi być liczbą + Błąd transferu + Nie można odczytać pliku do przechowywania + Poważny błąd: Nie można odblokować lub otworzyć bazy danych aplikacji. Proszę ponownie zainstaluj aplikację lub wyczyść dane. + Brak zainstalowanej aplikacji, która może otworzyć ten link! + Ustawienia konta + + Ostrzeżenie: To jest wczesna wersja Bezpiecznego komunikatora = ChatSecure więc może posiadać luki bezpieczeństwa. + + Grupy + Otwarte rozmowy + Wyjście + Panika + Znajomi + Kontakty + Zaakceptować Certyfikat Serwera? + Klucz + trwa ładowanie… + + O ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Lista kontaktów + Ustawienia + Lista kont + Wyloguj + Lista kontaktów + + Jabber (XMPP) + Lokalny Rejon (Bonjour/ZeroConf) + Konto Google + dukgo.com + + + Online + Zajęty + Z dala od komputera + Bezczynność + Offline + Pokazuj jako offline + Wesoły Smutny @@ -414,7 +512,7 @@ Śmieje się Zakłopotanie - + Wesoły Smutny @@ -453,171 +551,33 @@ :-D o_O - Lista kontaktów - Włączo/Wyłącz szyfrowanie - Połącz wykorzytując Tor (Wymaga aplikacji Orbot) - - Bezpieczny Komunikator = ChatSecure - Pierwszy raz używasz Bezpiecznyego Komunikatora = ChatSecure ? - Nie możesz doczekać się żeby zacząć ? :) - Zaczynij tutaj - Tworzenie konta - - O BezpiecznymKomunikatorze = ChatSecure - Bezpieczny komunikator = ChatSecure zapewnia bezpieczeństwo, które uniemożliwi śledzenie twoich rozmów.\n\nAplikacja wspiera każdy serwis komunikacyjny, który używa protokołów Jabber lub XMPP, jak na przykład Google GTalk lub Jabber.org. - - Jak to jest zabezpieczone ? - OTR, skrót od ang. \"Off-the-Record Messaging\", to system zabezpieczenia na celu umożliwienia prywatności poprzez naśladowanie charakterystyki prywatnej rozmowy w rzeczywistym świecie, w tym szyfrowanie, uwierzytelnianie, zaprzeczenie i \"forward secrecy\" . \n\nProtokół OTR jest zgodny z klientami czatu stacjonarnymi takie jak Adium lub Pidgin. - - Czy moje rozmowy są zabezpieczone? - Bezpieczny komunikator = ChatSecure działa w trybie bezpiecznym jeśli obaj uzytkownicy używają kompatybilnych aplikacji, więc upewnij się, że twoi znajomi korzystają z ChatSecure w komórkach bądź Adium lub Pidgin w przypadku korzystania z komputera. Pamiętaj aby ustawić jak i kiedy ChatSecure będzie szyfrował twoje rozmowy w Ustawieniach konta. - - Ustawienie wyrażenia hasłowego - Przed rozpoczęciem wybierz bezpieczne wyrażenie hasłowe aby chronić swój Bezpieczny Komunikator = SecureChat przed niechcianym/niebezpiecznym dostępem z zewnątrz. - Wyrażenie hasłowe: - Wyrażenie hasłowe (powtórz): - - użytkownik@domena.com - nowy użytkownik - dostawca serwisu (dukgo.com, jabber.ccc.de) - hasło - powtórz hasło - Zaawansowane ustawienia konta - Rodzaj konta - Zarejstruj konto - - Trwałość - Zapamiętaj hasło - Hasło zbuforowane - Hasło niezbuforowane - Automatycznie zaloguj - Połącz Bezpieczny Komunikator = ChatSecure przy starcie - Nie łącz Bezpieczny Komunikator = ChatSecure przy starcie - Zaloguj teraz - - Prywatny (opcja) - Alias konta (twoja imię) - Jak wygląda twoje konto w sieci - Profil - Krótka informacja o tobie - - Wymuś szyforwanie / odmawiaj używania zwykłego tekstu - Kiedy możliwe, szyfruj rozmowy automatycznie - Szyfruj rozmowy jak zażądane - Wyłącz szyfrowanie rozmów - - Trwa weryfikacja poświadczeń... - Generowanie klucza... - Trwa logowanie... - - Bezpieczy Komunikator = ChatSecure wykrył błąd w trakcie logowania - sprawdź ponownie nazwę użytkownika i hasło - Bezpieczny Komunikator = ChatSecure napotkał błąd w trakcie generowania klucza. - Bezpieczny Komunikator = ChatSecure napotkał błąd w trakcie podłączania się ze serwerem czat - sprawdź ustawienia i spróbuj ponownie. - Bezpieczny Komunikator = ChatSecure napotkał błąd w trakcie podłączania się - sprawdź łączność sieciową i spróbuj ponownie. - Ich odcisk palca - Twój odcisk palca - Zaloguj - Regenerowanie klucza - Bezpieczny komunikator = ChatSecure utracił połączenie do sieci - Bezpieczny komunikator próbuje nawiązać połączenie z siecią - Ostrzeżenie: Ta rozmowa nie jest kodowana ! - Ostrzeżenie: Ta rozmowa jest zaszyfrowana ALE użytkownicy nie są zweryfikowani ! - Ostrzeżenie: szyfrowanie zostało zaprzestane - Ta rozmowa jest zaszyfrowana i uzytkownicy zweryfikowani - Czarodziej Konta - ID twojego konta - Skonfiguruj serwer - Czy jesteś gotów? - Wpisz ID twojego konta by skonfigurować Bezpiecznego Komunikatora = ChatSecure dla twojego serwisu czata XMPP. Wygląda jak adres email: - Wprowadź ID twojego konta (użytkownik@nazwa_hosta): - Wprowadź lub edytuj nazwę hosta twojego serwera rozmów jabber/xmpp oraz numer portu (5222 jest domyślny). - Bezpieczny Komunikator = ChatSecure został skonfigurowany, teraz możesz się podłączyć do twojego serwisu i czatować, bezpiecznie i prywatnie! - Nie wprowadziłeś części @nazwa-hosta.com dla ID twojego konta. Spróbuj ponownie! - Twój hostname (nazwa hostu) serwera nie ma .com, .net lub podobnego załącznika. Spróbuj ponownie! - Wpisz swoje hasło: - - Ustawienia konta - domLok - Ostrzeżenie: To jest wczesna wersja Bezpiecznego komunikatora = ChatSecure więc może posiadać luki bezpieczeństwa. - - Grupy - Generowanie nowego klucza OTR... - Kontakt - Wpisz osobę z którą chcesz rozpocząć rozmowę - Idź - Język - Języki - Jaki język powinien być wyświetlany w InTheClear ? - Wyszukaj SRV - Użyj DNS SRV aby znaleźć aktualny serwer XMPP korzystając z nazwy domeny - Zezwól aby dane użytkownika i hasło zostały wysłane w postaci tekstu czyli nie były zaszyfrowane. Odradzamy - Zezwól autoryzację z użyciem niezaszyfrowanego tekstu - Zweryfikuj czy cerytfikat jest zaufany - Weryfikacja TLS - Wymaga połączenia TLS/SSL - Transportuj Szyfrowanie - jak zaszyfrowane rozmowy są rozpoczynane - Serwer do ustanowienia połącznia gdy nastąpi taka potrzeba - Połącz Serwer - Port TCP dla serwera XMPP - Port Serwera - Źródło XMPP - aby rozróżnić to połączenie od innych klientów też zalogownych - Priorytet Zasób XMPP - Wiadomości wysłane do klientów zawierających wielu aktywnych zasób zostaną wysłane do zasobu z najwyższym priorytetem - Dalej - Cofnij - Rozmowy - Nowa/e wiadomość/i od - Autentykacja - Wpisz pytanie aby wysłać do tego kontaktu i odpowiedź jakiej oczekujesz, w celu zweryfikowania toższamości osoby - pytanie, które chcesz zadać - oczekiwana odpowiedź - Twoj kontakt zweryfikował twoją tożsamość. Teraz ty zweryfikuj osobę po drugiej stronie zadając swoje pytanie. - Brak trwających rozmów\n\nKliknij tu aby rozpocząć rozmowę! - Użyj ciemnej skórki - Używasz Tor więc podaj XMPP \'Connect Server\' hostname bezpośrednio w Zaawansowanych ustawieniach konta - - Automatycznie włącz Bezpieczny komunikator = ChatSecure - Zawsze loguj automatycznie do poprzednio używanego konta lub kont - Nie masz skonfigurowanego konta.\n\nKliknij aby dodać konto! - Konto Google - Przetrzymywanie wiadomości jedynie w pamięci - Zapisuj wiadomości w pamięci, nie na pamięci flash, aby chronić przed wykradnięciem. (Może spowodować utratę wiadomości) - Obrazek tła - Wskaż scieżkę (\"/sdcard/foo.jpg\") dla obrazu tła w aplikacji - Zabezpiecznia i prywatność - Interfejs uzytkownika - Inne Dostrajanie - Otwarte rozmowy - Orbot (Tor) - Wyjście - - Czy chcesz wylogować się z serwisu i zakończyć wszystkie procesy (twarde wyłącznie) ? - Proszę wpisać *nowe* wyrażenie hasłowe. Wyrażenie musi zawierać przynajmniej dużą i małą literę oraz numer. Wyrażenie musi składać się również przynajmniej z 6 znaków. - Szyfrowanie aktualnych notatek z użyciem nowego wyrażenia hasłowego (cierpliwości...) - Wprowadź twoje wyrażenie hasłowe: - Witamy! Wprowadź mocne wyrażenie hasłowe aby zabezpiecznyć twoje rozmowy. Wyrażenie musi zawierać przynajmniej dużą i małą literę oraz numer. Wyrażenie musi składać się również przynajmniej z 6 znaków. - Twoje wyrażenie hasłowe nie jest wystarczająco długie (minimum 6 znaków) - Twoje wyrażenie hasłowe nie zawiera żadnej Dużej litery a powinno - Twoje wyrażenie hasłowe nie zawiera żadnej małej litery a powinno - Twoje wyrażenie hasłowe nie zawiera żadnej cyfry a powinno - Otwórz - Bezpieczny Komunikator = ChatSecure Zablokowany - Stwórz wyrażenie hasłowe - Wprowadź wyrażenie hasłowe - Wprowadź nowe wyrażenie hasłowe - Potwierdź nowe wyrażenie hasłowe - Wyrażenia hasłowe są od siebie różne, spróbuj ponownie - Bezpieczny Głos - Proszę wpisać twoje konto OStel.co lub inne konto serwisu zabezpieczone SIP tutaj dla integracji rozmowy - dukgo.com - Dostrzegliśmy zbiór kluczy OTR do importowania. Chcesz teraz zeskanować hasło QR? - Importuj Zbiór Kluczy OTR - Pomyślnie zaimportowany Zbiór Kluczy OTR - Zbiór Kluczy OTR nie został zaimportowany; Proszę sprawdzić, czy plik istnieje w właściwym formacie i lokalizacji - Hasło wygasło po przekroczeniu czasu - Jak długo szyfrowanie aplikacji powinno zostać odblokowane. + Istniejące Konto + Podłącz do mojego istniejącego konta na określonym serwerze Jabber / XMPP. + Konto Google + Czatuj z innymi użytkownikami Google, używając swojego konta Google. + Czat WiFi Mesh + Czatuj z innymi na tej samej sieci WiFi lub mesh - Internet lub serwer nie jest wymagany! + Włącz Czat przez WiFi + Nowe konto + Zarejestruj nowe, bezpłatne konto w usłudze z naszej wbudowanej listy lub twojego dowolnego wyboru. + Utwórz nowe konto + Sekretna Tożsamość! + Załóż anonimowe, jednorazowe konto czatu w jednym, prostym dotykiem (wymaga Orbot: Tor dla Androida) + Generuj Tożsamość + To jest czat grupowy + [wyślij ponownie] + [wyślij ponownie] + Nie można bezpiecznie podzielić się tym plikiem + Zainstalować Orbot? + Musisz mieć zainstalowaną i aktywną aplikację Orbot, aby przepuszczać ruch przez proxy. Czy chcesz go zainstalować? + Zawsze + Uruchomić Orbot? + Wygląda na to, że Orbot nie jest włączony. Czy chcesz go uruchomić i połączyć się z siecią Tor? + pseudonim który chcesz używać na czacie + nazwa czata do stworzenia lub dołączenia\" + serwer grupowego czata (conference.foo.com) + Twój zbiór kluczy jest uszkodzony. Proszę ponownie zainstaluj ChatSecure lub wyczyść dane aplikacji + Otrzymałeś nieczytelną zaszyfrowaną wiadomość + Nie można odszyfrować twojej wysłanej wiadomości + < przesuń w lewo i prawo po więcej opcji > diff --git a/res/values-pt-rBR/arrays.xml b/res/values-pt-rBR/arrays.xml new file mode 100644 index 000000000..bcae0a1d0 --- /dev/null +++ b/res/values-pt-rBR/arrays.xml @@ -0,0 +1,9 @@ + + + + Forçar / Exigir + Tentativa Automática + Como Solicitado + Desativado / Nunca + + diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..3b13df15f --- /dev/null +++ b/res/values-pt-rBR/strings.xml @@ -0,0 +1,62 @@ + + + + ChatSecure + ChatSecure + + Ler mensagens instantâneas + Permitir que aplicações leiam dados a partir do provedor de conteúdo IM. + escrever mensagens instantâneas + Permitir que aplicações escrevam dados no provedor de conteúdo IM. + + OK + Cancelar + OK + Cancelar + Próximo + Anterior + + + Sobre + + + + + + + + Bloqueado + Vai + + + Aceitar + Sim + Não + + + + + Cancelar + + + + + Idioma + + + + + + + + + Pânico + + + Configurações + + + + + + diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml deleted file mode 100644 index 2484b722b..000000000 --- a/res/values-pt-rPT/arrays.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Força / Exigir - Tentar automaticamente - Conforme solicitado - / Nunca condicionada - - diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml deleted file mode 100755 index 99919fb11..000000000 --- a/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,302 +0,0 @@ - - - - "Cancelar início de sessão" - "Adicionar contacto" - "Eliminar contacto" - "Bloquear contacto" - "Bloqueado" - "Lista de contas" - "Definições" - "Iniciar chat" - "Terminar sessão" - "Ver perfil" - "Terminar chat" - "Lista de contactos" - "Convidar..." - "Trocar chats" - "Inserir sorriso" - "Menu+" - "Entrada" - "Confirmar" - "O contacto \"%1$s\" será eliminado." - "O contacto \"%1$s\" será bloqueado." - "O contacto \"%1$s\" será desbloqueado." - "OK" - "Cancelar" - "OK" - "Cancelar" - "A sua sessão no %1$s foi terminada." - "A sua sessão no %1$s foi terminada porque %2$s." - "Adicionar conta %1$s" - "Nome de utilizador:" - "Palavra-passe:" - "Memorizar a minha palavra-passe." - "Iniciar sessão automaticamente" - "Não tem uma conta?" - "Esta opção inicia a sessão automaticamente sempre que abrir esta aplicação. Para desactivar a opção, termine a sessão e desmarque a caixa de verificação \"Iniciar sessão automaticamente\"." - "Iniciar sessão" - "A iniciar sessão no %1$s" - "A iniciar sessão..." - "Dados de fundo desactivados" - "O %1$s precisa que os dados de fundo sejam activados." - "Activar" - "Sair" - "Chats a decorrer (%1$d)" - "%1$d on-line" - "Convites de amigo" - "(""Desconhecido"")" - "Vazio" - "Sem conversas" - "Seleccione contacto(s) para convidar" - "Escreva para encontrar um contacto" - "Não foram encontrados contactos." - "Contactos bloqueados - %1$s" - "Sem contactos bloqueados." - "Perfil do contacto" - "Estado:" - "Tipo de cliente:" - "Computador" - "Telemóvel" - "On-line" - "Ocupado" - "Ausente" - "Inactivo" - "Off-line" - "Aparecer off-line" - "Chat com %1$s" - "Eu" - "Escrever para redigir" - "%1$s está on-line" - "%1$s está ausente" - "%1$s está ocupado" - "%1$s está off-line" - "%1$s aderiu" - "%1$s foi-se embora" - "'Enviado às 'hh':'mm' 'a' em 'EEEE" - "Enviar" - "Não foi possível enviar a mensagem." - "Perdeu-se a ligação ao servidor. As mensagens serão enviadas quando estiver on-line." - "%1$s está off-line. As mensagens que enviar serão entregues quando %1$s estiver on-line." - "%1$s não consta da sua lista de contactos." - "Seleccionar link" - "Não há chats activos." - "Adicionar contacto" - "Endereço de e-mail da pessoa que pretende convidar:" - "Seleccione uma lista:" - "Escreva um nome para adicionar a partir dos contactos." - "Enviar convite" - "Definições gerais" - "Ocultar contactos off-line" - "Definições de notificação" - "Notificações de MI" - "Notificar na barra de estado quando chega uma MI" - "Vibrar" - "Vibrar também quando chega uma MI" - "Som" - "Tocar também quando chega uma MI" - "Seleccionar toque" - "%1$s convidou-o para aderir a um chat em grupo." - "O convite foi enviado para %1$s." - "Aceitar" - "Recusar" - "%1$s convidou-o para fazer parte da lista de contactos." - "Aceitar" - "Recusar" - "Não foi possível aprovar a subscrição de %1$s. Tente novamente mais tarde." - "Não é possível recusar a subscrição de %1$s. Tente novamente mais tarde." - "Novas mensagens de %1$s" - "%1$d chats não lidos" - "Novo convite de amigo enviado por %s" - "Convite para chat em grupo" - "Novo convite para chat em grupo de %s" - "Contacto \"%1$s\" adicionado." - "Contacto \"%1$s\" eliminado." - "Contacto \"%1$s\" bloqueado." - "Contacto \"%1$s\" desbloqueado." - "iniciar serviço de MI" - "Permite que as aplicações iniciem o serviço de MI por finalidade." - "Atenção" - "Não foi possível iniciar sessão no serviço %1$s. Tente novamente mais tarde. "\n"(Detalhe: %2$s)" - "A lista não foi adicionada." - "O contacto não foi bloqueado." - "O contacto não foi desbloqueado." - "Seleccione primeiro um contacto." - "Desligado! "\n - "Erro no serviço!" - "A lista de contactos não foi carregada." - "Não é possível estabelecer ligação ao servidor. Verifique a sua ligação." - "%1$s já consta da sua lista de contactos." - "O contacto \"%1$s\" foi bloqueado." - "Aguarde enquanto a lista de contactos é carregada." - "Ocorreu um erro na rede." - "O servidor não suporta esta funcionalidade." - "A palavra-passe introduzida não é válida." - "O servidor encontrou um erro." - "O servidor não suporta esta funcionalidade." - "De momento, o servidor não está disponível." - "O servidor excedeu o limite de tempo." - "O servidor não suporta a versão actual." - "A fila de mensagens está cheia." - "O servidor não suporta reencaminhamento para o domínio." - "O nome de utilizador introduzido não é reconhecido." - "Lamentamos, mas foi bloqueado pelo utilizador." - "A sessão expirou. Inicie sessão novamente." - "iniciou sessão a partir de outro cliente." - "já tem sessão iniciada com outro cliente." - "Lamentamos, mas não é possível ler o número de telefone a partir do cartão SIM. Contacte o seu operador para obter ajuda." - "De momento não tem sessão iniciada." - "Erro de código %1$d" - - "Feliz" - "Triste" - "A piscar o olho" - "Língua de fora" - "Surpreendido" - "Beijoqueiro" - "A gritar" - "Fixe" - "Interesseiro" - "Arrependido" - "Envergonhado" - "Anjo" - "Indeciso" - "Chorão" - "Boca fechada" - "Risonho" - "Confuso" - -mensagens instantâneas - Permite que os aplicativos para leitura de dados do provedor de conteúdo de mensagens instantâneas. - escrever mensagens instantâneas - Permite que os aplicativos para gravar dados para o provedor de conteúdo de mensagens instantâneas. - Chat - Seleccione uma conta - Adicionar conta - Editar conta - Remover conta - Sair de todas as - Chat - Seleccione uma conta - Pesquisar Contactos - Iniciar Criptografia - Parar de criptografia - Clear Chat - Digitalização da impressão digital - Sua impressão digital - Verifique Fingerprint - Você quer sair todos os serviços? - canal de dados - codificação de dados - canal CIR - Host - MSISDN - HTTP - SMS - TCP - XML - WBXML - Guardar - - Feliz - Triste - Piscar - Língua de fora - Surpreendido - Beijar - Gritar - Cool - boca Dinheiro - Pé na boca - Embaraçado - Angel - Indeciso - Chorando - Lábios estão selados - Rir - Confuso - - Para sua segurança, se o telefone for perdido ou roubado, vá para o site no seu computador e alterar sua senha. - Lista de Contactos - Criptografar On/Off - Conecte-se através do TOR (Requer app Orbot) - Gibberbot - Primeira vez usando Gibberbot? - Can\'t esperar para começar? - Sobre Gibberbot - Configuração de Conta - Bem-vindo ao Gibberbot - Gibberbot é um aplicativo de mensagens instantâneas móveis, que fornece recursos de segurança extra que impede que outras pessoas bisbilhotando suas conversas e app communications.\n\nThe suporta qualquer serviço de chat que utiliza o protocolo Jabber ou XMPP, como o Google ou GTalk Jabber.org. - Como é seguro? - \'Off-the-record Messaging\ "é um sistema de segurança destinado a permitir a privacidade, simulando as características de uma conversa privada no mundo real, incluindo criptografia, autenticação e Deniability Secrecy.\n\nThe Forward OTR protocolo é compatível com desktops clientes de chat como Adiumou o Pidgin. - Are My Secure Bate-papo? - Gibberbot\ recurso de criptografia da conversa só funciona quando mensagens com outras pessoas usando um aplicativo ou programa compatível, portanto você deve assegurar os seus contatos estão usando Gibberbot para celular e Adium e Pidgin para o desktop. Você pode ajustar exatamente como e quando Gibberbot tentar criptografar as conversas em Acos Settings.\n\nLet\ unt \'começar! - Instalação Frase - Antes de começar, por favor escolha uma senha segura para proteger os seus dados de acesso Gibberbot injusto. - Passphrase: - Frase (novamente) : - Conta - Nome da Conta - Mostra o nome para essa conta - Nome de usuário (login) - Format: usuário host:port @ - Senha - Senha para autenticação - Persistência - Lembrar senha - Senha cache - A senha não cache - Entrar automaticamente - Ligue na Gibberbot start-up - Don\'t em contato Gibberbot start-up - Entrar Agora - Ligue configuração da conta seguinte - Don\'t contato seguinte configuração de conta - Pessoal (facultativo) - Conta Alias (seu nome) - Como aparece a sua conta online - Perfil - Uma breve sinopse sobre si mesmo - Avançado - Força a criptografia / plaintext recusar - Quando possível, chats criptografar automaticamente - Criptografar chats, conforme solicitado - Desativar bate-papo de criptografia - Validando credenciais ... - Gerar par de chaves ... - Assinatura polegadas .. - Gibberbot encontrou um erro ao validar o seu nome de usuário e senha - verifique-os e tente novamente. - Gibberbot encontrou um erro ao gerar um par de chaves. - Gibberbot encontrou um erro ao se conectar ao servidor de chat - por favor, verifique sua configuração e tente novamente. - Gibberbot encontrou um erro durante a conexão - por favor doublecheck sua conectividade de rede e tente novamente. - Sua impressão digital - Sua impressão digital - Entrar - Regen-chave - Gibberbot perdeu uma ligação à rede - Gibberbot está a tentar restabelecer uma ligação - Warning: Este bate-papo não são criptografados - Este chat é seguro, mas as identidades dos participantes não foram verificados - Warning: criptografia de bate-papo foi interrompido. - Este chat é garantido e verificou - Assistente de Contas - O ID da conta - Configurar o servidor - Are You Ready? - Digite o seu ID da conta para configurar automaticamente para você Gibberbot chat service.\n\nIt provavelmente se parece com um endereço de e-mail - para yourname@gmail.com ou someone@jabber.org, por exemplo. - Digite o seu ID da conta (user @ hostname) : - Por favor, introduzir ou editar sua jabber/xmpp chat nome do servidor eo número da porta (5222 é o padrão). - Gibberbot foi configurado, e agora é hora de se conectar ao seu serviço, e começar a conversar de forma segura, segura e privada! - Digite seu password: - diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml index 2484b722b..c82eaf25b 100644 --- a/res/values-pt/arrays.xml +++ b/res/values-pt/arrays.xml @@ -1,9 +1,9 @@ - + - - Força / Exigir - Tentar automaticamente - Conforme solicitado - / Nunca condicionada - + + Força / Exigir + Tentar automaticamente + Conforme solicitado + / Nunca condicionada + diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 4d5c3dbc7..78928aeff 100755 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -1,362 +1,458 @@ - - - - - - - - - - - - - - - Cancelar login - - - Adicionar contato - - Excluir contato - - Bloquear contato - - Bloqueado - - Lista de contas - - - - Configurações - - - Iniciar bate-papo - - Sair - - Visualizar perfil - - - Encerrar bate-papo - - - Lista de contatos - - Convidar… - - Alternar bate-papos - - Inserir smiley - - - Menu+ - - - + + + ChatSecure + ChatSecure + + lê mensagens instantâneas + Permite que as aplicações leiam os dados do provedor de conteúdo de Mensagem Instantânea + escreve mensagens instantâneas + Permite que as aplicações gravem dados para o provedor de conteúdo de Mensagem Instantânea + inicia o serviço de Mensagem Instantânea + Permite que as aplicações iniciem o serviço de Mensagem Instantânea via Internet. + Confirmar - - - - - - - - - OK - + ACEITAR Cancelar - - OK - + ACEITAR Cancelar - - - - - - - - - - Nome de usuário: - + Seguinte + Voltar + Conetar + Reproduzir + Configurar + + Abrir + ChatSecure Bloqueada + Definir Senha + Contrassenha + Senha + Pode opcionalmente definir uma senha mestra para a ChatSecure poder prevenir o acesso aos seus contactos e mensagens sem uma senha: + Confirmar Nova Senha + As senhas não correspondem, por favor, tente de novo + Ignorar >> + Janela de Informação + Por favor, Insira Senha + Por favor, senha… + Chave-mestra + Chave-mestra (novamente) + + Selecione uma conta + Selecione uma conta + (%1$d) + Adicionar conta %1$s + Sobre + Terminar sessão de todas as contas + Deseja terminar a sessão de todos os serviços? + A sua sessão da conta %1$s.foi terminada + A sua sessão da conta %1$s foi terminada porque %2$s + É a primeira vez que utiliza ChatSecure? + Não consegue esperar para começar? + Iniciação + Configuração da Conta + Configurar Chave-mestra + Antes de começar, por favor, escolha uma chave mestra segura para proteger os seus dados na ChatSecure de acessos indesejáveis. + Chave-mestra: + Chave-mestra (novamente): + Insira uma *nova* chave-mestra. Esta deverá conter pelo menos uma letra Maiúscula, uma letra Minúscula e um número, e ter mais de 6 carateres. + A encriptar as suas notas existentes com a nova chave-mestra (paciência…) + Insira a sua chave-mestra: + Bem-vindo! Insira uma chave-mestra forte para proteger as suas notas. Esta deve ter pelo menos uma letra maiúscula, uma letra minúscula e um número, e conter mais de 6 carateres. + A sua chave-mestra era inferior a 6 carateres + A sua chave-mestra não continha qualquer letra maiúscula + A sua chave-mestra não continha qualquer letra minúscula + A sua chave-mestra não continha qualuer número + + Sobre ChatSecure + ChatSecure é uma app de mensagem instantânea móvel que proporciona funções de segurança extra que impede que os outros metam o nariz nas suas conversações e comunicações.\n\nA app suporta qualquer serviço de conversação que use o protocolo Jabber ou XMPP, tal como, Google GTalk ou Jabber.org, + + Como é que esta é segura? + + As minhas conversações são seguras? + + Nova Conta + Adicionar Conta + Editar Conta + Remover Conta + Conta Existente + + Utilizador@Hospedeiro Senha: - - Lembrar minha senha. - - Fazer login automaticamente. - - Ainda não tem uma conta? - - Esta opção faz login automaticamente sempre que você abrir este aplicativo. Para desativá-la, saia e desmarque a caixa de seleção \\"Fazer login automaticamente\\". - - Fazer login - - - - Fazendo login... - - - Os dados de segundo plano estão desativados - - - - - - Ativar - - Sair - - - - - - + Lembrar a minha senha. + Iniciar a minha sessão automaticamente. + Ainda não possui uma conta? + Iniciar Sessão + Para a sua segurança, se o seu dispositivo for perdido ou roubado, vá até ao sítio da Web no seu computador e altere a sua senha. + Esta opção inicia a sua sessão automaticamente sempre que abrir esta aplicação. Para desativar esta opção, termine a sessão, e desmarque a caixa de seleção \\"Iniciar a minha sessão automaticamente\\". + Conetar via Tor (Requer a app Orbot) + utilizador@domínio + novo nome de utilizador + provedor de serviço (dukgo.com, jabber.ccc.de) + senha + contrassenha + Definições de Conta Avançadas + Configurar Conta + Registar Conta + Persistência + Lembrar Senha + Senha colocada em cache + A senha não foi colocada na cache + Iniciar Sessão Automaticamente + Conetar à ChatSecure no arranque + Não conetar à ChatSecure no arranque + Iniciar Sessão Agora + Conete seguindo a configuração da conta + Não conete seguindo a configuração da conta + Pessoal (opcional) + Pseudónimo da Conta (o seu nome) + Como a sua conta aparece on-line + Perfil + Um pequeno resumo sobre si + Forçar encriptação / recusar texto simples + Quando possível, encriptar automaticamente as conversas + Encriptar as conversas como solicitado + Desativar a encriptação da conversa + A validar as credenciais… + A gerar a chave… + A iniciar a sessão… + Assistente de Conta + A sua Id. da Conta + Configurar Servidor + Está preparado? + Insira a sua Id. da conta para configurar a ChatSecure para o seu serviço de conversação XMPP. Este parece-se com um endeço de correio eletrónico: + Por favor, insira a seu Id. da conta (utilizador@nome do domínio): + Por favor, insira ou edite o nome do hospedeiro do seu servidor de conversação jabber/xmpp e o números da porta (5222 é a predefinida). + ChatSecure foi configurada, e agora é hora de conetar ao seu serviço, e começar a conversar com segurança e privadamente! + Orbot (Tor) + Domínio do serviço de conversação + A registar a conta nova… + a continuar… + Cancelar início da sessão + A iniciar a sessãon\u2026 + A terminar a sessão\u2026 + + Verifique se o certificado é de confiança + Verificação TLS + Requer Conexão TLS + Encriptação de Transporte + como é que as conversas encripatadas são iniciadas + O servidor para se conetar, se necessário + Conetar Servidor + Porta TCP para o Servidor XMPP + Porta do Servidor + Recurso XMPP + para distinguir esta conexão dos outros clientes que também iniciaram a sessão + Prioridade de Recurso XMPP + As mensagens para clientes com múltiplos recursos ativos serão enviadas para o recurso com a prioridade mais elevada + + Conversações + Sem conversações.\n\nToque aqui para iniciar uma! + Não tem\ncontas configuradas.\n\nToque aqui para adicionar uma! + Lista de Contactos - %1$s + Adicionar Contacto + Apagar Contacto + Bloquear Contacto + Apelido do Contacto + Bloqueado + Apelido + O contacto \"%1$s\" irá ser apagado. + O contacto \"%1$s\" irá ser bloqueado. + O contacto \"%1$s\" irá ser desbloqueado. + Contato \"%1$s\" adicionado. + Contacto \"%1$s\" apagado. + Contato \"%1$s\" bloqueado. + Contato \"%1$s\" desbloqueado. + Nova Conversa + Procurar Contactos + Mostrar Grelha + Iniciar conversa + Ver Perfil + Verificar Contacto + Conversas em curso (%1$d) + %1$d on-line Convites de amigos - (\"\"Desconhecido\"\") - Vazio - - Sem conversas - - Selecione os contatos para enviar convite - Digite para encontrar o contato - Nenhum contato foi encontrado. - - - - Nenhum contato bloqueado. - - - Perfil do contato - - Status: - + Sem conversações abertas\n\nToque para inciar a conversa! + Contacto + digite o nome de um contacto para conversar + Avançar + Sem conversas ativas. + Adicionar contacto + Nome de utilizador ou JabberID da pessoa para adicionar: + Conta para adicionar a: + Digite um nome para adicionar a partir dos Contactos. + Enviar Convite + Perfil do Contacto + Estado:: Tipo de cliente: - Computador - - Celular - - - On-line - - Ocupado(a) - - Ausente - - Ocioso - - Off-line - - Aparecer off-line - - - - - + Dispositivo Móvel + Contactos bloqueados - %1$s + Sem contactos bloqueados. + + Converse com %1$s Eu - - Digite para escrever - - - - - - - - - - - - - - - - + %1$s está on-line + %1$s está ausente + %1$s está ocupado(a) + %1$s está off-line + %1$s entrou + %1$s saiu + Enviar Fotografia + Enviar Ficheiro + Enviar Áudio + Tirar Fotografia + Transferência de Ficheiro + Transferência Concluída + Transferência em progresso + Aceitar transferência? + pretende enviar-lhe o ficheiro + Sem ligação disponível para enviar a sua partilha! Enviar - - Não foi possível enviar a mensagem. - - A conexão com o servidor foi perdida. As mensagens serão enviadas quando a conexão for recuperada. - - - - - + Envia uma mensagem. + Envie uma mensagem segura + Reenviar + Terminar conversação + Limpar conversação + Inserir smiley + Alternar conversações + Menu+ Selecionar link - - Nenhum bate-papo ativo. - - - - Adicionar contato - - O endereço de e-mail da pessoa que você deseja convidar: - - Escolha uma lista: - - Digite um nome para adicionar a partir de Contatos. - - Enviar convite - - + Apagar + Manter Ficheiros + Apagar armazenamento seguro da sessão da conversação? + Todas as sessões e os ficheiros enviados serão apagados para sempre. Aviso: esta operação não pode ser anulada! + Apagar original? + Manter + Exportar + Exportar o ficheiro de multimédia? + Este ficheiro de multimédia será exportado para %1$s + Terminar conversação? + Terminar Conversa e Apagar Ficheiros + + %1$s convidou-o para se juntar a uma conversa em grupo. + O convite foi enviado para %1$s. + Aceitar + Recusar + Conversa em Grupo + Criar ou Aderir a Um Conversa em Grupo + A conetar a uma conversa em grupo… + Convidar… + Selecione os contatos para enviar convite + Digite para encontrar o contato + Contactos não encontrados. + +Clique para convidar. + Adicionar %1$s? + Sim + Não + Não é possível aprovar a inscrição de %1$s. Tente novamente mais tarde. + Não é possível recusar a inscrição de %1$s. Tente novamente mais tarde. + + Iniciar Criptografia + Interromper Criptografia + A iniciar a sessão da conversa encriptada… + A parar a sessão da conversa encriptada… + Assinatura Digital de Segurança + Assinatura Digital de Segurança + Iniciar Sessão + Regenerar Chave + Encriptação desligada + Encriptação ligada (Clique para verificar) + O seu contacto terminou a conversa encriptada. + Encriptada e verificada! + A gerar o novo par de chave OTR… + + Ativar Sincronização de Chave + + Digitalizar QR + Seu Fingerprint + Manual + Questão + Assinatura Digital de Segurança (Verificada) + Tem a certeza que deseja confirmar esta assinatura digital? + Verificar assinatura digital? + A assinatura digital remota foi verificada! + Assinatura digital para si + Assinatura digital para + Instalar o digitalizador de código de barras? + Esta aplicação requer Digitalizador de Código de Barras. Deseja instalá-lo? + + Autenticação + Insira uma questão para o seu contacto e a resposta que você espera de retorno, de forma a poder verificar a sua identidade. + a pergunta a colocar + a resposta esperada + O seu contacto autenticou-o com sucesso. Agora autentique o seu contacto, efetuando a sua própria pergunta. + OTR e Verificação + Encriptação da Conversa + Enviar + Cancelar + + Chamada Segura + Voz Segura + + Configurar Conta + + Segurança e Privacidade + Criptografia e Anonimato + Criptografia Ligada/Desligada + Tempo da Senha Expirado + + Hiperligações clicáveis no Tor + Para as contas que utilizam Tor, torne as hiperligações clicáveis nas conversas (AVISO, possivelmente uma fuga de privacidade!) + Interface do Utilizador + Língua + Línguas + Usar predefinição do sistema + Usar Tema Escuro + Alterar o tema da app para escuro + Imagem de Fundo + Mostrar Grelha do Contacto + Exibir a lista de contacto como grelha de avatar + Sim, Aceitar Tudo + Apagar Multimédia Insegura + Guardar Multimédia no Armazenamento Externo + + Armazenamento Externo em Falta + Armazenamento de Multimédia da Conversação Em Falta + Apagar Registo da Conversação + + Outros Ajustes + Iniciar Automaticamente a ChatSecure + Iniciar sempre e iniciar a sessão automaticamente nas contas em que já foram iniciadas a sessão Ocultar contatos off-line - + Usar prioridade de primeiro plano + Configurações de notificação - Notificações de mensagens instantâneas - Notificar na barra de status quando chegar uma mensagem instantânea - Vibrar - Também vibrar quando chegar uma mensagem instantânea - Som - Também reproduzir o toque ao chegar uma mensagem instantânea - - Selecionar toque - - - - - - - Aceitar - - Recusar - - - - Aceitar - - Recusar - - - - - - - - - - - - - Convite para bate-papo em grupo - - - - - - - - - - - - iniciar serviço de mensagem instantânea - Permite que o aplicativo inicie o serviço de mensagem instantânea por meio da internet. - - + Seleccione ConversaSegura toque personalizado. + + Ativar Registos de Depuração + + Os dados de segundo plano estão desativados + É necessário a ligação de dados da rede (incluindo os dados em segundo plano) para que a app inicie a sessão. + Ativar + Sair + Deseja terminar a sessão em todos os serviços E terminar todos os processos (saída forçada)? + Criar nova conta? + Criar uma nova conta de conversação para o nome de utilizador \'%1$s\'? + Informação do Certificado + Certificado: + Publicado por: + Asssinatura Digital SHA1 + Emitida: + Expira: + Juntar-se à Sala de Conversação? + Uma app externa está a tentar conetá-lo para uma sala de discussão. Permitir? + Escolher Fundo + Deseja selecionar uma imagem de fundo da Galeria? + Selecionar Imagem + + Novas mensagens do %1$s + %1$d conversas não lidas + Novo convite de amizade de %s + Novo ficheiro %1$s de %2$s + Convite para conversa em grupo + Novo convite de conversação em grupo de %s + Nova mensagem(s) de + A iniciar o serviço de Conversa Segura… + Ativar e Desbloquear + mensagem copiada para a área de transferência + Atenção - - - + Erro: + Código do erro %1$d + Não é possível fazer login no serviço %1$s. Tente novamente mais tarde."\n"(Detalhe: %2$s) A lista não foi adicionada. - O contato não foi bloqueado. - O contato não foi desbloqueado. - Selecione um contato primeiro. - \"Desconectado!\"\n - Erro de serviço! - A lista de contatos não carregou. - Não é possível se conectar ao servidor. Verifique a sua conexão. - - - - - + %1$s já existe na sua lista de contatos. + O contato \"%1$s\" foi bloqueado. Aguarde enquanto a sua lista de contatos carrega. - Ocorreu um erro de rede. - + É necessário a Rede Sem Fios para esta ligação O servidor não suporta este recurso. - A senha digitada não é válida. - O servidor encontrou um erro. - O servidor não suporta este recurso. - O servidor não está disponível no momento. - O tempo limite do servidor esgotou. - O servidor não suporta a versão atual. - A fila de mensagens está cheia. - O servidor não suporta o encaminhamento para o domínio. - O nome de usuário digitado não é reconhecido. - Desculpe, você está bloqueado pelo usuário. - A sessão expirou. Faça login novamente. - você fez login a partir de outro cliente. você já fez login a partir de outro cliente. - Desculpe, não é possível ler o número de telefone do seu cartão SIM. Entre em contato com a sua operadora para obter ajuda. - No momento você não está conectado. - - - + A rede está desligada + ChatSecure está a tentar reastabelecer a conexão + Não inseriu uma parte do @hostname.com para a sua Id. da conta. Tente novamente! + Insira a sua senha: + AVISO: Este serviço usa um certificado com criptografia WEAK. Por favor, peça ao administrador para o atualizar. + Não é possível criar ou aderir ao grupo de conversação + Desculpe, nós não podemos compartilhar esse tipo de ficheiro + Deve ativar a encriptação para compartilhar ficheiros + Por favor, ative a encriptação para compartilhar ficheiros + OS dados a receber não são suportados, não é possível compartilhar! + Não existe um visualizador para este formato de ficheiro + Por favor, inicie uma conversação segura antes de digitalizar os códigos + Não foi possível enviar a mensagem. + Mensagens serão enviadas na reconexão. + %1$s está off-line. As mensagens enviadas serão entregues quando %1$s estiver on-line. + %1$s não está na sua lista de contatos. + As suas senhas não correspondem + Prioridade deve ser um número entre [0 .. 127] + O número da porta deve ser um número + Erro de Transferência + Não é possível ler o ficheiro para o armazenamento + Não tem nenhuma app instalada para lidar com esta hiperligação! + Definições da Conta + + Grupos + conversações abertas + Encerrar e Bloquear + Pánico + Amigos + Contactos + Aceitar certificado do servidor? + Exibir A Sua Assinatura Digital + a carregar… + + Sobre a ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Lista de Contatos + Configurações + Lista de contas + Sair + Lista de contatos + + Jabber (XMPP) + Área Local (Bonjour/ZeroConf) + Conta Google + dukgo.com + + + On-line + Ocupado(a) + Ausente + Ocioso + Off-line + Aparecer off-line + Feliz Triste @@ -376,7 +472,7 @@ Rindo Confuso - + Feliz Triste @@ -396,21 +492,50 @@ Rindo Confuso - - - - - - - - - - - - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Conta Existente + Conetar à minha conta existente num servidor Jabber / XMPP específico. + Conta Google + Converse com os outros utilizadores da Google, usando a sua conta Google existente. + Conversa em Rede e Rede Sem Fios + Converse com os outros na mesma rede Sem Fios local ou rede - não é necessário a Internet ou servidor! + Ativar Conversa na Rede Sem Fios + Nova Conta + Registe uma conta nova e gratuita num serviço a partir da nossa lista incorporada, ou qualquer uma à sua escolha. + Criar Nova conta + Identidade Secreta! + Crie uma conta de conversação \"burner\" anónima, descartável com um único toque (requer Orbot: Tor para Android) + Gerar Identidade + Isto é uma conversa em grupo + [reenviar] + [reenviar] + Não é possível ciopartilhar este ficheiro com segurança + Instalar Orbot? + Deve instalar a Orbot e ativar o tráfego do proxy através do mesmo. Deseja instalá-la? + Sempre + Iniciar Orbot? + apelido para usar na sala + nome da sala a criar ou a aderir\" + servidor do grupo de conversação (conference.foo.com) + Recebeu uma mensagem encriptada ilegível + Não foi possível descodificar a mensagem que enviou + < deslize para a esquerda e direita para mais opções > diff --git a/res/values-ro/arrays.xml b/res/values-ro/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-ro/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml new file mode 100644 index 000000000..f8d10dd71 --- /dev/null +++ b/res/values-ro/strings.xml @@ -0,0 +1,81 @@ + + + + + citeste mesaje + Permite aplicatiilor sa citeasca date de la furnizorul de MI + scrie mesaje + porneste serviciul de mesagerie + + Confirma + OK + Anuleaza + OK + Anuleaza + Urmatorul + Precedentul + Conecteaza-te + Reda + Seteaza + + Deschide + Alege Parola + Confirma Parola + Parola + Optional, poti alege o parola principala pentru ChatSecure, ca sa previi accesul la contacte si mesaje fara o parola: + Confirma Parola Noua + Parolele nu coincid, mai incearca o data + Sari peste >> + Informatie + Te rugam introdu parola + Parola, te rugam… + + Alege un cont + Alege un cont + (%1$d) + Adauga cont %1$s + Despre + Deconecteaza-te de la toate serviciile + Doresti sa te deconectezi de la toate serviciile? + Ai fost deconectat de la %1$s. + Folosesti ChatSecure pentru prima data? + Abia astepti sa incepi? + Incepe + Configurarea Contului + Configurarea Parolei + Inainte de a incepe, te rog alege o parola ca sa protejezi ChatSecure impotriva accesului neautorizat. + + + + + + Configurarea Contului + + + + + + + + + Anuleaza + + + Configurarea Contului + + + + + + + + + + + + + + + + + diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml index 40b4505e3..27621b6d8 100644 --- a/res/values-ru/arrays.xml +++ b/res/values-ru/arrays.xml @@ -1,15 +1,9 @@ - Группа / Требовать - Автоматически пытается - В соответствии с просьбой - Инвалидов / Никогда - - - усилие - авто - запрошенный - бездействующий + Принудительно / По требованию + Попытаться автоматически + В соответствии с запросом + Отключено / Никогда diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 6c752130c..1cabcfa0c 100755 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -1,397 +1,434 @@ - + + + ChatSecure + ChatSecure + читать мгновенные сообщения - Позволяет приложениям для чтения данных из контент-провайдер мгновенных сообщений. + +Позволить приложениям чтение данных из мгновенных сообщений контент-провайдера. написать мгновенные сообщения - Позволяет приложениям для записи данных на контент-провайдер мгновенных сообщений. - - - + +Позволить приложениям запись данных в мгновенные сообщения контент-провайдера. + Включить чат + Разрешает приложениям включать чат через Intent. + + Подтвердить + ОК + Отмена + ОК + Отмена + Далее + Назад + Подключение + + Открыть + ChatSecure заблокирован + Установить Пароль + Подтверди Пароль + Пароль + Новый Пароль + Подтверди Новый Пароль + Кодовая фраза не совпадает, попробуйте еще раз + Я Ленивый (Без пароля!) + Чат - выберите учетную запись - + Чат - выберите учетную запись + (%1$d) + Добавить аккаунт %1$s О программе - + Выйти из всех учетных записей + Вы хотите выйти из всех учетных записей? + Вы вышли из службы: %1$s. + Первый раз используете ChatSecure? + Can\'t ждать, чтобы начать? + О ChatSecure + Создание учетной записи + Ключевая фраза установки + Прежде чем начать, пожалуйста введите кодовую фразу, чтобы защитить ваш ChatSecure от случайного доступа. + Ключевая фраза: + Ключевая фраза (еще раз) : + Введите *новый * пароль. Он должен содержать не менее одну заглавную, одну строчную и одну цифру, и быть длиннее чем шест символов. + Зашифрованием существующие заметки с новым паролем (пожалуйста, подождите) + Ввелите кодовое слово: + Добро пожаловать! Введите надежный пароль для обеспечения вашей заметки. Он должен содержать не менее одну заглавную, одну строчную и одну цифру, и быть длиннее чем шест символов. + Ваш пароль не был достаточно длинным + Ваш пароль не содержал заглавные буквы + Ваш пароль не содержал строчные буквы + Ваш пароль не содержал цифры + + О программе ChatSecure + ChatSecure - это программа обмена мгновенными сообщениями, которая обеспечивает повышенную безопасность переписки, скрывая ваше общение от постороннего доступа. + +Программа работает со всеми службами, использующими протокол Jabber или XMPP, в том числе Google Talk и Jabber.org. + + Насколько это безопасно? + Off-the-Record Messaging - протокол шифрования, который обеспечивает конфиденциальность в сетях обмена мгновенными сообщениями, заимствуя характеристики частных бесед в реальном мире. Он включает в себя шифрование, аутентификацию собеседников, отрицание и совершенную прямую секретность. Протокол OTR может использоваться в клиентах обмена мгновенными сообщениями для персональных компьютеров, таких как Adium и Pidgin. + + Безопасны ли мои разговоры? + Функция шифрования чатов в ChatSecure работает только, если у собеседника тоже установлена программа, которая ее поддерживает, поэтому убедитесь, что они используют ChatSecure на смартфоне и Adium или Pidgin на компьютере. В разделе Настройки можно выбрать, как и когда ChatSecure будет шифровать ваши беседы. + +Давайте начнем! + + Новая учетная запись Добавить учетную запись - Изменить учетную запись - Удалить учетную запись - - Выйти из всех - - - Чат - выберите учетную запись - - - + + Имя пользователя: + Пароль: + Запомнить пароль. + Входить автоматически. + У вас нет учетной записи? + Вход + Для вашей безопасности, если ваш телефон потерян или украден, зайдите на веб-сайт с компьютера и измените пароль доступа. + Автоматический вход в систему при запуске приложения. Чтобы отключить этот параметр, выйдите из системы, а затем снимите флажок \"Входить автоматически\". + Подключение через Tor (требуется установленное приложение Orbot) + user@domain.com + новый пользователь + сервис-провайдер (dukgo.com, jabber.ccc.de) + пароль + подтвердить пароль + Расширенные настройки учетной записи + Тип учетной записи + Зарегистрировать аккаунт + Настойчивость + Запомнить пароль + Пароль сохранен в кэше + Пароль не сохранен в кэше + Автоматический вход в систему, + Подключать ChatSecure при запуске + Не подключать ChatSecure при запуске + Войдите в систему + Подключите следующие настройки учетной записи + Don\'t связаться следующие настройки учетной записи + Личный (опционально) + Псевдоним (ваше имя) + Как вас будут видеть другие пользователи + Профиль + Немного о себе + Принудительное шифрование / не передавать открытым текстом + Когда это возможно, шифровать чаты автоматически + Шифрование чатов с просьбой + Не шифровать чаты + Проверка полномочий … + Создание пары ключей … + Подписание дюйма .. + Мастер учетных записей + Ваш аккаунт ID + Настройка сервера + Готовы ли вы? + Введите идентификатор учетной записи для автоматической настройки ChatSecure для ваш XMPP чат сервис. Вероятно, выглядит как адрес электронной почты: + Пожалуйста, введите Ваш идентификатор учетной записи (пользователь @ имя хоста) : + Пожалуйста, введите или измените jabber/xmpp чат имя сервера и номер порта (5222 по умолчанию). + ChatSecure была настроена, и теперь пришло время для подключения к вашим услугам, и начинайте общаться безопасно, надежно и конфиденциально! + Orbot (Tor) + Выбрать домен + Создание новой учетной записи… + запуск ChatSecure… Отмена входа - - + Выполняется вход… + Выполняется выход…\u2026 + + Поиск SRV записи + Использовать DNS SRV для определения XMPP из доменного имени + Разрешать передачу имени пользователя и пароля открытым текстом, если не используется протокол защиты транспортного уровня + Разрешать авторизацию открытым текстом (без шифрования) + Проверить доверие к сертификату + Проверка TLS + Требуется TLS-соединение + Защита транспортного уровня + как начинаются шифрованные чаты + Сервер для соединения, если он нужен + Соединиться с сервером + TCP порт сервера XMPP + Порт сервера + Ресурс XMPP + чтобы выделить соединение из других клиентов, которые тоже в сети + Приоритет ресурса XMPP + Сообщения собеседникам, у которых активно несколько клиентов, будут переданы в клиент с самым высоким приоритетом + + Беседы + Нет разговоров. + +Нажмите здесь, чтобы начать один! + У Вас нет настроенных⏎ +аккаунтов.⏎ +⏎ +Нажмите здесь для добавления! + Список Контактов - %1$s Добавить контакт - Удалить контакт - Заблокировать контакт - + прозвище контакта Заблокированные - - Список учетных записей - + Контакт \%1$s" будет удален. + Контакт \%1$s" будет заблокирован. + Контакт \%1$s" будет разблокирован. + Контакт \%1$s" добавлен. + Контакт \%1$s" удален. + Контакт \%1$s" заблокирован. + Контакт \%1$s" разблокирован. Новый чат - - Новая учетная запись - - Настройки - Поиск в списке контактов - + Показать Сетку Начать чат - - Выход - - Просмотреть профиль - - - Начать шифрование - Прекратить шифрование - Очистить чат - Закончить чат - - - Список контактов - - Пригласить пользователя... - - Сменить чат - - Вставить смайлик - Отправить заново - - Сканирование отпечатков пальцев - Отпечатки пальцев - Проверка отпечатков пальцев - Проверка по секрету - - Мenu+ - - - - Подтвердить - - Вы хотите выйти из всех учетных записей? - - - - - - - - ОК - - Отмена - - ОК - - Отмена - - - - - - - - - - Имя пользователя: - - Пароль: - - Запомнить пароль. - - Входить автоматически. - - У вас нет учетной записи? - - Для вашей безопасности, если ваш телефон потерян или украден, зайдите на веб-сайт с компьютера и измените пароль доступа. - Автоматический вход в систему при запуске приложения. Чтобы отключить этот параметр, выйдите из системы, а затем снимите флажок \"Входить автоматически\". - - Вход - - - - Выполняется вход... - - - Исходные данные отключены - - - - - - Включить - - Выйти - - - - - - + Просмотр профиля + Проверить ключ + Текущих чатов: (%1$d) + Пользователей в сети: %1$d Приглашения от друзей - (Неизвестный) - Пусто - Нет разговоров - - Выберите пользователя(ей), которого вы хотите пригласить - Введите текст для поиска контакта - Контакты не найдены. - - - - Нет заблокированных контактов. - - + Контакт + введите имя собеседника + Вперед + Нет активных чатов. + Добавить контакт + Адрес электронной почты человека, которого вы хотите пригласить: + Выберите список: + Чтобы добавить абонента из списка контактов, укажите его имя. + Отправить приглашение Профиль контакта - Статус: - Тип клиента: - Компьютер - Мобильное устройство - - - В сети - - Не беспокоить - - Отсутствует - - Нет на месте - - Не в сети - - Невидимый - - - - - + Заблокированные контакты – %1$s + Нет заблокированных контактов. + + Чат с пользователем %1$s Я - - Введите текст - - - - - - - - - - - - - - - - + Пользователь %1$s в сети + Пользователь %1$s отсутствует + Пользователь %1$s занят + Пользователь %1$s не в сети + Пользователь %1$s присоединился к чату + Пользователь %1$s покинул чат + Отправить Фото + Отправить файл + Отправить Аудио + Сделать снимок + Передача Файлов + Передача Завершена + Происходит передача + Подтвердить передачу? + хочет отправить вам файл Отправить - - Не удалось отправить сообщение. - - Соединение с сервером потеряно. Сообщения будут отправлены после входа в сеть. - - - - - + Отправить сообщение + Отправить защищенное сообщение + Отправить заново + Закончить чат + Очистить чат + Вставить смайлик + Сменить чат + Мenu+ Выберите ссылку - - Нет активных чатов. - + Удалить + + Пользователь %1$s приглашает вас в групповой чат. + Приглашение отправлено пользователю %1$s. + Принять + Отклонить + Групповой чат + Создать или присоединиться к групповому чату + Подключение к групповому чату… + Пригласить пользователя… + Выберите пользователя(ей), которого вы хотите пригласить + Введите текст для поиска контакта + Контактов не найдено.⏎ +⏎ +Нажмите для добавления. + Да + Нет + Не удалось одобрить запрос подписки от пользователя %1$s. Повторите попытку позже. + Не удалось отклонить запрос подписки от пользователя %1$s. Повторите попытку позже. + + Начать шифрование + Прекратить шифрование Начинаем зашифрованную сессию чата Заканчиваем зашифрованную сессию чата - - - Добавить контакт - - Адрес электронной почты человека, которого вы хотите пригласить: - - Выберите список: - - Чтобы добавить абонента из списка контактов, укажите его имя. - - Отправить приглашение - - Jabber/XMPP - Локальный XMPP без сервера - + Отпечатка Пальца Безопасность + Отпечатка Пальца Безопасность + Войдите в систему, + Дождь Ключевые + Шифрование отключено + Шифрование включено (Нажмите для подтверждения) + Ваш контакт остановил шифрованный чат. + Зашифровано и проверено + Генерируем новую пару ключей OTR + + Мы обнаружили, OTR ключей для импорта. Хотели бы вы сейчас отсканировать QR пароль? + Включить KeySync + OTR-ключи успешно импортированы + + Сканирование QR + Ваш отпечаток fingerprint + Руководство + Вопрос + Отпечатка Пальца Безопасность (Проверено) + Вы уверены, что хотите подтвердить эту отпечатку пальца? + Проверить отпечаток пальца? + Удаленная отпечатка пальца была проверена! + Отпечатка пальца для вас + Отпечатка пальца для + + Проверка подлинности + Введите вопрос, на который нужно будет ответить вашему собеседнику, а также правильный ответ на этот вопрос, для подтверждения их личности. + контрольный вопрос + правильный ответ + Ваш контакт успешно прошел аутентификацию вас. Теперь проверьте подлинности контакт, задавая свой ​​вопрос. + Отправить + Отменить + + Безопасный вызов + Безопасную Передачу Голоса + Введите Ваш OStel.co или другого безопасного обслуживания SIP счета для интеграции вызова + Создание учетной записи + + Безопасность и Конфиденциальность Шифрование и анонимность + Шифрование Вкл/Выкл + Пароль истек + Как долго шифрование приложений должно останется разблокированным + + Интерфейс Пользователя + Язык + Языки + Используйте темные темы + Изменить тему приложения на темную + Хранение Сообщений Только В Памяти + Храните сообщения только в памяти, а не на флэш-памяти, чтобы защититься от того чтобы ваши сообщения не были извлечены. (Может привести к потере сообщений) + Фоновое изображение + Установитe путь (\"/sdcard/foo.jpg\"), к фоновому изображению для приложения + Показать Сетку Контактов + Показать контакт-лист в виде сетки аватаров + Да, Подтвердить Все + + + Другие Hастройки + Запускать ChatSecure автоматически + Всегда начинайте и автоматически войдите в ранее зарегистрированные счета Скрыть контакты, отсутствующие в сети - - Настройки уведомлений - - Уведомления чата - - Уведомлять о получении сообщения чата в строке состояния - Выполнять с активным приоритетом. Уменьшить вероятность того, что Android перезагрузит службу соединения нашей программы. В этом случае в области уведомлений появится сообщение. Интервал сердцебиение Используйте более высокое значение (в минутах) для экономии заряда батареи. Высокое значение может привести к тому что соединение будет прервано провайдером из-за неактивности. + + Настройки уведомлений + Уведомления чата + Уведомлять о получении сообщения чата в строке состояния Вибрация - Сигнал вибрации при получении сообщения чата - Звуковой сигнал - Воспроизводить мелодию звонка при получении сообщения чата - - Выбрать мелодию звонка - - - - - - - Принять - - Отклонить - - - - Принять - - Отклонить - - - - - - - - - - - - + Используйте пользовательскую мелодию ChatSecure + + Включить протоколирование работы. + Показывать протокол работы в стандартный вывод / использовать logcat для ведения протокола. + + Исходные данные отключены + %1$s требует, чтобы использование фоновых данных было разрешено + Включить + Выйти + Вы хотите выйти из всех служб и убить все процессы (жесткий выход)? + Создать новую учетную запись? + Создать новую учетную запись чата для пользователя \'%1$s\'? + + Новые сообщения пользователя: %1$s Приглашение в групповой чат - - - - - - - - - - - - Включить чат - Разрешает приложениям включать чат через Intent. - - + Новое сообщение(я) от + Запуск ChatSecure… + Активировано & Разблокировано + Внимание - - - + Код ошибки: %1$d + Не удается выполнить вход в службу: %1$s. Повторите попытку позже."\n"(Подробности: %2$s) Список не был добавлен. - Не удалось заблокировать контакт. - Не удалось разблокировать контакт. - Сначала выберите контакт. - Соединение потеряно! \"\n - Ошибка чата. - Список контактов не был загружен. - Не удается подключиться к серверу. Проверьте соединение. - - - - - + Пользователь %1$s уже внесен в список контактов. + Контакт \%1$s" заблокирован. Дождитесь окончания загрузки списка контактов. - Произошла ошибка сети. Для этого соединения требуется WiFi - Эта функция не поддерживается сервером. - Неправильный пароль. - Произошла ошибка сервера. - Эта функция не поддерживается сервером. - В настоящий момент сервер недоступен. - Время ожидания сервера истекло. - Текущая версия не поддерживается сервером. - Очередь сообщений переполнена. - Сервер не поддерживает переадресацию на домен. - Указанное имя пользователя не опознано. - К сожалению, пользователь вас заблокировал. - Сеанс завершен, войдите в систему еще раз. - вы вошли с использованием другого клиента. вы уже вошли с использованием другого клиента. - Не удается получить номер телефона с SIM-карты. Обратитесь к своему оператору. - В настоящий момент вход в систему не выполнен. - - - + Произошла ошибка при проверке ваше имя пользователя или ваше пароль - пожалуйста проверьте и попробуйте снова. + Произошла ошибка при генерации ключей. + Произошла ошибка при подключении к чат-сервера - пожалуйста, проверьте настройки и попробуйте еще раз. + Произошла ошибка при подключении - пожалуйста, перепроверить вашего подключения к сети и попробуйте еще раз. + Потеряно соединение с сетью + ChatSecure пытается восстановить связь + В имени пользователя не хватает части типа @hostname.com. Попробуйте еще раз! + В имени сервера нет части типа .com, .net или другого окончания. Попробуйте еще раз! + Введите пароль: + Так как вы используете Tor, вы должны ввести имя XMPP хоста \'Connect Server\' непосредственно в Расширенные настройки учетной записи. + Невозможно создать или присоединиться к групповому чату + Извините, Вы не можете передавать этот тип файла + Для передачи файлов должно быть включено шифрование + Пожалуйста, включите чат шифрования для передачи файлов + ChatSecure обнаружил, что был сделан запрос, чтобы удалить этот процесс. ChatSecure, скорее всего, упал. Пожалуйста, воспользуйтесь пунктом меню \'Выход\' на экране списка аккаунтов вместо этого. + Для программы для открытия этого типа файлов. + Пожалуйста, начните безопасное общение, перед сканирование кодов. + OTR-ключи не импортированы. Проверьте наличие файла. + Скопируйте файл \'otr_keystore.ofcaes\' от настольного инструмента KeySync в корневую директорию вашего устройства хранения + Не удалось отправить сообщение. + Сообщение будет отправлено после подключения + Пользователь %1$s не в сети. Отправленные сообщения будут доставлены, когда пользователь %1$s выйдет в сеть. + Пользователь %1$s отсутствует в списке контактов. + Настройки учетной записи + + Группы + открытый разговор (ы) + Выход + Паника + Друзья + Контакты + Принять сертификат сервера? + Отпечаток пальца + + + Список контактов + Настройки + Список учетных записей + Выход + Список контактов + + Jabber/XMPP + Локальной (Bonjour/ZeroConf) + аккаунт Google + dukgo.com + + + В сети + Не беспокоить + Отсутствует + Нет на месте + Не в сети + Невидимый + Счастливы Печальный @@ -411,7 +448,7 @@ Смеясь Спутанный - + Счастливы Печальный @@ -450,127 +487,33 @@ :-D o_O - Список контактов - Шифрование Вкл/Выкл - Подключение через Tor (требуется установленное приложение Orbot) - - Can\'t ждать, чтобы начать? - О Gibberbot - Создание учетной записи - - - Насколько это безопасно? - Off-the-Record Messaging - протокол шифрования, который обеспечивает конфиденциальность в сетях обмена мгновенными сообщениями, заимствуя характеристики частных бесед в реальном мире. Он включает в себя шифрование, аутентификацию собеседников, отрицание и совершенную прямую секретность. Протокол OTR может использоваться в клиентах обмена мгновенными сообщениями для персональных компьютеров, таких как Adium и Pidgin. - - Безопасны ли мои разговоры? - - Ключевая фраза установки - Ключевая фраза: - Ключевая фраза (еще раз) : - - user@domain.com - пароль - Расширенные настройки учетной записи - Тип учетной записи - - Настойчивость - Запомнить пароль - Пароль сохранен в кэше - Пароль не сохранен в кэше - Автоматический вход в систему, - Войдите в систему - Подключите следующие настройки учетной записи - Don\'t связаться следующие настройки учетной записи - - Личный (опционально) - Псевдоним (ваше имя) - Как вас будут видеть другие пользователи - Профиль - Немного о себе - - Принудительное шифрование / не передавать открытым текстом - Когда это возможно, шифровать чаты автоматически - Шифрование чатов с просьбой - Не шифровать чаты - - Проверка полномочий ... - Создание пары ключей ... - Подписание дюйма .. - - Их отпечатков пальцев - Отпечатки пальцев - Войдите в систему, - Дождь Ключевые - Warning: Этот чат не шифруется - Этот чат является безопасным, но личности участников не были проверены - Warning: чат шифрования была остановлена. - Этот чат обеспеченных и проверено - Мастер учетных записей - Ваш аккаунт ID - Настройка сервера - Готовы ли вы? - Пожалуйста, введите Ваш идентификатор учетной записи (пользователь @ имя хоста) : - Пожалуйста, введите или измените jabber/xmpp чат имя сервера и номер порта (5222 по умолчанию). - В имени пользователя не хватает части типа @hostname.com. Попробуйте еще раз! - В имени сервера нет части типа .com, .net или другого окончания. Попробуйте еще раз! - Введите пароль: - - Настройки учетной записи - defLoc - - Группы - Генерируем новую пару ключей OTR - Контакт - введите имя собеседника - Вперед - Язык - Языки - Какой язык должен быть отображен? - Поиск SRV записи - Использовать DNS SRV для определения XMPP из доменного имени - Разрешать передачу имени пользователя и пароля открытым текстом, если не используется протокол защиты транспортного уровня - Разрешать авторизацию открытым текстом (без шифрования) - Проверить доверие к сертификату - Проверка TLS - Соединяться через TLS/SSL - Защита транспортного уровня - как начинаются шифрованные чаты - Сервер для соединения, если он нужен - Соединиться с сервером - TCP порт сервера XMPP - Порт сервера - Ресурс XMPP - чтобы выделить соединение из других клиентов, которые тоже в сети - Приоритет ресурса XMPP - Сообщения собеседникам, у которых активно несколько клиентов, будут переданы в клиент с самым высоким приоритетом - Далее - Назад - Беседы - Новое сообщение(я) от - Проверка подлинности - Введите вопрос, на который нужно будет ответить вашему собеседнику, а также правильный ответ на этот вопрос, для подтверждения их личности. - контрольный вопрос - правильный ответ - Ваш контакт успешно прошел аутентификацию вас. Теперь проверьте подлинности контакт, задавая свой ​​вопрос. - Нет разговоров.\n\nНажмите здесь, чтобы начать один! - Используйте темные темы - Так как вы используете Tor, вы должны ввести имя XMPP хоста \'Connect Server\' непосредственно в Расширенные настройки учетной записи. - - Всегда начинайте и автоматически войдите в ранее зарегистрированные счета - У вас нет аккаунт настроен.\n\nНажмите здесь, чтобы добавить! - аккаунт Google - Хранение Сообщений Только В Памяти - Храните сообщения только в памяти, а не на флэш-памяти, чтобы защититься от того чтобы ваши сообщения не были извлечены. (Может привести к потере сообщений) - Фоновое изображение - Установитe путь (\"/sdcard/foo.jpg\"), к фоновому изображению для приложения - Безопасность и Конфиденциальность - Интерфейс Пользователя - Другие Hастройки - открытый разговор (ы) - Orbot (Tor) - Выходите - - Вы хотите выйти из всех служб и убить все процессы (жесткий выход)? + Существующий аккаунт + Соединить с моим существующим аккаунтом на Jabber / XMPP сервере. + + Google аккаунт + Чат с другими пользователями Google, используя существующий Google аккаунт. + + WiFi Mesh Чат + Чат в той же локальной WiFi или Mesh сети - без необходимости подключения к Интернету или серверу! + Включить WiFi чат + + Новая учетная запись + Регистрация нового, бесплатного аккаунта на предложенном или своём сервере. + Создать новый аккаунт + + + Секретная личность! + Создать анонимный, одноразовый \"горящий\" аккаунт чата в один клик (требуется Orbot: Tor для Android) + Генерировать личность + + Это групповой чат + + "[resent] " + + "[resent] " + + Невозможно безопасно отправить этот файл + + Установить Orbot? + diff --git a/res/values-sc/arrays.xml b/res/values-sc/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-sc/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-sc/strings.xml b/res/values-sc/strings.xml new file mode 100644 index 000000000..22ef85d34 --- /dev/null +++ b/res/values-sc/strings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml new file mode 100644 index 000000000..b89c779f5 --- /dev/null +++ b/res/values-sk/arrays.xml @@ -0,0 +1,9 @@ + + + + Vynútiť / Vyžadovať + Automaticky sa pokúsiť + Len ak sa požaduje + Zakázať / Nikdy + + diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 92ccc76e2..a9fcf1ee9 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -1,347 +1,217 @@ - + + + prečítaných správ Umožňuje aplikáciám čítať dáta od poskytovateľa obsahu chatu. písať správy chatu Umožňuje aplikáciám zapisovať dáta na poskytovateľa obsahu chatu. - - - - Chat - Vyberte konto - - - Pridať účet - - Upraviť účet - - Odobrať účet - - Odhlásiť všetky - - - Chat - Vyberte konto - - - - Zrušiť prihlásenie - - - Pridať kontakt - - Odstrániť kontakt - - Blokovať kontakt - - Blokované - - Zoznam účtov - - Nový rozhovor - - - Nastavenie - - Vyhľadávanie Kontaktov - - Začať rozhovor - - Odhlásiť sa - - Overte - - - Štart Šifrovania - Stop Šifrovania - Vyčistiť rozhovor - Ukončiť rozhovor - - - Zoznam kontaktov - - - Switch chaty - - Vložiť smejka - - Zosnímať odtlačok prsta - Váš odtlačok prsta - Overte odtlačkov prstov - - Menu + \" - - - + štart IM službu + Umožňuje, aby aplikácia spustiť IM služby prostredníctvom zámeru. + Potvrdiť - - Chcete sa odhlásiť zo všetkých služieb? - - - - OK - Zrušiť - OK - Zrušiť - - - - - - - + + + Chat - Vyberte konto + Chat - Vyberte konto + Odhlásiť všetky + Chcete sa odhlásiť zo všetkých služieb? + Can\'t čakať začať? + O ChatSecure + Nastavenie účtu + Nastavenie heslo + Passphrase: + Heslo (znovu) : + + + Ako je to bezpečné? + \'Off - Record Messaging \'je bezpečnostný systém navrhnutý tak, aby súkromní napodobovaním vlastnosti súkromného rozhovoru v reálnom svete, vrátane šifrovania, autentifikácie, deniability a Vpred Secrecy.\n\nThe OTR-protokol je kompatibilný s desktop chat klientmi ako Adiumalebo Pidgin. + + My sa Chaty bezpečne? + + Pridať účet + Upraviť účet + Odobrať účet + User @ host - Heslo: - Pamätaj si moje heslo. - Prihlasovať automaticky. - Nemáte účet? - + Prihlásiť sa Pre vašu bezpečnosť, ak bol váš telefón stratený alebo ukradnutý, prejdite na webstránku a zmeňte si vaše heslo. Táto možnosť vás automaticky prihlási vždy, keď otvoríte aplikáciu. Pre zakázanie sa odhláste a odznačte pole \"Prihlasovať automaticky\". - - Prihlásiť sa - - - + Pripojenie cez Tor (vyžaduje Orbot app) + user@domain.com + heslo + Pokročilé nastavenia účtu + Nastavenie účtu + Perzistencia + Zapamätať heslo + Heslo medzipamäte + Heslo nie je medzipamäte + Automaticky prihlásiť + Prihlásiť sa teraz + Pripojte nasledujúce nastavenia účtu + Don\'t pripojiť nasledujúce nastavenia účtu + Osobné (nepovinné) + Alias účet (Vaše meno) + Ako vyzerá váš účet on-line + Profil + Stručný reklama o sebe + Vynútiť šifrovanie / odmietnuť holého + Pokiaľ je to možné, šifrovať chaty automaticky + Šifrovať chaty ako to žiadala + Zakázať chat šifrovanie + Overovanie poverovacích listín … + Generovanie páru kľúčov … + Podpisovanie v. .. + Sprievodca účet + Vaše ID účtu + Konfigurácia servera + Ste pripravení? + Prosím, zadajte svoje ID účtu (user @ hostname) : + Prosím, zadajte alebo upravte jabber/xmpp chatu servera meno počítača a číslo portu (predvolené je 5222). + Zrušiť prihlásenie Prihlasovanie sa\u2026 - - - Pozadie zdravotne dáta - - - - - Umožniť - - Odísť - - - - + + Pri použití nešifrovaného prenosu povoliť odoslanie mena a hesla v nezabezpečenej forme + + Pridať kontakt + Odstrániť kontakt + Blokovať kontakt + Blokované + Nový rozhovor + Vyhľadávanie Kontaktov + Začať rozhovor Priateľ pozvánky - - + (Neznámy) Prázdny - Žiadne konverzácie - - Vyberte kontakt (y) vyzvať - Typ nájsť kontakt - Neboli nájdené žiadne kontakty. - - - Č blokovaných kontaktov. - - + Kontakt + zadajte meno kontaktu s ktorým chcete zahájiť komunikáciu + Ísť + Žiadne aktívne chaty. + Pridať kontakt + E-mailovú adresu osoby, ktorej chcete invite: + Vyberte list: + Zadajte názov pre pridanie z kontaktov. + Poslať pozvánku Kontakt profil - Status: - Klient type: - Počítače - Mobilný - - - On-line - - Zaneprázdnený - - Preč - - Nečinný - - Offline - - Zobrazí offline - - - - + Č blokovaných kontaktov. + Mňa - - Typ skladať - - - - - - - - - Poslať - - Túto správu sa nepodarilo odoslať. - - Došlo k strate spojenia so serverom. Správy budú odoslané pri on-line. - - - + Ukončiť rozhovor + Vyčistiť rozhovor + Vložiť smejka + Switch chaty + Menu + \" Vyberte odkaz - - Žiadne aktívne chaty. - - - - Pridať kontakt - - E-mailovú adresu osoby, ktorej chcete invite: - - Vyberte list: - - Zadajte názov pre pridanie z kontaktov. - - Poslať pozvánku - - + + Pozvánka bola zaslaná% 1 $ s. + Prijať + Pokles + Prizvať\u2026 + Vyberte kontakt (y) vyzvať + Typ nájsť kontakt + + Štart Šifrovania + Stop Šifrovania + Prihlásiť sa + Regen Key + Generuje sa nový pár OTR kľúčov… + + + Váš odtlačok prsta + + Poslať + Zrušiť + + Nastavenie účtu + + Šifrovanie a anonymita + Šifrovať On/Off + + Jazyk + Jazyk + Skryť offline kontakty - + Nastavenie upozornenia - IM oznámenia - Oznámi v stavovom riadku, keď príde IM - Vibrovať - Tiež vibrácie pri prijatí správy IM - Zvuk - Tiež hrať zvonenie pri chate dorazí - - Vyberte zvonenie - - - - - - Prijať - - Pokles - - - Prijať - - Pokles - - - - - - - + + + Pozadie zdravotne dáta + Umožniť + Odísť + Skupina pozvánky do chatu - - - - - - - - štart IM službu - Umožňuje, aby aplikácia spustiť IM služby prostredníctvom zámeru. - - + Pozor - - Zoznam nebol pridaný. - Kontakt nebol blokovaný. - Kontakt nebol odblokovaný. - Prosím, vyberte kontakt ako prvý. - Odpojený! \n - Chyba služby! - Zoznam kontaktov ani načítať. - Nedá sa pripojiť k serveru. Skontrolujte prosím svoje pripojenie. - - - Počkajte prosím, zoznamu kontaktov zaťaženie. - Došlo k chybe siete. - Tento server nepodporuje túto funkciu. - Zadané heslo nie je platné. - Na serveri došlo k chybe. - Tento server nepodporuje túto funkciu. - Tento server je momentálne nedostupný. - Tento server vypršal. - Tento server nepodporuje aktuálnu verziu. - Fronty správ je plná. - Tento server nepodporuje presmerovanie do domény. - Zadané používateľské meno nie je rozpoznaný. - Ospravedlňujeme sa, ste zablokované užívateľom. - Relácia vypršala, prosím prihláste sa znovu. - Prihlásili ste sa z iného klienta. už ste prihlásení od iného klienta. - Ospravedlňujeme sa, telefónne číslo, nedá čítať z karty SIM. Prosím, kontaktujte svojho operátora o pomoc. - Nachádzate sa v súčasnosti nie sú prihlásení - - + Server, na ktorý sa pokúšate pripojiť nemá .com, .net ani podobnú príponu. Skúste to znovu! + Zadajte svoje password: + Túto správu sa nepodarilo odoslať. + Nastavenia účtu + + Skupiny + + + Zoznam kontaktov + Nastavenie + Zoznam účtov + Odhlásiť sa + Zoznam kontaktov + + + + On-line + Zaneprázdnený + Preč + Nečinný + Offline + Zobrazí offline + Šťastný Smutný @@ -361,7 +231,7 @@ Smavý Zmätený - + Šťastný Smutný @@ -381,67 +251,4 @@ Smavý Zmätený - Zoznam kontaktov - Šifrovať On/Off - Pripojenie cez Tor (vyžaduje Orbot app) - - Can\'t čakať začať? - O Gibberbot - Nastavenie účtu - - - Ako je to bezpečné? - - My sa Chaty bezpečne? - - Nastavenie heslo - Passphrase: - Heslo (znovu) : - - - Perzistencia - Zapamätať heslo - Heslo medzipamäte - Heslo nie je medzipamäte - Automaticky prihlásiť - Prihlásiť sa teraz - Pripojte nasledujúce nastavenia účtu - Don\'t pripojiť nasledujúce nastavenia účtu - - Osobné (nepovinné) - Alias účet (Vaše meno) - Ako vyzerá váš účet on-line - Profil - Stručný reklama o sebe - - Vynútiť šifrovanie / odmietnuť holého - Pokiaľ je to možné, šifrovať chaty automaticky - Šifrovať chaty ako to žiadala - Zakázať chat šifrovanie - - Overovanie poverovacích listín ... - Generovanie páru kľúčov ... - Podpisovanie v. .. - - Ich odtlačok prsta - Váš odtlačok prsta - Prihlásiť sa - Regen Key - Warning: Tento chat nie je šifrovaná - Tento chat je bezpečné, ale totožnosť účastníkov nebola overená - Warning: chat šifrovanie bola zastavená. - Tento chat je zabezpečené a overený - Sprievodca účet - Vaše ID účtu - Konfigurácia servera - Ste pripravení? - Prosím, zadajte svoje ID účtu (user @ hostname) : - Prosím, zadajte alebo upravte jabber/xmpp chatu servera meno počítača a číslo portu (predvolené je 5222). - Zadajte svoje password: - - - - diff --git a/res/values-sl-rSI/arrays.xml b/res/values-sl-rSI/arrays.xml index 63d602719..19ef80818 100644 --- a/res/values-sl-rSI/arrays.xml +++ b/res/values-sl-rSI/arrays.xml @@ -1,69 +1,9 @@ - Zahtevaj + Vsili / Zahtevaj Samodejno poskusi Če je zahtevano Onemogočeno / Nikoli - - zahtevaj - samodejno - zahtevano - onemogočeno - - - Standardno - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-sl-rSI/strings.xml b/res/values-sl-rSI/strings.xml index ebe6176c0..550d6018b 100644 --- a/res/values-sl-rSI/strings.xml +++ b/res/values-sl-rSI/strings.xml @@ -1,321 +1,388 @@ - + + + ChatSecure + ChatSecure + bere hipna sporočila Dovoli aplikacijam dostop do podatkov ponudnika vsebin hipnega sporočanja. piše hipna sporočila Dovoli aplikacijam, da zapišejo podatke pri ponudnikhu vsebin hipnega sporočanja. - - - - + začeni storitev hipnega sporočanja + Dovoli aplikacijam, da zaženejo storitev hipnega sporočanja prek interneta. + + Potrdi + V redu + Prekliči + V redu + Prekliči + Naprej + Nazaj + Poveži + + Odpri + + Izberi račun + Izberi račun + + O programu - + Odjavi vse + Se želite odjaviti iz vseh storitev? + + Uporabljate ChatSecure prvič? + Ne morete počakati na začetek? + Pa začnimo + Nastavitve računa + Nastavitev gesla + Geslo: + Geslo (ponovno): + + O programu ChatSecure + + Kako je varnejše? + \'Off-the-Record Messaging\' + + Ali so moji pogovori varni? + + Nov račun Dodaj račun - Uredi račun - Odstrani račun - - Odjavi vse - - - - - - + + Uporabnik@Gostitelj + Geslo: + Zapomni si moje geslo. + Samodejno me prijavi. + Še nimate računa? + Prijavi se + V kolikor izgubite telefon ali vam ga ukradejo, poskrbite za svojo varnost, tako da prek spleta z vašega računalnika spremenite geslo. + Ta možnost vas avtomatsko prijavi vsako krat, ko zaženete to aplikacijo. Da bi jo onemogočili, se odjavite, nato odstranite kljukico pred \"Vpiši me avtomatsko\". + Poveži se prek Tora (potrebuje aplikacijo Orbot) + user@domain.com + novo uporabniško ime + ponudnik storitve (dukgo.com, jabber.ccc.de) + geslo + potrdi geslo + Napredne nastavitve računa + Vrsta računa + Ustvari račun + Trajnost + Zapomni si geslo + Geslo zapomnjeno + Geslo ni zapomnjeno + Samodejna prijava + Prijavi se sedaj + Po nastavitvi računa se poveži + Po nastavitvi računa se ne poveži + Osebno (neobvezno) + Vzdevek računa (vaše ime) + Kako vaš račun zgleda na spletu + Profil + Kratko besedilce o vas samih + Vsili šifriranje / zavrni navadno besedilo + Ko je možno, šifriraj klepete samodejno + Šifriraj pogovore, če je tako določeno + Onemogoči šifriranje pogovorov + Preverjanje poverilnice … + Generiram par ključev … + Prijavljanje … + Čarovnik za račun + ID vašega računa + Nastavi strežnik + Ste pripravljeni? + Prosimo, vnesite ID vašega računa (uporabnik@strežnik): + Vnesite ali spremenite vaše jabber/xmpp ime gostitelja in vrata (5222 so privzeta). + Orbot (Tor) + Izberi domeno Prekini prijavo - - + Prijavljanje\u2026 + + Napravi lookup strežnika + Uporabi domenski strežnik za preverjanje domene trenutnega XMM strežnika + Dovoli, da sta uporabniško ime in geslo poslana kot navadno besedilo ob uporabi nešifriranega transporta + Dovoli prijavo v navadnem besedilu + Preveri, da je certifikat zaupanja vreden + TLS preverjanje + Zahtevaj povezavo TLS + Šifriranje prenosa + kako se šifrirani pogovori pričnejo + Strežnika za se povezati z, če je potrebno + Poveži server + TCP vrata za XMPP strežnik + Vrata strežnika + XMPP viri + + Pogovori + Nimate nastavljenega +nobenega računa. + +Dotaknite se tu in ga dodajte! + Seznam stikov - %1$s Dodaj stik - Odstrani stik - Blokiraj stik - Blokiran - - - Nov pogovor - - - Nastavitve - - Išči stike - - Začni pogovor - - Odajvi se - - Prevri - - - Začni šifriranje - Končaj šifriranje - Zbriši pogovor - Končaj pogovor - - - Seznam stikov - - Povabi\u2026 - - Zamenjaj pogovore - - Vstavi - - Skeniraj prstni odtis - Vaš prstni odtis - - - - - Potrdi - - Se želite odjaviti iz vseh storitev? - - - - - V redu - - Prekliči - - V redu - - Prekliči - - - - - - - - - Uporabnik@Gostitelj - - Geslo: - - Zapomni si moje geslo. - - Samodejno me prijavi. - - Še nimate računa? - - - - - - - - - - - - - - - - - - - + Stik "%1$s" dodan. + Stik "%1$s" izbrisan. + Stik "%1$s" blokiran. + Stik "%1$s" odblokiran. + Nov pogovor + Išči stike + Začni pogovor + Trajajoči pogovori (%1$d) + %1$d povezan + Povabila prijateljev + (Neznano) + Prazno Brez pogovoro - - Izberite kontakt(e), ki jih želite povabiti. - Pišite za iskanje kontakta - Ni najdenih kontaktov. - - - Brez blokiranih kontaktov. - - + Stik + vpiši ime stika, s katerim želiš začeti pogovor + Išči + Brez aktivnih klepetov. + Dodaj kontakt + E-poštni naslov osebe, ki jo želite povabiti: + Izberite seznam: + Vpišite ime, da bi dodali iz stikov. + Pošlji vabilo Profil kontakta - Status: - Tip odjemalca: - Računalnik - Mobilni telefon - - - Na voljo - - Zaseden - - Odsoten - - Neaktiven - - Ni na voljo - - Na videz odsoten - - - - - - - - - - - - - - + Blokirani stiki - %1$s + Brez blokiranih kontaktov. + + Klepetaj z %1$s + Jaz + %1$s je povezan + %1$s je odsoten + %1$s je zaseden + %1$s ni povezan + %1$s se je pridružil + %1$s je zapustil Pošlji - - To sporočilo ne more biti poslano. - - Povezava s strežnikom prekinjena. Sporočila bodo poslana, ko bo povezava obnovljena. - - - - - Brez aktivnih klepetov. - - - - Dodaj kontakt - - - - - - - Skrij nedosegljive kontakte - - - - - Vibriraj - - - Zvok - - - - - - + Pošlji ponovno + Končaj pogovor + Zbriši pogovor + Vstavi + Zamenjaj pogovore + Menu+ + Izberi povezavo + Zbriši + + %1$s vas je povabil, da se pridružiti skupinskemu klepetu. + Vabilo je bilo poslano %1$s. Sprejmi - Zavrni - - - Sprejmi - - Zavrni - - - - - - - + Skupinski pogovor + Začni ali se pridruži skupinskemu pogovoru + Povabi\u2026 + Izberite kontakt(e), ki jih želite povabiti. + Pišite za iskanje kontakta + Da + Ne + Ni bilo možno potrditi vpisa od %1$s. Poizkusite ponovno kasneje. + Ni bilo možno zavrniti vpisa od %1$s. Poizkusite ponovno kasneje. + + Začni šifriranje + Končaj šifriranje + Začetek šifrirane pogovorne seje … + Konec šifrirane pogovorne seje … + Varnostni prstni odtis + Varnostni prstni odtis + Prijavi se + Ponovno ustvari ključ + Šifriranje je izklopljeno + Šifriranje je vklopljeno (preveri z dotikom) + Šifrirano in preverjeno! + Generiram nov par OTR ključev … + + + Odčitaj QR + Vaš prstni odtis + Ročno + Vprašanje + Varnostni prstni odtis (preverjen) + Ali ste prepričani, da želite potrditi ta prstni odtis? + Preveri prstni odtis? + Oddaljeni prstni odtis je bil preverjen! + + Avtentikacija + zastavi vprašanje + pričakovan odgovor + Vaš stik vas je uspešno prepoznal. Zdaj prepoznajte še vi njega, tako da mu zastavite svoje vprašanje. + Pošlji + Prekini + + Varen klic + + Nastavitve računa + + Varnost in zasebnost + Šifriranje in anonimnost + Vklop/Izklop šifriranja + Potek pomnjenja gesla + + Uporabniški vmesnik + Jezik + Jeziki + Spremeni temo aplikacije v temno + Slika ozadja + Nastavi pot (\"/sdcard/krneki.jpg\") do slike, ki naj se prikazuje v ozadju aplikacije + + Skrij nedosegljive stike + Hearthbit interval + Uporabite višjo vrednost (v minutah), da boste varčevali z baterijo. Visoka vrednost lahko povzroči, da pride do prekinitve povezave pri ponudniku zaradi neaktivnosti. + + Nastavitve obveščanja + Obvestilo o sporočilu + Ko pride sporočilo me obvesti v statusni vrstici + Vibriraj + Ko pride sporočilo tudi vibriraj + Zvok + Ko pride sporočilo tudi zazvoni + + + + Za prijavo je potrebna podatkovna povezava (tudi background data) . + + Omogoči + Izhod + Se želite odjaviti iz vseh storitev IN končati vse procese (grob izhod)? + + Nova %1$s sporočila Povabilo v skupinski klepet - - - - - - - - - Opozorilo - - - + Novo sporočilo od + ChatSecure storitev se zaganja … + + Pozor + Koda napake %1$d + Seznam ni bil dodan. Kontakt ni bil blokiran. - Kontakt ni bil odblokiran. - Najprej izberite kontakt. - - - - - - - - - - + Odklopljen!\n + Napaka storitve! + Seznam stikov se ni naložil. + Povezovanje na strežnik ni uspelo. Preverite vašo povezavo. + "%1$s" že obstaja v vašem seznamu stikov. + Stik "%1$s" je bil blokiran. + Prosimo, počakajte da se naloži seznam vaših stikov. + Prišlo je do mrežne napake. + Ta strežnik ne podpira te funkcionalnosti. Vneseno geslo ni veljavno. - - - + Strežnik je naletel na težavo. + Strežnik ne podpira te funkcionalnosti. Strežnik je trenutno nedosegljiv. - - - - - - - - - - - - - - Poveži se prek Tora (potrebuje aplikacijo Orbot) - - - - - - - - - - - - + Strežnik je prekoračil čas. + Strežnik ne podpira tekoče različice. + Vrsta s sporočili je polna. + Strežnik ne podpira posredovanja tej domeni. + Vpisano uporabniško ime ni prepoznano. + Oprostite, uporabnik vas je blokiral. + Seja je potekla, prosim, prijavite se ponovno. + vpisali ste se z drugega odjemalca. + vpisali ste se z drugega odjemalca. + Oprostite, telefonska številka ne more biti prebrana z vaše SIM kartice. Prosimo, da se po pomoč obrnete k vašemu operaterju. + Trenutno niste prijavljeni. + Niste vnesli @hostname.com dela ID računa. Poizkusite ponovno! + Ime gostitelja tvojega strežnika ni imelo .com, .net ali podobne končnice. Poizkusite ponovno! + Vnesite vaše geslo: + Začenjanje ali se pridruževanje skupinskemu pogovoru ni uspelo + Ne morete delite te vrste datotek + Morate omogočiti šifriranje, da boste lahko delili datoteke + To sporočilo ne more biti poslano. + %1$s ni povezan. Sporočilo, ki ste ga poslali, bo dostavljeno, ko se %1$s poveže. + %1$s ni na seznamu vaših stikov. + Nastavitve računa + - - Uporabniški vmesnik - Orbot (Tor) + Skupine Izhod - + Panika + Prijatelji + Želite sprejeti digitalno potrdilo strežnika? + Prstni odtis + + + Seznam stikov + Nastavitve + Računi + Odajvi se + Seznam stikov + + Račun Google + dukgo.com + + + Na voljo + Zaseden + Odsoten + Neaktiven + Nedosegljiv + Na videz nedosegljiv + + + Vesel + Žalosten + Mežika + Kaže jezik + Presenečen + Poljublja + Vpije + Z očali + Pogolten na denar + Foot in mouth + V zadregi + Angelček + Neodločen + Joka + Z zaprtimi usti + Se smeji + Zmeden + + + + Vesel + Žalosten + Mežika + Kaže jezik + Presenečen + Poljublja + Vpije + Z očali + Pogolten na denar + Foot in mouth + V zadregi + Angelček + Neodločen + Joka + Z zaprtimi usti + Se smeji + Zmeden + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Nov račun diff --git a/res/values-sn/arrays.xml b/res/values-sn/arrays.xml new file mode 100644 index 000000000..c757504ac --- /dev/null +++ b/res/values-sn/arrays.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-sn/strings.xml b/res/values-sn/strings.xml index 6fc1ca76b..642dd60bb 100644 --- a/res/values-sn/strings.xml +++ b/res/values-sn/strings.xml @@ -1,247 +1,49 @@ - - - - - + + + + + + Maererano - - wedzera homwe - - - - - - - - - - - - - - - - - - zvamada - - - - Buda - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Svombunuro: - - - - - Pinda - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bvunda - - - Mand - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - svombunuro - Mhando Yehomwe - Pinda - - - - + + + + + Pinda - + + + + + + + + + + Bvunda + Mand + + + + - - + + + zvamada + Buda + + + + + diff --git a/res/values-sr/arrays.xml b/res/values-sr/arrays.xml new file mode 100644 index 000000000..1580752b7 --- /dev/null +++ b/res/values-sr/arrays.xml @@ -0,0 +1,9 @@ + + + + Присили/захтевај + Покушај аутоматски + На захтев + Онемогућено/никад + + diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml new file mode 100644 index 000000000..9b9c82f51 --- /dev/null +++ b/res/values-sr/strings.xml @@ -0,0 +1,575 @@ + + + + Сигурно ћаскање + Сигурно ћаскање + + читање брзих порука + Дозвољава апликацијама да читају податке са брзогласничког даваоца садржаја. + писање брзих порука + Дозвољава апликацијама да уписују податке у брзогласнички давалац садржаја. + покретање брзогласничког сервиса + Дозвољава апликацијама да покрену брзогласнички сервис преко интента. + + Потврда + У реду + Одустани + У реду + Одустани + Следеће + Назад + Повежи + Пусти + Поставка + + Отвори + Сигурно ћаскање је закључано + Постави лозинку + Потврди лозинку + Лозинка + Можете по избору да поставите главну лозинку за Сигурно ћаскање да бисте онемогућили приступ вашим контактима и порукама без лозинке: + Потврда нове лозинке + Лозинке се не поклапају, покушајте поново + Прескочи >> + Упит за податке + Унесите лозинку + Лозинку, молим… + Лозинка + Лозинка (поново) + + Изаберите налог + Изаберите налог + (%1$d) + Додај %1$s налог + О програму + Одјави све + Желите ли да се одјавите са свих сервиса? + Одјављени сте са %1$s. + Одјављени сте са %1$s због %2$s + Први пут користите Сигурно ћаскање? + Једва чекате да почнете? + Први кораци + Подешавање налога + Постављање лозинке + Пре него почнете одредите сигурну лозинку како бисте заштитили податке вашег Сигурног ћаскања од нежељеног приступа. + Лозинка: + Лозинка (поново): + Унесите *нову* лозинку. Мора да садржи најмање по једно велико и мало слово, један број, и да буде дужа од 6 знакова. + Шифрујем ваше постојеће белешке новом лозинком (стрпите се…) + Унесите вашу лозинку: + Добро дошли! Унесите јаку лозинку да бисте обезбедили ваше белешке. Мора да садржи најмање по једно велико и мало слово, један број, и да буде дужа од 6 знакова. + Ваша лозинка није довољно дугачка + Ваша лозинка не садржи ниједно велико слово + Ваша лозинка не садржи ниједно мало слово + Ваша лозинка не садржи ниједан број + + О Сигурном ћаскању + Сигурно ћаскање је мобилна апликација за брзо гласништво која пружа додатне безбедносне функције које спречавају њушкање ваших преписки и комуникације.\n\nАпликације подржава било који сервис ћаскања који користи Џабер или ИксМПП протокол, као што је Гуглов Гталк или Jabber.org. + + Како је сигурно? + „Оф-д-рекорд гласништво“ је безбедносни систем дизајниран да омогући приватност опонашајући карактеристике приватног разговора у стварном свету, укључује шифровање, аутентификацију, порецивост и прослеђену тајност.\n\nОТР протокол је компатибилан са клијентима ћаскања за рачунар, као што су Адијум или Пиџин. + + Да ли су моја ћаскања безбедна? + Функција шифровања ћаскања ради само ако друга страна користи компатибилну апликацију или програм, па бисте се требали уверити да ваши контакти користе Сигурно ћаскање на мобилном, и Адијум или Пиџин на рачунару. Можете фино да подесите тачно како и кад ће Сигурно ћаскање да покуша шифровање ваших ћаскања у поставкама налога.\n\nПочнимо! + + Нови налог + Додај налог + Уреди налог + Уклони налог + Постојећи налог + + korisnik@domaćin + Лозинка: + Запамти моју лозинку. + Пријави ме аутоматски. + Немате налог? + Пријава + Због ваше безбедности, ако сте изгубили телефон или вам је украден, идите на веб сајт на вашем рачунару и промените лозинку. + Ово ће вас аутоматски пријавити сваки пут кад отворите ову апликацију. Да то онемогућите, одјавите се и онда очистите „Пријави ме аутоматски“ кућицу. + Повежи се преко Тора (захтева Орбот) + korisnik@domaćin + ново корисничко име + давалац услуге (dukgo.com, jabber.ccc.de) + лозинка + потврда лозинке + Напредне поставке налога + Тип налога + Региструј налог + Истрајност + Запамти лозинку + Лозинка кеширана + Лозинка није кеширана + Аутоматска пријава + Повежи при покретању Сигурног ћаскања + Не повезуј при покретању Сигурног ћаскања + Пријави се сада + Повежи након поставке налога + Не повезуј након поставке налога + Лично (необавезно) + Псеудоним налога (ваше име) + Како се ваш налог појављује на вези + Профил + Кратка похвала о себи + Присили шифровање/одбиј обичан текст + Кад је могуће, шифруј ћаскања аутоматски + Шифруј ћаскања на захтев + Онемогући шифровање ћаскања + Оверавам акредитиве… + Генеришем пар кључева… + Пријављујем се… + Чаробњак налога + ИД вашег налога + Подесите сервер + Да ли сте спремни? + Унесите ИД вашег налога да бисте подесили Сигурно ћаскање за ваш ИксМПП сервис ћаскања. ИД изгледа као е-адреса: + Унесите ИД вашег налога (korisnik@imedomaćina): + Унесите или уредите име домаћина и број порта (5222 је подразумеван) вашег Џабер/ИксМПП сервера ћаскања. + Сигурно ћаскање је подешено, и сад је време да се повежете на ваш сервис и почнете да ћаскате сигурно, безбедно и приватно! + „Орбот (Тор)“ + Домен сервиса ћаскања + Региструјем нови налог… + пријављујем… + Откажи пријављивање + Пријављујем се\u2026 + Одјављујем се\u2026 + + СРВ потрага + Користи ДНС СРВ за налажење стварног ИксМПП сервера из домена + Дозволи слање корисничког имена и лозинке као обичног текста при коришћењу нешифрованог преноса + Дозволи аутентификацију обичним текстом + Овери поузданост сертификата + ТЛС оверавање + Захтевај ТЛС везу + Шифровање преноса + Како почињу шифрована ћаскања + Сервер за повезивање, ако је потребно + Сервер за повезивање + ТЦП порт ИксМПП сервера + Порт сервера + ИксМПП ресурс + да би се распознала ова веза од осталих клијената који су такође пријављени + Приоритет ИксМПП ресурса + Поруке клијентима са више активних ресурса биће послате ресурсу највишег приоритета + + Преписке + Нема преписки.\n\nДодирните овде да почнете једну! + Немате\nподешених налога.\n\nДодирните овде да додате један! + Списак контаката - %1$s + Додај контакт + Обриши контакт + Блокирај контакт + Надимак контакта + Блокиран + Надимак + Контакт „%1$s“ ће бити обрисан. + Контакт „%1$s“ ће бити блокиран. + Контакт „%1$s“ ће бити деблокиран. + Контакт „%1$s“ је додат. + Контакт „%1$s“ је обрисан. + Контакт „%1$s“ је блокиран. + Контакт „%1$s“ је деблокиран. + Ново ћаскање + Тражи контакте + Прикажи мрежу + Почни ћаскање + Прикажи профил + Овери контакт + Текућа ћаскања (%1$d) + %1$d на вези + Позивнице за пријатељство + (непознат) + Празно + Нема преписки\n\nДодирните да почнете ћаскати! + Контакт + укуцајте име контакта за ћаскање + Иди + Нема активних ћаскања. + Додавање контакта + Корисничко име или Џабер ИД особе за додавање: + Додај у налог: + Укуцајте име да бисте додали из контаката. + Пошаљи позивницу + Профил контакта + Стање: + Тип клијента: + Рачунар + Мобилни + Блокирани контакти - %1$s + Нема блокираних контаката. + + Ћаскање са %1$s + Ја + %1$s је на вези + %1$s је одсутан/а + %1$s је заузет(а) + %1$s је ван везе + %1$s се придружи + %1$s оде + Пошаљи слику + Пошаљи фајл + Пошаљи аудио + Фотографиши + Пренос фајла + Пренос завршен + Пренос у току + Да прихватим пренос? + жели да вам пошаље фајл + Нема доступне везе за слање вашег дељења! + Пошаљи + Пошаљи поруку + Пошаљи сигурну поруку + Поново пошаљи + Окончај ћаскање + Очисти ћаскање + Уметни смешак + Пребаци ћаскања + Мени+ + Изаберите везу + Обриши + Задржи фајлове + Да обришем безбедно складиште сесије ћаскања? + Сви отпремљени и преузети фајлови у овој сесији ће бити трајно обрисани. Упозорење: ова радња не може бити опозвана! + Да обришем оригинал? + Овај фајл ће бити копиран у безбедно складиште пре слања. Желите ли да обришете оригинални фајл са небезбедног складишта уређаја? + Задржи + Извези + Да извезем медија фајл? + Овај медија фајл ће бити извезен у %1$s + Да окончам ћаскање? + Све безбедне медија ставке ове сесије ће бити обрисане. Извезите ставку медија дугим додиром на икону сличице. + Окончај ћаскање и обриши фајлове + + %1$s вас позва да се придружите групном ћаскању. + Позивница је послата за %1$s. + Прихвати + Одбиј + Групно ћаскање + Направи/придружи се групном ћаскању + Повезујем се на групно ћаскање… + Позови\u2026 + Изаберите контакт(е) за позивање + Додирните да нађете контакт + Нема контаката.\n\nДодирните да позовете. + Да додам %1$s? + Да + Не + Не могу да одобрим претплату од %1$s. Покушајте поново касније. + Не могу да одбијем претплату од %1$s. Покушајте поново касније. + + Почни шифровање + Заустави шифровање + Покрећем шифровану сесију ћаскања… + Заустављам шифровану сесију ћаскања… + Сигурносни отисак + Сигурносни отисак + Пријави се + Регенериши кључ + Шифровање је искључено + Шифровање је укључено (додирните за оверу) + Ваш контакт је зауставио шифровану сесију ћаскања. + Шифровано и оверено! + Генеришем нови ОТР пар кључева… + + Откривено је ОТР складиште кључева за увоз. Желите ли одмах да очитате бар-код лозинку? + Активирај синхронизацију кључева + Успешно увезен ОТР привезак + + Очитај бар-код + Ваш отисак + Ручно + Питање + Сигурносни отисак (оверен) + Желите ли заиста да потврдите овај отисак? + Да оверим отисак? + Удаљени отисак је оверен! + Отисак за вас + Отисак за + Да инсталирам Баркод Скенер? + Ова апликација захтева Баркод Скенер. Желите ли да је инсталирате? + + Аутентификација + Укуцајте питајте које ће бити послато вашем контакту, и очекивани одговор, да бисте потврдили да су они за које се издају. + питање + очекивани одговор + Ваш контакт вас је успешно аутентификовао. Сада аутентификујте ваш контакт постављајући му сопствено питање. + ОТР оверавање + Шифровање ћаскања + Пошаљи + Одустани + + Сигурни позив + Сигурни глас + Унесите ваш OStel.co или неки други налог сигурног СИП сервиса за интеграцију позива + + Подешавање налога + + Сигурност и приватност + Шифровање и анонимност + Шифровање иск/укљ + Време истека лозинке + Време за које апликација остаје откључана + + Кликабилне везе на Тору + За налоге које користе Тор, учини везе у ћаскањима кликабилним (ПАЖЊА, могуће цурење приватних података!) + Корисничко сучеље + Језик + Језици + Системски подразумевано + Тамна тема + Користи тамну тему апликације + Складиштење порука само у меморији + Складишти поруке само у меморији, не на флеш диску, да би се спречило извлачење порука. (Може да узрокује губитак порука) + Слика позадине + Поставите путању („/sdcard/foo.jpg“) до слике за позадину апликације + Прикажи мрежу контаката + Прикажи списак контаката као мрежу аватара + Да, прихвати све + Обриши небезбедни медиј + Након дељења слике или фајла, аутоматски их обриши са изворног, небезбедног складишта након увоза + Уписуј медије на спољашње складиште + Медијални фајлови из сесија ћаскања се складиште у шифровани контејнер који може бити складиштен на унутрашње или спољашње складиште. + + Нема спољашњег складишта + Ваши дневници ћаскања су уписани на СД картицу, али нема СД картице. Уметните исправну СД картицу, или обришите постојећи дневник ћаскања и поново покрените Сигурно ћаскање. + Нема складишта за мултимедију ћаскања + Ваши дневници ћаскања су уписани на СД картицу, али фајл недостаје са текуће СД картице. Уметните исправну СД картицу, или обришите постојећи дневник ћаскања и поново покрените Сигурно ћаскање. + Обриши дневник ћаскања + + Остала подешавања + Покрени Сигурно ћаскање аутоматски + Увек покрени и аутоматски пријави претходно пријављене налоге + Сакриј контакте ван везе + Користи приоритет првог плана + Смањује шансу да Андроид рестартује везу нашег сервиса. Ово ће да смести обавештење у траци стања. + Интервал откуцаја + Користите већу вредност (у минутама) да поштедите батерију. Већа вредност може да узрокује да провајдер затвори вашу везу услед неактивности. + + Поставке обавештења + Брзогласничка обавештења + Обавештење у траци стања по доласку брзе поруке + Вибрирај + Вибрирај по доласку брзе поруке + Звук + Пусти звук по доласку брзе поруке + Изаберите посебни звук + + Дневник исправљања грешака + Испиши дневник апликације у стандардни излаз/логкат за исправљање грешака + + Интернет мобилне мреже онемогућен + Потребна је веза на интернет мобилне мрежне (укључујући позадинске податке) да би се апликација пријавила. + Омогући + Напусти + Желите ли да одјавите све сервисе И убијете све процесе (потпуни излаз)? + Да направим нови налог? + Да направим нови налог за корисничко име „%1$s“? + Подаци о сертификату + Сертификат: + Издавач: + СХА1 отисак: + Издат: + Истиче: + Придружити се соби за ћаскање? + Спољашња апликација покушава да вас повеже на собу за ћаскање. Да дозволим? + Избор позадине + Желите ли да изаберете слику за позадину из галерије? + Изаберите слику + + Нових %1$s порука + %1$d непрочитаних ћаскања + Нова позивница за пријатељство од %s + Нови фајл %1$s од %2$s + Позивница за групно ћаскање + Нова позивница за групно ћаскање од %s + Нове поруке од + Покрећем сервис Сигурног ћаскања… + Активирано и откључано + порука копирана на клипборд + + Пажња + Грешка: + Код грешке %1$d + Не могу да се пријавим на %1$s сервис. Покушајте поново касније.\n(Детаљи: %2$s) + Списак није додат. + Контакт није блокиран. + Контакт није деблокиран. + Најпре изаберите контакт. + Искључен са везе!\n + Грешка сервиса! + Списак контаката се није учитао. + Не могу да се повежем на сервер. Проверите вашу везу. + "%1$s" већ постоји на вашем списку контаката. + Контакт „%1$s“ је блокиран. + Сачекајте док се ваш списак контаката учита. + Дошло је до грешке на мрежи. + Бежични је потребан за ову везу. + Сервер не подржава ову функционалност. + Лозинку коју сте унели није исправна. + Сервер је наишао на грешку. + Сервер не подржава ову функционалност. + Сервер је тренутно недоступан. + Истекло прековреме сервера. + Сервер не подржава текуће издање. + Ред чекања порука је пун. + Сервер не подржава прослеђивање на домен. + Унето корисничко име није препознато. + Нажалост, корисник вас је блокирао. + Сесија је истекла, пријавите се поново. + пријавили сте се са другог клијента. + пријавили сте се са другог клијента. + Не могу да очитам број телефона са ваше СИМ картице. Контактирајте вашег оператера за помоћ. + Тренутно нисте пријављени. + Сигурно ћаскање је наишло на грешку приликом овере вашег корисничког имена или лозинке — проверите их и покушајте поново. + Сигурно ћаскање је наишло на грешку приликом генерисања пара кључева. + Сигурно ћаскање је наишло на грешку приликом повезивања на сервер ћаскања — проверите ваше поставке и покушајте поново. + Сигурно ћаскање је наишло на грешку приликом повезивања — проверите везу ваше мреже и покушајте поново. + Мрежа је ван везе + Сигурно ћаскање покушава поново да успостави везу + Нисте унели @imedomaćina.com део за ваш ИД налога. Покушајте поново! + Име домаћина вашег сервера нема .com, .net или сличан наставак. Покушајте поново! + Унесите вашу лозинку: + Пошто користите Тор, морате да унесете ИксМПП име домаћина „сервера за повезивање“ директно у напредним поставкама налога + УПОЗОРЕЊЕ: Овај сервис користи сертификат са СЛАБОМ криптографијом. Замолите администратора да га ажурира. + Приложени сертификат не одговара закаченом сертификату: + Не могу да направим или се придружим групном ћаскању + Не можете да поделите тај тип фајла + Морате да омогућите шифровање да бисте делили фајлове + Омогућите шифровање ћаскања да бисте делили фајлове + Неподржани долазни подаци, не могу да делим! + Сигурно ћаскање је открило захтев за уклањањем свог процеса. Сигурно ћаскање ће вероватно да се сруши. Уместо тога користите „Одјави све“ ставку менија са екрана списка налога. + Нема доступног прегледача за овај формат фајлова + Почните сигурну преписку пре очитавања кодова + ОТР привезак кључева није увезен; Проверите да ли је фајл исправног формата и на правом месту + Копирајте фајл „otr_keystore.ofcaes“ са радне површи KeySync алатке у корени директоријум складишта вашег уређаја + Порука није могла бити послата. + Нисте повезани. Поруке ће бити послате када се поново повежете. + %1$s је ван везе. Поруке које пошаљете ће бити испоручене кад %1$s буде на вези. + %1$s није на вашем списку контаката. + Лозинке се не поклапају + Приоритет мора бити број у распону [0 .. 127] + Порт мора бити број + Грешка преноса + Не могу да очитам фајл за складиштење + Кобна грешка: Не могу да откључам или учитам базу података апликације. Инсталирајте поново апликацију или очистите податке. + Нема апликације за руковање том везом! + Поставке налога + + Групе + отворених преписки + Угаси и закључај + Паника + Другари + Контакти + Да прихватим сертификат сервера? + Прикажите ваш отисак + учитавам… + + О Сигурном ћаскању\nhttps://guardianproject.info/apps/chatsecure/ + + Списак контаката + Поставке + Управљај налозима + Одјави се + Списак контаката + + Џабер (ИксМПП) + Локално подручје (Бонжур/зироконф) + Гугл налог + dukgo.com + + + На вези + Заузет(а) + Одсутан/а + Мирује + Ван везе + Невидљив(а) + + + Срећа + Туга + Намигивање + Језик + Изненађење + Пољубац + Галама + Опуштено + Новац + Стопало + Срамота + Анђео + Неодлучност + Плач + Уста затворена + Смех + Збуњеност + + + + Срећа + Туга + Намигивање + Језик + Изненађење + Пољубац + Галама + Опуштено + Новац + Стопало + Срамота + Анђео + Неодлучност + Плач + Уста затворена + Смех + Збуњеност + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Постојећи налог + Повежи се на мој постојећи налог на одређеном Џабер/ИксМПП серверу. + Гугл налог + Ћаскајте са другим Гугл корисницима користећи ваш постојећи Гугл налог. + Бежично мрежно ћаскање + Ћаскајте са другима на истој бежичној мрежи — интернет или сервер нису потребни! + Омогући бежично ћаскање + Нови налог + Региструјте нови, бесплатни налог на сервисима са нашег уграђеног списка, или који код изаберете. + Направи нови налог + Тајни идентитет! + Направите анонимни, једнократни „одбациви“ налог ћаскања једним једноставним додиром (захтева Орбот: Тор за Андроид) + Направи идентитет + Ово је групно ћаскање + [поново послато] + [поново послато] + Не могу да безбедно делим овај фајл + Да инсталирам Орбот? + Требате имати Орбот инсталиран и активиран да преусмерава саобраћај. Желите ли да га инсталирате? + Увек + Да покренем Орбот? + Изгледа да Орбот није покренут. Желите ли да га покренете и повежете на Тор? + надимак за коришћење у соби + име собе за прављење или придруживање + сервер групног ћаскања (conference.foo.com) + Ваше складиште кључева је оштећено. Поново инсталирајте Сигурно ћаскање или „обришите податке“ за апликацију + Примили сте нечитљиву шифровану поруку + Нисам могао да дешифрујем поруку коју сте послали + < превуците прстом лево и десно за још опција > + diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml index a9d22c136..dc7473ae9 100644 --- a/res/values-sv/arrays.xml +++ b/res/values-sv/arrays.xml @@ -1,69 +1,9 @@ - Force / Kräv - Automatiskt att försöka - Som begärts - Funktionshindrade / aldrig - - - kräv - auto - begärd - inaktiverad - - - Standard - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el + Tvinga / kräv + Försök automatiskt + Som begärt + Avaktiverad / aldrig diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index b137bc9e0..164311c1f 100755 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -1,400 +1,492 @@ - + + + ChatSecure + ChatSecure + läs meddelanden - \nTillåt applikationer att läsa data från IM innehållsleverantör. + +Tillåt applikationer att läsa data från IM innehållsleverantör. skriv meddelanden - \nTillåt applikationer att skriva data till IM innehållsleverantör.\n - - ChatSecure - - - Välj ett konto - - Om - - Lägg till konto - - Ändra konto - - Ta bort konto - - Logga ut alla - - - Välj ett konto - - - - Avbryt inloggning - - - Lägg till kontakt - - Ta bort kontakt - - Blockera kontakt - - Blockerad - - Kontolista - - Ny chatt - - Nytt konto - - Inställningar - - Sök kontakter - - Öppna chatt - - Logga ut - - Visa profil - - - Starta kryptering - Stoppa kryptering - Rensa chatt - Avsluta chatt - Säkert samtal - - - Kontaktlista - - Bjud in… - - Byt chatt - - Infoga känsloikon - Skicka igen - - Skanna fingeravtryck - Ditt fingeravtryck - Verifiera fingeravtryck - Verifiera hemlighet - - Meny+ - - - + +Tillåt applikationer att skriva data till IM innehållsleverantör. + + öppna chattjänsten + Tillåter att ett program avsiktligen öppnar chattjänsten. + Bekräfta - - Vill du logga ut ur alla tjänster? - - - - - - - OK - Avbryt - OK - Avbryt - - - - - - - - - + Nästa + Bakåt + Anslut + + Öppna + ChatSecure låst + Bestäm lösenord + Bekräfta lösenord + Lösenord + Nytt lösenord + Bekräfta nytt lösenord + Lösenord matchade inte, var vänlig försök igen + Jag är lat (inget lösenord!) + Informationsprompt + Ange lösenord + + Välj ett konto + Välj ett konto + (%1$d) + Lägg till kontot %1$s + Om + Logga ut alla + Vill du logga ut från alla tjänster? + Du har loggats ut från %1$s. + Du har loggats ut från %1$s på grund av %2$s + Första gången du använder ChatSecure? + Kan du inte vänta på att komma igång? + Kom igång + Kontoinstallation + Lösenordsetup + Innan du startar var vänlig välj ett säkert lösenord för att skydda din ChatSecure data från obehörig tillgång. + Lösenord: + Lösenord (igen): + Skriv in ett *nytt* lösenord. Det måste innehålla minst en stor bokstav, en liten bokstav, och en siffra, och vara längre än sex karaktärer. + Krypterar dina existerande noteringar med det nya lösenordet (tålamod…) + Skriv in ditt lösenord: + Välkommen! Skriv in ett starkt lösenord för att säkra dina noteringar. Det måste innehålla minst en stor bokstav, en liten bokstav, en siffra, och vara längre än sex karaktärer. + Ditt lösenord var inte långt nog + Ditt lösenord innehåll inte någon stor bokstav + Ditt lösenord innehåll inte någon liten bokstav + Ditt lösenord innehöll inte någon siffra + + Om ChatSecure + ChatSecure är en chattapp för mobiler med extra säkerhetsfunktioner som hindrar andra från att spionera på din kommunikation. + +Appen har stöd för alla chatttjänster som använder Jabber eller XMPP-protokollen, t.ex. Google Talk eller Jabber.org. + + Hur är det säkert? + \'Off-the-Record chatt\' är ett säkerhetssystem designat för att möjliggöra sekretess genom att härma egenskaperna av ett privat samtal i den riktiga världen, inklusive kryptering, autentisering, förnekande, och framåt sekretess. + +OTR-protokollet är kompatibelt med chatt klienter för datorer som t.ex. Adium eller Pidgin. + + Är mina chattar säkra? + ChatSecures chattkryptering fungerar bara när du chattar med andra som använder en kompatibel app eller program, så se till att dina kontakter också använder ChatSecure för mobilen och Adium eller Pidgin för datorn. Du kan ställa in exakt hur och när ChatSecure försöker kryptera dina chatter i Kontoinställningar. + +Låt oss börja! + + Nytt konto + Lägg till konto + Ändra konto + Ta bort konto + Existerande konto + Användarnamn: - Lösenord - Kom ihåg mitt lösenord. - Logga in mig automatiskt. - Har du inget konto? - - För din säkerhet, om din telefon blir borttappad eller stulen, gå till webbsidan på din dator och ändra ditt lösenord. - Det här alternativet loggar in dig automatiskt varje gång du öppnar programmet. Om du vill inaktivera alternativet loggar du ut och avmarkerar kryssrutan Logga in mig automatiskt. - Logga in - - + Om din telefon blir borttappad eller stulen gå till webbsidan på din dator och ändra ditt lösenord. + Det här alternativet loggar in dig automatiskt varje gång du öppnar programmet. Om du vill inaktivera alternativet loggar du ut och avmarkerar kryssrutan Logga in mig automatiskt. + Anslut via Tor (kräver appen Orbot) + user@domain.com + nytt användarnamn + serviceleverantör (dukgo.com, jabber.ccc.de) + lösenord + bekräfta lösenord + Avancerade kontoinställningar + Kontotyp + Registrera Konto + Uthållighet + Kom ihåg lösenord + Lösenord lagrat + Lösenord ej lagrat + Logga in automatisk + Anslut vid ChatSecure uppstart + Anslut ej vid ChatSecure uppstart + Logga in nu + Anslut följande konto setup + Anslut ej följande konto setup + Personligt (valfritt) + Kontoalias (ditt namn) + Hur ditt konto ser ut online + Profil + En kort text om dig själv + Tvinga kryptering / vägra klartext + Kryptera chatt automatiskt när det är möjligt + Kryptera chatter vid begäran + Inaktivera chattkryptering + Validerar referenser… + Genererar nyckelpar… + Loggar in… + Kontoguide + Ditt konto ID + Konfigurera server + Är du redo? + Skriv in ditt konto ID för att konfigurera ChatSecure för din XMPP chattservice. Den ser ut som en email adress: + Var vänlig skriv in ditt konto ID (användare@värd): + Var vänlig skriv in eller redigera ditt jabber/xmpp chattserver värdnamn och portnummer (5222 är standard). + ChatSecure har konfigurerats och nu är det dags att ansluta till din service, och börja chatta säkert och privat! + Orbot (Tor) + Välj en domän + Registrerar nytt konto… startar upp ChatSecure - + Avbryt inloggning Loggar in… - - - Bakgrundsdata inaktiverat - - - - - - Aktivera - - Avsluta - - - - - - + Loggar ut\u2026 + + Gör SRV koll + Använd DNS SRV för att hitta XMPP servern from domännamnet + Tillåt användarnamnet och lösenordet att skickas som klartext när okrypterad transport används + Tillåt klartext autentisering + Verifiera att certifikatet är pålitligt + TLS verifikation + Kräv TLS-anslutning + Transportkryptering + hur krypterade chatter startas + Servern att ansluta till, vid behov + Anslut server + TCP port för XMPP server + Serverport + XMPP resurs + för att skilja denna anslutning från andra klienter som också är inloggade + XMPP resursprioritet + Meddelanden till klienter med flera aktiva resurser kommer skickas till resursen med högst prioritet + + Konversationer + Inga konversationer. + +Klicka här för att starta en! + Du har inga +konton konfigurerade. + +Tappa här för att lägga till ett! + Kontaktlista - %1$s + Lägg till kontakt + Ta bort kontakt + Blockera kontakt + Kontaktsmeknamn + Blockerad + Kontakten %1$s tas bort. + Kontakten %1$s blockeras. + Blockering hävs för kontakten %1$s. + Kontakten %1$s har lagts till. + Kontakten %1$s har tagits bort. + Kontakten %1$s är blockerad. + Blockering hävs för kontakten %1$s. + Ny chatt + Sök kontakter + Visa rutnät + Öppna chatt + Se profil + Verifiera nyckel + Pågående chattar (%1$d) + %1$d online Kompisinbjudningar - (\"\"Okänd\"\") - Logga ut - Inga konversationer - - Välj vilka kontakter du vill bjuda in - Skriv för att hitta kontakt - Hittade inga kontakter. - - - - Inga blockerade kontakter. - - + Kontakt + skriv namnet på en kontakt du vill chatta med + Börja + Ingen aktiv chatt. + Lägg till kontakt + E-postadress till den du vill bjuda in: + Välj en lista: + Ange ett namn som du vill lägga till från Kontakter. + Skicka inbjudan Kontaktprofil - Status: - Klienttyp: - Dator - Mobil - - - Online - - Upptagen - - Borta - - Inaktiv - - Offline - - Visa som offline - - - - - + Blockerade kontakter – %1$s + Inga blockerade kontakter. + + Chatt med %1$s Jag - - Börja skriva - - - - - - - - - - - - - - - - + %1$s är online + %1$s är borta + %1$s är upptagen + %1$s är offline + %1$s är med + %1$s har lämnat chatten + Skicka foto + Skicka fil + Skicka ljud + Ta bild + Filöverföring + Överföring färdig + Pågående överföring + Acceptera överföring? + vill skicka dig filen + Ingen anslutning tillgänglig för att skicka din delning! Skicka - - Det gick inte att skicka meddelandet. - - Anslutningen till servern bröts. Meddelandena skickas när du är online igen. - - - - - + Skicka ett meddelande + Skicka säkert meddelande + Skicka igen + Avsluta chatt + Rensa chatt + Infoga känsloikon + Byt chatt + Meny+ Välj länk - - Ingen aktiv chatt. - - Startar krypterad chattsession... - Stoppar krypterad chattsession... - - - Lägg till kontakt - - E-postadress till den du vill bjuda in: - - Välj en lista: - - Ange ett namn som du vill lägga till från Kontakter. - - Skicka inbjudan - - Jabber (XMPP) - Lokal nätverkschatt (Bonjour/ZeroConf) - - Kontoinstallation + Radera + Behåll filer + Radera lagrade data för chattsession? + Alla upp- och nedladdade filer kommer raderas permanent. Denna åtgärd kan inte ångras! + Radera original? + Denna fil kommer kopieras till den säkra lagringsytan innan den skickas. Vill du radera originalfilen från enhetens vanliga lagringsyta? + Behåll + Exportera + Exportera mediafil? + Denna mediafil kommer exporteras till %1$s + Avsluta chatt? + Alla säkra mediafiler för denna session kommer raderas. Exportera mediafiler genom långt tryck på ikonen. + Avsluta chatt och radera filer + + %1$s har bjudit in dig till en gruppchatt. + Inbjudan har skickats till %1$s. + Acceptera + Avvisa + Gruppchatt + Skapa eller gå med i gruppchatt + Ansluter till gruppchatt… + Bjud in… + Välj vilka kontakter du vill bjuda in + Skriv för att hitta kontakt + Inga kontakter funna. + +Klicka för att bjuda in. + Det gick inte att godkänna prenumeration från %1$s. Försök igen lite senare. + Det gick inte att avvisa prenumeration från %1$s. Försök igen senare. + + Starta kryptering + Stoppa kryptering + Startar krypterad chattsession… + Stoppar krypterad chattsession… + Säkerhetsfingeravtryck + Säkerhetsfingeravtryck + Logga in + Regenerera nyckel + Kryptering är av + Kryptering är på (Knacka för att verifiera) + Din kontakt har stoppat den krypterade chatten. + Krypterad och verifierad! + Genererar nytt OTR nyckelpar… + + Vi har upptäckt en OTR keystore för import. Vill du skanna QR lösenordet nu? + Aktivera KeySync + Framgågsrik import av OTR nyckelring + + Skanna QR + Ditt fingeravtryck + Manuell + Fråga + Säkerhetsfingeravtryck (verifierad) + Är du säker att du vill bekräfta detta fingeravtryck? + Verifiera fingeravtryck? + Det avlägsna fingeravtrycket har verifierats! + Fingeravtryck för dig + Fingeravtryck för + Installera Barcode Scanner? + Barcode Scanner behövs för denna applikation. Vill du installera den? + + Autentisering + Skriv en fråga och skicka till din kontakt, och svaret du förväntas få, för att verifiera att dom verkligen är de dom påstår. + fråga + förväntat svar + Din kontakt har framgångsrikt autentiserat dig. Autentisera nu din kontakt genom att fråga in egen fråga. + OTR Q&A verifiering + Chattkryptering + Skicka + Avbryt + + Säkert samtal + Säkert samtal + Skriv in din OStel.co eller annat säkert SIP service konto här för samtalsintegration + + Kontoinställningar + + Säkerhet och sekretess Kryptering och anonymitet - Dölj kontakter som är offline - - Aviseringsinställningar - - Chattavisering - - Avisering i statusfältet när jag får chattmeddelanden - + Kryptering på/av + Lösenord timeout + Tid som applikationskrypteringen ska vara olåst + + Klickbara länkar i Tor + För konton som använder Tor, gör länkar i konversationer klickbara (VARNING, möjlig integritetsläcka!) + Användargränssnitt + Språk + Språk + Använd systemets standardinställning + Använd mörkt tema + Byt till mörkt apptema + Endast lagring av meddelanden i minnet + Lagra endast meddelanden i minnet, inte på flash lagringen, för att skydda mot att någon kommer åt meddelandena. (Kan orsaka förlorade meddelanden) + Bakgrundsbild + Ställ in sökväg (\"/sdcard/foo.jpg\") till en bakgrundsbild för appen + Visa kontakter i rutnät + Visa kontaktlista som avatar rutnät + Ja, acceptera alla + Radera osäker media + När du delat en bild eller en fil - radera automatiskt originalfilen från den ursprungliga lagringsplatsen + + Andra inställningar + Starta ChatSecure automatiskt + Starta alltid och logga automatiskt in på tidigare inloggade konton + Dölj offlinekontakter Använd förgrundsprioritet Minska chansen att Android startar om vår anslutningstjänst. Detta placerar en notifikation i notifikationsområdet. Pulsintervall Använd ett högre värde (i minuter) för att spara batteri. Ett högt värde kan göra att leverantören stänger din anslutning på grund av inaktivitet. + + Aviseringsinställningar + Chattavisering + Avisering i statusfältet när jag får chattmeddelanden Vibrera - - Vibrera även när jag får chattmeddelanden - + Vibrera vid chattmeddelanden Ljud - - Spela ringsignalen även när jag får chattmeddelande - - Välj ringsignal - - - - - - - Acceptera - - Avvisa - - - - Acceptera - - Avvisa - - - - - - - - - - - - + Spela ringsignal vid chattmeddelande + Använd ChatSecure anpassad ringsignal + + Aktivera debuggloggar + Skicka loggdata för appen till standard out / logcat för debuggning + + Datatrafik inaktiverat + +Nätverkstrafik (även i bakgrunden) krävs för att kunna logga in med appen. + Aktivera + Avsluta + Vill du logga ut ur alla tjänster OCH döda alla processer (hård avslutning)? + Skapa nytt konto? + Skapa nytt chattkonto för användarnamn \'%1$s\'? + Certifikatinfo + Certifikat: + Utfärdat av: + SHA1 Fingeravtryck: + Utfärdat: + Förfaller: + Anslut till chattrum? + En extern app försöker ansluta dig till ett chattrum. Tillåt? + Välj bakgrund + Vill du välja en bakgrundsbild från bildgalleriet? + Välj bild + + Nya meddelanden från %1$s + %1$d olästa chattmeddelanden + Ny kontaktförfrågan från %s + Ny fil %1$s från %2$s Inbjudan till gruppchatt - - - - - - - - - - - - öppna chattjänsten - Tillåter att ett program avsiktligen öppnar chattjänsten. - - + Ny gruppchattinbjudan från %s + Nytt meddelande(n) från + Startar ChatSecure service… + Aktiverad & olåst + meddelande kopierat till urklipp + Obs! - - - + Fel: + Felkod %1$d + Det gick inte att logga in till tjänsten %1$s. Försök igen senare."\n"(Information: %2$s) Det gick inte att lägga till listan. - Kontakten blockerades inte. - Blockeringen hävdes inte för kontakten. - Du måste välja en kontakt först. - \"Frånkopplad!\"\n - Fel på tjänsten! - Det gick inte att hämta kontaktlistan. - Det går inte att ansluta till servern. Kontrollera anslutningen. - - - - - + %1$s finns redan i din Kontaktlista. + Kontakten %1$s har blockerats. Vänta medan vi hämtar din Kontaktlista. - - Ett nätverksfel har inträffat. + Ett nätverksfel inträffade. WiFi behövs för denna anslutning. - Servern stöder inte den här funktionen. - - Lösenordet som du angav är ogiltigt. - + Lösenordet du angav är ogiltigt. Servern upptäckte ett fel. - Servern stöder inte den här funktionen. - Servern är inte tillgänglig för tillfället. - Tidsgränsen för servern har överskridits. - Servern stöder inte den aktuella versionen. - Meddelandekön är full. - Servern stöder inte vidarebefordran till domänen. - - Användarnamnet som du angav är okänt. - + Användarnamnet du angav är okänt. Du har tyvärr blockerats av användaren. - Besöket har upphört att gälla. Logga in igen. - du har loggat in från en annan klient. du redan har loggat in från en annan klient. - Det går tyvärr inte att läsa telefonnumret från ditt SIM-kort. Kontakta din operatör. - Du är inte inloggad för närvarande. - - - + ChatSecure stötte på ett fel vid validering av ditt användernamn och lösenord - var vänlig kolla dem och försök igen. + ChatSecure stötte på ett fel vid generering av ett nyckelpar. + ChatSecure stötte på ett fel vid anslutning till chattservern - var vänlig kolla din konfigurering och försök igen. + ChatSecure stötte på ett fel vid anslutning - var vänlig dubbelkolla din nätverksanslutning och försök igen. + ChatSecure har tappat anslutningen till nätverket + ChatSecure försöker återetablera en anslutning + Du skrev inte in en @värdnamn.com del av ditt konto ID. Försök igen! + Ditt server värdnamn hade inte ett .com, .net, eller liknande appendix. Försök igen! + Skriv in ditt lösenord: + Eftersom du använder Tor måste du skriva in XMPP \'Anslutningsserver\' värdnamn direkt i Avancerade kontoinställningar + VARNING: Denna tjänst använder ett certifikat med svag kryptering. Vänligen be administratören att uppgradera. + Erhållet certifikat matchar inte PINNED certificate: + Kan inte skapa eller gå med i grupp chatt + Vi kan tyvärr inte dela denna filtyp + Du måste aktivera kryptering för att dela filer + Var vänlig aktivera chattkryptering för att dela filer + Ej supporterat inkommande data, kan inte dela! + ChatSecure upptäckte att en begäran gjordes för att ta bort sin uppgift. ChatSecure kommer troligtvis att krascha. Var vänlig använd \"Logga ut alla\" alternativet i menyn från kontolistan istället. + Det finns ingen visare för detta filformat + Vänligen starta en säker konversation innan avläsning av koder + OTR nyckelring ej importerad; Var vänlig kolla att filen existerar i rätt format och på rätt plats + Var vänlig kopiera \'otr_keystore.ofcaes\' filen från KeySync verktyget till rotkatalogen på din enhet + Det gick inte att skicka meddelandet. + Meddelanden kommer skickas vid återanslutning + %1$s är offline. Meddelanden som du skickar levereras när %1$s är online igen. + %1$s är inte med i din Kontaktlista. + Dina lösenord matchar inte + Prioritet måste anges som ett tal mellan [0 .. 127] + Portnummer måste vara ett tal + Överföringsfel + Kan inte läsa fil till lagringsplats + Allvarligt fel: kan inte låsa upp eller ladda appdatabasen. Vänligen installera om appen eller rensa data. + Ingen installerad applikation kan hantera den länken! + Kontoinställningar + + VARNING: Detta är en tidig release av ChatSecure som fortfarande kan innehålla säkerhetshål eller buggar. + + Grupper + öppna konversation(er) + Avsluta + Panik + Kompisar + Kontakter + Acceptera servercertifikat? + Fingeravtryck + laddar… + + Om ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + Kontaktlista + Inställningar + Kontolista + Logga ut + Kontaktlista + + Jabber (XMPP) + Lokalt område (Bonjour/ZeroConf) + Googlekonto + dukgo.com + + + Online + Upptagen + Borta + Inaktiv + Offline + Visa som offline + Glad Ledsen @@ -414,7 +506,7 @@ Skrattar Förvirrad - + Glad Ledsen @@ -453,173 +545,32 @@ :-D o_O - Kontaktlista - Kryptering av/på - Anslut via Tor (kräver Orbot app) - - ChatSecure - Första gången du använder ChatSecure? - Kan du inte vänta på att komma igång? - Kom igång - Kontoinstallation - - Om ChatSecure - ChatSecure är en chatt app för mobiler som ger extra säkerhetsfunktioner som hindrar andra från att spionera på dina konversationer och kommunikationer.\n\nAppen har stöd för alla chatt tjänster som använder Jabber eller XMPP protokollen, t.ex. Google Talk eller Jabber.org. - - Hur är det säkert? - \'Off-the-Record chatt\' är ett säkerhetssystem designat för att möjliggöra sekretess genom att härma egenskaperna av ett privat samtal i den riktiga världen, inklusive kryptering, autentisering, förnekande, och framåt sekretess.\n\nOTR-protokollet är kompatibelt med chatt klienter för datorer som t.ex. Adium eller Pidgin. - - Är mina chattar säkra? - ChatSecure\'s chatt kryptering fungerar endast när du chattar med andra som använder en kompatibel app eller program, så se till att dina kontakter också använder ChatSecure för mobilen och Adium eller Pidgin för datorn. Du kan ställa in exakt hur och när ChatSecure försöker kryptera dina chatter i Kontoinställningar.\n\nLåt oss börja! - - Lösenordsetup - Innan du startar var vänlig välj ett säkert lösenord för att skydda din ChatSecure data från obehörig tillgång. - Lösenord: - Lösenord (igen): - - user@domain.com - nytt användarnamn - serviceleverantör (dukgo.com, jabber.ccc.de) - lösenord - bekräfta lösenord - Avancerade kontoinställningar - Kontotyp - Registrera Konto - - Uthållighet - Kom ihåg lösenord - Lösenord lagrat - Lösenord ej lagrat - Logga in automatisk - Anslut vid ChatSecure uppstart - Anslut ej vid ChatSecure uppstart - Logga in nu - Anslut följande konto setup - Anslut ej följande konto setup - - Personligt (valfritt) - Kontoalias (ditt namn) - Hur ditt konto ser ut online - Profil - En kort text om dig själv - - Tvinga kryptering / vägra klartext - När möjligt, kryptera chatter automatiskt - Kryptera chatter vid begäran - Inaktivera chatt kryptering - - Validerar referenser... - Genererar nyckelpar... - Loggar in... - - ChatSecure stötte på ett fel vid validering av ditt användernamn och lösenord - var vänlig kolla dem och försök igen. - ChatSecure stötte på ett fel vid generering av ett nyckelpar. - ChatSecure stötte på ett fel vid anslutning till chattservern - var vänlig kolla din konfigurering och försök igen. - ChatSecure stötte på ett fel vid anslutning - var vänlig dubbelkolla din nätverksanslutning och försök igen. - Deras fingeravtryck - Ditt fingeravtryck - Logga in - Regenerera nyckel - ChatSecure har tappat anslutningen till nätverket - ChatSecure försöker återetablera en anslutning - Varning: Denna chatt är INTE krypterad - Denna chatt är säker men identiteterna av deltagarna har INTE verifierats - Varning: Chattkryptering har stoppats. - Denna chatt är säker och verifierad - Kontoguide - Ditt konto ID - Konfigurera server - Är du redo? - Skriv in ditt konto ID för att konfigurera ChatSecure för din XMPP chattservice. Den ser ut som en email adress: - Var vänlig skriv in ditt konto ID (användare@värd): - Var vänlig skriv in eller redigera ditt jabber/xmpp chattserver värdnamn och portnummer (5222 är standard). - ChatSecure har konfigurerats och nu är det dags att ansluta till din service, och börja chatta säkert och privat! - Du skrev inte in en @värdnamn.com del av ditt konto ID. Försök igen! - Ditt server värdnamn hade inte ett .com, .net, eller liknande appendix. Försök igen! - Skriv in ditt lösenord: - - Kontoinställningar - defLoc - VARNING: Detta är en tidig release av ChatSecure som fortfarande kan innehålla säkerhetshål eller buggar. - - Grupper - Genererar nytt OTR nyckelpar... - Kontakt - skriv namnet på en kontakt du vill chatta med - Börja - Språk - Språk - Vilket språk ska InTheClear visa? - Gör SRV koll - Använd DNS SRV för att hitta XMPP servern from domännamnet - Tillåt användarnamnet och lösenordet att skickas som klartext när okrypterad transport används - Tillåt klartext autentisering - Verifiera att certifikatet är pålitligt - TLS verifikation - Kräv TLS/SSL anslutning - Transportkryptering - hur krypterade chatter startas - Servern att ansluta till, vid behov - Anslut server - TCP port för XMPP server - Serverport - XMPP resurs - för att skilja denna anslutning från andra klienter som också är inloggade - XMPP resursprioritet - Meddelanden till klienter med flera aktiva resurser kommer skickas till resursen med högst prioritet - Nästa - Bakåt - Konversationer - Nytt meddelande(n) från - Autentisering - Skriv en fråga och skicka till din kontakt, och svaret du förväntas få, för att verifiera att dom verkligen är de dom påstår. - fråga - förväntat svar - Din kontakt har framgångsrikt autentiserat dig. Autentisera nu din kontakt genom att fråga in egen fråga. - Inga konversationer.\n\nKlicka här för att starta en! - Använd mörkt tema - Eftersom du använder Tor måste du skriva in XMPP \'Anslutningsserver\' värdnamn direkt i Avancerade kontoinställningar - - Starta ChatSecure automatiskt - Starta alltid och logga automatiskt in på tidigare inloggade konton - Du har inga konton konfigurerade.\n\nKlicka här för att lägga till ett! - Googlekonto - Endast lagring av meddelanden i minnet - Lagra endast meddelanden i minnet, inte på flash lagringen, för att skydda mot att någon kommer åt meddelandena. (Kan orsaka förlorade meddelanden) - Bakgrundsbild - Ställ in sökväg (\"/sdcard/foo.jpg\") till en bakgrundsbild för appen - Säkerhet och sekretess - Användargränssnitt - Annan inställning - öppna konversation(er) - Orbot (Tor) - Avsluta - - Vill du logga ut ur alla tjänster OCH döda alla processer (hård avslutning)? - Skriv in ett *nytt* lösenord. Det måste innehålla minst en stor bokstav, en liten bokstav, och en siffra, och vara längre än sex karaktärer. - Krypterar dina existerande noteringar med det nya lösenordet (tålamod...) - Skriv in ditt lösenord: - Välkommen! Skriv in ett starkt lösenord för att säkra dina noteringar. Det måste innehålla minst en stor bokstav, en liten bokstav, en siffra, och vara längre än sex karaktärer. - Ditt lösenord var inte långt nog - Ditt lösenord innehåll inte någon stor bokstav - Ditt lösenord innehåll inte någon liten bokstav - Ditt lösenord innehöll inte någon siffra - Öppna - ChatSecure låst - Skapa lösenord - Skriv in lösenord - Skriv in nytt lösenord - Bekräfta nytt lösenord - Lösenord matchade inte, var vänlig försök igen - Säkert samtal - Skriv in din OStel.co eller annat säkert SIP service konto här för samtalsintegration - dukgo.com - Vi har upptäckt en OTR nyckel att importera. Vill du skanna QR lösenordet nu? - Importera OTR nyckellagring - Framgågsrik import av OTR nyckelring - OTR nyckelring ej importerad; Var vänlig kolla att filen existerar i rätt format och på rätt plats - Lösenord timeout - Tid som applikationskrypteringen ska vara olåst + Existerande konto + Anslut till mitt konto på en specifik Jabber / XMPP-server. + Googlekonto + Chatta med andra Googleanvändare genom ditt existerande Googlekonto. + Wifi Mesh Chatt + Chatta med andra på samma lokala WiFi-nätverk - ingen internetanslutning eller server behövs! + Aktivera WiFi chatt + Nytt konto + Registrera ett nytt, gratis konto på en av tjänsterna i vår lista eller på en tjänst du väljer själv. + Skapa nytt konto + Hemlig identitet! + Skapa ett anonymt engångskonto med ett enkelt klick (kräver Orbot: Tor for Android) + Skapa identitet + Detta är en gruppchatt + [senaste] + [senaste] + Kan inte dela denna fil säkert + Installera Orbot? + Alltid + Starta Orbot? + Orbot verkar inte köras. Vill du starta Orbot och ansluta till Tor? + nick att använda i chattrum + namn på chattrum att skapa eller ansluta till\" + gruppchattserver conference.foo.com) + Din nyckellagring är korrupt. Installera om ChatSecure eller radera data för appen + Du tog emot ett oläsbart krypterat meddelande + Jag kunde inte dekryptera meddelandet du skickade + < svep vänster och höger för fler alternativ > diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml index 15f0258b6..a760a8372 100644 --- a/res/values-tr/arrays.xml +++ b/res/values-tr/arrays.xml @@ -6,64 +6,4 @@ Talep olarak Engelli / Asla - - zorunlu - otomatik - istenen - geçersiz - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 1badfe37a..5b7494ad1 100755 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -1,401 +1,473 @@ - + + + ChatSecure + ChatSecure + hızlı iletiler oku Uygulamaların IM (anlık mesajlaşma) sağlayıcısından bilgi almasına izin verin. hızlı iletiler yaz Uygulamaların IM (anlık mesajlaşma) sağlayıcısından bilgi almasına izin verin. - - ChatSecure - - + IM hizmetini başlat + Amacını bildirerek uygulamaların IM hizmetini başlatmasına izin verir. + + Doğrula + Tamam + İptal + Tamam + İptal + İleri + Geri + Bağlan + Oynat + Kur + + + ChatSecure Kilitlendi + Parola Oluştur + Parolayı Onayla + Parola + Yeni Parola + Yeni Parolayı Onayla + Parola uyumlu değil, lütfen yeniden deneyin + Atla >> + Bilgi İstemi + Lütfen Parolayı Girin + Parola lütfen… + Parola + Parola (tekrar) + Hesap seç - + Hesap seç + (%1$d) + %1$s hesabını ekle Hakkında - - Hesap ekle - - Hesabı düzenle - - Hesabı sil - Hepsinden çıkış yap - - - Hesap seç - - Bağlantı numarası - - + Tüm servislerden çıkış yapmak istiyor musunuz? + %1$s oturumunuz kapatıldı. + %1$s oturumunuz %2$s nedeniyle kapatıldı. + ChatSecure\'ü ilk kez mi kullanıyorsunuz? + Başlamak için sabırsızlanıyor musunuz? + Başlarken + Hesap Kurulumu + Parola Ayarlama + Lütfen başlamadan önce ChatSecure bilgilerinizi izinsiz girişlerden korumak için güvenli bir parola oluşturun. + Parola: + Parola (tekrar): + *Yeni* bir parola giriniz. Parolanız en az bir büyük harf, bir küçük harf ve bir numara barındırmalı ve 6 karakterden daha uzun olmalıdır. + Varolan notlarınızı yeni parola ile şifreliyoruz (lütfen bekleyiniz…) + Parolanızı girin: + Hoşgeldiniz! Notlarınızı koruma altına almak için güçlü bir parola oluşturun. Parolanız en az bir büyük harf, bir küçük harf ve bir numara barındırmalı ve 6 karakterden daha uzun olmalıdır. + Parolanız yeterince uzun değil. + Parolanız hiç büyük harf barındırmıyor. + Parolanız hiç küçük harf barındırmıyor. + Parolanız hiç sayı barındırmıyor. + + ChatSecure Hakkında + ChatSecure başkalarının görüşmelerinizi dinlemesini engelleyen bir mobil anlık mesajlaşma uygulamasıdır. Bu uygulama Jabber veya XMPP protokolünü kullanan tüm sohbet servisleri ile uyumludur; örneğin Google GTalk veya Jabber.org + + Nasıl güvenli? + \'Kayıt-dışı mesajlaşma\', Off-the-Record -OTR, gerçek hayattaki gizli konuşmayı örnek alarak geliştirilmiş bir güvenlik sistemidir; Şifreleme, Onaylama, Reddedilebilirlik ve Gizliliğin Aktarılması gibi özelliklere sahiptir. OTR masaüstü bilgisayarlarla da uyumludur; örneğin Adium ve Pidgin. + + Sohbetlerim Güvende Mi? + ChatSecure\'ün sohbet şifreleme özelliği ancak diğer taraf da uyumlu bir uygulama veya yazılım kullandığında geçerlidir; yani bağlantılarınızın mobil iletişimde ChatSecure, masaüstü bilgisayarlarında ise Adium veya Pidgin kullandıklarına emin olun. ChatSecure\'ün sohbetlerinizi hangi durumlarda ve nasıl şifreleyeceğini ayarlamak için Hesap Ayarları menüsünü kullanabilirsiniz. Hadi başlayın! + + Yeni Hesap + Hesap Ekle + Hesabı Düzenle + Hesabı Kaldır + Mevcut Hesap + + Kullanıcı@Sunucu + Parola: + Parolamı hatırla. + Oturumumu otomatik olarak aç. + Hesabınız yok mu? + Oturum aç + Güvenliğiniz için, eğer telefonunuz kaybolur veya çalınırsa, bilgisayarınızdan web sayfasına gidip parolanızı değiştirin. + Bu seçenek, bu uygulamayı her açtığınızda oturumunuzu otomatik olarak açar. Bu seçeneği devre dışı bırakmak için oturumu kapatın ve ardından \\"Oturumumu otomatik olarak aç\\" onay kutusunun işaretini kaldırın. + Tor kullanarak bağlan (Orbot uygulamasını yeniden başlatmanız gerekir) + kullanıcı@alanadı + yeni kullanıcı adı + servis sağlayıcı (dukgo.com, jabber.ccc.de) + parola + parolayı onayla + Gelişmiş Hesap Ayarları + Hesap Kurulumu + Hesabı Kaydet + Süreklilik + Parolayı Hatırla + Parola önbelleğe alındı + Parola önbelleğe alınmadı + Otomatik Giriş Yap + ChatSecure başlatıldığında bağlan + ChatSecure başlatıldığında bağlanma + Şimdi Giriş Yap + Hesap kurulumuna göre bağlan + Hesap kurulumuna göre bağlanma + Kişisel (isteğe bağlı) + Hesap Adı (isminiz) + Hesabınız nasıl görünüyor + Profil + Kendinizi kısacık tanıtın + Şifrelemeyi zorunlu kıl / açık metni reddet + Mümkün olduğunda sohbetleri otomatik olarak şifrele + Sohbetleri talep edildiğinde şifrele + Sohbetleri şifrelemeyi kaldır + Giriş bilgileri doğrulanıyor… + Anahtar çifti oluşturuluyor… + Giriş yapılıyor… + Hesap Sihirbazı + Hesabınızın ID\'si + Sunucu Yapılandır + Hazır mısınız? + XMPP sohbet servisine bağlanırken kullandığınız ChatSecure ayarlarınızı yapmak için hesap bilgilerinizi girin. Bir email hesabı gibi görünür: + Lütfen ID\'nizi girin (user@hostname) + Lütfen jabber/xmpp sohbet sunucu adresini ve portunu girin veya düzenleyin (varsayılan 5222). + ChatSecure ayarlarınız yapıldı, şimdi servise bağlanıp güvenli ve gizli sohbet edebilirsiniz! + Orbot (Tor) + Sohbet servisi alan adı + Yeni hesap kaydediliyor… + ChatSecure açılıyor… Oturum açmayı iptal et - - + Oturum açılıyor\u2026 + Oturum kapatılıyor\u2026 + + SRV sorgulaması yap + Alan adından XMPP sunucusu bulmak için DNS SRV kullan + Şifrelenmemiş bir bağlantı üzerindeyken kullanıcı adı ve parolanın şifrelenmeden gönderilmesine izin ver + Açık metin ile onaylamaya izin ver + Sertifikanın güvenilir olduğunu doğrula + TLS Doğrulaması + TLS bağlantısı gerekli + İletişim şifrelemesi + Şifreli sohbet nasıl başlatılır + Gerektiğinde bağlanılacak sunucu + Sunucuya Bağlan + XMPP Sunucusu için TCP Portu + Sunucu Portu + XMPP Kaynağı + Bağlanan diğer kullanıcılardan bu bağlantıyı ayırmak için + XMPP Kaynak Önceliği + Birden çok aktif kaynağı olan kullanıcılara gönderilecen iletiler en öncelikli kaynağa gönderilecek + + Görüşmeler + Görüşme yok.\n \n Başlatmak için buraya basın! + Yapılandırılacak bir +hesabınız yoktur. + +Eklemek için basın! + Bağlantı listesi Kişi ekle - Kişiyi sil - Kişiyi engelle - + Kişi takma adı Engellenenler - - Hesap listesi - + Rumuz + \"%1$s\" kişisi silinecek. + \"%1$s\" kişisi engellenecek. + \"%1$s\" kişisinin engellemesi kaldırılacak. + \"%1$s\" kişisi eklendi. + \"%1$s\" kişisi silindi. + \"%1$s\" kişisi engellendi. + \"%1$s\" kişisinin engellemesi kaldırıldı. Yeni Sohbet - - Yeni Hesap - - Ayarlar - Kişileri Ara - + Izgarayı Göster Sohbeti başlat - - Çıkış - Profili görüntüle - - - Şifrelemeyi Başlat - Şifrelemeyi Durdur - Sohbeti Temizle - Sohbeti sonlandır - Güvenli Konuşma - - - Kişi listesi - - Davet et... - - Sohbetler arasında geçiş yap - - İfade ekle - Tekrar gönder - - Parmak İzi Tara - Parmak iziniz - Parmak İzini Doğrula - Gizliliği Doğrula - - Menü+ - - - - Doğrula - - Tüm servislerden çıkış yapmak istiyor musunuz? - - - - - - - - Tamam - - İptal - - Tamam - - İptal - - - - - - - - - - Kullanıcı adı: - - Şifre: - - Şifremi hatırla. - - Oturumumu otomatik olarak aç. - - Hesabınız yok mu? - - Güvenliğiniz için, eğer telefonunuz kaybolur veya çalınırsa, bilgisayarınızdan web sayfasına gidip şifrenizi değiştirin. - Bu seçenek, bu uygulamayı her açtığınızda oturumunuzu otomatik olarak açar. Bu seçeneği devre dışı bırakmak için oturumu kapatın ve ardından \"Oturumumu otomatik olarak aç\" onay kutusunun işaretini kaldırın. - - Oturum aç - - - ChatSecure açılıyor... - - Oturum açılıyor... - - - Arka plan verileri devre dışı - - - - - - Etkinleştir - - Çık - - - - - - + Anahtarı Doğrula + Devam eden sohbetler (%1$d) + %1$d çevrimiçi Arkadaş davetiyeleri - (\"\"Bilinmiyor\"\") - Boş - Konuşma yok - - Davet edilecek kişileri seç - Kişiyi bulmak üzere yazın - Hiçbir kişi bulunamadı. - - - - Engellenmiş kişi yok. - - + Kişi + Sohbet edilecek bağlantının ismini yazın + Git + Etkin sohbet yok. + Kişi ekle + Davet etmek istediğiniz kullanıcının e-posta adresi: + Liste seçin: + Kişiler\'den eklemek için bir ad yazın. + Davetiye gönder Kişi profili - Durum: - İstemci türü: - Bilgisayar - Mobil - - - Çevrimiçi - - Meşgul - - Dışarıda - - Boşta - - Çevrimdışı - - Çevrimdışı göster - - - - - + Engellenmiş kişiler - %1$s + Engellenmiş kişi yok. + + %1$s ile sohbet et Ben - - Oluşturmak için yazın - - - - - - - - - - - - - - - - + %1$s çevrimiçi + %1$s dışarıda + %1$s meşgul + %1$s çevrimdışı + %1$s katıldı + %1$s ayrıldı + Fotoğraf Gönder + Dosya Gönder + Ses Gönder + Fotoğraf Çek + Dosya Aktarımı + Aktarım Tamamla + Aktarım devam eden + Aktarımı kabul? + size dosyayı göndermek istiyor + Paylaşımını göndermek için uygun bağlantı yok! Gönder - - Bu ileti gönderilemedi. - - Sunucu bağlantısı kesildi. İletiler çevrimiçi olunduğunda gönderilecek. - - - - - + Bir ileti gönder + Güvenli ileti gönder + Tekrar gönder + Sohbeti sonlandır + Sohbeti Temizle + İfade ekle + Sohbetler arasında geçiş yap + Menü+ Bağlantıyı seç - - Etkin sohbet yok. - - Şifrelenmiş sohbet oturumu başlatılıyor... - Şifrelenmiş sohbet oturumu durduruluyor... - - - Kişi ekle - - Davet etmek istediğiniz kullanıcının e-posta adresi: - - Liste seçin: - - Kişiler\'den eklemek için bir ad yazın. - - Davetiye gönder - - Jabber (XMPP) - Yerel Ağ Sohbeti (Bonjour/ZeroConf) - + Sil + Dosyaları Tut + Sohbet Oturumunu Güvenli Depolamadan Sil? + Tüm oturumların yükleme ve indirme dosyaları kalıcı olarak silinecektir. Uyarı: Bu işlem geri alınamaz! + Orjinali Sil? + Sakla + Dışarı aktar + Medya Dosyası Dışa Aktarılsın Mı? + Sohbet sonlandırılsın mı? + Sohbeti Bitir ve Dosyaları Sil + + %1$s sizi bir grup sohbetine katılmaya davet etti. + %1$s kullanıcısına davetiye gönderildi. + Kabul Et + Reddet + Grup Sohbet + Grup Sohbet Oluştur ya da Katıl + Grup sohbete bağlanılıyor… + Davet et… + Davet edilecek kişileri seç + Kişiyi bulmak üzere yazın + Hiçbir kişi bulunamadı. + +Davet etmek için buraya basın. + %1$s eklensin mi? + Evet + Hayır + %1$s kullanıcısından gelen abonelik onaylanamıyor. Lütfen daha sonra tekrar deneyin. + %1$s kullanıcısından gelen abonelik reddedilemiyor. Lütfen daha sonra tekrar deneyin. + + Şifrelemeyi Başlat + Şifrelemeyi Durdur + Şifrelenmiş sohbet oturumu başlatılıyor… + Şifrelenmiş sohbet oturumu durduruluyor… + Parmak İzi Güvenliği + Parmak İzi Güvenliği + Giriş Yap + Anahtarı yenile + Şifreleme Kapalı + Şifreleme Açık (Kontrol etmek için basın) + Karşı taraf şifreli görüşmeyi durdurdu. + Şifrelendi ve doğrulandı! + Yeni OTR anahtar çifti oluşturuluyor… + + Bir OTR anahtarı algılandı. QR parolasını şimdi taramak ister misiniz? + KeySync Etkinleştir + OTR anahtar demeti başarıyla aktarıldı + + QR Tara + Parmak iziniz + El ile + Soru + Güvenli Parmak İzi (Doğrulandı) + Bu parmak izini onaylamak istediğinizden emin misiniz? + Parmak İzini Doğrula? + Uzak parmak izi doğrulandı! + Senin için parmak izi + Parmak izi için + Barkod Tarayıcıyı Kur? + + Onaylama + Karşı tarafın iddia ettiği kişi olduğunu kanıtlaması için soracağınız soruyu ve beklediğiniz cevabı yazın. + sorulacak soru + beklenen cevap + Bağlantınız sizi onayladı. | Şimdi de siz ona sorunuzu sorarak onaylayın. + OTR Q&A Doğrulaması + Sohbet Şifreleme + Gönder + İptal + + Güvenli Konuşma + Secure Voice + Arama bütünleşmesi için OStel.co veya başka güvenli SIP servis hesabınızı buraya girin + Hesap Kurulumu + + Güvenlik ve Gizlilik Şifreleme ve Anonimlik + Şifreleme Aç/Kapa + Parola zamanaşımına uğradı + Uygulamanın şifreleme dışı sayılma zaman sınırı + + Kullanıcı Arayüzü + Dil + Diller + Sistem öntanımını kullan + Koyu Temayı Kullan + Koyu renkli uygulama teması olarak değiştir + İletileri sadece hafızaya al + İletilerin alınmasını engellemek için onları çıkarılabilir diskte değil sadece hafızada tut. (İletilerin kaybolmasına yol açabilir.) + Arkaplan görüntüsü + Bu dosya yolunu uygulamanın arkaplan görüntüsü olarak seç (\"/sdcard/foo.jpg\") + Bağlantı Izgarasını Göster + Bağlantı listesini avatar ızgarası olarak göster + Evet, Hepsi Kabul Et + Güvensiz Medyaları Sil + + Sohbet Kayıtlarını Sil + + Diğer Ayarlar + CharSecure\'ü otomatik olarak başlat + Önceden girilen hesaplara otomatik olarak bağlan Çevrimdışı kişileri gizle - - Bildirim ayarları - - IM bildirimleri - - IM geldiğinde durum çubuğunda bildir - Öncelikli süreçleri kullan Android\'in bağlantı servisini yeniden başlatma şansını azalt. | Bu, bildirim alanında gösterilecektir. Sinyal Aralığı Şarj tasarrufu için yüksek bir değer girin (dk). Yüksek bir değer içerik sağlayıcının etkin olmadığınızdan bağlantınızı kesmenize neden olabilir. + + Bildirim ayarları + IM bildirimleri + IM geldiğinde durum çubuğunda bildir Titreşim - IM geldiğinde aynı zamanda titret - Ses - IM geldiğinde aynı zamanda zil sesi çal - - Zil sesini seç - - - - - - - Kabul Et - - Reddet - - - - Kabul Et - - Reddet - - - - - - - - - - - - + ChatSecure özel zil sesi kullan + + Hata Ayıklama günlüğünü etkinleştir + Uygulama çıkış kayıt verisini hata ayıklama için standart çıkışa / logcat\'e + + Arka plan verileri devre dışı + %1$s arka plan verilerinin etkinleştirilmesine gereksinim duyuyor. + Etkinleştir + Çık + Tüm servislerden çıkmak ve tüm işlemleri durdurmak mı istiyorsunuz (hard exit)? + Yeni hesap oluştur? + \'%1$s\' kullanıcısı için yeni bir sohbet hesabı oluşturulsun mu? + Serfitica Bilgisi + Serfitika: + Yayınlayan: + SHA1 Parmak İzi: + Yayınlanma: + Sona Erme: + Sohbet Odasına Katıl? + Arkaplan Seç + Resiö Seç + + Yeni %1$s iletileri + %1$d okunmamış sohbet + %s kullanıcısından yeni arkadaş davetiyesi Grup sohbeti davetiyeleri - - - - - - - - - - - - IM hizmetini başlat - Amacını bildirerek uygulamaların IM hizmetini başlatmasına izin verir. - - + %s kullanıcısından gelen yeni grup sohbeti daveti + Gelen yeni ileti(ler) + ChatSecure servisi başlatılıyor… + Etkinleştirildi & kilidi açıldı + mesaj panoya kopyalandı + Dikkat - - - + Hata: + Hata kodu %1$d + %1$s hizmetinde oturum açılamıyor. Lütfen daha sonra tekrar deneyin."\n"(Ayrıntılı bilgi: %2$s) Liste eklenmedi. - Kişi engellenmedi. - Kişinin engellemesi kaldırılmadı. - Lütfen önce bir kişi seçin. - \"Bağlantı kesildi!\"\n - Hizmet hatası! - Kişi listesi yüklenmedi. - Sunucuya bağlanılamıyor. Lütfen bağlantınızı kontrol edin. - - - - - + %1$s zaten Kişi listenizde var. + \"%1$s\" kişisi engellenmiş. Kişi listeniz yüklenirken lütfen bekleyin. - Bir ağ hatası oluştu. Bu bağlantı için WiFi gereklidir. - Sunucu bu işlevi desteklemiyor. - - Girdiğiniz şifre geçerli değil. - + Girdiğiniz parola geçerli değil. Sunucu bir hata ile karşılaştı. - Sunucu bu işlevi desteklemiyor. - Sunucu şu an kullanılamıyor. - Sunucu zaman aşımına uğradı. - Sunucu geçerli sürümü desteklemiyor. - İleti kuyruğu dolu. - Sunucu alan adına yönlendirmeyi desteklemiyor. - Girdiğiniz kullanıcı adı tanınmıyor. - Maalesef, kullanıcı tarafından engellenmişsiniz. - Oturum zaman aşımına uğradı, lütfen tekrar oturum açın. - başka bir istemciden oturum açtınız. başka bir istemciden zaten oturum açtınız. - Maalesef, telefon numarası SIM kartınızdan okunamıyor. Lütfen yardım için operatörünüze başvurun. - Şu anda oturumunuz açık değil. - - - + ChatSecure kullanıcı adınızı veya parolanızı onaylarken bir sorunla karşılaştı - lütfen kontrol edip yeniden deneyin. + ChatSecure anahtar eşi oluştururken bir sorunla karşılaştı. + ChatSecure sohbet sunucusuyla bağlanırken bir sorunla karşılaştı - lütfen ayarlarınızı gözden geçirip tekrar deneyin. + ChatSecure bağlanırken bir sorunla karşılaştı - lütfen internet bağlantınızı kontrol edip tekrar deneyin. + ChatSecure ağdaki bir bağlantıyı kaybetti + ChatSecure yeniden bağlantı kurmaya çalışıyor + ID\'nizin bir parçası olan @hostname.com kısmını girmediniz. Yeniden deneyin! + Sunucu ismi .com .net vb. bir uzantıya sahip değil. Yeniden deneyin! + Parolanızı girin: + Tor ağını kullandığınız için, XMPP \'Bağlantı Sunucusu\' ismini Gelişmiş Hesap Ayarları menüsünden kendiniz girmelisiniz + UYARI: Bu ​​hizmet ZAYIF şifrelemeli bir sertifika kullanıyor. Lütfen, yöneticiye yükseltmek için bildirin. + Grup sohbet oluştur ya da katıl devre dışı bırak + Üzgünüz, bu dosya türü paylaşılamaz + Dosyaları paylaşmak için şifreleme etkinleştirmeniz gerekir + Dosyaları paylaşmak için sohbet şifrelemeyi etkinleştirmeniz gerekir + ChatSecure kendi görevini kaldırmak için yapılmış bir istek algıladı. ChatSecure muhtemelen çökecek. Bunun yerine lütfen hesap listesi ekranından Hepsinden çıkış yap menü öğresini kullanın. + Bu dosya türü için hiçbir görüntüleyici bulunmuyor + Lütfen kodlarını taramadan önce güvenli bir konuşma başlat + OTR anahtar demeti aktarılmadı; lütfen dosyanın var olduğunu, doğru formatta ve klasörde olduğunu kontrol edin + Lütfen \'otr_keystore.ofcaes\' dosyasını masaüstü KeySync aracından cihazınızın kök dizinine kopyalayın + Bu ileti gönderilemedi. + Yeniden bağlantı kurulduğunda iletiler gönderilecek + %1$s çevrimdışı. Gönderdiğiniz iletiler %1$s çevrimiçi olduğunda teslim edilecek. + %1$s Kişi listenizde yok. + Parolanız eşleşmiyor + Port numarası bir sayı olmalıdır + Aktarım Hatası + Hesap Ayarları + + Gruplar + Görüşme(ler)i aç + Çıkış + Panik + Arkadaşlar + Kişiler + Sunucu Sertifikası Kabul Et? + Parmak İzi + yükleniyor… + + ChatSecure Hakkında\nhttps://guardianproject.info/apps/chatsecure/ + + Kişi Listesi + Ayarlar + Hesap listesi + Çıkış + Kişi listesi + + Jabber (XMPP) + Yerel Ağ Sohbeti (Bonjour/ZeroConf) + Google hesabı + dukgo.com + + + Çevrimiçi + Meşgul + Dışarıda + Boşta + Çevrimdışı + Çevrimdışı göster + Mutlu Üzgün @@ -415,7 +487,7 @@ Gülme Kafası karışmış - + Mutlu Üzgün @@ -448,180 +520,26 @@ :-! :-[ O:-) - :-\ + :-\\ :\'( :-X :-D o_O - Bağlantı listesi - Kişi Listesi - Şifreleme Aç/Kapa - Tor kullanarak bağlan (Orbot uygulamasını yeniden başlatmanız gerekir) - - ChatSecure - ChatSecure\'ü ilk kez mi kullanıyorsunuz? - Başlamak için sabırsızlanıyor musunuz? - Başlayın - Hesap Kurulumu - - ChatSecure hakkında - ChatSecure başkalarının görüşmelerinizi dinlemesini engelleyen bir mobil anlık mesajlaşma uygulamasıdır. Bu uygulama Jabber veya XMPP protokolünü kullanan tüm sohbet servisleri ile uyumludur; örneğin Google GTalk veya Jabber.org - - Nasıl güvenli? - \'Kayıt-dışı mesajlaşma\', Off-the-Record -OTR, gerçek hayattaki gizli konuşmayı örnek alarak geliştirilmiş bir güvenlik sistemidir; Şifreleme, Onaylama, Reddedilebilirlik ve Gizliliğin Aktarılması gibi özelliklere sahiptir. OTR masaüstü bilgisayarlarla da uyumludur; örneğin Adium ve Pidgin. - - Sohbetlerim Güvende Mi? - ChatSecure\'ün sohbet şifreleme özelliği ancak diğer taraf da uyumlu bir uygulama veya yazılım kullandığında geçerlidir; yani bağlantılarınızın mobil iletişimde ChatSecure, masaüstü bilgisayarlarında ise Adium veya Pidgin kullandıklarına emin olun. ChatSecure\'ün sohbetlerinizi hangi durumlarda ve nasıl şifreleyeceğini ayarlamak için Hesap Ayarları menüsünü kullanabilirsiniz. Hadi başlayın! - - Parola Ayarlama - Lütfen başlamadan önce ChatSecure bilgilerinizi izinsiz girişlerden korumak için güvenli bir parola oluşturun. - Parola: - Parola (tekrar): - - kullanıcı@alanadı.com - Yeni kullanıcı adı - Servis sağlayıcı (dukgo.com, jabber.ccc.de) - parola - Parolayı onayla - Gelişmiş Hesap Ayarları - Hesap Türü - Hesabı kaydet - - Süreklilik - Parolayı Hatırla - Parola önbelleğe alındı - Parola önbelleğe alınmadı - Otomatik Giriş Yap - ChatSecure başlatıldığında bağlan - ChatSecure başlatıldığında bağlanma - Şimdi giriş yap - Hesap kurulumuna göre bağlan - Hesap kurulumuna göre bağlan - - Kişisel (isteğe bağlı) - Hesap Adı (isminiz) - Hesabınız nasıl görünüyor - Profil - Kendinizi kısacık tanıtın - - Şifrelemeyi zorunlu kıl / açık metni reddet - Mümkün olduğunda sohbetleri otomatik olarak şifrele - Sohbetleri talep edildiğinde şifrele - Sohbetleri şifrelemeyi kaldır - - Giriş bilgileri doğrulanıyor... - Anahtar çifti oluşturuluyor... - Giriş yapılıyor... - - ChatSecure kullanıcı adınızı veya parolanızı onaylarken bir sorunla karşılaştı - lütfen kontrol edip yeniden deneyin. - ChatSecure anahtar eşi oluştururken bir sorunla karşılaştı. - ChatSecure sohbet sunucusuyla bağlanırken bir sorunla karşılaştı - lütfen ayarlarınızı gözden geçirip tekrar deneyin. - ChatSecure bağlanırken bir sorunla karşılaştı - lütfen internet bağlantınızı kontrol edip tekrar deneyin. - Karşı tarafın parmak izi - Parmak iziniz - Giriş Yap - Şifreyi yenile - ChatSecure ağdaki bir bağlantıyı kaybetti - ChatSecure yeniden bağlantı kurmaya çalışıyor - Uyarı: Bu sohbet şifrelenmedi - Bu sohbet güvenli fakat katılımcıların kimlikleri doğrulanmadı - Uyarı: sohbet şifrelemesi durduruldu. - Bu sohbet güvenli ve doğrulandı. - Hesap Sihirbazı - Hesabınızın ID\'si - Sunucu Yapılandır - Hazır mısınız? - XMPP sohbet servisine bağlanırken kullandığınız ChatSecure ayarlarınızı yapmak için hesap bilgilerinizi girin. Bir email hesabı gibi görünür: - Lütfen ID\'nizi girin (user@hostname) - Lütfen jabber/xmpp sohbet sunucu adresini ve portunu girin veya düzenleyin (varsayılan 5222). - ChatSecure ayarlarınız yapıldı, şimdi servise bağlanıp güvenli ve gizli sohbet edebilirsiniz! - ID\'nizin bir parçası olan @hostname.com kısmını girmediniz. Yeniden deneyin! - Sunucu ismi .com .net vb. bir uzantıya sahip değil. Yeniden deneyin! - Parolanızı girin: - - Hesap Ayarları - defLoc - DİKKAT: ChatSecure\'ün bu eski sürümü güvenlik açıkları barındırıyor olabilir. - - Gruplar - Yeni OTR anahtar çifti oluşturuluyor... - Kişi - Sohbet edilecek bağlantının ismini yazın - Git - Dil - Diller - InTheClear hangi dili kullansın? - SRV sorgulaması yap - Alan adından XMPP sunucusu bulmak için DNS SRV kullan - Şifrelenmemiş bir bağlantı üzerindeyken kullanıcı adı ve parolanın şifrelenmeden gönderilmesine izin ver - Açık metin ile onaylamaya izin ver - Sertifikanın güvenilir olduğunu doğrula - TLS Doğrulaması - TLS/SSL bağlantısını zorunlu kıl - İletişim şifrelemesi - Şifreli sohbet nasıl başlatılır - Gerektiğinde bağlanılacak sunucu - Sunucuya Bağlan - XMPP Sunucusu için TCP Portu - Sunucu Portu - XMPP Kaynağı - Bağlanan diğer kullanıcılardan bu bağlantıyı ayırmak için - XMPP Kaynak Önceliği - Birden çok aktif kaynağı olan kullanıcılara gönderilecen mesajlar en öncelikli kaynağa gönderilecek - İleri - Geri - Görüşmeler - Gelen yeni mesaj(lar) - Onaylama - Karşı tarafın iddia ettiği kişi olduğunu kanıtlaması için soracağınız soruyu ve beklediğiniz cevabı yazın. - sorulacak soru - beklenen cevap - Bağlantınız sizi onayladı. | Şimdi de siz ona sorunuzu sorarak onaylayın. - Görüşme yok.\n \n Başlatmak için buraya basın! - Koyu Temayı Kullan - Tor ağını kullandığınız için, XMPP \'Bağlantı Sunucusu\' ismini Gelişmiş Hesap Ayarları menüsünden kendiniz girmelisiniz - - CharSecure\'ü otomatik olarak başlat - Önceden girilen hesaplara otomatik olarak bağlan - Yapılandırılacak bir hesabınız yoktur. Eklemek için tıklayın! - Google hesabı - Mesajları sadece hafızaya al - Mesajların alınmasını engellemek için onları çıkarılabilir diskte değil sadece hafızada tut. (Mesajların kaybolmasına yol açabilir.) - Arkaplan görüntüsü - Bu dosya yolunu uygulamanın arkaplan görüntüsü olarak seç (\"/sdcard/foo.jpg\") - Güvenlik ve Gizlilik - Kullanıcı Arayüzü - Diğer Ayarlar - Görüşme(ler)i aç - Orbot (Tor) - Çıkış - - Tüm servislerden çıkmak ve tüm işlemleri durdurmak mı istiyorsunuz (hard exit)? - *Yeni* bir parola giriniz. Parolanız en az bir büyük harf, bir küçük harf ve bir numara barındırmalı ve 6 karakterden daha uzun olmalıdır. - Varolan notlarınızı yeni parola ile şifreliyoruz (lütfen bekleyiniz...) - Parolanızı girin: - Hoşgeldiniz! Notlarınızı koruma altına almak için güçlü bir parola oluşturun. Parolanız en az bir büyük harf, bir küçük harf ve bir numara barındırmalı ve 6 karakterden daha uzun olmalıdır. - Parolanız yeterince uzun değil. - Parolanız hiç büyük harf barındırmıyor. - Parolanız hiç küçük harf barındırmıyor. - Parolanız hiç sayı barındırmıyor. - - ChatSecure kilitlendi - Parola oluştur - Parola gir - Yeni parola gir - Yeni parolayı onayla - Parolalar uyumlu değil, lütfen yeniden deneyin - Secure Voice - Arama bütünleşmesi için OStel.co veya başka güvenli SIP servis hesabınızı buraya girin - dukgo.com - Bir OTR anahtarı algılandı. QR parolasını şimdi taramak ister misiniz? - OTR anahtarı aktar - OTR anahtar demeti başarıyla aktarıldı - OTR anahtar demeti aktarılmadı; lütfen dosyanın var olduğunu, doğru formatta ve klasörde olduğunu kontrol edin - Parola zamanaşımına uğradı - Uygulamanın şifreleme dışı sayılma zaman sınırı + Mevcut Hesap + Google Hesabı + Aynı yerel WiFi veya mesh ağı üzerinde diğer kişilerle sohbet et - internet veya sunucuya gerek yok! + WiFi Sohbeti Etkinleştir + Yeni Hesap + Yeni Hesap Oluştur + Gizli Kimlik! + Kimlik Oluştur + Bu bir grup sohbetidir + Orbot Kur? + Her zaman + Orbot\'u Başlat? + odada kullanmak için takma ad + oluşturmak veya katılma için oda adı + grup sohbet sunucusu (konferans.sitesi.com) + Okunamayan bir şifreli mesaj aldınız diff --git a/res/values-uk/arrays.xml b/res/values-uk/arrays.xml new file mode 100644 index 000000000..d945592b3 --- /dev/null +++ b/res/values-uk/arrays.xml @@ -0,0 +1,9 @@ + + + + Примусово / вимагається + Автоматичний дозвіл + За запитом + Вимкнено / Ніколи + + diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml new file mode 100644 index 000000000..1cc23f2bf --- /dev/null +++ b/res/values-uk/strings.xml @@ -0,0 +1,215 @@ + + + + ChatSecure + ChatSecure + + + Підтвердити + Гаразд + Скасувати + Гаразд + Скасувати + Далі + Назад + Підключити + Грати + + Відкрити + ChatSecure заблоковано + Укажіть пароль + Повторіть пароль + Пароль + Підтвердіть новий пароль + Паролі не збігаються, спробуйте знову + Пропустити >> + Запит інформації + Уведіть пароль, будь ласка + Пароль, будь ласка… + Фраза-ключ + Фраза-ключ (знову) + + Оберіть екаунт + Оберіть екаунт + (%1$d) + Додати екаунт %1$s + Про програму + Вперше користуєтесь ChatSecure? + Налаштування екаунту + Фраза-ключ: + Вітаємо! Уведіть надійну фразу-ключ для захисту Ваших записів. Вона повинна містити принаймні одну літеру у верхньому реєстрі, одну у нижньому і одну цифру, і бути довшою за шість знаків. + Ваша фраза-ключ закоротка + Ваша фраза-ключ не містить жодної літери у верхньому реєстрі + Ваша фраза-ключ не містить жодної літери у нижньому регістрі + + Про ChatSecure + + Як відбувається захист? + + + Новий екаунт + Додати екаунт + Редагувати екаунт + Вилучити екаунт + Існуючий екаунт + + Користувач@Хост + Пароль: + Запам’ятати пароль. + Входити автоматично. + Не маєте екаунту? + Увійти + користувач@домен + новий користувач + провайдер сервісу (dukgo.com, jabber.ccc.de) + пароль + повторіть пароль + Розширені налаштування екаунту + Реєстрація екаунту + Пароль закешовано + Пароль не закешовано + Автоматичний вхід + Профіль + Триває вхід… + Майстер створення екаунту + ID вашого екаунту + Налаштування сервера + Ви готові? + Orbot (Tor) + Триває реєстрація нового екаунту… + Скасувати вхід + + Порт сервера + + Список контактів - %1$s + Додати контакт + Видалити контакт + Блокувати контакт + Нік контакта + Заблоковано + Нік + Контакт "%1$s" буде вилучено. + Новий чат + Пошук контактів + Показати сітку + Розпочати чат + Переглянути профіль + Перевірка контактів + %1$d в мережі + Контакт + Вперед + Додати контакт + Надіслати запрошення + Профіль контакта + Статус: + Тип клієнта: + Комп’ютер + Телефон + + Я + %1$s в мережі + %1$s відійшов + %1$s зайнятий + %1$s поза мережею + %1$s приєднався + %1$s покинув кімнату + Надіслати фото + Надіслати файл + Надіслати аудіо + Зробити фото + Передача файлів + Передачу завершено + Передача триває + Прийняти передачу? + бажає надіслати вам файл + Надіслати + Надіслати повідомлення + Надіслати захищене повідомлення + Повторно надіслати + Закрити чат + Очистити чат + Вставити посмішку + Перемкнути чат + Меню+ + Вставити посилання + Вилучити + Залити файли + Залишити + Експортувати + Експортувати медіа-файли? + Цей медіа-файл буде експортовано до %1$s + Завершити чат? + Завершити чат і видалити файли + + %1$s запрошує вас приєднатись до групового чату. + Прийняти + Відхилити + Груповий чат + Під’єднання до групового чату… + Запросити\u2026 + + Почати шифрування + Завершити шифрування + Увійти + + + Сканувати QR + Питання + + Авторизація + Надіслати + Скасувати + + Захищений дзвінок + + + + Інтерфейс + Мова + Мови + Системна + Використовувати темну тему + Змінити тему додатку на темну + Зображення тла + Так, приймати все + + + Вібрація + Звук + + Увімкнути журнали відлагодження + + Увімкнути + Вийти + Створити новий екаунт? + Минає: + Оберіть зображення + + + Увага + Помилка: + Помилка сервісу! + Сервер недосяжний. + Час очікування сервера минув. + Мережа відсутня + Уведіть свій пароль: + Паролі не співпадають + Номер порту має бути числом + Помилка передачі + + Групи + Контакти + завантаження… + + + Список контактів + Налаштування + Керування екаунтом + Вийти + Список контактів + + + + + + diff --git a/res/values-v14/styles_chatsecure.xml b/res/values-v14/styles_chatsecure.xml deleted file mode 100644 index 24cc3e6b7..000000000 --- a/res/values-v14/styles_chatsecure.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/values-vi/arrays.xml b/res/values-vi/arrays.xml index 9ee5fd80e..9e686a21a 100644 --- a/res/values-vi/arrays.xml +++ b/res/values-vi/arrays.xml @@ -6,64 +6,4 @@ Như đã yêu cầu Tắt / Không bao giờ - - bắt buộc - tự động - yêu cầu - tắt - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tibetan བོད་སྐད། - Tiếng Việt - Tϋrkçe - Čeština - ελληνικά - - - xx - ar - fa - zh-rCN - ja - ko - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - bo - vi - tr - cs - el - diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 3d17ef07b..950531701 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -1,401 +1,437 @@ - - đọc tin nhắn nhanh - \nCho phép ứng dụng đọc dữ liệu từ dịch vụ tin nhắn nhanh - viết tin nhắn nhanh - \nCho phép ứng dụng viết/thêm dữ liệu vào dịch vụ tin nhắn nhanh - + + + ChatSecure ChatSecure - - + + đọc nội dung chat + +Cho phép ứng dụng đọc dữ liệu từ dịch vụ chat. + gõ vào nội dung chat + +Cho phép ứng dụng viết/thêm dữ liệu vào dịch vụ chat. + Bắt đầu thao tác chat + Cho phép các ứng dụng bắt đầu sử dụng dịch vụ chat thông qua Internet. + + Xác nhận + Đồng Ý + Hủy Bỏ + Đồng Ý + Hủy Bỏ + Tiếp Theo + Quay Lại + Kết nối + + Mở + Đã khóa ChatSecure + Đặt mật khẩu + Xác nhận mật khẩu + Mật khẩu + Tạo mật khẩu mới + Xác nhận mật khẩu mới + Mật khẩu không khớp, xin vui lòng thử lại lần nữa + Tôi không muốn dùng mật khẩu (không cần điền mật khẩu!) + Chọn một tài khoản - - Về chúng tôi - + Chọn một tài khoản + (%1$d) + Thêm %1$s tài khoản + Thông tin + Đăng xuất toàn bộ tài khoản + Bạn muốn đăng xuất ra khỏi tất cả các dịch vụ? + Bạn đã đăng xuất ra khỏi %1$s. + Lần đầu dùng ChatSecure? + Bạn muốn bắt đầu ngay bây giờ ư? + Bắt đầu + Điều chỉnh tài khoản + Chỉnh sửa cụm từ mật khẩu + Trước khi bạn bắt đầu xin vui lòng chọn một cụm từ an toàn để bảo vệ dữ liệu ChatSecure. + Cụm từ mật khẩu: + Cụm từ mật khẩu (gõ lại): + Điền vào các ký tự cho phần mật khẩu, nên nhớ, ký tự mật khẩu được hiển thị sẽ ở dạng *. Phần mật khẩu của bạn ít nhất phải có độ dài tối thiểu là 6 ký tự, trong đó có ít nhất một chữ cái được in hoa, một chữ cái in thường và một con số kèm theo. Việc làm này nhằm tăng tính bảo mật của mật khẩu. + Đang mã hóa các ghi chú hiện có của bạn với các cụm từ mới (hãy kiên nhẫn đợi trong giây lát…) + Đánh vào cụm từ mật khẩu: + Chào bạn! Hãy điền vào các cụm từ bảo mật để giúp cho ghi chú của bạn được an toàn hơn. Các cụm từ này ít nhất phải có độ dài tối thiểu là 6 ký tự, trong đó có ít nhất một chữ cái được in hoa, một chữ cái in thường và một con số kèm theo. + Cụm từ bảo mật không đạt yêu cầu về độ dài ký tự + Cụm từ bảo mật không chứa bất kỳ ký tự nào được in hoa + Cụm từ bảo mật không chứa bất kỳ ký tự in thường nào + Cụm từ bảo mật không chứa bất kỳ ký tự là chữ số nào + + Thông tin về ChatSecure + ChatSecure là một dịch vụ tin nhắn nhanh di động với những chức năng ngăn chận những kẻ muốn rình mò những cuộc chuyện trò hoặc trao đổi thông tin của bạn. + +Dịch vụ này hỗ trợ các dịch vụ tin nhắn dùng giao thức Jabber hoặc XMPP, như Google Gtalk hoặc Jabber.org. + + Ứng dụng này liệu chăng có an toàn hay không? + \'Off-the-Record Messaging\' là một hệ thống bảo mật nhằm để bảo đảm quyền riêng tư bằng cách mô phỏng những đặc tính của một cuộc đối thoại riêng tư ở ngoài đời bao gồm Mã hóa, Xác thực, Phủ nhận, Bí mật Chuyển tiếp. + +Giao thức OTR tương thích với các ứng dụng chat của máy vi tính như Adium hoặc Pidgin. + + Nội dung các đoạn hội thoại chat của tôi có an toàn hay không? + Tính năng mã hóa nội dung chat hoặc còn gọi là tán gẫu trên ChatSecure chỉ có thể được thực hiện khi đối tượng mà bạn gửi cũng đồng thời sử dụng ChatSecure nếu dùng di động hoặc Adium hay Pidgin khi dùng trên máy tính. Bạn có thể tự mình chỉnh sửa loại, mức độ mã hóa thông tin của ChatSecure trong phần Thiết Lập Tài Khoản. + +Hãy bắt đầu trải nghiệm đi nào! + + Tạo tài khoản mới Thêm tài khoản - Chỉnh sửa tài khoản - Xóa tài khoản - - Đăng xuất ra hết - - - Chọn một tài khoản - - - - - Hủy bỏ đăng nhập - - - Thêm tên liên lạc - - Xoá tên liên lạc - - Chặn tên liên lạc - - Đã chặn - - Các tài khoản - - Chat mới - - Tài khoản mới - - Cấu hình - - Tìm kiếm tên liên lạc - - Bắt đầu chat - - Đăng Xuất - - Xác minh - - - Bắt đầu mã hoá - Ngừng mã hoá - Xoá chat - Kết thúc chat - Cuộc gọi an toàn - - - Danh sách liên lạc - - Mời... - - chuyển qua chat khác - - Thêm biểu hiện cười - Gửi lại - - Scan vân tay - Vân tay của bạn - Xác nhận vân tay - Xác nhận bí mật - - Menu+ - - - - Xác nhận - - Bạn muốn đăng xuất ra khỏi tất cả các dịch vụ? - - - - - - - - Ok - - Hủy bỏ - - Ok - - Hủy bỏ - - - - - - - - - + Người dùng@Máy chủ - Mật khẩu: - - Nhớ mật khẩu của tôi - - Tự động đăng nhập - + Nhớ mật khẩu của tôi. + Tự động đăng nhập. Bạn không có tài khoản? - - Vì lý do an ninh, nếu điện thoại của bạn bị đánh cắp hoặc mất, hãy vào trang mạng qua máy vi tính để thay đổi mật khẩu. - Chọn lựa này tự động đăng nhập mỗi khi bạn cho chạy ứng dụng này. Để bỏ chức năng này, bạn đăng xuất, rồi xóa sạch ô \"Đăng nhập tự động\". - Đăng nhập - - - đang khởi động ChatSecure... - - Đang đăng nhập - - - Dữ liệu trong hậu cảnh đã được vô hiệu hoá - - - - - - Kích hoạt - - Thoát - - - - - - - Mời bạn - + Vì lý do an ninh, nếu điện thoại của bạn bị đánh cắp hoặc mất, hãy vào website của chúng tôi bằng máy vi tính để thay đổi mật khẩu. + Khi chọn phần này, chương trình sẽ tự động đăng nhập thông tin đăng nhập mà bạn đã chọn. Để bỏ chức năng này, bạn hãy đăng xuất, rồi bỏ chọn phần \"Đăng nhập tự động\". + Kết nối qua mạng dạng Tor (Cần kết nối với ứng dụng Orbot ) + user@domain.com + tên người dùng mới + dịch vụ cung cấp (dukgo.com, jabber.ccc.de) + mật khẩu + xác nhận mật khẩu + Điều chỉnh tài khoản nâng cao + Loại tài khoản + Đăng ký Tài Khoản + Luôn giữ kết nối + Ghi nhớ mật khẩu + Ghi nhớ mật khẩu trong bộ nhớ đệm + Mật khẩu không được lưu trong bộ nhớ đệm + Tự động đăng nhập + Kết nối vào ChatSecure khi bắt đầu + Đừng kết nối vào ChatSecure khi bắt đầu + Đăng nhập ngay + Kết nối vào sau khi cài đặt tài khoản xong + Đừng kết nối vào sau khi cài đặt tài khoản xong + Cá nhân hóa (có hoặc không) + Nickname (biệt danh của bạn) + Cách hiển thị tài khoản của bạn khi đang online + Hồ sơ + Đôi chút thông tin về bản thân bạn + Bắt buộc dùng hệ thống mã hoá / không dùng dạng văn bản thông thường + Tự động mã hóa khi có thể + Buộc phải mã hóa nội dung chat + Vô hiệu hóa tính năng mã hóa khi chat + Đang xác nhận thông tin cá nhân… + Đang tạo ra bộ khoá mã hoá + Đang đăng nhập vào… + Hướng dẫn điều chỉnh tài khoản + Tên tài khoản bạn + Cấu hình máy chủ + Bạn đã sẵn sàng chưa? + Hãy đánh vào tên tài khoản của bạn để cấu hình ChatSecure cho dịch vụ tin nhắn XMPP. Tên tài khoản nhìn giống như địa chỉ email: + Hãy gõ vào tên tài khoản (user@hostname): + Hãy gõ vào hoặc điều chỉnh tên máy chủ và mã số cổng ( mã số cổng mặc định là 5222 ) cho dịch vụ jabber/xmpp của bạn. + ChatSecure đã cấu hình xong, bây giờ bạn có thể sử dụng và bắt đầu chat một cách an toàn trong riêng tư. + Orbot (Tor) + Chọn một tên miền + Đăng ký tài khoản mới + đang khởi động ChatSecure… + Hủy bỏ đăng nhập + Đang đăng nhập\u2026 + Đang đăng xuất\u2026 + + Thực hiện việc dò tìm dạng SRV + Dùng DNS SRV để tìm máy chủ XMPP từ tên miền + Cho phép tên tài khoản và mật khẩu được gửi đi theo dạng văn bản thường khi sử dụng kết nối chưa được mã hoá + Cho phép xác thực bằng văn bản thường + Xác nhận chứng chỉ bảo mật này là an toàn + Xác nhận dạng TLS + Yêu cầu cần có kết nối dạng TLS + Quy Tắc Truyền Thông Tin Mã Hoá + cách thức mã hóa nội dung chat lúc ban đầu + Máy chủ kết nối vào, khi cần + Kết nối máy chủ + Cổng TCP cho máy chủ XMPP + Cổng máy chủ + Nguồn dịch vụ XMPP + để phân biệt những kết nối từ nhiều ứng dụng khác cũng đã được đăng nhập vào + Tài nguyên ưu tiên cho XMPP + Những tin gửi cho ứng dụng khách mà có nhiều tài nguyên đang hoạt động sẽ được ưu tiên khi gửi đi + + Nội dung chat + Không có cuộc trò chuyện nào + +Hãy gõ vào đây để bắt đầu + Bạn vẫn chưa +cấu hình tài khoản nào. + +Hãy chạm vào đây để thêm vào! + Danh Sách Bạn Chat - %1$s + Thêm bạn chat + Xóa bạn chat + Chặn bạn chat + Tên liên lạc + Đã chặn + Bạn chat %1$s sẽ bị xóa. + Bạn chat %1$s sẽ bị chặn. + Bạn chat %1$s sẽ không còn bị chặn. + Đã thêm %1$s vào Danh Sách Bạn Chat. + Đã xoá bạn chat %1$s. + Đã chặn bạn chat %1$s. + Không còn chặn bạn chat %1$s + Tạo phần chat mới + Tìm kiếm bạn chat + Hiển thị ô lưới + Bắt đầu chat + Hồ Sơ Cá Nhân + Xác Nhận Khóa + Các phần chat đang diễn ra (%1$d) + %1$d; đang online + Gửi lời mời chat (Không rõ) - - Trống rỗng - - Không có cuộc đối thoại nào - - Chọn liên lạc để mời - Đánh vào để tìm tên liên lạc - Không tìm thấy tên liên lạc - - - - Không có liên lạc bị chặn - - - Hồ sơ về liên lạc - - Trạng thái - - Loại ứng dụng: - - Máy vi tính - + Hiện còn trống + Hiện không có phần chat nào + Bạn chat + gõ vào tên bạn chat muốn chat + Thực hiện + Hiện không có phần chat nào đang hoạt động. + Thêm bạn chat + Địa chỉ email của bạn chat mà bạn muốn thêm vào: + Chọn một danh sách: + Gõ vào tên bạn chat từ phần Danh Bạ trên điện thoại + Gửi lời mời kết nối + Hồ sơ về bạn chat + Trạng thái: + Loại máy trạm: + Máy tính PC Di động - - - Online - - Bận - - Vắng - - Không làm gì - - Offline - - Có vẻ offline - - - - - + Những bạn chat đã bị chặn - %1$s + Hiện không có bạn chat nào bị chặn. + + Chat với %1$s Tôi - - Đánh chữ vào - - - - - - - - - - - - - - - - + %1$s đang online + %1$s hiện không rảnh + %1$s đang bận việc + %1$s offline + %1$s đã tham gia kết nối + %1$s đã rời khỏi phần chat + Gửi hình + Gửi tập tin + Gửi tập tin âm thanh + Chụp Ảnh + Truyền tải tập tin + Tiến trình truyền tải đã hoàn tất + Tiến trình truyền tải dữ liệu đang được tiến hành + Chấp nhận việc truyền tải dữ liệu? + muốn gửi đến bạn một số tập tin Gửi đi - - Tin nhắn này không gửi đi được - - Mất liên lạc với máy chủ. Các tin nhắn sẽ được gửi đi khi nối mạng lại. - - - - - - Chọn đường dẫn - - Không có chat nào đang diễn ra. - - Đang bắt đầu mã hoá cuộc trò chuyện này... - Ngưng mã hoá cuộc trò chuyện... - - - Thêm vào liên lạc - - Địa chỉ email bạn muốn mời vào: - - Chọn một danh sách: - - Đánh vào tên để thêm vào Danh Bạ - - Gửi thư mời - - Jabber (XMPP) - Chào Bạn (ZeroConf) - - Thiết lập tài khoản + Gửi một tin nhắn + Gửi tin nhắn mã hóa + Gửi lại + Kết thúc chat + Xoá nội dung chat + Thêm biểu hiện cười + Chuyển người chat + Menu+ + Chọn đường dẫn liên kết + Xóa + + %1$s đã mời bạn tham gia vào phần chat nhóm + Thư mời kết nối đã được gửi đến %1$s + Chấp thuận + Từ chối + Trò chuyện theo nhóm + Tạo hoặc tham gia vào trò chuyện nhóm + Đang kết nối tới nhóm chat … + Mời\u2026 + Chọn (các)bạn chat để gửi lời mời + Gõ vào thông tin để tìm bạn chat + Không tìm thấy liên lạc nào. + +Chạm vào để mời kết nối. + Hiện không thể đăng ký ở %1$s. Xin vui lòng thử lại sau. + Không thể từ chối phần đăng ký từ %1$s. Xin vui lòng thử lại sau. + + Bắt đầu mã hoá + Ngừng mã hoá + Đang bắt đầu mã hoá nội dung chat này… + Ngưng lại thao tác mã hóa nội dung chat… + Bảo mật vân tay + Bảo mật vân tay + Đăng nhập + Tạo Khóa + Đã tắt phần mã hóa + Đã mở phần mã hóa (Chạm để xác nhận) + Người đang chat với bạn đã dừng chế độ mã hóa trong chat + Đã mã hóa và xác nhận! + Tạo ra cặp chìa khoá mã hoá OTR mới + + Chương trình nhận diện một khóa OTR được nhập vào. Bạn có muốn quét mật khẩu cho QR bây giờ không? + Kích hoạt Đồng bộ khóa + Đã nhập thành công khóa OTR + + Quét mã QR + Vân tay của bạn + Tự điều chỉnh + Thắc mắc + Bảo mật vân tay (đã được xác nhận) + Bạn có chắc muốn xác nhận vân tay này? + Xác nhận vân tay? + Đã xác nhận vân tay từ xa thành công! + Vân tay dành cho bạn + Vân tay dành cho + + Xác nhận + Đánh vào một câu hỏi để gửi đi đến liên lạc của bạn, và câu trả lời bạn mong đợi, để xác nhận họ có phải là người bạn muốn trao đổi hay không + câu hỏi để hỏi + câu trả lời cần có + Liên lạc của bạn đã xác thực bạn thành công. Bây giờ hãy xác thực họ bằng cách đặt câu hỏi của bạn. + Gửi đi + Hủy Bỏ + + Cuộc gọi an toàn + Bảo mật giọng nói + Điền vào OStel.co hoặc các dịch vụ tài khoản bảo mật SIP để tích hợp với cuộc gọi + + Chỉnh sửa tài khoản + + An Ninh và Riêng Tư Mã hoá và Ẩn Danh - Dấu liên lạc không trực tuyến - - Cấu hình thông báo - - Thông báo cho Tin Nhắn Nhanh - - Thông báo trong thanh trạng thái khi nhận tin nhắn - + Mã hoá Tắt/Mở + Thời hạn cho mật khẩu + Thời hạn mà ứng dụng mã hóa bị khóa lại + + Giao Diện Sử Dụng + Ngôn Ngữ + Các ngôn ngữ + Dùng định dạng nền tối + Thay đổi giao diện sang màu tối + Lưu Trữ Tin Nhắn Trong Bộ Nhớ Mà Thôi + Chỉ giữ tin nhắn trong bộ nhớ, không giữ trên bộ nhớ flash để tránh tin bị chép ra. (Tin có thể bị mất) + Hình nền + Chọn tên tập tin của hình ảnh nền cho app + Hiển thị danh sách theo ô + Hiển thị danh sách lliên lạc dưới dạng các hình đại diện theo từng ô + Đồng ý, chấp nhận tất cả + + Các Chỉnh Sửa Khác + Để ChatSEcure tự động chạy + Tự động chạy và đăng nhập vào tài khoản từng dùng trước đó + Ẩn bạn chat không online Dùng tiền cảnh ưu tiên Giảm xác suất mà Android có thể khởi động lại dịch vụ kết nối của chúng ta. Sẽ để thông báo vào trong khu vực thông báo. Cường độ nhịp tim Dùng chỉ số cao hơn (theo phút) để tiết kiệm pin. Chỉ số cao sẽ làm cho nhà cung cấp dịch vụ đóng kết nối lại vì lầm tưởng là không có hoạt động. - Rung - - Và rung khi nhận tin nhắn - - Âm Thanh - - Và có tiếng reng khi tin nhận tin nhắn - - Chọn tiếng reng - - - - - - - Nhận - - Từ chối - - - - Nhận - - Từ Chối - - - - - - - - - - - - - Thư mời chat nguyên nhóm - - - - - - - - - - - - Bắt đầu dịch vụ tin nhắn nhanh - Cho phép các ứng dụng bắt đầu dịch vụ tin nhắn nhanh qua \"intent\". - - - Chú ý - - - - Danh sách không được thêm - - Liên lạc không bị chận. - - Liên lạc vẫn chưa được hết chận. - - Xin hãy chọn liên lạc trước. - - Ngắt kết nối! - - Máy chủ bị lỗi! - - Danh sách liên lạc không tải được. - - Không kết nối với máy chủ được. Xin hãy xem lại mạng của bạn. - - - - - - Xin hãy đợi trong giây lát khi danh sách liên lạc của bạn đang khởi động. - - Có lỗi mạng. + + Chỉnh sửa phần thông báo + Thông báo khi có phần chat mới + Thông báo ở thanh trạng thái khi nhận nội dung chat mới + Sử dụng chế độ rung + Sử dụng chế độ rung khi có phần chat mới + Âm Báo + Phát nhạc chuông trên điện thoại khi có nội dung chat mới + Sử dụng nhạc chuông tự chọn cho phần trò chuyện bảo mật + + Cho Phép Ghi Thông Tin Gỡ Rối + Hiển thị thông tin này trên thiết bị chuẩn hoặc trên thiết bị chuyên dụng cho việc gỡ rối + + Đã vô hiệu hóa phần dữ liệu mạng + + Phần kết nối dữ liệu mạng (bao gồm cả dữ liệu nền) đều cần thiết để cho phép ứng dụng đăng nhập vào. + + Cho phép + Thoát + Bạn muốn rời khỏi mọi dịch vụ VÀ hủy tất cả quá trình đang chạy (rời hoàn toàn)? + Tạo Tài Khoản Mới + Tạo một tài khoản chat mới cho \'%1$s\'? + + %1$s nội dung chat mới + Gửi lời mời chat theo nhóm + (Các) phần chat mới từ + Đang bắt đầu dịch vụ ChatSecure… + Đã kích hoạt& mở khóa + + Lưu Ý + Mã số lỗi %1$d + Không thêm vào được danh sách. + Bạn chat chưa bị chặn. + Bạn chat vẫn chưa được bỏ chặn. + Trước tiên xin vui lòng chọn một bạn chat. + Kết nối đã bị ngắt! + + Lỗi dịch vụ chat! + Chưa tải được danh sách bạn chat. + Hiện không thể kết nối đến phía máy chủ. Xin bạn hãy vui lòng kiểm tra lại kết nối của mình. + %1$s hiện đã có trong Danh Sách Bạn Chat. + Bạn chat %1$s đã bị chặn. + Xin vui lòng đợi trong giây lát, chương trình đang tải Danh Sách Bạn Chat của bạn. + Xảy ra lỗi về mạng. Cần mạng WIFI để kết nối. - - Máy chủ không hỗ trợ chức năng này - - Mật khẩu đánh vào không đúng. - - Máy chủ đang gặp trở ngại - - Máy chủ không hỗ trợ chức năng này - - Máy chủ hiện thời không hoạt động - - Thời gian kết nối với máy chủ đã hết hạn. - - Máy chủ không hỗ trợ cho phiên bản hiện thời - - Hàng đợi tin nhắn đã đầy - + Máy chủ không hỗ trợ chức năng này. + Mật khẩu bạn vừa điền vào không hợp lệ. + Phía máy chủ gặp lỗi. + Máy chủ không hỗ trợ chức năng này. + Máy chủ hiện thời không hoạt động. + Đã quá thời hạn cho phép dành cho kết nối đến phía máy chủ. + Phía máy chủ hiện không hỗ trợ phiên bản ứng dụng hiện tại. + Hàng đợi nội dung chat đã đầy Máy chủ không hỗ trợ chuyển tiếp đến tên miền. - - Không nhận diện được tên người dùng mà bạn điền vào. - - Xin lỗi, bạn bị người dùng này chận lại. - - Phiên thời gian đã hết, xin bạn đăng nhập vào lại. - - bạn đã đăng nhập vào từ một ứng dụng khác. - bạn đã đăng nhập vào từ một ứng dụng khác. - - Xin lỗi, không thể đọc được số điện thoại từ SIM của bạn. Xin liên lạc với tổng đài để nhận giúp đỡ. - - Bạn chưa đăng nhập vào. - - - + Không nhận diện được tên tài khoản mà bạn điền vào. + Xin lỗi, bạn bị chặn bởi phía người dùng. + Phiên đăng nhập của bạn đã hết hạn, xin hãy vui lòng đăng nhập trở lại. + bạn đã đăng nhập vào dịch vụ từ một ứng dụng khác. + bạn đã đăng nhập vào dịch vụ từ một ứng dụng khác. + Xin thứ lỗi, chúng tôi không thể đọc được số điện thoại từ SIM của bạn. Xin liên lạc với tổng đài để nhận giúp đỡ. + Bạn hiện vẫn chưa đăng nhập vào. + ChatSecure đã gặp lỗi khi đang xác nhận tên dùng và mật khẩu của bạn, xin hay xem và thử lại. + ChatSecure đã gặp lỗi khi đang tạo ra chì khoá mã hoá + ChatSecure đã gặp lỗi khi đang kết nối và máy chủ - xin vui lòng xem lại cấu hình của bạn và thử lại. + ChatSecure đã gặp lỗi khi đang kết nối và máy chủ - xin hãy xem lại mạng kết nối của bạn và thử lại. + ChatSecure đã bị mất kết nối vào mạng lưới + ChatSecure đang thử kết nối lại + Bạn chưa gõ vào phần @hostname.com của tài khoản. Xin hãy thử lại! + Tên máy chủ của bạn thiếu phần .com,.net hoặc những đuôi tên miền khác. Xin hãy thử lại! + Gõ vào mật khẩu: + Vì bạn đang xài Tor, bạn phải đánh vào tên máy chủ của XMPP \'Connect Server\' thẳng vào trong phần Cấu Hình Cao Cấp Tài Khoản. + Không thể tạo hoặc tham gia trò chuyện theo nhóm + Rất tiếc chúng tôi không thể chia sẻ loại tập tin đó + Bạn phải bật tính năng mã hóa để chia sẻ tập tin + Cho phép mã hóa phần trò chuyện khi chia sẻ tập tin + ChatSecure đã nhận ra một yeu cầu được gỡ bỏ khỏi phần tác vụ. ChatSecure sẽ có thể bị dừng hoạt động bất chợt. Xin vui lòng đăng xuất ra khỏi các tài khoản. + Không tồn tại chương trình có thể đọc định dạng file này + Hãy bắt đầu mã hóa đoạn hội thoại trước khi scan mã + OTP vẫn chưa được nhập vào; Vui lòng kiểm tra tập tin có thẻ được tồn tại trong hệ thống ở một định dạng và địa điểm khác + Xin vui lòng sao chép tập tin \'otr_keystore.ofcaes\' từ desktop KeySync tool đến thư mục gốc trong thẻ nhớ của bạn + Không thể gửi được tin nhắn này. + Các tin nhắn sẽ được gửi khi được kết nối lại + %1$s hiện đang offline. Phần nội dung chat mà bạn gửi đi sẽ được %1$s đọc khi online trở lại. + %1$s không nằm trong Danh Sách Bạn Chat của bạn. + Thiết lập tài khoản + + Cảnh Báo: Đây là phiên bản cũ của ChatSecure và có thể có những lổ hỗng an ninh hoặc lỗi. + + Nhóm + Mở xem các trao đổi + Rời + Nghiêm trọng + Bạn bè + Danh bạ + Chấp nhận chứng chỉ từ phía máy chủ? + Vân tay + + + Danh Sách Bạn Chat + Điều chỉnh + Các tài khoản + Đăng Xuất + Danh sách bạn chat + + Jabber (XMPP) + Khu vực địa phương (Bonjour/ZeroConf) + Tài khoản Google + dukgo.com + + + Online + Đang bận việc + Hiện không rảnh + Ở trạng thái chờ + Offline + Hiển thị dưới dạng offline + Vui Buồn @@ -415,7 +451,7 @@ Cười To Rối trí - + Vui Buồn @@ -454,174 +490,5 @@ :-D o_O - - Danh sách liên lạc - Mã hoá tắt/mở - Kết nối qua mạng Tor (Cần ứng dụng Orbot ) - - ChatSecure - Lần đầu dùng ChatSecure? - Nôn nóng muốn bắt đầu ngay? - Bắt đầu - Cài đặt tài khoản - - Thông tin về ChatSecure - ChatSecure là một dịch vụ tin nhắn nhanh di động với những chức năng ngăn chận những kẻ muốn rình mò những cuộc chuyện trò hoặc trao đổi thông tin của bạn.\n\nDịch vụ này hỗ trợ các dịch vụ tin nhắn dùng giao thức Jabber hoặc XMPP, như Google Gtalk hoặc Jabber.org. - - Ứng dụng này an toàn như thế nào? - \'Off-the-Record Messaging\' là một hệ thống an ninh để bảo đảm quyền riêng tư bằng cách mô phỏng những đặc tính của một cuộc đối thoại riêng tư ở ngoài đời bao gồm Mã hóa, Xác thực, Phủ nhận, Bí mật Chuyển tiếp.\n\nGiao thức OTR tương hợp với các ứng dụng chat của máy vi tính như Adium hoặc Pidgin. - - Chat của tôi có được an toàn không? - Tính năng mã hóa nội dung chat hoặc còn gọi là tán gẫu trên ChatSecure chỉ có thể được thực hiện khi đối tượng mà bạn gửi cũng đồng thời sử dụng ChatSecure nếu dùng di động hoặc Adium hay Pidgin khi dùng trên máy tính. Bạn có thể tự mình chỉnh sửa loại, mức độ mã hóa thông tin của ChatSecure trong phần Thiết Lập Tài Khoản.\n\nHãy bắt đầu trải nghiệm đi nào! - - Cài đặt cụm từ mật khẩu - Trước khi bạn bắt đầu xin vui lòng chọn một cụm từ an toàn để bảo vệ dữ liệu ChatSecure. - Cụm từ mật khẩu: - Cụm từ mật khẩu (đánh lại): - - user@domain.com - tên người dùng mới - dịch vụ cung cấp (dukgo.com, jabber.ccc.de) - mật khẩu - xác nhận mật khẩu - Cấu hình cao cấp tài khoản - Loại tài khoản - Đăng ký Tài Khoản - - Kiên trì - Nhớ mật khẩu - Nhớ mật khẩu trong bộ đệm - Mật khẩu không được nhớ trong bộ đệm - Tự động đăng nhập - Kết nối vào ChatSecure khi bắt đầu - Đừng kết nối vào ChatSecure khi bắt đầu - Đăng nhập vào ngay - Kết nối vào sau khi cài đặt tài khoản xong - Đừng kết nối vào sau khi cài đặt tài khoản xong - - Cá nhân (tuỳ chọn) - Biệt hiệu của tài khoản( tên của bạn ) - Tài khoản của bạn sẽ hiện ra như vầy - Hồ sơ - Vài dòng về bạn - - Bắt buộc dùng hệ thống mã hoá / không dùng dạng văn bản bình thường - Khi có thể, hãy tự động mã hoá - Mã hoá chat theo yêu cầu - Tắt đi mã hoá cho cuộc trò chuyện - - Đang xác nhận lý lịch - Đang tạo ra bộ chìa khoá mã hoá - Đang đăng nhập vào - - ChatSecure đã gặp lỗi khi đang xác nhận tên dùng và mật khẩu của bạn, xin hay xem và thử lại. - ChatSecure đã gặp lỗi khi đang tạo ra chì khoá mã hoá - ChatSecure đã gặp lỗi khi đang kết nối và máy chủ - xin vui lòng xem lại cấu hình của bạn và thử lại. - ChatSecure đã gặp lỗi khi đang kết nối và máy chủ - xin hãy xem lại mạng kết nối của bạn và thử lại. - Vân tay của họ - Vân tay của bạn - Đăng nhập - Tạo ra chìa khoá - ChatSecure đã bị mất kết nối vào mạng lưới - ChatSecure đang thử kết nối lại - Cảnh Báo: Cuộc chuyện trò này không được mã hoá - Cuộc chuyện trò này an toàn nhưng căn cước của người tham gia CHƯA ĐƯỢC xác nhận - Cảnh Báo: mã hoá cho cuộc chuyện trò đã ngưng - Cuộc trò chuyện này đã được xác nhận và xem là an toàn - Cài đặt tài khoản theo từng bước - Tên tài khoản bạn - Cấu hình máy chủ - Bạn sẵn sàng chưa? - Hãy đánh vào tên tài khoản của bạn để cấu hình ChatSecure cho dịch vụ tin nhắn XMPP. Tên tài khoản nhìn giống như địa chỉ email: - Hãy đánh vào tên tài khoản (user@hostname): - Hãy đánh vào hoặc điều chỉnh tên máy chủ và số cổng ( số mặc định là 5222 ) cho dịch vụ jabber/xmpp của bạn. - ChatSecure đã cấu hình xong, bây giờ bạn có thể sử dụng và bắt đầu chat một cách an toàn trong riêng tư. - Bạn chưa đánh vào phần @máy chủ của tên tài khoản của bạn. Xin hãy thử lại. - Tên máy chủ của bạn thiếu phần .com,.net hoặc những đuôi tên miền khác. Xin hãy thử lại. - Đánh vào mật khẩu: - - Cấu hình tài khoản - defLoc - Cảnh Báo: Đây là phiên bản cũ của ChatSecure và có thể có những lổ hỗng an ninh hoặc lỗi. - - Nhóm - Tạo ra cặp chìa khoá mã hoá OTR mới - Liên lạc - đánh vào tên liên lạc để trò chuyện - Đi - Ngôn Ngữ - Ngôn ngữ - Ngôn ngữ nào nên hiển thị trong InTheClear? - Tìm trong SRV - Dùng DNS SRV để tìm máy chủ XMPP từ tên miền - Cho phép tên dùng và mật khẩu được gửi đi theo dạng văn bản thường khi dùng cách chuyển không mã hoá. - Cho phép xác thực bằng văn bản thường - Xác nhận là những chứng nhận này tin tưởng được - Xác nhận dạng TLS - Cần kết nối theo dạng TLS/SSL - Cách Chuyển Mã Hoá - cách chat mã hoá được bắt đầu - Máy chủ kết nối vào, khi cần - Kết nối máy chủ - Cổng TCP cho máy chủ XMPP - Cổng máy chủ - Tài nguyên XMPP - để phân biệt những kết nối từ nhiều ứng dụng khác cũng đã được đăng nhập vào - Tài nguyên ưu tiên cho XMPP - Những tin gửi cho ứng dụng khách mà có nhiều tài nguyên đang hoạt động sẽ được ưu tiên khi gửi đi - Tới - Lui - Cuộc trò chuyện - Tin nhắn mới từ - Xác thực - Đánh vào một câu hỏi để gửi đi đến liên lạc của bạn, và câu trả lời bạn mong đợi, để xác nhận họ có phải là người bạn muốn trao đổi hay không - câu hỏi được hỏi - câu trả lời mong đợi - Liên lạc của bạn đã xác thực bạn thành công. Bây giờ hãy xác thực họ bằng cách đặt câu hỏi của bạn. - Không có cuộc trò chuyện nào\n\nHãy gõ vào đây để bắt đầu - Dùng định dạng nền tối - Vì bạn đang xài Tor, bạn phải đánh vào tên máy chủ của XMPP \'Connect Server\' thẳng vào trong phần Cấu Hình Cao Cấp Tài Khoản. - - Để ChatSEcure tự động chạy - Tự động chạy và đăng nhập vào tài khoản từng dùng trước đó - Bạn chưa cấu hình tài khoản nào cả.\n\nBấm vào đây để thêm vào! - Tài khoản Google - Lưu Trữ Tin Nhắn Trong Bộ Nhớ Mà Thôi - Chỉ giữ tin nhắn trong bộ nhớ, không giữ trên bộ nhớ flash để tránh tin bị chép ra. (Tin có thể bị mất) - Hình nền - Chọn tên tập tin của hình ảnh nền cho app - An Ninh và Riêng Tư - Giao Diện Sử Dụng - Các Chỉnh Sửa Khác - Mở xem các trao đổi - Orbot (Tor) - Rời - - Bạn muốn rời khỏi mọi dịch vụ VÀ hủy tất cả quá trình đang chạy (rời hoàn toàn)? - Điền vào các ký tự cho phần mật khẩu, nên nhớ, ký tự mật khẩu được hiển thị sẽ ở dạng *. Phần mật khẩu của bạn ít nhất phải có độ dài tối thiểu là 6 ký tự, trong đó có ít nhất một chữ cái được in hoa, một chữ cái in thường và một con số kèm theo. Việc làm này nhằm tăng tính bảo mật của mật khẩu. - Đang mã hóa các ghi chú hiện có của bạn với các cụm từ mới (hãy kiên nhẫn đợi trong giây lát...) - Đánh vào cụm từ mật khẩu: - Chào bạn! Hãy điền vào các cụm từ bảo mật để giúp cho ghi chú của bạn được an toàn hơn. Các cụm từ này ít nhất phải có độ dài tối thiểu là 6 ký tự, trong đó có ít nhất một chữ cái được in hoa, một chữ cái in thường và một con số kèm theo. - Cụm từ bảo mật không đạt yêu cầu về độ dài ký tự - Cụm từ bảo mật không chứa bất kỳ ký tự nào được in hoa - Cụm từ bảo mật không chứa bất kỳ ký tự in thường nào - Cụm từ bảo mật không chứa bất kỳ ký tự là chữ số nào - Mở - Đã khóa ChatSecure - Tạo cụm từ bảo mật - Đánh vào cụm từ mật khẩu: - Đánh vào cụm từ mật khẩu mới: - Xác nhận cụm từ bảo mật mới - Cụm từ bảo mật không khớp, xin thử lại lần nữa - Bảo mật giọng nói - Điền vào OStel.co hoặc các dịch vụ tài khoản bảo mật SIP để tích hợp với cuộc gọi - dukgo.com - Chương trình nhận diện một khóa OTR được nhập vào. Bạn có muốn quét mật khẩu cho QR bây giờ không? - Nhập phần lưu OTR - Đã nhập thành công khóa OTR - OTP vẫn chưa được nhập vào; Vui lòng kiểm tra tập tin có thẻ được tồn tại trong hệ thống ở một định dạng và địa điểm khác - Thời hạn cho mật khẩu - Thời hạn mà ứng dụng mã hóa bị khóa lại + Tạo tài khoản mới diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml index 422f9b7b6..560b9cb86 100644 --- a/res/values-zh-rCN/arrays.xml +++ b/res/values-zh-rCN/arrays.xml @@ -1,9 +1,9 @@ - + - - 要求部队/ - 自动尝试 - 按照要求 - 残疾人/从不 - + + 强制要求 + 自动尝试 + 按照要求 + 禁止/从不 + diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 39002755f..70104003a 100755 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -1,382 +1,500 @@ - - 读即时信息 - 允许应用程序读取数据IM内容提供商。 - 写即时消息 - 允许程序写入数据即时信息内容提供商。 - - - + + + ChatSecure + ChatSecure + + 阅读即时信息 + +允许程序读取从IM内容提供商的数据 + 编辑即时信息 + +允许程序写入输入到IM内容提供商 + 启动即时消息服务 + 允许应用程序通过 intent 启动即时消息服务。 + + 确认 + 确定 + 取消 + 确定 + 取消 + 下一步 + 返回 + 连接 + 播放 + 安装 + + 打开 + ChatSecure已锁定 + 设置密码 + 确认密码 + 密码 + 新密码 + 确认新密码 + 密码不匹配,请重试 + 懒人模式(无密码) + 弹出信息 + 请输入密码 + 请输入密码… + 口令 + 口令(重复) + 聊天 - 选择一个帐户 - - + 聊天 - 选择一个帐户 + (%1$d) + 添加 %1$s 帐户 + 关于 + 登出所有 + 你要退出所有的服务? + 您已经退出了 %1$s + 您已退出 %1$s,因为%2$s + 是否第一次使用ChatSecure? + Can\'t迫不及待地开始了吗? + 关于Gibberbot + 帐户设置 + 口令设置 + 在您开始之前请先选择一个安全密码来保护您的ChatSecure数据来防止不正当的访问。 + Passphrase: + 密码(再次): + 输入一个新的密码,这个密码必须包含至少一个大写字母,一个小写字母和一个数字,并且要大于6位数字。 + 使用这个新的密码来加密您已经存在的笔记 + 输入您的密码: + 欢迎!请输入一个强壮的密码来保护您的笔记,这个密码必须包含至少一个大写字母,小写字母和数字,而且必须大于6位数。 + 您的密码不足够长 + 您的密码没包含任何大写字母 + 您的密码没包含任何小写字母 + 您的密码没包含任何数字 + + 关于ChatSecure + ChatSecure是一个手机即时通讯应用提供额外的安全特性,防止他人截取您的对话。 +该应用支持任何使用Jabber和XMPP协议的聊天服务,比如谷歌的Gtalk或者Jabber.org + + 它是如何保障? + OTR消息是一种安全系统用来模仿真实世界中的对话(就像面对面说话),包括加密,认证,不可否认性以及正向加密。 +OTR协议跟桌面客户端(比如:Adium或Pidgin)兼容 + + 是我的聊天安全吗? + ChatSecure的加密聊天特性只工作在当你的聊天对象也在使用兼容的app或程序的时候,所以您需要确保您的联系人也正在使用手机的ChatSecure或电脑的Adium或Pidgin。在账户设置中您可以微调何时以何种方法让ChatSecure尝试加密您的聊天。 +来开始吧! + + 新账户 新增帐户 - 编辑帐户 - 删除帐户 - - 登出所有 - - - 聊天 - 选择一个帐户 - - - + 现有帐号 + + User@Host + 密码: + 记住密码 + 自动登录 + n\u2019t有帐户吗? + 登录 + 为安全起见,如果您的手机丢失或被盗,请使用您的电脑访问网站并修改密码 + 启用此选项,每次程序启动后将会自动为您自动登录。如要取消,请去除“为我自动登录”选项 + 通过Tor连接(需要Orbot应用程序) + user@domain.com + 新用户名 + 服务提供者(dukgo.com, jabber.ccc.de) + 密码 + 确认密码 + 高级帐号设置 + 账户种类 + 注册帐号 + 坚持 + 记住密码 + 密码缓存 + 密码不缓存 + 自动登录 + 在ChatSecure启动的时候连接 + 不要在ChatSecure启动的时候连接 + 立即登录 + 连接以下帐户设置 + Don\'t联络以下帐户设置 + 个人(可选) + 账户别名(您的姓名) + 如何在网络上出现您的帐户 + 简介 + 关于自己短暂的Blurb的 + 垃圾强制加密明文/ + 如果可能的话,会自动加密聊天 + 按要求加密聊天 + 禁用聊天加密 + 验证凭据… + 生成密钥对… + 登入中.. + 帐户向导 + 您的帐户ID + 配置服务器 + 你准备好了吗? + 输入您的账户ID以为您的XMPP聊天服务配置ChatSecure。这个账户看起来像一个电子邮件地址: + 请输入您的帐户ID(用户@主机名): + 请输入或编辑您的jabber/xmpp聊天服务器的主机名和端口号(默认是5222)。 + ChatSecure已经完成配置,现在您可以连接到您的服务并开始安全得,保密得,隐私得聊天。 + Orbot (Tor代理) + 选择域 + 正在注册新账户 + 获取ChatSecure状态 取消登录 - - + 正以\u2026登录 + 正在退出\u2026 + + 进行SRV查阅 + 从域名中使用DNS SRV查找XMPP服务器 + 当使用非加密传输模式时,允许用户名和密码以明文方式传送 + 允许明文授权 + 检测证书是否可信 + TLS确认 + 要求TLS连接 + 加密传输 + 加密聊天已启动 + 必要时连接服务器 + 连接服务器 + XMPP服务器的TCP端口 + 服务器端口 + XMPP资源 + 识别此连接是否已被其它客户登录 + XMPP资源优先级 + 发送给具有多个活动资源的客户端上的消息将被发送到具有最高优先级的源上 + + 会话 + 没有聊天。 +点击这里来开始一个! + 你没有⏎ +被设置的账户。⏎ +⏎ +点击这里来添加! + 联系人列表 - %1$s 添加联系人 - 删除联系人 - 阻止联系人 - + 联系人昵称 已阻止 - - 帐户列表 - - - - 设置 - + 昵称 + 将会删除联系人“%1$s”。 + 将会阻止联系人“%1$s”。 + 将会解除阻止联系人“%1$s”。 + 已添加联系人“%1$s”。 + 联系人“%1$s”已删除。 + 已阻止联系人“%1$s”。 + 联系人“%1$s”已解除阻止。 + 新聊天 搜索联系人 - + 显示格子 开始聊天 - - 退出 - - 查看个人资料 - - - 启动加密 - 停止加密 - 清除聊天 - 结束聊天 - - - 联系人列表 - - 邀请... - - 切换聊天 - - 插入表情符 - - 扫描指纹 - 指纹 - 指纹验证 - - MENU+ - - - - 确认 - - 你要退出所有的服务? - - - - - - - - 确定 - - 取消 - - 确定 - - 取消 - - - - - - - - - - 用户名: - - 密码: - - 记住密码。 - - 自动登录。 - - 没有帐户? - - 为了您的安全,如果您的手机丢失或被盗,请在您的电脑网站,并更改密码。 - 启用该选项后,您每次打开此应用程序时都会自动登录。要停用该选项,请退出,然后取消选中“自动登录”复选框。 - - 登录 - - - - 正在登录... - - - 已停用背景数据 - - - - - - 启用 - - 退出 - - - - - - + 查看资料 + 验证钥匙 + 当前聊天 (%1$d) + %1$d 人在线 好友邀请 - (\"\"未知\"\") - - 无会话 - - 选择要邀请的联系人 - 输入拼音以查找联系人 - 未找到联系人。 - - - - 无受阻止的联系人。 - - + 联系 + 输入联系人名称发起聊天 + 开始 + 无活动聊天。 + 添加联系人 + 您希望邀请的用户的电子邮件地址: + 选择一个列表: + 输入用户名以从联系人添加。 + 发送邀请 联系人个人资料 - 状态: - 客户端类型: - 计算机 - 移动设备 - - - 在线 - - 忙碌 - - 离开 - - 空闲 - - 离线 - - 显示为离线 - - - - - + 已阻止的联系人 - %1$s + 无已阻止的联系人。 + + %1$s聊天 - - 输入内容 - - - - - - - - - - - - - - - - + %1$s在线 + %1$s离开 + %1$s正忙 + %1$s处于离线状态 + %1$s已加入 + %1$s已离开 + 发送照片 + 发送文件 + 发送音频 + 照相 + 文件传输 + 传输完成 + 正在传输 + 是否接受传输? + 想发送给您文件 + 没有连接可用于发送您的分享! 发送 - - 无法发送此消息。 - - 与服务器失去连接。消息将在下次上线时发送。 - - - - - + 发送消息 + 发送安全消息 + 重新发送 + 结束聊天 + 清除聊天 + 插入表情符 + 切换聊天 + MENU+ 选择链接 - - 无活动聊天。 - - - - 添加联系人 - - 您希望邀请的用户的电子邮件地址: - - 选择一个列表: - - 输入用户名以从联系人添加。 - - 发送邀请 - - + 删除 + 保留文件 + 删除聊天会话的安全存储? + 此会话上传和下载的所有文件都将被永久删除。警告:此操作不可撤销! + 删除原件? + 此文件将在发送前被复制到安全存储。您想删除在设备的不安全存储上的原件吗? + 保留 + 导出 + 导出媒体文件? + 此媒体文件将被导出到 %1$s + 结束聊天? + 本次会话的所有安全的媒体项都将被删除。长按缩略图图标导出媒体项目。 + 结束聊天并删除文件 + + %1$s邀请您加入群聊。 + 已向%1$s发送邀请。 + 接受 + 拒绝 + 群聊 + 创建或加入群聊 + 正在连接到群聊 + 邀请\u2026 + 选择要邀请的联系人 + 输入查找联系人 + 没有找到任何联系人。 +点击邀请。 + 添加 %1$s + + + 无法批准来自%1$s的好友申请。请稍后再试。 + 无法拒绝来自%1$s的好友申请。请稍后再试。 + + 启动加密 + 停止加密 + 正在启动加密聊天会话 + 正在停止加密聊天会话 + 安全指纹 + 安全指纹 + 登录 + 再生的关键 + 加密已关闭 + 加密运行中(点击验证) + 您的联系人已经停止此加密聊天 + 已加密和验证! + 正在转换新的OTR密钥… + + 我们监测到可被倒入的OTR秘钥库。你是否愿意开始QR密码扫描? + 启用KeySync + 成功导入OTR密钥环 + + 扫描QR + 您的指纹 + 手册 + 问题 + 安全指纹(已验证) + 确定要验证该指纹吗? + 是否验证指纹? + 远端的指纹成功验证! + 指纹为您 + 指纹为 + 安装条形码扫描器? + 本应用程序需要条形码扫描器。您想要安装吗? + + 验证 + 输入一个问题给你的联系人并且得到你希望的答案以验证他们是不是你想联系的人(对暗号) + 要问的问题 + 希望得到的答案 + 您的联系人已经成功的认证了您,现在您可以问一些自己的问题来验证您的联系人。 + OTR Q&A 验证 + 聊天加密 + 发送 + 取消 + + 安全电话 + 安全语音 + 在这里输入您的OStel.co或其他安全SIP服务账户以集成电话。 + 帐户设置 + + 安全和隐私 + 加密和匿名 + 加密On/Off + 密码超时 + 程序加密保持解锁状态时间 + + 在 Tor 上可点击的链接 + 对于使用 Tor 的帐号,使聊天中的链接可点击(警告,存在隐私泄漏风险) + 用户界面 + 语言 + 多国语言 + 使用系统默认 + 使用暗色主题 + 改为使用深色应用主题 + 只在内存中存储消息 + 只把消息存在内存中而不存在闪存中以抵御电子数据提取(可能会丢失消息) + 背景图片 + 将(“/sdcard/foo.jpg”)设置为背景图片壁纸 + 显示联系人格子 + 显示联系人列表作为头像格子 + 接受全部 + 删除不安全的媒体 + 在分享一个照片或文件后,自动从原位置删除它,在导入不安全的存储后 + 在外部存储存储媒体 + 聊天会话中的媒体文件将被存储在一个加密容器中,它可以被存储在内部或外部存储中。 + + 缺少外部存储 + 您的聊天记录将存储在一个SD卡上,但目前不存在SD卡。请插入正确的SD卡,或者删除现有的聊天记录然后再启动 ChatSecure。 + 缺少聊天媒体存储 + 您的聊天记录将存储在一个SD卡上,但当前的SD卡上缺少该文件。请插入正确的SD卡,或者删除现有的聊天记录然后再启动 ChatSecure。 + 删除聊天记录 + + 其他调整 + 自动开始ChatSecure + 始终启动并且自动登录所有的帐号 隐藏离线联系人 - + 使用前景优先级 + 减少安卓重启链接服务的几率。这将放一个通知在通知栏。 + 心跳包间隔 + 使用高值(分钟)来节省电池。一个过高的值可能会导致您的链接被提供者因长时间不活动而切断。 + 通知设置 - 即时消息通知 - 收到即时消息时在状态栏中显示通知 - 振动 - 在收到即时消息的同时振动 - 声音 - 在收到即时消息的同时播放铃声 - - 选择铃声 - - - - - - - 接受 - - 拒绝 - - - - 接受 - - 拒绝 - - - - - - - - - - - - + 使用ChatSecure自定义铃声 + + 开启纠错记录 + 为纠错将应用的登陆数据导出为标准格式 + + 已停用背景数据 + %1$s 需要启用背景数据。 + 启用 + 退出 + 你要退出所有的服务并关闭所有进程吗?(硬退出) + 建立新账户? + 建立一个用户名为\'%1$s\'的新的聊天账户? + 证书信息 + 证书: + 签发者: + SHA1 指纹: + 签发时间: + 过期时间: + 加入聊天室? + 一个外部应用尝试将您连接到一个聊天室。允许? + 选择背景 + 您想从相册中选择一个背景图像吗? + 选择图片 + + 来自%1$s的新消息 + %1$d 条未读聊天消息 + 来自 %s 的新好友邀请 + 新的文件 %1$s 来自 %2$s 群聊邀请 - - - - - - - - - - - - 启动即时消息服务 - 允许应用程序通过 intent 启动即时消息服务。 - - + 来自 %s 的新群聊邀请 + 有新消息 + 正在启动ChatSecure服务… + 激活的 & 已解锁的 + 消息已复制到剪贴板 + 注意 - - - + 错误: + 错误代码 %1$d + 无法登录%1$s服务。请稍后再试。"\n"(详细信息:%2$s 未添加该列表。 - 未阻止联系人。 - 联系人未解除阻止。 - 请先选择一个联系人。 - \"已断开连接!\"\n - 服务错误! - 未载入联系人列表。 - 无法连接到服务器。请检查您的连接。 - - - - - + %1$s已在您的联系人列表中。 + 已阻止联系人“%1$s”。 正在载入联系人列表,请稍候。 - 出现网络错误。 - + 此次连接需要WiFi 服务器不支持此功能。 - 您输入的密码无效。 - 服务器遇到一个错误。 - 服务器不支持此功能。 - 服务器当前不可用。 - 服务器已超时。 - 服务器不支持当前版本。 - 消息队列已满。 - 服务器不支持转发至该域。 - 您输入的用户名无法识别。 - 抱歉,您已被该用户阻止。 - 此会话已过期,请重新登录。 - 您已从另一个客户端登录。 您已从另一个客户端登录。 - 抱歉,无法从您的 SIM 卡读取手机号码。请与运营商联系寻求帮助。 - 您当前未登录。 - - - + ChatSecure在验证您的用户或密码的时候遇到了错误,请检查并重试。 + ChatSecure在生成一对密钥的时候遇到错误。 + ChatSecure连接聊天服务器的时候发生错误,请检查网络连接。 + ChatSecure连接聊天服务器时发生错误,请检查您的网络后重试。 + ChatSecure失去网络连接 + ChatSecure正在尝试重新建立链接 + 您的帐号无法进入@hostname.com,请重试 + 您的服务器主机没有包括.com、.net或详细后缀,请重试 + 输入您的password: + 当您使用Tor的时候,您必须直接的输入XMPP服务器的主机名到高级账户设置中。 + 警告:此服务器使用的证书加密算法脆弱。请告知管理员进行升级。 + 提供的证书不匹配固定的证书: + 无法创建或加入群聊 + 对不起,我们无法共享此类文件类型 + 你必须启用加密才能共享文件 + 请启用加密聊天以共享文件 + 不支持的传入数据,不能共享! + ChatSecure检测到一个请求去删除自身任务. ChatSecure将有可能崩溃, 请从帐户列表使用退出所有帐号选项。 + 没有针对该格式的阅览器可用 + 请在扫码之前开始一个安全聊天. + OTR密钥环没有被导入; 请检查文件是否为正确的格式和路径 + 请从桌面KeySync工具将\'otr_keystore.ofcaes\'文件复制到你设备存储空间的根目录。 + 无法发送此消息。 + 消息将在重新连接后再发送 + %1$s处于离线状态。您发送的消息将在%1$s下次上线时传送出去。 + %1$s不在您的联系人列表中。 + 您的密码不匹配 + 优先级必须是范围内的数字 [0 .. 127] + 端口号必须是数字 + 传输错误 + 无法读取文件以存储 + 重要错误:无法解锁或载入应用数据库。请重新安装本应用或者清除数据。 + 没有安装可处理此链接的应用! + 帐号设置 + + 群组 + 打开对话 + 退出 + 恐慌 + 好友 + 联系人 + 是否接受服务器证书? + 指纹 + 正在载入… + + 关于 ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + 联系人列表 + 设置 + 帐户列表 + 退出 + 联系人列表 + + Jabber (XMPP) + 本地区域 (Bonjour/ZeroConf) + 谷歌账户 + dukgo.com + + + 在线 + 忙碌 + 离开 + 空闲 + 离线 + 显示为离线 + 快乐 伤心 @@ -396,7 +514,7 @@ 困惑 - + 快乐 伤心 @@ -416,67 +534,52 @@ 困惑 - 联系人列表 - 加密On/Off - 通过Tor连接(需要Orbot应用程序) - - Can\'t迫不及待地开始了吗? - 关于Gibberbot - 帐户设置 - - - 它是如何保障? - - 是我的聊天安全吗? - - 口令设置 - Passphrase: - 密码(再次): - - - 坚持 - 记住密码 - 密码缓存 - 密码不缓存 - 自动登录 - 立即登录 - 连接以下帐户设置 - Don\'t联络以下帐户设置 - - 个人(可选) - 账户别名(您的姓名) - 如何在网络上出现您的帐户 - 简介 - 关于自己短暂的Blurb的 - - 垃圾强制加密明文/ - 如果可能的话,会自动加密聊天 - 按要求加密聊天 - 禁用聊天加密 - - 验证凭据... - 生成密钥对... - 登入中.. - - 他们的指纹 - 指纹 - 登录 - 再生的关键 - Warning:此聊天是不加密 - 此聊天是安全的,但参加者的身份尚未被验证 - Warning:聊天加密已停止。 - 此聊天是安全和验证 - 帐户向导 - 您的帐户ID - 配置服务器 - 你准备好了吗? - 请输入您的帐户ID(用户@主机名): - 请输入或编辑您的jabber/xmpp聊天服务器的主机名和端口号(默认是5222)。 - 输入您的password: - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + 现有帐号 + 连接到我现有的帐号,在指定的 Jabber / XMPP 服务器。 + Google 帐号 + 使用您现有的 Google 帐号与其他 Google 用户聊天。 + WiFi Mesh 聊天 + 与在同一个本地 WiFi 网络或者网状网络(Mesh)的其他人聊天——不需要互联网连接或者服务器! + 启用 WiFi 聊天 + 新账户 + 选择我们内置列表中一个服务注册一个新的免费账号,或者您自行选择任何服务。 + 创建新帐号 + 秘密身份! + 创建一个匿名、可抛弃的聊天帐号,一键完成(需要 Orbot: Tor for Android) + 生成身份 + 这是一个群组聊天 + [重新发送] + [重新发送] + 无法安全地分享此文件 + 安装 Orbot? + 您必须安装 Orbot 并激活通过它进行代理传输。您想安装它吗? + 始终 + 启动 Orbot? + Orbot 看起来并未运行。您想启动它并连接到 Tor 吗? + 昵称在房间中已被使用 + 要创建或加入的房间名称 + 群组聊天服务器 (conference.foo.com) + 您的密钥存储已损坏。请重新安装 ChatSecure 或者清除本应用的数据 + 您收到了一条不可读的已加密消息 + 我无法解密你发出的消息 + < 向左或向右滑动获得更多选项 > diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml index ff404bb79..50f4f222c 100644 --- a/res/values-zh-rTW/arrays.xml +++ b/res/values-zh-rTW/arrays.xml @@ -1,9 +1,9 @@ - + - - 要求部隊 / - 自動嘗試 - 按照要求 - 殘疾人 /從不 - + + 強制 /要求 / + 自動嘗試 + 依照要求 + 停用 /從不 + diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index fe350b146..855e99300 100755 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -1,362 +1,491 @@ - - - - - - - - - - - - - - - 取消登入 - - - 新增聯絡人 - - 刪除聯絡人 - - 封鎖聯絡人 - - 封鎖 - - 帳戶清單 - - - - 設定 - - - 開始即時通訊 - - 登出 - - 觀看個人檔案 - - - 結束即時通訊 - - - 聯絡人清單 - - 邀請… - - 切換即時通訊 - - 插入表情符號 - - - [Menu] + - - - + + + ChatSecure + ChatSecure + + 讀取即時訊息 + 允許應用程式讀取即時通訊商提供的資料 + 撰寫即時訊息 + 允許應用程式讀取即時通訊商提供的資料 + 開始即時訊息服務 + 允許應用程式透過 intent 啟動即時訊息服務 + 確認 - - - - - - - - 確定 - 取消 - 確定 - 取消 - - - - - - - - - - 使用者名稱: - + 下一個 + 返回 + 連線 + 播放 + 設定 + + 開啟 + 鎖定 ChatSecure + 設定密碼 + 確認密碼 + 密碼 + 你可以設定一個主密碼來防止其他人存取你的聯絡人和訊息: + 確認新密碼 + 密碼並不一致, 請再試一次 + 跳過 + 訊息提示 + 請輸入密碼 + 請輸入密碼… + Passphrase + Passphrase (再一次) + + 選取帳戶 + 選取帳戶 + (%1$d) + 新增 %1$s 帳戶 + 關於 + 全部登出 + 你真的要登出所有服務? + 您已登出 %1$s + 你已登出 %1$s 。原因: %2$s + 第一次使用 ChatSecure? + 等不及開始啦? + 開始嚕 + 帳戶設定 + Passphrase 設定 + 啟用前,請你選擇一個安全的 passphrase 來保護你的 ChatSecure 以防止資料被竊取 + passphrase: + passphrase (再打一次): + 輸入一個**新的** passphrase. 必須包含至少一個大寫, 一個小寫和一個數字, 並且大於 6 字元. + 正使用新的 passphrase 來加密已存在的訊息 (要耐心啊…) + 輸入你的密碼 + 歡迎 ! 輸入一個強一點的 passphrase 以保護你的訊息。它必須包含至少一個英文大寫, 一個小寫與一個數字, 並且起碼超過6個字元. + 你的 passphrase 字數不夠長 + 你的 passphrase 不含任何大寫字母 + 你的 passphrase 不含任何小寫字母 + 你的 passphrase 是空白的 + + 關於 ChatSecure + ChatSecure 是一套行動裝置聊天軟體, 經由預防別人窺探你的交談來提供額外安全性.\n\n本軟體支援任何用 jabber 或 XMPP 協定的服務, 如 Google GTalk 或 Jabber.org. + + 它有多安全? + \'Off-the-Record Messaging (OTR)\' 是一種安全的系統, 用以模仿真實世界的私人交談,達到保護個人隱私. 包含加密、認證、可否認性和傳輸保密. \n\nOTR 協定相容於桌上型電腦客戶端, 如 Adium 或 Pidgin. + + 我的聊天安全嗎? + ChatSecure 的聊天加密功能只在對方使用相容的 app 或程式時生效, 所以你必須確保連絡人在行動裝置使用 ChatSecure 和桌上型電腦使用 Adium 或 Pidgin。你可以從帳號設定中微調 ChatSecure 在交談時如何加密你的會話。 \n\n讓我們開始吧! + + 新帳戶 + 新增帳戶 + 編輯帳戶 + 移除帳戶 + 已存在帳戶 + + 使用者名稱: (User@Host) 密碼: - 記住我的密碼。 - 自動登入 - 您有帳戶嗎? - - 此選項可讓您開啟應用程式時自動登入。如要停用,請登出並清除 [自動登入] 核取方塊。 - 登入 - - - + 為了安全起見, 一旦你的手機遺失或被偷, 請用電腦連上這網站修改你的密碼。 + 此選項可讓您開啟應用程式時自動登入。如要停用,請登出並清除 [自動登入] 核取方塊。 + 透過 Tor 連線(需用Orbot app) + user@domain.com + 新加入的使用者名稱 + 服務提供者 (dukgo.com, jabber.ccc.de) + 密碼 + 密碼再確認 + 進階帳戶設定 + 帳戶設定 + 註冊帳戶 + 堅持 + 記住密碼 + 密碼已快取 + 密碼未快取 + 自動登入 + 啟用 ChatSecure 時開啟連線 + 啟用 ChatSecure 時不開啟連線 + 馬上登入 + 採用下列帳戶設定連線 + 不採用下列帳戶設定連線 + 個人化(選用) + 帳戶別名(你的名稱) + 你的帳戶上線模式 + 個人資料 + 個人主要簡介 + 強制性加密/拒絕純文字 + 一旦有可能, 聊天就自動加密 + 當被要求時加密聊天 + 停止聊天加密 + 憑據驗證中… + 產生識別碼字串中… + 登入中… + 帳戶精靈 + 識別帳戶 + 設定伺服器 + 準備好了嗎? + 輸入你的帳戶以設定 ChatSecure 使用你的 XMPP 服務。他長得和 email 很像 : + 請打入你的帳戶ID (user@hostname): + 請打入你或編輯 jabber/xmpp聊天伺服器的主機名稱跟埠號 (5222是預設值). + ChatSecure 設定完成, 現在你可以連線到你的服務, 並且開始安全地, 安心地和私密地交談 ! + Orbot (Tor) + 選取網域 + 註冊新帳號… + 安全聊天進行中 + 取消登入 登入中… - - - 已停用背景資料 - - - - - - 啟用 - - 結束 - - - - - - - 邀請朋友 - + 登出 + + 進行 SRV 查冊 + 啟用 DNS 服務以便從網域名稱找到真正的 XMPP 伺服器 + 當採用未加密傳輸時,允許使用者以純文字傳送帳戶名稱跟密碼 + 允許純文字驗證 + 確定驗證證書可被信任 + TLS驗證書 + 必須用TLS連線 + 傳輸加密 + 加密聊天啟用方式 + 有必要的話,伺服器要連線到哪 + 連線伺服器 + XMPP伺服器採用的 TCP 埠號 + 伺服器埠號 + XMPP 資源 + 要區分這個連線與其他已登入的客戶端 + XMPP 資源優先權 + 送到客戶端的訊息若有多數個有效資源, 將會傳送到最高優先權那個 + + 對話 + 沒有對話. \n\n輕敲這裡開始! + 沒有設定好的帳戶.\n\n輕敲這裡新增! + 聯絡簿 - %1$s + 新增聯絡人 + 刪除聯絡人 + 封鎖聯絡人 + 聯絡人匿稱 + 封鎖 + 匿稱 + 刪除「%1$s」? + 封鎖「%1$s」。 + %1$s」會被解除封鎖。 + %1$s」已新增。 + %1$s」已刪除。 + %1$s」已封鎖。 + %1$s」已解除封鎖。 + 新聊天 + 搜尋聯絡人 + 顯示方格 + 開始即時通訊 + 顯示個人檔案 + 查證聯絡人 + 進行中的即時通訊 (%1$d) + 線上人數 %1$d + 來自好友的邀請 (\"\" 未知的 \"\") - 沒有聯絡人 - 沒有對話 - - 選取要邀請的聯絡人 - 輸入關鍵字尋找聯絡人 - 找不到聯絡人。 - - - - 沒有封鎖的聯絡人。 - - + 聯絡人 + 打入聯絡人的名字以便聊天喇賽 + 進行 + 沒有進行中的即時通訊 + 新增聯絡人 + 您要邀請對象的帳號: + 帳號加到: + 輸入聯絡人姓名。 + 傳送邀請 聯絡人檔案 - 狀態: - 用戶端類型: - 電腦 - 行動裝置 - - - 線上 - - 忙碌 - - 外出 - - 閒置 - - 離線 - - 顯示離線 - - - - - + 已封鎖聯絡人 - %1$s + 沒有封鎖的聯絡人。 + + %1$s進行即時通訊 - - 請在此輸入文字 - - - - - - - - - - - - - - - - + %1$s 在線上 + %1$s 顯示離開 + %1$s 目前忙碌 + %1$s 顯示離線 + %1$s 已加入 + %1$s 已離開 + 傳送照片 + 傳送檔案 + 傳送聲音 + 傳送圖片 + 檔案傳輸 + 傳送完成 + 正在傳送中 + 是否接受傳檔? + 想要傳送檔案給你 + 沒有可用的連線來傳送資料! 傳送 - - 訊息無法傳送。 - - 與伺服器連線中斷。您的訊息將在下次登入時傳送。 - - - - - + 傳送訊息 + 傳送安全的訊息 + 重新傳送 + 結束即時通訊 + 聊天清除 + 插入笑臉表情 + 切換即時通訊 + [Menu] + 選取連結 - - 沒有進行中的即時通訊 - - - - 新增聯絡人 - - 您要邀請的對象電子郵件地址: - - 選擇一個清單: - - 輸入聯絡人姓名。 - - 傳送邀請 - - + 刪除 + 保留檔案 + 要刪除此對話進程的保險箱嗎? + 此進程已上載及下載的檔案將被永久刪除—注意:是永久的喔! + 要刪除原始版本? + 傳送之前,此檔案將被複製到保險箱。你想刪除設備上未有加密的原始檔案嗎? + 保留 + 匯出 + 匯出媒體檔案? + 媒體檔案將會匯出到 %1$s + 結束聊天? + 此進程保險箱內的所有物件將被刪除。長按縮圖以匯出檔案。 + 結束聊天並刪除檔案 + + %1$s邀請您加入群組即時通訊。 + 已傳送邀請給 %1$s + 接受 + 拒絕 + 群組聊天 + 建立或加入群組聊天室 + 連線到群組聊天室… + 邀請… + 選取要邀請的聯絡人 + 輸入關鍵字尋找聯絡人 + 沒有聯絡人.\n\n輕敲這裡來邀請. + 要加入 %1$s 嗎? + + + 訂閱 %1$s 資訊失敗。請稍後再試一次。 + 拒絕訂閱 %1$s 資訊失敗。請稍後再試一次。 + + 加密啟用 + 加密停止 + 加密聊天會談啟動中 + 停用加密聊天會談 + 可靠的指紋 + 可靠的指紋 + 登入 + 重新產生 Key + 加密關閉 + 加密啟用(點擊驗證) + 對方停止了加密通訊. + 已加密和驗證 + 產生新的一次性OTR字串 + + 我們發現了一個可供匯入的密鑰庫。你要掃描QR code 密碼嗎? + 啟動 KeySync + 成功匯入OTR金鑰圈 + + 掃描 QRcode + 你的指紋辨識碼 + 使用手冊 + 問題 + 安全的指紋 (已驗證) + 你確定要確認這個指紋? + 是否查證指紋? + 遠端指紋已經查證! + 給你的指紋 + 指紋: + 要安裝條碼掃描器嗎? + 此app需要條碼掃描器。你要安裝它嗎? + + 認證 + 為了要驗證對方身分, 打入一個要問聯絡人的問題, 跟對方回答你預想好的正確的回應 + 要問的問題 + 預期中的回答 + 你的聯絡人已經成功驗證了你. 現在換你問問題驗證你的聯絡人. + OTR 問答驗證 + 對話加密 + 傳送 + 取消 + + 安全呼叫 + 安全語音 + 在這裡打入你的 OStel.co 或其它安全SIP服務商帳戶以便整合呼叫 + + 帳戶設定 + + 安全性和隱私權 + 加密且匿名 + 加密開/關 + 密碼過期 + app 應維持解鎖狀態的時間 + + 可進入的 Tor 連結 + 令使用 Tor 的帳戶可以按下對話中的連結 (***警告***這可能會有安全漏洞!) + 使用者介面 + 語言 + 語言 + 使用系統預設 + 採用暗色主題 + 轉換到暗色主題 + 訊息僅儲存在記憶體中 + 只允許儲存訊息在記憶體中而非隨身儲存設備, 以防止訊息被解壓縮.(有可能會漏失訊息) + 背景底圖 + 把這路徑 (\"/sdcard/foo.jpg\") 設定到這支App的背景影像桌布 + 顯示方格 + 以人像方格顯示聯絡人 + 是,全部接受 + 清除未加密的檔案 + 分享圖片或檔案並匯入後,自動刪除原始、不安全的版本。 + 在外置 SD 儲存檔案 + 對話進程的檔案會被儲存在保險箱內,並可選擇存至內置或外置 SD。 + + 沒有外置 SD + 你已設定儲存對話紀錄到 SD 卡,但 SD 卡並不存在。請插入正確的 SD 卡,或者刪除已存在對話紀錄並重啟 ChatSecure 。 + 儲存對話多媒體檔的裝置不存在 + 你已設定儲存對話紀錄到 SD 卡,但現有 SD 卡沒有該對話檔案。請插入正確的 SD 卡,或者刪除已存在對話紀錄並重啟 ChatSecure 。 + 刪除對話紀錄 + + 其它微調 + 自動開啟 ChatSecure + 總是自動啟用上一次登入的帳戶 隱藏離線聯絡人 - + 採用前台優先 + 減少 Android 從新啟動連線服務的機會。將會在提醒區內放置對話提醒。 + 互動間隔 + 採用較高的數值(以分鐘算計)以便省電. 但較高的數值可能會讓提供者認為你沒在用而關閉連線 + 通知設定 - 即時訊息通知 - 收到即時訊息時,在狀態列中顯示通知 - 震動 - 收到即時訊息時振動提醒 - 音效 - 收到即時訊息時播放鈴聲 - - 選取鈴聲 - - - - - - - 接受 - - 拒絕 - - - - 接受 - - 拒絕 - - - - - - - - - - - - + 選取自定鈴聲 + + 啟動除錯紀錄 + 輸出除錯紀錄到 stdout / logcat + + 已停用背景資料 + app 需要啟用網絡資料連線 (包括背景資料) 來登入。 + 啟用 + 結束 + 你真的要登出所有服務並且刪除任何執行緒 (強制離開)? + 要註冊新帳戶嗎? + 要為用戶 \'%1$s\' 註冊新帳戶嗎? + 憑證資訊 + 憑證 : + 發佈自 : + SHA1 辦識碼 : + 發佈時間 : + 過期時間 : + 要加入聊天室嗎? + 有第三方應用嘗試連結你至一個聊天室。要允許嗎? + 選擇背景 + 你想從圖庫選擇背景圖案嗎? + 選取圖片 + + 來自 %1$s 的新訊息 + %1$d 個未讀對話 + 來自%s的邀請 + 來自 %2$s 的新檔案 : %1$s 群組即時通訊邀請 - - - - - - - - - - - - 開始即時訊息服務 - 允許應用程式透過網際網路啟動即時訊息服務 - - + 來自 %s 的群組對話邀請 + 新訊息來自 + 啟動 ChatSecure 服務中… + 已啟動及解鎖 + 信息已複製到剪貼簿 + 注意 - - - + 錯誤: + 錯誤代碼 %1$d + 無法登入 %1$s 服務。請稍候再試一次。"\n" (詳細內容:%2$s) 清單未增加 - 聯絡人未封鎖。 - 聯絡人未解除封鎖。 - 請先選取聯絡人。 - \"已中斷連線!\"\n - 服務錯誤! - 聯絡人清單未載入。 - 無法連線到伺服器。請檢查連線狀態。 - - - - - + %1$s已在您的聯絡人清單。 + %1$s」已封鎖。 請等待聯絡人清單載入完成。 - 網路發生問題。 - + 要連線必需要開通WIFI無線網路 伺服器不支援此功能。 - 您輸入的密碼無效。 - 伺服器發生問題。 - 伺服器不支援此功能。 - 目前無法存取伺服器。 - 伺服器連線逾時。 - 伺服器不支援目前版本。 - 訊息佇列已滿。 - 伺服器不支援網域名稱轉址。 - 您輸入的使用者名稱無法辨識。 - 很抱歉,您被此使用者封鎖。 - 登入時限已到,請重新登入。 - 您已在其他用戶端登入。 您已在其他用戶端登入。 - 很抱歉,無法從 SIM 卡讀取電話號碼。請向您的電信業者尋求協助。 - 您尚未登入。 - - - + ChatSecure 驗證你的使用者名稱和密碼時遭逢一個錯誤-請檢查並且重試一次. + ChatSecure 在產生識別碼字串的時候遭逢一個錯誤。 + ChatSecure 連接伺服器的時候遭逢一個錯誤。請檢查你的設定並重試。 + ChatSecure 連接伺服器的時候遭逢一個錯誤。請檢查你的網絡並重試。 + 未有連接網絡 + ChatSecure 正嘗試重新連線 + 你尚未打入你帳戶採用的主機名稱的任何部分.再試一次! + 你的伺服器主機名稱尚缺.com, .net之類的尾碼. 再試一次! + 打入你的密碼 + 因為你使用 Tor匿名路由, 所以你必須按下XMPP \'Connect Server\' 主機名稱以便直接進入進階使用者帳戶設定 + + ***警告*** 此服務使用不安全的加密方式。請通知管理員進行升級。 + 已提供的憑證與本 app 的並不一致 : + 無法建立或加入聊天 + 抱歉,我們未能分享此檔案類型 + 你必須啟用加密以傳送檔案。 + 請啟用對話加密以傳送檔案 + 未能支持並分享傳入的資訊! + ChatSecure 偵察到一個停止工作的要求,並很可能因此崩潰。請使用帳戶列表菜單中的「全部登出」。 + 沒有可查看此檔案格式的應用 + 掃描條碼前,請先啟動一個安全對話。 + OTR金鑰圈尚未匯入; 請確認這個檔案已存放在正確的格式和位置 + 請從KeySync tool 複製一份名為「otr_keystore.ofcaes」的檔案到儲存裝置的根目錄 + 訊息無法傳送。 + 訊息重新連線時將會再度被傳送 + %1$s 顯示離線。您的訊息將在 %1$s 登入時傳送。 + %1$s不在您的聯絡人清單。 + 你的密碼並不一致 + 優先次序必須是 0 至 27 的整數 + 埠號必須是整數 + 傳輸錯誤 + 無法輸出檔案至儲存裝置 + 嚴重錯誤 : 無法解鎖或載入應用的資料庫。請重新安裝應用或清除應用資料。 + 沒有能處理該連結的應用! + 帳戶設定 + + 群組 + 開啟會談 + 離開 + 恐慌 + 伙伴 + 聯絡人 + 要接受伺服器憑證嗎? + 顯示你的辦識碼 + 載入中… + + 關於 ChatSecure : \nhttps://guardianproject.info/apps/chatsecure/ + + 聯絡人清單 + 設定 + 帳戶清單 + 登出 + 聯絡人清單 + + Jabber (XMPP)協定 + 內部區域 (Bonjour/ZeroConf) + Google 帳戶 + dukgo.com + + + 線上 + 忙碌 + 外出 + 閒置 + 離線 + 顯示離線 + 開心 傷心 @@ -376,7 +505,7 @@ 開懷大笑 疑惑 - + 開心 傷心 @@ -396,21 +525,52 @@ 開懷大笑 疑惑 - - - - - - - - - - - - - - - + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + 已存在帳戶 + 連接至我在Jabber / XMPP 伺服器的已存在帳戶 + Google 帳戶 + 使用你已存在的 Google 帳戶與其他 Google 用家對話 + Wi-Fi 網絡對話 + 與同在本地 Wi-Fi 網絡的用家對話—這不需要任何互聯網或伺服器! + 啟用 Wi-Fi 對話 + 新帳戶 + 在列表上的服務註冊一個免費帳戶。當然,你也可以手動輸入不在列表中的服務。 + 註冊新帳戶 + 秘密身份! + 輕輕一敲,製造一個即棄的匿名帳戶 (需要 Orbot: Tor for Android) + 生成身份 + 這是一個群組對話 + [resent] + [resent] + 無法安全分享此檔案 + 要安裝 Orbot 嗎? + 你必須安裝並啟動 Orbot 來通過它傳輸資料。你想安裝它嗎? + 常駐 + 要啟動 Orbot 嗎? + Orbot 好像未啟動啊。你想喚醒它然後連至 Tor 網絡嗎? + 要在聊天室內使用的匿稱 + 要新增或加入的聊天室 + 群組對話伺服器 (例:conference.foo.com) + 你的密鑰庫已損壞。請重新安裝 ChatSecure 或者清除應用資料 + 你收到了一份無法解讀的加密訊息 + 我無法解密你發出的訊息 + < 滑左或滑右以發現更多選項 > diff --git a/res/values-zh-rTW/values-zh_TW/strings.xml b/res/values-zh-rTW/values-zh_TW/strings.xml deleted file mode 100644 index 979377a5f..000000000 --- a/res/values-zh-rTW/values-zh_TW/strings.xml +++ /dev/null @@ -1,375 +0,0 @@ - - - read instant messages - - Allows applications to read data from the IM content provider. - - write instant messages - - Allows applications to write data to the IM content provider. - - Gibberbot - Chat - Select an account - About - Add account - Edit account - Remove account - Sign out all - Chat - Select an account - (%1$d) - 取消登入 - 新增聯絡人 - 刪除聯絡人 - 封鎖聯絡人 - 封鎖 - 帳戶清單 - New Chat - 設定 - Search Contacts - 開始即時通訊 - 登出 - 觀看個人檔案 - Start Encryption - Stop Encryption - Clear Chat - 結束即時通訊 - 聯絡人清單 - 邀請… - 切換即時通訊 - 插入表情符號 - Scan Fingerprint - Your Fingerprint - Verify Fingerprint - [Menu] + - 確認 - Do you want to sign out all services? - 刪除「%1$s」? - 封鎖「%1$s」。 - %1$s」會被解除封鎖。 - 確定 - 取消 - 確定 - 取消 - 您已登出 %1$s - 由於 %2$s,您已登出 %1$s - 新增 %1$s 帳戶 - 使用者名稱: - 密碼: - 記住我的密碼。 - 自動登入 - 您有帳戶嗎? - For your security, if your phone is lost or stolen, go to the Web site on your computer and change your password. - 此選項可讓您開啟應用程式時自動登入。如要停用,請登出並清除 [自動登入] 核取方塊。 - 登入 - 登入 %1$s - 登入中… - 已停用背景資料 - %1$s 需要啟用背景資料。 - 啟用 - 結束 - 進行中的即時通訊 (%1$d) - 線上人數 %1$d - 邀請朋友 - ("" 未知的 "") - 沒有聯絡人 - 沒有對話 - 選取要邀請的聯絡人 - 輸入關鍵字尋找聯絡人 - 找不到聯絡人。 - 已封鎖聯絡人 - %1$s - 沒有封鎖的聯絡人。 - 聯絡人檔案 - 狀態: - 用戶端類型: - 電腦 - 行動裝置 - 線上 - 忙碌 - 外出 - 閒置 - 離線 - 顯示離線 - %1$s settings (dev only) - Data channel - Data encoding - CIR channel - Host - MSISDN - HTTP - SMS - TCP - XML - WBXML - Save - %1$s進行即時通訊 - - 請在此輸入文字 - %1$s 在線上 - %1$s 顯示離開 - %1$s 目前忙碌 - %1$s 顯示離線 - %1$s 已加入 - %1$s 已離開 - \'傳送時間:\'hh\':\'mm\' \'a\',\'EEEE - 傳送 - 訊息無法傳送。 - 與伺服器連線中斷。您的訊息將在下次登入時傳送。 - %1$s 顯示離線。您的訊息將在 %1$s 登入時傳送。 - %1$s不在您的聯絡人清單。 - 選取連結 - 沒有進行中的即時通訊 - 新增聯絡人 - 您要邀請的對象電子郵件地址: - 選擇一個清單: - 輸入聯絡人姓名。 - 傳送邀請 - Jabber/XMPP - Account Setup - Encryption and Anonymity - 隱藏離線聯絡人 - 通知設定 - 即時訊息通知 - 收到即時訊息時,在狀態列中顯示通知 - 震動 - 收到即時訊息時振動提醒 - 音效 - 收到即時訊息時播放鈴聲 - 選取鈴聲 - %1$s邀請您加入群組即時通訊。 - 已傳送邀請給 %1$s - 接受 - 拒絕 - %1$s想要將您新增到聯絡人清單。 - 接受 - 拒絕 - 訂閱 %1$s 資訊失敗。請稍後再試一次。 - 拒絕訂閱 %1$s 資訊失敗。請稍後再試一次。 - 來自 %1$s 的新訊息 - %1$d 個未讀即時訊息 - %s傳送朋友邀請給您 - 群組即時通訊邀請 - 來自%s的群組即時通訊邀請 - %1$s」已新增。 - %1$s」已刪除。 - %1$s」已封鎖。 - %1$s」已解除封鎖。 - 開始即時訊息服務 - 允許應用程式透過網際網路啟動即時訊息服務 - 注意 - 無法登入 %1$s 服務。請稍候再試一次。"\n" (詳細內容:%2$s) - 清單未增加 - 聯絡人未封鎖。 - 聯絡人未解除封鎖。 - 請先選取聯絡人。 - "已中斷連線!"\n - 服務錯誤! - 聯絡人清單未載入。 - 無法連線到伺服器。請檢查連線狀態。 - %1$s已在您的聯絡人清單。 - %1$s」已封鎖。 - 請等待聯絡人清單載入完成。 - 網路發生問題。 - 伺服器不支援此功能。 - 您輸入的密碼無效。 - 伺服器發生問題。 - 伺服器不支援此功能。 - 目前無法存取伺服器。 - 伺服器連線逾時。 - 伺服器不支援目前版本。 - 訊息佇列已滿。 - 伺服器不支援網域名稱轉址。 - 您輸入的使用者名稱無法辨識。 - 很抱歉,您被此使用者封鎖。 - 登入時限已到,請重新登入。 - 您已在其他用戶端登入。 - 您已在其他用戶端登入。 - 很抱歉,無法從 SIM 卡讀取電話號碼。請向您的電信業者尋求協助。 - 您尚未登入。 - 錯誤代碼 %1$d - 開心 -傷心 -眨眼 -吐舌頭 -驚訝 -紅唇 -大喊 - -滿嘴錢 -說錯話 -害羞 -天使 -還沒決定 -嚎啕大哭 -不要告訴別人 -開懷大笑 -疑惑 - - :-) -:-( -;-) -:-P -=-O -:-* -:O -B-) -:-$ -:-! -:-[ -O:-) -:-\\ -:\'( -:-X -:-D -o_O - - Happy -Sad -Winking -Tongue sticking out -Surprised -Kissing -Yelling -Cool -Money mouth -Foot in mouth -Embarrassed -Angel -Undecided -Crying -Lips are sealed -Laughing -Confused - - :-) -:-( -;-) -:-P -=-O -:-* -:O -B-) -:-$ -:-! -:-[ -O:-) -:-\\ -:\'( -:-X -:-D -o_O - - Contact List - %1$s - Contact List - Encrypt On/Off - Connect via Tor (Requires Orbot app) - Gibberbot - First time using Gibberbot? - Can\'t wait to get started? - Get Started - Account Setup - About Gibberbot - Gibberbot is a mobile instant messaging app that provides extra security features that prevent others from snooping on your conversations and communications.\n\nThe app supports any chat service that uses the Jabber or XMPP protocol, such as Google GTalk or Jabber.org. - How is it secure? - \'Off-the-Record Messaging\' is a security system designed to enable privacy by mimicking the characteristics of a private conversation in the real world, including Encryption, Authentication, Deniability and Forward Secrecy.\n\nThe OTR-protocol is compatible with desktop chat clients such as Adium or Pidgin. - Are My Chats Secure? - Gibberbot\'s chat encryption feature only works when messaging with others using a compatible app or program, so you should ensure your contacts are using Gibberbot for mobile and Adium or Pidgin for the desktop. You can fine-tune exactly how and when Gibberbot attempts to encrypt your chats in Account Settings.\n\nLet\'s get started! - Passphrase Setup - Before you get started please choose a secure passphrase to protect your Gibberbot data from unjust access. - Passphrase: - Passphrase (again): - user@domain.com - password - Advanced Account Settings - Persistence - Remember Password - Password cached - Password not cached - Automatically Sign In - Connect at Gibberbot start-up - Don\'t connect at Gibberbot start-up - Sign In Now - Connect following account setup - Don\'t connect following account setup - Personal (optional) - Account Alias (your name) - How your account appears online - Profile - A brief blurb about yourself - Force encryption / refuse plaintext - When possible, encrypt chats automatically - Encrypt chats as requested - Disable chat encryption - Validating credentials... - Generating keypair... - Signing in... - Gibberbot encountered an error while validating your username or password - please check them and try again. - Gibberbot encountered an error while generating a keypair. - Gibberbot encountered an error while connecting to the chat server - please check your configuration and try again. - Gibberbot encountered an error while connecting - please doublecheck your network connectivity and try again. - Their Fingerprint - Your Fingerprint - Sign In - Regen Key - Gibberbot has lost a connection to the network - Gibberbot is attempting to restablish a connection - Warning: This chat is NOT encrypted - This chat is secure but the identities of the participants have NOT been verified - Warning: chat encryption has been stopped. - This chat is secured and verified - Account Wizard - Your Account ID - Configure Server - Are You Ready? - Enter your account ID to configure Gibberbot for your XMPP chat service. It looks like an email address: - Please enter your account ID (user@hostname): - Please enter or edit your jabber/xmpp chat server hostname and port number (5222 is default). - Gibberbot has been configured, and now it is time to connect to your service, and start chatting safely, securely and privately! - You didn\'t enter an @hostname.com part for your account ID. Try again! - Your server hostname didn\'t have a .com, .net or similar appendix. Try again! - Enter your password: - pref_account_user - pref_account_pass - pref_account_domain - pref_account_xmpp_resource - pref_account_port - pref_account_server - pref_security_allow_plain_auth - pref_security_require_tls - pref_security_tls_cert_verify - pref_security_otr_mode - pref_security_use_tor - pref_security_do_dns_srv - pref_hide_offline_contacts - pref_enable_notification - pref_notification_vibrate - pref_notification_sound - pref_notification_ringtone - Account Settings - WARNING: This is an early release of Gibberbot that may still include security holes or bugs. - Groups - Generating new OTR keypair... - Contact - type the name of a contact to chat with - Go - Language - Languages - What language should InTheClear display? - Do SRV Lookup - Use DNS SRV to find actual XMPP server from domain name - Allow the username and password to be sent as plain text when using an unencrypted transport - Allow Plain Text Auth - Verify that the certificate is trusted - TLS Verification - Require TLS/SSL connection - Transport Encryption - how encrypted chats are started - The server to connect to, if needed - Connect Server - TCP Port for XMPP Server - Server Port - XMPP Resource - to distinguish this connection from other clients that are also logged in - Next - Back - Conversations - diff --git a/res/values-zh/strings.xml b/res/values-zh/strings.xml deleted file mode 100644 index 33f24936e..000000000 --- a/res/values-zh/strings.xml +++ /dev/null @@ -1,355 +0,0 @@ - - - 读即时信息 - 允许应用程序读取数据IM内容提供商。 - 写即时消息 - 允许程序写入数据即时信息内容提供商。 - Gibberbot - 聊天 - 选择一个帐户 - About - 新增帐户 - 编辑帐户 - 删除帐户 - 登出所有 - 聊天 - 选择一个帐户 - (%1$d) - 取消登录 - 添加联系人 - 删除联系人 - 阻止联系人 - 已阻止 - 帐户列表 - New Chat - 设置 - 搜索联系人 - 开始聊天 - 退出 - 查看个人资料 - 启动加密 - 停止加密 - 清除聊天 - 结束聊天 - 联系人列表 - 邀请... - 切换聊天 - 插入表情符 - 扫描指纹 - 指纹 - 指纹验证 - MENU+ - 确认 - 你要退出所有的服务? - 将会删除联系人“%1$s”。 - 将会阻止联系人“%1$s”。 - 将会解除阻止联系人“%1$s”。 - 确定 - 取消 - 确定 - 取消 - 您已经退出了 %1$s - 由于%2$s,您已经退出 %1$s - 添加 %1$s 帐户 - 用户名: - 密码: - 记住密码。 - 自动登录。 - 没有帐户? - 为了您的安全,如果您的手机丢失或被盗,请在您的电脑网站,并更改密码。 - 启用该选项后,您每次打开此应用程序时都会自动登录。要停用该选项,请退出,然后取消选中“自动登录”复选框。 - 登录 - 正在登录 %1$s - 正在登录... - 已停用背景数据 - %1$s 需要启用背景数据。 - 启用 - 退出 - 当前聊天 (%1$d) - %1$d 人在线 - 好友邀请 - (""未知"") - - 无会话 - 选择要邀请的联系人 - 输入拼音以查找联系人 - 未找到联系人。 - 已阻止的联系人 - %1$s - 无受阻止的联系人。 - 联系人个人资料 - 状态: - 客户端类型: - 计算机 - 移动设备 - 在线 - 忙碌 - 离开 - 空闲 - 离线 - 显示为离线 - %1$s settings (dev only) - 数据通道 - 数据编码 - 税务局局长通道 - 主机 - MSISDN - HTTP的 - 短信 - 技术合作计划 - XML的 - WBXML - 保存 - %1$s聊天 - - 输入内容 - %1$s在线 - %1$s离开 - %1$s正忙 - %1$s处于离线状态 - %1$s已加入 - %1$s已离开 - \'发送时间:\'EEEE - a\' \'hh\':\'mm - 发送 - 无法发送此消息。 - 与服务器失去连接。消息将在下次上线时发送。 - %1$s处于离线状态。您发送的消息将在%1$s下次上线时传送出去。 - %1$s不在您的联系人列表中。 - 选择链接 - 无活动聊天。 - 添加联系人 - 您希望邀请的用户的电子邮件地址: - 选择一个列表: - 输入用户名以从联系人添加。 - 发送邀请 - Jabber/XMPP - Account Setup - Encryption and Anonymity - 隐藏离线联系人 - 通知设置 - 即时消息通知 - 收到即时消息时在状态栏中显示通知 - 振动 - 在收到即时消息的同时振动 - 声音 - 在收到即时消息的同时播放铃声 - 选择铃声 - %1$s邀请您加入群聊。 - 已向%1$s发送邀请。 - 接受 - 拒绝 - %1$s已邀请您加入其联系人列表。 - 接受 - 拒绝 - 无法批准来自%1$s的好友申请。请稍后再试。 - 无法拒绝来自%1$s的好友申请。请稍后再试。 - 来自%1$s的新消息 - %1$d 条未读聊天消息 - 来自%s的新好友邀请 - 群聊邀请 - 来自%s的新群聊邀请 - 已添加联系人“%1$s”。 - 联系人“%1$s”已删除。 - 已阻止联系人“%1$s”。 - 联系人“%1$s”已解除阻止。 - 启动即时消息服务 - 允许应用程序通过 intent 启动即时消息服务。 - 注意 - 无法登录%1$s服务。请稍后再试。"\n"(详细信息:%2$s - 未添加该列表。 - 未阻止联系人。 - 联系人未解除阻止。 - 请先选择一个联系人。 - "已断开连接!"\n - 服务错误! - 未载入联系人列表。 - 无法连接到服务器。请检查您的连接。 - %1$s已在您的联系人列表中。 - 已阻止联系人“%1$s”。 - 正在载入联系人列表,请稍候。 - 出现网络错误。 - 服务器不支持此功能。 - 您输入的密码无效。 - 服务器遇到一个错误。 - 服务器不支持此功能。 - 服务器当前不可用。 - 服务器已超时。 - 服务器不支持当前版本。 - 消息队列已满。 - 服务器不支持转发至该域。 - 您输入的用户名无法识别。 - 抱歉,您已被该用户阻止。 - 此会话已过期,请重新登录。 - 您已从另一个客户端登录。 - 您已从另一个客户端登录。 - 抱歉,无法从您的 SIM 卡读取手机号码。请与运营商联系寻求帮助。 - 您当前未登录。 - 错误代码 %1$d - 幸福 -悲伤 -眨眼 -吐舌头 -惊讶 -亲吻 -大喊 - -财迷 -说错了话 -尴尬 -天使 -犹豫 -哭泣 -保密 -大笑 -困惑 - - :-) -:-( -;-) -:-P -=-O -:-* -:O -B-) -:-$ -:-! -:-[ -O:-) -:-\\ -:\'( -:-X -:-D -o_O - - 快乐 -伤心 -眨眼 -舌头伸出来 -惊讶 -接吻 -大喊 - -钱口 -脚口 -尴尬 -天使 -未定 -哭泣 -守口如瓶 - -困惑 - - :-) -:-( -;-) -:-P -=-O -:-* -:O -B-) -:-$ -:-! -:-[ -O:-) -:-\\ -:\'( -:-X -:-D -o_O - - Contact List - %1$s - 联系人列表 - 加密On/Off - 通过Tor连接(需要Orbot应用程序) - Gibberbot - 第一次使用Gibberbot? - Can\'t迫不及待地开始了吗? - 关于Gibberbot - 帐户设置 - 欢迎来到Gibberbot - Gibberbot是一个移动的即时消息应用程序,提供额外的安全功能,防止窃听你的谈话和communications.\n\nThe应用程序支持使用任何聊天服务或Jabber的XMPP协议,如谷歌第一时间告诉Jabber.org,等等。 - 它是如何保障? - \'Off -的记录Messaging\'是一个安全系统设计,使通过模仿在现实世界中,包括加密,认证,不可否认性和远期Secrecy.\n\nThe工程机械协议的私人谈话的特点隐私与桌面聊天客户端兼容例如Adium的或洋泾浜。 - 是我的聊天安全吗? - Gibberbot\的聊天加密功能只适用时,使用兼容的应用程序或程序的其他信息,所以你应该确保您的联系人使用手机和Adium的或桌面洋泾浜Gibberbot。你可以微调究竟如何以及何时Gibberbot试图加密阿科你聊天\'UNT的Settings.\n\nLet\开始吧! - 口令设置 - 在你开始之前,请选择安全的密码来保护您的Gibberbot从不公正的访问数据。 - Passphrase: - 密码(再次): - user@domain.com - password - Advanced Account Settings - 坚持 - 记住密码 - 密码缓存 - 密码不缓存 - 自动登录 - 连接在Gibberbot启动 - Don\'t连接在Gibberbot启动 - 立即登录 - 连接以下帐户设置 - Don\'t联络以下帐户设置 - 个人(可选) - 账户别名(您的姓名) - 如何在网络上出现您的帐户 - 简介 - 关于自己短暂的Blurb的 - 垃圾强制加密明文/ - 如果可能的话,会自动加密聊天 - 按要求加密聊天 - 禁用聊天加密 - 验证凭据... - 生成密钥对... - 登入中.. - Gibberbot遇到了一个错误在验证您的用户名和密码 - 请检查并再试一次。 - Gibberbot遇到了一个错误,而生成一个密钥对。 - Gibberbot遇到了一个错误,而连接到聊天服务器 - 请检查您的配置,然后再试一次。 - Gibberbot连接时遇到了一个错误 - 请doublecheck您的网络连接,然后再试一次。 - 他们的指纹 - 指纹 - 登录 - 再生的关键 - Gibberbot已经失去了一个连接到网络 - Gibberbot试图restablish连接 - Warning:此聊天是不加密 - 此聊天是安全的,但参加者的身份尚未被验证 - Warning:聊天加密已停止。 - 此聊天是安全和验证 - 帐户向导 - 您的帐户ID - 配置服务器 - 你准备好了吗? - 请输入您的帐户ID,自动配置Gibberbot为你聊天service.\n\nIt可能看起来像一个电子邮件地址 - 这样yourname@gmail.com,或someone@jabber.org,例如。 - 请输入您的帐户ID(用户@主机名): - 请输入或编辑您的jabber/xmpp聊天服务器的主机名和端口号(默认是5222)。 - Gibberbot已经配置了,现在是时候来连接您的服务,并开始聊天安全,安全和私下! - You didn\'t enter an @hostname.com part for your account ID. Try again! - Your server hostname didn\'t have a .com, .net or similar appendix. Try again! - 输入您的password: - Account Settings - WARNING: This is an early release of Gibberbot that may still include security holes or bugs. - Groups - Generating new OTR keypair... - Contact - type the name of a contact to chat with - Go - Language - Languages - What language should InTheClear display? - Do SRV Lookup - Use DNS SRV to find actual XMPP server from domain name - Allow the username and password to be sent as plain text when using an unencrypted transport - Allow Plain Text Auth - Verify that the certificate is trusted - TLS Verification - Require TLS/SSL connection - Transport Encryption - how encrypted chats are started - The server to connect to, if needed - Connect Server - TCP Port for XMPP Server - Server Port - XMPP Resource - to distinguish this connection from other clients that are also logged in - Next - Back - Conversations - diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 3f88b25c7..1a4a02aa2 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1,94 +1,25 @@ - - - + - Force / Require - Automatically Attempt - As Requested - Disabled / Never + @string/otr_options_force + @string/otr_options_auto + @string/otr_options_attempt + @string/otr_options_disabled - force auto requested disabled - - - Default - العربية - فارسی - 中文(简体) - 日本語 - 한국어 - မြန်မာဘာသာ - Tibetan བོད་སྐད། - Tiếng Việt - Dansk - Deutsch - English - Español - Esperanto - Français - Italiano - Magyar - Nederlands - Norwegian Bokmål - Português - Pyccĸий - Slovenčina - Swedish - Tϋrkçe - Čeština - ελληνικά - - - - xx - ar - fa - zh-rCN - ja - ko - my - bo - vi - da - de - en - es_ES - eo - fr - it - hu - nl - nb_NO - pt - ru - sk - sv - tr - cs - el - - - + dukgo.com - jabber.ccc.de - gabbler.de - blah.im - xmpp.kz - jabber.at - inbox.im - jabber.postel.org - chatme.im - jabb3r.net - jabber.belnet.be + jabber.otr.im + jabber.calyxinstitute.org xmpp.jp - xmpp.ru.net + jabberpl.org + neko.im - - + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml new file mode 100644 index 000000000..1d18251a0 --- /dev/null +++ b/res/values/attrs.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/cacheword.xml b/res/values/cacheword.xml index 12d9ffae0..27fa491df 100644 --- a/res/values/cacheword.xml +++ b/res/values/cacheword.xml @@ -2,9 +2,7 @@ - ChatSecure Unlocked - Passphrase stored in memory - Passphrase stored in memory + false - 5 + -1 + + + false diff --git a/res/values/colors.xml b/res/values/colors.xml index 7b9ab350a..18f19b933 100755 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -25,13 +25,49 @@ #ff000000 #ffcc5555 - #FFA4C639 - #aaA4C639 - #FF58BAED - #FF777777 - #FFEFEFEF - #FFdddddd - #FF343434 + #ccA4C639 + #ccA4C639 + #cc58BAED + #CFD8DC + + #DDDEDEDE + #DDABABAB + #DD343434 + + + #DEDEDE + #FFFFFF + + #333333 + #565656 + + + #cc33b5e5 + + #cc99cc00 + + #ccff4444 + + #cc0099cc + + #cc669900 + + #cccc0000 + + #ccaa66cc + + #ccffbb33 + + #ccff8800 + + #cc00ddff + + + #ECEFF1 + + + #CACDD0 #ffcaca #ffecc0 @@ -52,4 +88,13 @@ #AAEAF7FB #333333 + #aaffffff + + #aaffffff + #aa555555 + + #37474F + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a1dbf66c4..7f9350e70 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -22,7 +22,9 @@ 8dp 16dp 32dp - 150dp + 100dp 15dp + 120dip + 120dip \ No newline at end of file diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml new file mode 100644 index 000000000..52e24896d --- /dev/null +++ b/res/values/donottranslate.xml @@ -0,0 +1,9 @@ + + + + market://details?id=org.torproject.android + + defLoc + key_store_media_on_external_storage_pref + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 4801addfe..fa7f11f16 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,4 +1,4 @@ - + - - read instant messages - - Allows applications to read data from the IM content provider. - - - write instant messages - - Allows applications to write data to the IM content provider. - - - - ChatSecure - - - - Select an account - - - About - - - Add account - - Edit account - - Remove account - - Sign out all - - - - Select an account - - (%1$d) - - - - Cancel signin - - - - Add contact - - Delete contact - - Block contact - - Blocked - - Accounts - - New Chat - - - New Account - - Settings - - Search Contacts - - Start chat - - Sign out - - Verify - - - - - - Start Encryption - Stop Encryption - - Clear Chat - - End chat - - Secure Call - - - Contact list - - Invite\u2026 - - Switch chats - - Insert smiley - - Resend - - - Scan Fingerprint - Your Fingerprint - Verify Fingerprint - Verify Secret - - - Menu+ - - - - - - Confirm - - Do you want to sign out all services? - - - The contact \"%1$s\" will be deleted. - - The contact \"%1$s\" will be blocked. - - The contact \"%1$s\" will be unblocked. - - OK - - Cancel - - OK - - Cancel - - - You have been signed out of %1$s. - - You have been signed out of %1$s because %2$s - - - - Add %1$s account - - - - User@Host - - Password: - - Remember my password. - - Sign me in automatically. - - Don\u2019t have an account? - - For your security, if your phone is lost or stolen, go to the Web site on your computer and change your password. - This option automatically signs you in every time you open this application. To disable the option, sign out, then clear the \"Sign me in automatically\" check box. - - Sign in - - - - getting the ChatSecure going... - - Signing in\u2026 - - - Network Data disabled - - - - Network data connectivity (including background data) is needed for the app to login. - - - - Enable - - Quit - - - - Ongoing chats (%1$d) - - %1$d online - - Friend invitations - - (Unknown) - - Empty - - No conversations - - - Select contact(s) to invite - Type to find contact - No contacts found.\n\nTap to invite. - - - Blocked contacts - %1$s - - No blocked contacts. - - - - Contact profile - - Status: - - Client type: - - Computer - - Mobile - - - - Online - - Busy - - Away - - Idle - - Offline - - Appear offline - - - %1$s settings (dev only) - Data channel - Data encoding - CIR channel - Host - MSISDN - HTTP - SMS - TCP - XML - WBXML - Save - - - - Chat with %1$s - - Me - - Type to compose - - %1$s is online - - %1$s is away - - %1$s is busy - - %1$s is offline - - %1$s has joined - - %1$s has left - - - \'Sent at\' hh:mm a \'on\' EEEE - - - - Send - - This message could not be sent. - - Messages will be sent on reconnect - - %1$s is offline. Messages you send will be delivered when %1$s comes online. - - %1$s is not in your Contact list. - - Select link - - No active chats. - - Starting encrypted chat session... - Stopping encrypted chat session... - - - - Add contact - - Email address of person you wish to invite: - - Choose a list: - - Type a name to add from Contacts. - - Send invitation - - - Jabber (XMPP) - Local Network Chat (Bonjour/ZeroConf) - - - - Account Setup - Encryption and Anonymity - Hide offline contacts - - Notification settings - - IM notifications - - Notify in status bar when IM arrives - - Use foreground priority - Reduce the chance that Android will restart our connection service. This will place a notification in the notification area. - Heartbeat interval - Use a higher value (in minutes) to save battery. A high value may cause the provider to close your connection due to inactivity. - Vibrate - - Also vibrate when IM arrives - - Sound - - Also play ringtone when IM arrives - - Select ringtone - - - - %1$s has invited you to join a group chat. - - Invitation has been sent to %1$s. - - Accept - - Decline - - - %1$s has invited you to be on their Contact list. - - Accept - - Decline - - Unable to approve subscription from %1$s. Please try again later. - - Unable to decline subscription from %1$s. Please try again later. - - - New %1$s messages - - - %1$d unread chats - - - - New friend invitation from %s - - - Group chat invitation - - - New group chat invitation from %s - - - - Contact \"%1$s\" added. - - Contact \"%1$s\" deleted. - - Contact \"%1$s\" blocked. - - Contact \"%1$s\" unblocked. - - - - start IM service - Allows applications to start IM service via intent. - - - - Attention - - Unable to sign in to the %1$s service. Please try again later.\n(Detail: %2$s) - - The list was not added. - - Contact was not blocked. - - Contact was not unblocked. - - Please select a contact first. - - Disconnected!\n - - Service error! - - Contact list did not load. - - Cannot connect to server. Please check your connection. - - "%1$s" already exists in your Contact list. - - Contact \"%1$s\" has been blocked. - - Please wait while your Contact list loads. - - A network error occurred. - WiFi is required for this connection. - - The server does not support this functionality. - - The password you entered is not valid. - - The server encountered an error. - - The server does not support this functionality. - - The server is currently unavailable. - - The server has timed out. - - The server does not support the current version. - - The message queue is full. - - The server does not support forwarding to the domain. - - The username you entered is not recognized. - - Sorry, you are blocked by the user. - - The session has expired, please sign in again. - - you have signed in from another client. - you have signed in from another client. - - Sorry, the phone number cannot be read from your SIM card. Please contact your operator for help. - - You are currently not signed in. - - Error code %1$d - - - - Happy - Sad - Winking - Tongue sticking out - Surprised - Kissing - Yelling - Cool - Money mouth - Foot in mouth - Embarrassed - Angel - Undecided - Crying - Lips are sealed - Laughing - Confused - - - - :-) - :-( - ;-) - :-P - =-O - :-* - :O - B-) - :-$ - :-! - :-[ - O:-) - :-\\ - :\'( - :-X - :-D - o_O - - - - Happy - Sad - Winking - Tongue sticking out - Surprised - Kissing - Yelling - Cool - Money mouth - Foot in mouth - Embarrassed - Angel - Undecided - Crying - Lips are sealed - Laughing - Confused - - - - :-) - :-( - ;-) - :-P - =-O - :-* - :O - B-) - :-$ - :-! - :-[ - O:-) - :-\\ - :\'( - :-X - :-D - o_O - - Contact List - %1$s - Contact List - - Encrypt On/Off - Connect via Tor (Requires Orbot app) - - - ChatSecure - First time using ChatSecure? - Can\'t wait to get started? - Get Started - Account Setup - - - About ChatSecure - ChatSecure is a mobile instant messaging app that provides extra security features that prevent others from snooping on your conversations and communications.\n\nThe app supports any chat service that uses the Jabber or XMPP protocol, such as Google GTalk or Jabber.org. - - - How is it secure? - \'Off-the-Record Messaging\' is a security system designed to enable privacy by mimicking the characteristics of a private conversation in the real world, including Encryption, Authentication, Deniability and Forward Secrecy.\n\nThe OTR-protocol is compatible with desktop chat clients such as Adium or Pidgin. - - - Are My Chats Secure? - ChatSecure\'s chat encryption feature only works when messaging with others using a compatible app or program, so you should ensure your contacts are using ChatSecure for mobile and Adium or Pidgin for the desktop. You can fine-tune exactly how and when ChatSecure attempts to encrypt your chats in Account Settings.\n\nLet\'s get started! - - - Passphrase Setup - Before you get started please choose a secure passphrase to protect your ChatSecure data from unjust access. - Passphrase: - Passphrase (again): - - - user@domain.com - new username - service provider (dukgo.com, jabber.ccc.de) - - password - - confirm password - - Advanced Account Settings - Account Type - - Register Account - - - Persistence - Remember Password - Password cached - Password not cached - Automatically Sign In - Connect at ChatSecure start-up - Don\'t connect at ChatSecure start-up - Sign In Now - Connect following account setup - Don\'t connect following account setup - - - - Personal (optional) - Account Alias (your name) - How your account appears online - Profile - A brief blurb about yourself - - - Force encryption / refuse plaintext - When possible, encrypt chats automatically - Encrypt chats as requested - Disable chat encryption - - - Validating credentials... - Generating keypair... - Signing in... - - - ChatSecure encountered an error while validating your username or password - please check them and try again. - ChatSecure encountered an error while generating a keypair. - ChatSecure encountered an error while connecting to the chat server - please check your configuration and try again. - ChatSecure encountered an error while connecting - please doublecheck your network connectivity and try again. - - - Their Fingerprint - - Your Fingerprint - Sign In - Regen Key - - ChatSecure has lost a connection to the network - ChatSecure is attempting to restablish a connection - - Encryption is off - Encryption is on (Tap to verify) - Encryption has been stopped - Encrypted and verified! - - Account Wizard - Your Account ID - Configure Server - Are You Ready? - - - Enter your account ID to configure ChatSecure for your XMPP chat service. It looks like an email address: - Please enter your account ID (user@hostname): - Please enter or edit your jabber/xmpp chat server hostname and port number (5222 is default). - ChatSecure has been configured, and now it is time to connect to your service, and start chatting safely, securely and privately! - - - You didn\'t enter an @hostname.com part for your account ID. Try again! - Your server hostname didn\'t have a .com, .net or similar appendix. Try again! - Enter your password: - - + + + ChatSecure + ChatSecure + ChatSecure is a free, unlimited, private messaging app that based on open standards. Unlike other apps that keep you stuck in their walled garden, ChatSecure is fully interoperable with other clients that support OTR and XMPP, such as Adium, Jitsi, and more. You can connect using your DuckDuckGo and Google accounts, create new accounts on public XMPP servers (including via Tor), or even connect to your own server for extra security. + + + read instant messages + Allows applications to read data from the IM content provider. + write instant messages + Allows applications to write data to the IM content provider. + start IM service + Allows applications to start IM service via intent. + + + Confirm + OK + Cancel + OK + Cancel + Next + Back + Connect + Play + Setup + + + Open + ChatSecure Locked + Set Password + Confirm Password + Password + You can optionally set a master password for ChatSecure to prevent access to your contacts and messages without a password: + Confirm New Password + Password did not match, please try again + Skip >> + Information Prompt + Enter Password Please + Password please… + Passphrase + Passphrase (again) + + + Select an account + Select an account + (%1$d) + Add %1$s account + About + + Sign out all + Do you want to sign out all services? + You have been signed out of %1$s. + You have been signed out of %1$s because %2$s + + + First time using ChatSecure? + Can\'t wait to get started? + Get Started + Account Setup + + Passphrase Setup + Before you get started please choose a secure passphrase to protect your ChatSecure data from unjust access. + Passphrase: + Passphrase (again): + + Enter a *new* passphrase. It must contain at least one uppercase, one lowercase and one number, and be longer than six characters. + Encrypting your existing notes with the new passphrase (patience…) + Enter your passphrase: + Welcome! Enter a strong passphrase to secure your notes. It must contain at least one uppercase, one lowercase and one number, and be longer than six characters. + Your passphrase was not long enough + Your passphrase did not contain any uppercase letters + Your passphrase did not contain any lowercase letters + Your passphrase did not contain any numbers + + + About ChatSecure + ChatSecure is a mobile instant messaging app that provides extra security features that prevent others from snooping on your conversations and communications.\n\nThe app supports any chat service that uses the Jabber or XMPP protocol, such as Google GTalk or Jabber.org. + + + How is it secure? + \'Off-the-Record Messaging\' is a security system designed to enable privacy by mimicking the characteristics of a private conversation in the real world, including Encryption, Authentication, Deniability and Forward Secrecy.\n\nThe OTR-protocol is compatible with desktop chat clients such as Adium or Pidgin. + + + Are My Chats Secure? + ChatSecure\'s chat encryption feature only works when messaging with others using a compatible app or program, so you should ensure your contacts are using ChatSecure for mobile and Adium or Pidgin for the desktop. You can fine-tune exactly how and when ChatSecure attempts to encrypt your chats in Account Settings.\n\nLet\'s get started! + + + New Account + Add Account + Edit Account + Remove Account + Existing Account + + + User@Host + Password: + Remember my password. + Sign me in automatically. + Don\u2019t have an account? + Sign in + For your security, if your phone is lost or stolen, go to the Web site on your computer and change your password. + This option automatically signs you in every time you open this application. To disable the option, sign out, then clear the \"Sign me in automatically\" check box. + Connect via Tor (Requires Orbot app) + + user@domain + new username + service provider (dukgo.com, jabber.ccc.de) + password + confirm password + Advanced Account Settings + Account Setup + Register Account + + Persistence + Remember Password + Password cached + Password not cached + Automatically Sign In + Connect at ChatSecure start-up + Don\'t connect at ChatSecure start-up + Sign In Now + Connect following account setup + Don\'t connect following account setup + + Personal (optional) + Account Alias (your name) + How your account appears online + Profile + A brief blurb about yourself + + Force encryption / refuse plaintext + When possible, encrypt chats automatically + Encrypt chats as requested + Disable chat encryption + + Validating credentials… + Generating keypair… + Signing in… + + Account Wizard + Your Account ID + Configure Server + Are You Ready? + + Enter your account ID to configure ChatSecure for your XMPP chat service. It looks like an email address: + Please enter your account ID (user@hostname): + Please enter or edit your jabber/xmpp chat server hostname and port number (5222 is default). + ChatSecure has been configured, and now it is time to connect to your service, and start chatting safely, securely and privately! + + "Orbot (Tor)" + Chat service domain + Registering new account… + + getting going… + Cancel signin + Signing in\u2026 + Signing out\u2026 + + + Do SRV Lookup + Use DNS SRV to find actual XMPP server from domain name + Allow the username and password to be sent as plain text when using an unencrypted transport + Allow Plain Text Auth + Verify that the certificate is trusted + TLS Verification + Require TLS Connection + Transport Encryption + how encrypted chats are started + The server to connect to, if needed + Connect Server + TCP Port for XMPP Server + Server Port + XMPP Resource + to distinguish this connection from other clients that are also logged in + XMPP Resource Priority + Messages to clients with multiple active resources will be sent to the resource with the highest priority + + + Conversations + No conversations.\n\nTap here to start one! + You have no\naccounts configured.\n\nTap here to add one! + Contact List - %1$s + Add Contact + Delete Contact + Block Contact + Contact nickname + Blocked + Nickname + + The contact \"%1$s\" will be deleted. + The contact \"%1$s\" will be blocked. + The contact \"%1$s\" will be unblocked. + + Contact \"%1$s\" added. + Contact \"%1$s\" deleted. + Contact \"%1$s\" blocked. + Contact \"%1$s\" unblocked. + + New Chat + Search Contacts + + Show Grid + Start chat + View Profile + Verify Contact + + Ongoing chats (%1$d) + %1$d online + Friend invitations + (Unknown) + Empty + No conversations open\n\nTap to start chatting! + + Contact + type the name of a contact to chat with + Go + + No active chats. + + Add contact + Username or JabberID of person to add: + Account to add to: + Type a name to add from Contacts. + Send Invite + + Contact profile + Status: + + Client type: + Computer + Mobile + + Blocked contacts - %1$s + No blocked contacts. + + + Chat with %1$s + Me + + %1$s is online + %1$s is away + %1$s is busy + %1$s is offline + %1$s has joined + %1$s has left + + Send Photo + Send File + Send Audio + Take Picture + + File Transfer + Transfer Complete + Transfer in progress + Accept transfer? + wants to send you the file + No connection available for sending your share! + + Send + Send a message + Send secure message + Resend + End chat + Clear chat + + Insert smiley + Switch chats + Menu+ + Select link + + Delete + Keep Files + Delete Chat Session Secured Storage? + All the session\'s upload and download files will be permanently deleted. Warning: this operation can not be undone! + Delete Original? + This file will be copied into the secured storage before being sent. Would you like to delete the original file from the device\'s unsecured storage? + Keep + Export + Export Media File? + This media file will be exported to %1$s + End chat? + All secured media items of this session will be deleted. Export a media item using a long click on the thumbnail icon. + End Chat and Delete Files + + + %1$s has invited you to join a group chat. + Invitation has been sent to %1$s. + Accept + Decline + Group Chat + Create or Join Group Chat + Connecting to group chat… + + Invite\u2026 + Select contact(s) to invite + Type to find contact + No contacts found.\n\nTap to invite. + + Add %1$s? + Yes + No + Unable to approve subscription from %1$s. Please try again later. + Unable to decline subscription from %1$s. Please try again later. + + + Start Encryption + Stop Encryption + Starting encrypted chat session… + Stopping encrypted chat session… + Security Fingerprint + Security Fingerprint + Sign In + Regen Key + Encryption is off + Encryption is on (Tap to verify) + Your contact has stopped the encrypted chat. + Encrypted and verified! + Generating new OTR keypair… + + + We have detected an OTR keystore to import. Would you like to scan the QR password now? + Activate KeySync + Successfully imported OTR keyring + + + Scan QR + Your Fingerprint + Manual + Question + Security Fingerprint (Verified) + Are you sure you want to confirm this fingerprint? + Verify Fingerprint? + The remote fingerprint has been verified! + Fingerprint for you + "Fingerprint for " + Install Barcode Scanner? + This application requires Barcode Scanner. Would you like to install it? + + + Authentication + Enter a question to send to your contact, and the answer you expect them to give, in order to verify they are who they claim to be. + the question to ask + the expected answer + Your contact has successfully authenticated you. Now authenticate your contact by asking your own question. + OTR Q&A Verification + Chat Encryption + Send + Cancel + + + Secure Call + Secure Voice + Enter your OStel.co or other secure SIP service account here for call integration + + + Account Setup + + + Security and Privacy + Encryption and Anonymity + Encrypt On/Off + Password Timeout + Time that app encryption should say unlocked + + + Clickable links on Tor + For accounts using Tor, make the links in chats clickable (WARNING, possible privacy leak!) + User Interface + Language + Languages + Use system default + + Use Dark Theme + Change the app theme to dark + In-Memory Message Storage Only + Only store messages in memory, not on flash storage, to defend against messages being extracted. (May cause lost messages) + Background Image + Set path ("/sdcard/foo.jpg") to a background image wallpaper for the app + Show Contact Grid + Display contact list as avatar grid + Yes, Accept All + Delete Insecure Media + After sharing a photo or file, automatically delete it from the original, insecure storage after import + Store Media On External Storage + Media files from chat sessions are stored in an encrypted container that can be stored on internal or external storage. + + + External Storage Missing + Your chat logs are stored on an SD Card, but no SD Card is present. Please insert the correct SD Card, or delete the existing chat log and launch ChatSecure again. + Chat Media Store Missing + Your chat logs are stored on the SD Card, but the file is missing from the current SD Card. Please insert the correct SD Card, or delete the existing chat log and launch ChatSecure again. + Delete Chat Log + + + Other Tuning + Start ChatSecure Automatically + Always start and automatically sign into previously logged in accounts + Hide offline contacts + Use foreground priority + Reduce the chance that Android will restart our connection service. This will place a notification in the notification area. + Heartbeat interval + Use a higher value (in minutes) to save battery. A high value may cause the provider to close your connection due to inactivity. + + + Notification settings + IM notifications + Notify in status bar when IM arrives + Vibrate + Also vibrate when IM arrives + Sound + Also play ringtone when IM arrives + Select Custom Ringtone + + + Enable Debug Logs + Output app log data to standard out / logcat for debugging + + + Network Data disabled + Network data connectivity (including background data) is needed for the app to login. + Enable + Quit + Do you want to sign out all services AND kill all processes (hard exit)? + Create new account? + Create a new chat account for username \'%1$s\'? + Certificate Info + Certificate: + Issued by: + SHA1 Fingerprint: + Issued: + Expires: + Join Chat Room? + An external app is attempting to connect you to a chatroom. Allow? + Choose Background + Do you want to select a background image from the Gallery? + Select Picture + + + New %1$s messages + %1$d unread chats + New friend invitation from %s + New file %1$s from %2$s + Group chat invitation + New group chat invitation from %s + New message(s) from + Starting the ChatSecure service… + Activated & unlocked + message copied to the clipboard + + + Attention + Error: + Error code %1$d + Unable to sign in to the %1$s service. Please try again later.\n(Detail: %2$s) + The list was not added. + Contact was not blocked. + Contact was not unblocked. + Please select a contact first. + Disconnected!\n + Service error! + Contact list did not load. + Cannot connect to server. Please check your connection. + "%1$s" already exists in your Contact list. + Contact \"%1$s\" has been blocked. + Please wait while your Contact list loads. + A network error occurred. + WiFi is required for this connection. + The server does not support this functionality. + The password you entered is not valid. + The server encountered an error. + The server does not support this functionality. + The server is currently unavailable. + The server has timed out. + The server does not support the current version. + The message queue is full. + The server does not support forwarding to the domain. + The username you entered is not recognized. + Sorry, you are blocked by the user. + The session has expired, please sign in again. + you have signed in from another client. + you have signed in from another client. + Sorry, the phone number cannot be read from your SIM card. Please contact your operator for help. + You are currently not signed in. + ChatSecure encountered an error while validating your username or password - please check them and try again. + ChatSecure encountered an error while generating a keypair. + ChatSecure encountered an error while connecting to the chat server - please check your configuration and try again. + ChatSecure encountered an error while connecting - please doublecheck your network connectivity and try again. + Network is offline + ChatSecure is attempting to restablish a connection + You didn\'t enter an @hostname.com part for your account ID. Try again! + Your server hostname didn\'t have a .com, .net or similar appendix. Try again! + Enter your password: + Since you are using Tor, you must enter the XMPP \'Connect Server\' hostname directly into Advanced Account Settings + WARNING: This service uses a certificate with WEAK cryptography. Please tell the administrator to upgrade. + "Provided Certificate Does Not Match PINNED Certifcate: " + Unable to create or join group chat + Sorry we cannot share that file type + You must enable encryption to share files + Please enable chat encryption to share files + Unsupported incoming data, cannot share! + ChatSecure detected that a request was made to remove its task. ChatSecure will likely crash. Please use the Sign out all menu item from the account list screen instead. + There is no viewer available for this file format + Please start a secure conversation before scanning codes + OTR keyring not imported; Please check the file exists in the proper format and location + Please copy the \'otr_keystore.ofcaes\' file from the desktop KeySync tool to the root directory of your device storage + This message could not be sent. + You are disconnected. Messages will be sent when you reconnect. + %1$s is offline. Messages you send will be delivered when %1$s comes online. + %1$s is not in your Contact list. + Your passwords do not match + Priority must be a number in the range [0 .. 127] + Port number must be a number + Transfer Error + Unable to read file to storage + Major Error: Unable to unlock or load app database. Please re-install the app or clear data. + No app installed that can handle that link! Account Settings - defLoc - - WARNING: This is an early release of ChatSecure that may still include security holes or bugs. Groups - - Generating new OTR keypair... - - Contact - type the name of a contact to chat with - Go - - Language - Languages - What language should InTheClear display? - Do SRV Lookup - Use DNS SRV to find actual XMPP server from domain name - Allow the username and password to be sent as plain text when using an unencrypted transport - Allow Plain Text Auth - Verify that the certificate is trusted - TLS Verification - Require TLS Connection - Transport Encryption - how encrypted chats are started - The server to connect to, if needed - Connect Server - TCP Port for XMPP Server - Server Port - XMPP Resource - to distinguish this connection from other clients that are also logged in - XMPP Resource Priority - Messages to clients with multiple active resources will be sent to the resource with the highest priority - - Next - Back - - Conversations - - - New message(s) from - - Authentication - Enter a question to send to your contact, and the answer you expect them to give, in order to verify they are who they claim to be. - - the question to ask - the expected answer - Your contact has successfully authenticated you. Now authenticate your contact by asking your own question. - - - No conversations.\n\nTap here to start one! - - Use Dark Theme - - Since you are using Tor, you must enter the XMPP \'Connect Server\' hostname directly into Advanced Account Settings - - - - Start ChatSecure Automatically - Always start and automatically sign into previously logged in accounts - You have no\naccounts configured.\n\nTap here to add one! + " open conversation(s)" - Google Account - - In-Memory Message Storage Only - Only store messages in memory, not on flash storage, to defend against messages being extracted. (May cause lost messages) - - - Background Image - Set path ("/sdcard/foo.jpg") to a background image wallpaper for the app - - Security and Privacy - User Interface - Other Tuning - " open conversation(s)" - "Orbot (Tor)" - - Exit - - - Do you want to sign out all services AND kill all processes (hard exit)? - - Enter a *new* passphrase. It must contain at least one uppercase, one lowercase and one number, and be longer than six characters. - Encrypting your existing notes with the new passphrase (patience...) - Enter your passphrase: - Welcome! Enter a strong passphrase to secure your notes. It must contain at least one uppercase, one lowercase and one number, and be longer than six characters. - Your passphrase was not long enough - Your passphrase did not contain any uppercase letters - Your passphrase did not contain any lowercase letters - Your passphrase did not contain any numbers - - Open - ChatSecure Locked - Confirm Passphrase - Enter passphrase - Enter new passphrase - Confirm new passphrase - Passphrases did not match, please try again - - Secure Voice - Enter your OStel.co or other secure SIP service account here for call integration - - dukgo.com - - We have detected an OTR key store to import. Would you like to scan the QR password now? + Shutdown & Lock + + Panic + Buddies - Import OTR Keystore + Contacts - Successfully imported OTR keyring - - OTR keyring not imported; Please check the file exists in the proper format and location + - Password Timeout - Time that app encryption should say unlocked + Accept Server Certificate? - - Group Chat - Create or Join Group Chat - Connect - Their Fingerprint (Verified) - Are you sure you want to confirm this key? - Verify key? - The remote key fingerprint has been verified! - Panic - Unable to create or join group chat + Display Your Fingerprint - Buddies - Choose a domain - + loading… + + + About ChatSecure\nhttps://guardianproject.info/apps/chatsecure/ + + + Contact List + Settings + Manage Accounts + Sign out + Contact list + + + + Jabber (XMPP) + Local Area (Bonjour/ZeroConf) + Google Account + dukgo.com + + + %1$s settings (dev only) + Data channel + Data encoding + CIR channel + Host + MSISDN + HTTP + SMS + TCP + XML + WBXML + Save + + + + Online + Busy + Away + Idle + Offline + Appear Offline + + + + Happy + Sad + Winking + Tongue sticking out + Surprised + Kissing + Yelling + Cool + Money mouth + Foot in mouth + Embarrassed + Angel + Undecided + Crying + Lips are sealed + Laughing + Confused + + + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + + + Happy + Sad + Winking + Tongue sticking out + Surprised + Kissing + Yelling + Cool + Money mouth + Foot in mouth + Embarrassed + Angel + Undecided + Crying + Lips are sealed + Laughing + Confused + + + + :-) + :-( + ;-) + :-P + =-O + :-* + :O + B-) + :-$ + :-! + :-[ + O:-) + :-\\ + :\'( + :-X + :-D + o_O + + Existing Account + Connect to my existing account on a specific Jabber / XMPP server. + + Google Account + Chat with other Google users using your existing Google account. + + WiFi Mesh Chat + Chat with others on the same local WiFi network or mesh - no Internet or server required! + Enable WiFi Chat + + New Account + Register a new, free account on a service from our built-in list, or any you choose. + Create New Account + + + Secret Identity! + Create an anonymous, disposable \"burner\" chat account in one, simple tap (requires Orbot: Tor for Android) + Generate Identity + + This is a group chat + + "[resent] " + + "[resent] " + + Unable to securely share this file + + Install Orbot? + + You must have Orbot installed and activated to proxy traffic through it. Would you like to install it? + + Always + + Start Orbot? + + Orbot doesn\'t appear to be running. Would you like to start it up and connect to Tor? + + nickname to use in room + name of room to create or join" + groupchat server (conference.foo.com) + Your keystore is corrupted. Please re-install ChatSecure or \'clear data\' for the app + You received an unreadable encrypted message + I could not decrypt the message you sent + + < swipe left and right for more options > + + Always Require + Automatically Attempt + As Requested + Disabled + + diff --git a/res/values/styles.xml b/res/values/styles.xml index f583e440e..aee5cb1fd 100755 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -17,18 +17,10 @@ */ --> + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/res/values/styles_chatsecure.xml b/res/values/styles_chatsecure.xml deleted file mode 100644 index 5de2eec03..000000000 --- a/res/values/styles_chatsecure.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/values/themes.xml b/res/values/themes.xml deleted file mode 100755 index d44334cbb..000000000 --- a/res/values/themes.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - diff --git a/res/xml/account_settings.xml b/res/xml/account_settings.xml index 82fa82511..af5ec99e8 100755 --- a/res/xml/account_settings.xml +++ b/res/xml/account_settings.xml @@ -29,6 +29,7 @@ android:summary="@string/xmpp_resource_prio_summary" android:defaultValue="20" android:numeric="integer" + android:inputType="numberSigned" android:title="@string/xmpp_resource_prio_title" /> - - + + + - - + --> @@ -60,21 +66,45 @@ - - - - + + + + + + + - + + + + + @@ -85,12 +115,11 @@ android:summary="@string/start_on_boot_summary" android:defaultValue="true" /> - - + + + + + --> + + + + + + diff --git a/robo-tests/.classpath b/robo-tests/.classpath index bfcd35f37..ef8104879 100644 --- a/robo-tests/.classpath +++ b/robo-tests/.classpath @@ -2,12 +2,12 @@ - + - + diff --git a/robo-tests/.project b/robo-tests/.project index aff6c1762..2a3ada756 100644 --- a/robo-tests/.project +++ b/robo-tests/.project @@ -1,6 +1,6 @@ - GibberbotJVMTests + ChatSecureJVMTests diff --git a/robo-tests/README.md b/robo-tests/README.md index 0583e278b..2755823ab 100644 --- a/robo-tests/README.md +++ b/robo-tests/README.md @@ -1,6 +1,6 @@ === Testing === -This sub-project contains JUnit-4 tests for Gibberbot. +This sub-project contains JUnit-4 tests for ChatSecure. Use `cd .. ; mvn test` to run the tests from the command line. diff --git a/src/com/google/zxing/integration/android/IntentIntegrator.java b/src/com/google/zxing/integration/android/IntentIntegrator.java index 1cd38e59a..d16518253 100644 --- a/src/com/google/zxing/integration/android/IntentIntegrator.java +++ b/src/com/google/zxing/integration/android/IntentIntegrator.java @@ -1,275 +1,430 @@ /* * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.google.zxing.integration.android; -import android.app.AlertDialog; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import android.app.Activity; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +import info.guardianproject.otr.app.im.R; /** - *

A utility class which helps ease integration with Barcode Scanner via - * {@link Intent}s. This is a simple way to invoke barcode scanning and receive - * the result, without any need to integrate, modify, or learn the project's - * source code.

- * + *

A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple + * way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the + * project's source code.

+ * *

Initiating a barcode scan

- * - *

Integration is essentially as easy as calling - * {@link #initiateScan(Activity)} and waiting for the result in your app.

- * - *

It does require that the Barcode Scanner application is installed. The - * {@link #initiateScan(Activity)} method will prompt the user to download the - * application, if needed.

- * - *

There are a few steps to using this integration. First, your - * {@link Activity} must implement the method - * {@link Activity#onActivityResult(int, int, Intent)} and include a line of - * code like this:

- * - *

{@code public void onActivityResult(int requestCode, int resultCode, - * Intent intent) IntentResult scanResult = - * IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); if - * (scanResult != null) // handle scan result } // else continue with any other - * code you need in the method ... } }

- * - *

This is where you will handle a scan result. Second, just call this in - * response to a user action somewhere to begin the scan process:

- * - *

{@code IntentIntegrator.initiateScan(yourActivity);}

- * - *

You can use - * {@link #initiateScan(Activity, CharSequence, CharSequence, CharSequence, CharSequence)} - * or {@link #initiateScan(Activity, int, int, int, int)} to customize the - * download prompt with different text labels.

- * - *

Note that {@link #initiateScan(Activity)} returns an {@link AlertDialog} - * which is non-null if the user was prompted to download the application. This - * lets the calling app potentially manage the dialog. In particular, ideally, - * the app dismisses the dialog if it's still active in its - * {@link Activity#onPause()} method.

- * + * + *

To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait + * for the result in your app.

+ * + *

It does require that the Barcode Scanner (or work-alike) application is installed. The + * {@link #initiateScan()} method will prompt the user to download the application, if needed.

+ * + *

There are a few steps to using this integration. First, your {@link Activity} must implement + * the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:

+ * + *
{@code
+ * public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ *   IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ *   if (scanResult != null) {
+ *     // handle scan result
+ *   }
+ *   // else continue with any other code you need in the method
+ *   ...
+ * }
+ * }
+ * + *

This is where you will handle a scan result.

+ * + *

Second, just call this in response to a user action somewhere to begin the scan process:

+ * + *
{@code
+ * IntentIntegrator integrator = new IntentIntegrator(yourActivity);
+ * integrator.initiateScan();
+ * }
+ * + *

Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the + * user was prompted to download the application. This lets the calling app potentially manage the dialog. + * In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()} + * method.

+ * + *

You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use + * {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and + * yes/no button labels can be changed.

+ * + *

Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used + * to invoke the scanner. This can be used to set additional options not directly exposed by this + * simplified API.

+ * + *

By default, this will only allow applications that are known to respond to this intent correctly + * do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}. + * For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.

+ * *

Sharing text via barcode

- * - *

To share text, encoded as a QR Code on-screen, similarly, see - * {@link #shareText(Activity, CharSequence)}.

- * - *

Some code, particularly download integration, was contributed from the - * Anobiit application.

- * + * + *

To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.

+ * + *

Some code, particularly download integration, was contributed from the Anobiit application.

+ * + *

Enabling experimental barcode formats

+ * + *

Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as + * PDF417. Use {@link #initiateScan(java.util.Collection)} with + * a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such + * formats.

+ * * @author Sean Owen * @author Fred Lin * @author Isaac Potoczny-Jones * @author Brad Drehmer + * @author gcstang */ -public final class IntentIntegrator { +public class IntentIntegrator { - public static final int REQUEST_CODE = 0x0ba7c0de; // get it? + public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits + private static final String TAG = IntentIntegrator.class.getSimpleName(); - public static final String DEFAULT_TITLE = "Install Barcode Scanner?"; - public static final String DEFAULT_MESSAGE = "This application requires Barcode Scanner. Would you like to install it?"; - public static final String DEFAULT_YES = "Yes"; - public static final String DEFAULT_NO = "No"; + private static final String BS_PACKAGE = "com.google.zxing.client.android"; + private static final String BSPLUS_PACKAGE = "com.srowen.bs.android"; - // supported barcode formats - public static final String PRODUCT_CODE_TYPES = "UPC_A,UPC_E,EAN_8,EAN_13"; - public static final String ONE_D_CODE_TYPES = PRODUCT_CODE_TYPES + ",CODE_39,CODE_93,CODE_128"; - public static final String QR_CODE_TYPES = "QR_CODE"; - public static final String ALL_CODE_TYPES = null; + // supported barcode formats + public static final Collection PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14"); + public static final Collection ONE_D_CODE_TYPES = + list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128", + "ITF", "RSS_14", "RSS_EXPANDED"); + public static final Collection QR_CODE_TYPES = Collections.singleton("QR_CODE"); + public static final Collection DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX"); - private IntentIntegrator() { - } + public static final Collection ALL_CODE_TYPES = null; + + public static final List TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE); + public static final List TARGET_ALL_KNOWN = list( + BSPLUS_PACKAGE, // Barcode Scanner+ + BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple + BS_PACKAGE // Barcode Scanner + // What else supports this intent? + ); + + private final Activity activity; + private String title; + private String message; + private String buttonYes; + private String buttonNo; + private List targetApplications; + private final Map moreExtras; + + public IntentIntegrator(Activity activity) { + this.activity = activity; + title = activity.getString(R.string.install_barcode_scanner_title); + message = activity.getString(R.string.install_barcode_scanner_message); + buttonYes = activity.getString(R.string.yes); + buttonNo = activity.getString(R.string.no); + targetApplications = TARGET_ALL_KNOWN; + moreExtras = new HashMap(3); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setTitleByID(int titleID) { + title = activity.getString(titleID); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setMessageByID(int messageID) { + message = activity.getString(messageID); + } + + public String getButtonYes() { + return buttonYes; + } + + public void setButtonYes(String buttonYes) { + this.buttonYes = buttonYes; + } + + public void setButtonYesByID(int buttonYesID) { + buttonYes = activity.getString(buttonYesID); + } + + public String getButtonNo() { + return buttonNo; + } + + public void setButtonNo(String buttonNo) { + this.buttonNo = buttonNo; + } - /** - * See - * {@link #initiateScan(Activity, CharSequence, CharSequence, CharSequence, CharSequence)} - * -- same, but uses default English labels. - */ - public static AlertDialog initiateScan(Activity activity) { - return initiateScan(activity, DEFAULT_TITLE, DEFAULT_MESSAGE, DEFAULT_YES, DEFAULT_NO); + public void setButtonNoByID(int buttonNoID) { + buttonNo = activity.getString(buttonNoID); + } + + public Collection getTargetApplications() { + return targetApplications; + } + + public final void setTargetApplications(List targetApplications) { + if (targetApplications.isEmpty()) { + throw new IllegalArgumentException("No target applications"); } + this.targetApplications = targetApplications; + } + + public void setSingleTargetApplication(String targetApplication) { + this.targetApplications = Collections.singletonList(targetApplication); + } + + public Map getMoreExtras() { + return moreExtras; + } + + public final void addExtra(String key, Object value) { + moreExtras.put(key, value); + } - /** - * See - * {@link #initiateScan(Activity, CharSequence, CharSequence, CharSequence, CharSequence)} - * -- same, but takes string IDs which refer to the {@link Activity}'s - * resource bundle entries. - */ - public static AlertDialog initiateScan(Activity activity, int stringTitle, int stringMessage, - int stringButtonYes, int stringButtonNo) { - return initiateScan(activity, activity.getString(stringTitle), - activity.getString(stringMessage), activity.getString(stringButtonYes), - activity.getString(stringButtonNo)); + /** + * Initiates a scan for all known barcode types. + */ + public final AlertDialog initiateScan() { + return initiateScan(ALL_CODE_TYPES); + } + + /** + * Initiates a scan only for a certain set of barcode types, given as strings corresponding + * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants + * like {@link #PRODUCT_CODE_TYPES} for example. + * + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + */ + public final AlertDialog initiateScan(Collection desiredBarcodeFormats) { + Intent intentScan = new Intent(BS_PACKAGE + ".SCAN"); + intentScan.addCategory(Intent.CATEGORY_DEFAULT); + + // check which types of codes to scan for + if (desiredBarcodeFormats != null) { + // set the desired barcode types + StringBuilder joinedByComma = new StringBuilder(); + for (String format : desiredBarcodeFormats) { + if (joinedByComma.length() > 0) { + joinedByComma.append(','); + } + joinedByComma.append(format); + } + intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString()); } - /** - * See - * {@link #initiateScan(Activity, CharSequence, CharSequence, CharSequence, CharSequence, CharSequence)} - * -- same, but scans for all supported barcode types. - * - * @param stringTitle title of dialog prompting user to download Barcode - * Scanner - * @param stringMessage text of dialog prompting user to download Barcode - * Scanner - * @param stringButtonYes text of button user clicks when agreeing to - * download Barcode Scanner (e.g. "Yes") - * @param stringButtonNo text of button user clicks when declining to - * download Barcode Scanner (e.g. "No") - * @return an {@link AlertDialog} if the user was prompted to download the - * app, null otherwise - */ - public static AlertDialog initiateScan(Activity activity, CharSequence stringTitle, - CharSequence stringMessage, CharSequence stringButtonYes, CharSequence stringButtonNo) { - - return initiateScan(activity, stringTitle, stringMessage, stringButtonYes, stringButtonNo, - ALL_CODE_TYPES); + String targetAppPackage = findTargetAppPackage(intentScan); + if (targetAppPackage == null) { + return showDownloadDialog(); } + intentScan.setPackage(targetAppPackage); + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intentScan); + startActivityForResult(intentScan, REQUEST_CODE); + return null; + } - /** - * Invokes scanning. - * - * @param stringTitle title of dialog prompting user to download Barcode - * Scanner - * @param stringMessage text of dialog prompting user to download Barcode - * Scanner - * @param stringButtonYes text of button user clicks when agreeing to - * download Barcode Scanner (e.g. "Yes") - * @param stringButtonNo text of button user clicks when declining to - * download Barcode Scanner (e.g. "No") - * @param stringDesiredBarcodeFormats a comma separated list of codes you - * would like to scan for. - * @return an {@link AlertDialog} if the user was prompted to download the - * app, null otherwise - * @throws InterruptedException if timeout expires before a scan completes - */ - public static AlertDialog initiateScan(Activity activity, CharSequence stringTitle, - CharSequence stringMessage, CharSequence stringButtonYes, CharSequence stringButtonNo, - CharSequence stringDesiredBarcodeFormats) { - Intent intentScan = new Intent("com.google.zxing.client.android.SCAN"); - intentScan.addCategory(Intent.CATEGORY_DEFAULT); - - // check which types of codes to scan for - if (stringDesiredBarcodeFormats != null) { - // set the desired barcode types - intentScan.putExtra("SCAN_FORMATS", stringDesiredBarcodeFormats); - } + /** + * Start an activity. This method is defined to allow different methods of activity starting for + * newer versions of Android and for compatibility library. + * + * @param intent Intent to start. + * @param code Request code for the activity + * @see android.app.Activity#startActivityForResult(Intent, int) + * @see android.app.Fragment#startActivityForResult(Intent, int) + */ + protected void startActivityForResult(Intent intent, int code) { + activity.startActivityForResult(intent, code); + } - try { - activity.startActivityForResult(intentScan, REQUEST_CODE); - return null; - } catch (ActivityNotFoundException e) { - return showDownloadDialog(activity, stringTitle, stringMessage, stringButtonYes, - stringButtonNo); + private String findTargetAppPackage(Intent intent) { + PackageManager pm = activity.getPackageManager(); + List availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (availableApps != null) { + for (String targetApp : targetApplications) { + if (contains(availableApps, targetApp)) { + return targetApp; } + } } + return null; + } - private static AlertDialog showDownloadDialog(final Activity activity, - CharSequence stringTitle, CharSequence stringMessage, CharSequence stringButtonYes, - CharSequence stringButtonNo) { - AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); - downloadDialog.setTitle(stringTitle); - downloadDialog.setMessage(stringMessage); - downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialogInterface, int i) { - Uri uri = Uri.parse("market://search?q=pname:com.google.zxing.client.android"); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - activity.startActivity(intent); - } - }); - downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialogInterface, int i) { - } - }); - return downloadDialog.show(); + private static boolean contains(Iterable availableApps, String targetApp) { + for (ResolveInfo availableApp : availableApps) { + String packageName = availableApp.activityInfo.packageName; + if (targetApp.equals(packageName)) { + return true; + } } + return false; + } - /** - *

Call this from your {@link Activity}'s - * {@link Activity#onActivityResult(int, int, Intent)} method.

- * - * @return null if the event handled here was not related to - * {@link IntentIntegrator}, or else an {@link IntentResult} - * containing the result of the scan. If the user cancelled - * scanning, the fields will be null. - */ - public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) { - if (requestCode == REQUEST_CODE) { - if (resultCode == Activity.RESULT_OK) { - String contents = intent.getStringExtra("SCAN_RESULT"); - String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT"); - return new IntentResult(contents, formatName); - } else { - return new IntentResult(null, null); - } + private AlertDialog showDownloadDialog() { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); + downloadDialog.setTitle(title); + downloadDialog.setMessage(message); + downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String packageName; + if (targetApplications.contains(BS_PACKAGE)) { + // Prefer to suggest download of BS if it's anywhere in the list + packageName = BS_PACKAGE; + } else { + // Otherwise, first option: + packageName = targetApplications.get(0); } - return null; - } + Uri uri = Uri.parse("market://details?id=" + packageName); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + try { + activity.startActivity(intent); + } catch (ActivityNotFoundException anfe) { + // Hmm, market is not installed + Log.w(TAG, "Google Play is not installed; cannot install " + packageName); + } + } + }); + downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) {} + }); + return downloadDialog.show(); + } - /** - * See - * {@link #shareText(Activity, CharSequence, CharSequence, CharSequence, CharSequence, CharSequence)} - * -- same, but uses default English labels. - */ - public static void shareText(Activity activity, CharSequence text) { - shareText(activity, text, DEFAULT_TITLE, DEFAULT_MESSAGE, DEFAULT_YES, DEFAULT_NO); + + /** + *

Call this from your {@link Activity}'s + * {@link Activity#onActivityResult(int, int, Intent)} method.

+ * + * @return null if the event handled here was not related to this class, or + * else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning, + * the fields will be null. + */ + public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + String contents = intent.getStringExtra("SCAN_RESULT"); + String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT"); + byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES"); + int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE); + Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation; + String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL"); + return new IntentResult(contents, + formatName, + rawBytes, + orientation, + errorCorrectionLevel); + } + return new IntentResult(); } + return null; + } + - /** - * See - * {@link #shareText(Activity, CharSequence, CharSequence, CharSequence, CharSequence, CharSequence)} - * -- same, but takes string IDs which refer to the {@link Activity}'s - * resource bundle entries. - */ - public static void shareText(Activity activity, CharSequence text, int stringTitle, - int stringMessage, int stringButtonYes, int stringButtonNo) { - shareText(activity, text, activity.getString(stringTitle), - activity.getString(stringMessage), activity.getString(stringButtonYes), - activity.getString(stringButtonNo)); + /** + * Defaults to type "TEXT_TYPE". + * @see #shareText(CharSequence, CharSequence) + */ + public final AlertDialog shareText(CharSequence text) { + return shareText(text, "TEXT_TYPE"); + } + + /** + * Shares the given text by encoding it as a barcode, such that another user can + * scan the text off the screen of the device. + * + * @param text the text string to encode as a barcode + * @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants. + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + */ + public final AlertDialog shareText(CharSequence text, CharSequence type) { + Intent intent = new Intent(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setAction(BS_PACKAGE + ".ENCODE"); + intent.putExtra("ENCODE_TYPE", type); + intent.putExtra("ENCODE_DATA", text); + String targetAppPackage = findTargetAppPackage(intent); + if (targetAppPackage == null) { + return showDownloadDialog(); } + intent.setPackage(targetAppPackage); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intent); + activity.startActivity(intent); + return null; + } - /** - * Shares the given text by encoding it as a barcode, such that another user - * can scan the text off the screen of the device. - * - * @param text the text string to encode as a barcode - * @param stringTitle title of dialog prompting user to download Barcode - * Scanner - * @param stringMessage text of dialog prompting user to download Barcode - * Scanner - * @param stringButtonYes text of button user clicks when agreeing to - * download Barcode Scanner (e.g. "Yes") - * @param stringButtonNo text of button user clicks when declining to - * download Barcode Scanner (e.g. "No") - */ - public static void shareText(Activity activity, CharSequence text, CharSequence stringTitle, - CharSequence stringMessage, CharSequence stringButtonYes, CharSequence stringButtonNo) { - - Intent intent = new Intent(); - intent.setAction("com.google.zxing.client.android.ENCODE"); - intent.putExtra("ENCODE_TYPE", "TEXT_TYPE"); - intent.putExtra("ENCODE_DATA", text); - try { - activity.startActivity(intent); - } catch (ActivityNotFoundException e) { - showDownloadDialog(activity, stringTitle, stringMessage, stringButtonYes, - stringButtonNo); - } + private static List list(String... values) { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + private void attachMoreExtras(Intent intent) { + for (Map.Entry entry : moreExtras.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // Kind of hacky + if (value instanceof Integer) { + intent.putExtra(key, (Integer) value); + } else if (value instanceof Long) { + intent.putExtra(key, (Long) value); + } else if (value instanceof Boolean) { + intent.putExtra(key, (Boolean) value); + } else if (value instanceof Double) { + intent.putExtra(key, (Double) value); + } else if (value instanceof Float) { + intent.putExtra(key, (Float) value); + } else if (value instanceof Bundle) { + intent.putExtra(key, (Bundle) value); + } else { + intent.putExtra(key, value.toString()); + } } + } } diff --git a/src/com/google/zxing/integration/android/IntentIntegratorSupportV4.java b/src/com/google/zxing/integration/android/IntentIntegratorSupportV4.java new file mode 100644 index 000000000..0ef0155a9 --- /dev/null +++ b/src/com/google/zxing/integration/android/IntentIntegratorSupportV4.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.zxing.integration.android; + +import android.content.Intent; +import android.support.v4.app.Fragment; + +/** + * IntentIntegrator for the V4 Android compatibility package. + * + * @author Lachezar Dobrev + */ +public final class IntentIntegratorSupportV4 extends IntentIntegrator { + + private final Fragment fragment; + + /** + * @param fragment Fragment to handle activity response. + */ + public IntentIntegratorSupportV4(Fragment fragment) { + super(fragment.getActivity()); + this.fragment = fragment; + } + + @Override + protected void startActivityForResult(Intent intent, int code) { + fragment.startActivityForResult(intent, code); + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/integration/android/IntentIntegratorV30.java b/src/com/google/zxing/integration/android/IntentIntegratorV30.java new file mode 100644 index 000000000..8d3d2a028 --- /dev/null +++ b/src/com/google/zxing/integration/android/IntentIntegratorV30.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.integration.android; + +import android.app.Fragment; +import android.content.Intent; + +/** + * IntentIntegrator for Android version 3.0 and beyond. + * + * @author Lachezar Dobrev + */ +public final class IntentIntegratorV30 extends IntentIntegrator { + + private final Fragment fragment; + + /** + * @param fragment Fragment to handle activity response. + */ + public IntentIntegratorV30(Fragment fragment) { + super(fragment.getActivity()); + this.fragment = fragment; + } + + @Override + protected void startActivityForResult(Intent intent, int code) { + fragment.startActivityForResult(intent, code); + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/integration/android/IntentResult.java b/src/com/google/zxing/integration/android/IntentResult.java index 4949c38df..5becd7f1c 100644 --- a/src/com/google/zxing/integration/android/IntentResult.java +++ b/src/com/google/zxing/integration/android/IntentResult.java @@ -1,48 +1,95 @@ /* * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.google.zxing.integration.android; /** - *

Encapsulates the result of a barcode scan invoked through - * {@link IntentIntegrator}.

- * + *

Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.

+ * * @author Sean Owen */ public final class IntentResult { - private final String contents; - private final String formatName; - - IntentResult(String contents, String formatName) { - this.contents = contents; - this.formatName = formatName; - } - - /** @return raw content of barcode */ - public String getContents() { - return contents; - } - - /** - * @return name of format, like "QR_CODE", "UPC_A". See - * BarcodeFormat for more format names. - */ - public String getFormatName() { - return formatName; - } + private final String contents; + private final String formatName; + private final byte[] rawBytes; + private final Integer orientation; + private final String errorCorrectionLevel; + + IntentResult() { + this(null, null, null, null, null); + } + + IntentResult(String contents, + String formatName, + byte[] rawBytes, + Integer orientation, + String errorCorrectionLevel) { + this.contents = contents; + this.formatName = formatName; + this.rawBytes = rawBytes; + this.orientation = orientation; + this.errorCorrectionLevel = errorCorrectionLevel; + } + + /** + * @return raw content of barcode + */ + public String getContents() { + return contents; + } + + /** + * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names. + */ + public String getFormatName() { + return formatName; + } + + /** + * @return raw bytes of the barcode content, if applicable, or null otherwise + */ + public byte[] getRawBytes() { + return rawBytes; + } + + /** + * @return rotation of the image, in degrees, which resulted in a successful scan. May be null. + */ + public Integer getOrientation() { + return orientation; + } + + /** + * @return name of the error correction level used in the barcode, if applicable + */ + public String getErrorCorrectionLevel() { + return errorCorrectionLevel; + } + + @Override + public String toString() { + StringBuilder dialogText = new StringBuilder(100); + dialogText.append("Format: ").append(formatName).append('\n'); + dialogText.append("Contents: ").append(contents).append('\n'); + int rawBytesLength = rawBytes == null ? 0 : rawBytes.length; + dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n"); + dialogText.append("Orientation: ").append(orientation).append('\n'); + dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n'); + return dialogText.toString(); + } } diff --git a/src/info/guardianproject/onionkit/ui/CertDisplayActivity.java b/src/info/guardianproject/onionkit/ui/CertDisplayActivity.java new file mode 100644 index 000000000..79bd6d452 --- /dev/null +++ b/src/info/guardianproject/onionkit/ui/CertDisplayActivity.java @@ -0,0 +1,82 @@ + +package info.guardianproject.onionkit.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; +import android.os.Bundle; + +public class CertDisplayActivity extends Activity { + + private AlertDialog ad; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String issuer = getIntent().getStringExtra("issuer"); + String fingerprint = getIntent().getStringExtra("fingerprint"); + String subject = getIntent().getStringExtra("subject"); + String issuedOn = getIntent().getStringExtra("issued"); + String expiresOn = getIntent().getStringExtra("expires"); + String msg = getIntent().getStringExtra("msg"); + + StringBuilder sb = new StringBuilder(); + + if (msg != null) + sb.append(msg).append("\n\n"); + + if (subject != null) + sb.append("Certificate: ").append(subject).append("\n\n"); + + if (issuer != null) + sb.append("Issued by: ").append(issuer).append("\n\n"); + + if (fingerprint != null) + sb.append("SHA1 Fingerprint: ").append(fingerprint).append("\n\n"); + + if (issuedOn != null) + sb.append("Issued: ").append(issuedOn).append("\n\n"); + + if (expiresOn != null) + sb.append("Expires: ").append(expiresOn).append("\n\n"); + + showDialog(sb.toString()); + } + + private void showDialog(String msg) { + + ad = new AlertDialog.Builder(this).setTitle("Certificate Info").setMessage(msg).show(); + + ad.setOnDismissListener(new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface arg0) { + + CertDisplayActivity.this.finish(); + + } + + }); + + } + + @Override + protected void onPause() { + super.onPause(); + + if (ad != null) + ad.cancel(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (ad != null) + ad.cancel(); + + } + +} diff --git a/src/info/guardianproject/onionkit/ui/OrbotHelper.java b/src/info/guardianproject/onionkit/ui/OrbotHelper.java new file mode 100644 index 000000000..40dd5f3fc --- /dev/null +++ b/src/info/guardianproject/onionkit/ui/OrbotHelper.java @@ -0,0 +1,114 @@ + +package info.guardianproject.onionkit.ui; + +import info.guardianproject.otr.app.im.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; + +public class OrbotHelper { + + private final static int REQUEST_CODE_STATUS = 100; + + public final static String URI_ORBOT = "org.torproject.android"; + public final static String TOR_BIN_PATH = "/data/data/org.torproject.android/app_bin/tor"; + + public final static String ACTION_START_TOR = "org.torproject.android.START_TOR"; + public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT"; + public final static int HS_REQUEST_CODE = 9999; + + private Context mContext = null; + + public OrbotHelper(Context context) + { + mContext = context; + } + + public boolean isOrbotRunning() + { + int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH); + + return (procId != -1); + } + + public boolean isOrbotInstalled() + { + return isAppInstalled(URI_ORBOT); + } + + private boolean isAppInstalled(String uri) { + PackageManager pm = mContext.getPackageManager(); + boolean installed = false; + try { + pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); + installed = true; + } catch (PackageManager.NameNotFoundException e) { + installed = false; + } + return installed; + } + + public void promptToInstall(Activity activity) + { + String uriMarket = activity.getString(R.string.market_orbot); + // show dialog - install from market, f-droid or direct APK + showDownloadDialog(activity, activity.getString(R.string.install_orbot_), + activity.getString(R.string.you_must_have_orbot), + activity.getString(R.string.yes), activity.getString(R.string.no), uriMarket); + } + + private static AlertDialog showDownloadDialog(final Activity activity, + CharSequence stringTitle, CharSequence stringMessage, CharSequence stringButtonYes, + CharSequence stringButtonNo, final String uriString) { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); + downloadDialog.setTitle(stringTitle); + downloadDialog.setMessage(stringMessage); + downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + Uri uri = Uri.parse(uriString); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity.startActivity(intent); + } + }); + downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + } + }); + return downloadDialog.show(); + } + + public void requestOrbotStart(final Activity activity) + { + + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); + downloadDialog.setTitle(R.string.start_orbot_); + downloadDialog + .setMessage(R.string.orbot_doesn_t_appear_to_be_running_would_you_like_to_start_it_up_and_connect_to_tor_); + downloadDialog.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + Intent intent = new Intent(URI_ORBOT); + intent.setAction(ACTION_START_TOR); + activity.startActivityForResult(intent, 1); + } + }); + downloadDialog.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + } + }); + downloadDialog.show(); + + } + + public void requestHiddenServiceOnPort(Activity activity, int port) + { + Intent intent = new Intent(URI_ORBOT); + intent.setAction(ACTION_REQUEST_HS); + intent.putExtra("hs_port", port); + + activity.startActivityForResult(intent, HS_REQUEST_CODE); + } +} diff --git a/src/info/guardianproject/onionkit/ui/TorServiceUtils.java b/src/info/guardianproject/onionkit/ui/TorServiceUtils.java new file mode 100644 index 000000000..80a812344 --- /dev/null +++ b/src/info/guardianproject/onionkit/ui/TorServiceUtils.java @@ -0,0 +1,234 @@ +/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package info.guardianproject.onionkit.ui; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.URLEncoder; +import java.util.StringTokenizer; + +import android.util.Log; + +public class TorServiceUtils { + + private final static String TAG = "TorUtils"; + // various console cmds + public final static String SHELL_CMD_CHMOD = "chmod"; + public final static String SHELL_CMD_KILL = "kill -9"; + public final static String SHELL_CMD_RM = "rm"; + public final static String SHELL_CMD_PS = "ps"; + public final static String SHELL_CMD_PIDOF = "pidof"; + + public final static String CHMOD_EXE_VALUE = "700"; + + public static boolean isRootPossible() + { + + StringBuilder log = new StringBuilder(); + + try { + + // Check if Superuser.apk exists + File fileSU = new File("/system/app/Superuser.apk"); + if (fileSU.exists()) + return true; + + fileSU = new File("/system/app/superuser.apk"); + if (fileSU.exists()) + return true; + + fileSU = new File("/system/bin/su"); + if (fileSU.exists()) + { + String[] cmd = { + "su" + }; + int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true); + if (exitCode != 0) + return false; + else + return true; + } + + // Check for 'su' binary + String[] cmd = { + "which su" + }; + int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true); + + if (exitCode == 0) { + Log.d(TAG, "root exists, but not sure about permissions"); + return true; + + } + + } catch (IOException e) { + // this means that there is no root to be had (normally) so we won't + // log anything + Log.e(TAG, "Error checking for root access", e); + + } catch (Exception e) { + Log.e(TAG, "Error checking for root access", e); + // this means that there is no root to be had (normally) + } + + Log.e(TAG, "Could not acquire root permissions"); + + return false; + } + + public static int findProcessId(String command) + { + int procId = -1; + + try + { + procId = findProcessIdWithPidOf(command); + + if (procId == -1) + procId = findProcessIdWithPS(command); + } catch (Exception e) + { + try + { + procId = findProcessIdWithPS(command); + } catch (Exception e2) + { + Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2); + } + } + + return procId; + } + + // use 'pidof' command + public static int findProcessIdWithPidOf(String command) throws Exception + { + + int procId = -1; + + Runtime r = Runtime.getRuntime(); + + Process procPs = null; + + String baseName = new File(command).getName(); + // fix contributed my mikos on 2010.12.10 + procPs = r.exec(new String[] { + SHELL_CMD_PIDOF, baseName + }); + // procPs = r.exec(SHELL_CMD_PIDOF); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line = null; + + while ((line = reader.readLine()) != null) + { + + try + { + // this line should just be the process id + procId = Integer.parseInt(line.trim()); + break; + } catch (NumberFormatException e) + { + Log.e("TorServiceUtils", "unable to parse process pid: " + line, e); + } + } + + return procId; + + } + + // use 'ps' command + public static int findProcessIdWithPS(String command) throws Exception + { + + int procId = -1; + + Runtime r = Runtime.getRuntime(); + + Process procPs = null; + + procPs = r.exec(SHELL_CMD_PS); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line = null; + + while ((line = reader.readLine()) != null) + { + if (line.indexOf(' ' + command) != -1) + { + + StringTokenizer st = new StringTokenizer(line, " "); + st.nextToken(); // proc owner + + procId = Integer.parseInt(st.nextToken().trim()); + + break; + } + } + + return procId; + + } + + public static int doShellCommand(String[] cmds, StringBuilder log, boolean runAsRoot, + boolean waitFor) throws Exception + { + + Process proc = null; + int exitCode = -1; + + if (runAsRoot) + proc = Runtime.getRuntime().exec("su"); + else + proc = Runtime.getRuntime().exec("sh"); + + OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream()); + + for (int i = 0; i < cmds.length; i++) + { + // TorService.logMessage("executing shell cmd: " + cmds[i] + + // "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor); + + out.write(cmds[i]); + out.write("\n"); + } + + out.flush(); + out.write("exit\n"); + out.flush(); + + if (waitFor) + { + + final char buf[] = new char[10]; + + // Consume the "stdout" + InputStreamReader reader = new InputStreamReader(proc.getInputStream()); + int read = 0; + while ((read = reader.read(buf)) != -1) { + if (log != null) + log.append(buf, 0, read); + } + + // Consume the "stderr" + reader = new InputStreamReader(proc.getErrorStream()); + read = 0; + while ((read = reader.read(buf)) != -1) { + if (log != null) + log.append(buf, 0, read); + } + + exitCode = proc.waitFor(); + + } + + return exitCode; + + } +} diff --git a/src/info/guardianproject/otr/AndroidLogHandler.java b/src/info/guardianproject/otr/AndroidLogHandler.java new file mode 100644 index 000000000..a2c91d5b3 --- /dev/null +++ b/src/info/guardianproject/otr/AndroidLogHandler.java @@ -0,0 +1,172 @@ +package info.guardianproject.otr; + + +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import android.util.Log; + +import info.guardianproject.otr.app.im.app.ImApp; +import info.guardianproject.util.Debug; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The + * implementation is rather straightforward. The name of the logger serves as + * the log tag. Only the log levels need to be converted appropriately. For + * this purpose, the following mapping is being used: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
logger levelAndroid level
+ * SEVERE + * + * ERROR + *
+ * WARNING + * + * WARN + *
+ * INFO + * + * INFO + *
+ * CONFIG + * + * DEBUG + *
+ * FINE, FINER, FINEST + * + * VERBOSE + *
+ */ +public class AndroidLogHandler extends Handler { + /** + * Holds the formatter for all Android log handlers. + */ + private static final Formatter THE_FORMATTER = new Formatter() { + @Override + public String format(LogRecord r) { + Throwable thrown = r.getThrown(); + if (thrown != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + sw.write(r.getMessage()); + sw.write("\n"); + thrown.printStackTrace(pw); + pw.flush(); + return sw.toString(); + } else { + return r.getMessage(); + } + } + }; + + /** + * Constructs a new instance of the Android log handler. + */ + public AndroidLogHandler() { + setFormatter(THE_FORMATTER); + } + + @Override + public void close() { + // No need to close, but must implement abstract method. + } + + @Override + public void flush() { + // No need to flush, but must implement abstract method. + } + + @Override + public void publish(LogRecord record) { + int level = getAndroidLevel(record.getLevel()); + String tag = ImApp.LOG_TAG; + try { + String message = getFormatter().format(record); + Log.println(level, tag, message); + } catch (RuntimeException e) { + Log.e("AndroidHandler", "Error logging message.", e); + } + } + + public void publish(Logger source, String tag, Level level, String message) { + // TODO: avoid ducking into native 2x; we aren't saving any formatter calls + int priority = getAndroidLevel(level); + + if (!Debug.DEBUG_ENABLED) { + return; + } + + try { + Log.println(priority, tag, message); + } catch (RuntimeException e) { + Log.e("AndroidHandler", "Error logging message.", e); + } + } + + /** + * Converts a {@link java.util.logging.Logger} logging level into an Android one. + * + * @param level The {@link java.util.logging.Logger} logging level. + * + * @return The resulting Android logging level. + */ + static int getAndroidLevel(Level level) { + int value = level.intValue(); + if (value >= 1000) { // SEVERE + return Log.ERROR; + } else if (value >= 900) { // WARNING + return Log.WARN; + } else if (value >= 800) { // INFO + return Log.INFO; + } else { + return Log.DEBUG; + } + } +} diff --git a/src/info/guardianproject/otr/IOtrChatSession.aidl b/src/info/guardianproject/otr/IOtrChatSession.aidl index 94bc31447..35ae2e41c 100644 --- a/src/info/guardianproject/otr/IOtrChatSession.aidl +++ b/src/info/guardianproject/otr/IOtrChatSession.aidl @@ -32,5 +32,46 @@ interface IOtrChatSession { * respond to the SMP verification process */ void respondSmpVerification(String answer); + + + /** + * Verify the key for a given address. + */ + void verifyKey(String address); + + /** + * Revoke the verification for the key for a given address. + */ + void unverifyKey(String address); + + /** + * Tells if the fingerprint of the remote user address has been verified. + */ + boolean isKeyVerified(String address); + + /** + * Returns the fingerprint for the local user's key for a given account address. + */ + String getLocalFingerprint(); + + /** + * Returns the fingerprint for a remote user's key for a given account address. + */ + String getRemoteFingerprint(); + + /** + * generate a new local private/public key pair. + */ + void generateLocalKeyPair(); + /** + * Returns the user id (jabberid) for the local user + */ + String getLocalUserId(); + + /** + * Returns the user id (jabberid) for the remote user + */ + String getRemoteUserId(); + } diff --git a/src/info/guardianproject/otr/IOtrKeyManager.aidl b/src/info/guardianproject/otr/IOtrKeyManager.aidl index 9510e5a0b..13f8ebd11 100644 --- a/src/info/guardianproject/otr/IOtrKeyManager.aidl +++ b/src/info/guardianproject/otr/IOtrKeyManager.aidl @@ -5,36 +5,46 @@ package info.guardianproject.otr; interface IOtrKeyManager { - + /** * Verify the key for a given address. */ - void verifyKey(String address); + void verifyUser(String address); /** * Revoke the verification for the key for a given address. */ - void unverifyKey(String address); + void unverifyUser(String address); /** * Tells if the fingerprint of the remote user address has been verified. */ - boolean isKeyVerified(String address); + boolean isVerifiedUser(String address); /** - * Returns the fingerprint for the local user's key for a given account address. + * Returns the fingerprint for the local user's key for a given JID */ - String getLocalFingerprint(); + String getLocalFingerprint(String address); /** - * Returns the fingerprint for a remote user's key for a given account address. + * Returns the fingerprint for a remote user's key for a given JID */ - String getRemoteFingerprint(); + String getRemoteFingerprint(String address); + + + /** + * Returns the fingerprints for a remote user's keys for a given user@domain + */ + String[] getRemoteFingerprints(String addressNoResource); /** * generate a new local private/public key pair. */ - void generateLocalKeyPair(); - - + void generateLocalKeyPair(String address); + + /** + * import otr key store + */ + boolean importOtrKeyStoreWithPassword (String filePath, String password); + } diff --git a/src/info/guardianproject/otr/OpenSSLPBEInputStream.java b/src/info/guardianproject/otr/OpenSSLPBEInputStream.java index 1e0dd2c4c..191ad45ca 100644 --- a/src/info/guardianproject/otr/OpenSSLPBEInputStream.java +++ b/src/info/guardianproject/otr/OpenSSLPBEInputStream.java @@ -62,6 +62,11 @@ public int read() throws IOException { } maxIndex = bufferClear.length - 1; } + + if (bufferClear == null || bufferClear.length == 0) { + return -1; + } + return bufferClear[index++] & 0xff; } diff --git a/src/info/guardianproject/otr/OtrAndroidKeyManagerImpl.java b/src/info/guardianproject/otr/OtrAndroidKeyManagerImpl.java index 32d828705..c6a5a12ff 100644 --- a/src/info/guardianproject/otr/OtrAndroidKeyManagerImpl.java +++ b/src/info/guardianproject/otr/OtrAndroidKeyManagerImpl.java @@ -1,6 +1,5 @@ package info.guardianproject.otr; -import info.guardianproject.bouncycastle.util.encoders.Hex; import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.otr.app.im.engine.Address; @@ -27,6 +26,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; @@ -39,9 +39,6 @@ import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.SessionID; - -import org.jivesoftware.smack.util.Base64; - import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -52,16 +49,16 @@ import android.net.Uri; import android.os.Environment; import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Base64; import android.util.Log; import android.widget.Toast; -import com.google.common.collect.Sets; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -public class OtrAndroidKeyManagerImpl implements OtrKeyManager { - - private static final boolean REGENERATE_LOCAL_PUBLIC_KEY = false; +public class OtrAndroidKeyManagerImpl extends IOtrKeyManager.Stub implements OtrKeyManager { + public static final String TAG = "OtrAndroidKeyManagerImpl"; private SimplePropertiesStore store; @@ -69,45 +66,79 @@ public class OtrAndroidKeyManagerImpl implements OtrKeyManager { private final static String KEY_ALG = "DSA"; private final static int KEY_SIZE = 1024; - private final static Version CURRENT_VERSION = new Version("1.0.0"); + private final static Version CURRENT_VERSION = new Version("2.0.0"); private static OtrAndroidKeyManagerImpl _instance; - private static final String FILENAME = "otr_keystore.ofc"; - + private static final String FILE_KEYSTORE_ENCRYPTED = "otr_keystore.ofc"; + private static final String FILE_KEYSTORE_UNENCRYPTED = "otr_keystore"; + + private final static String STORE_ALGORITHM = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"; - + private static String mKeyStorePassword = null; - + public static void setKeyStorePassword (String keyStorePassword) { mKeyStorePassword = keyStorePassword; + + if (_instance != null) + { + _instance = null; + + } } - + public static synchronized OtrAndroidKeyManagerImpl getInstance(Context context) - throws IOException { - - if (_instance == null && mKeyStorePassword != null) { - File f = new File(context.getApplicationContext().getFilesDir(), FILENAME); - _instance = new OtrAndroidKeyManagerImpl(f,mKeyStorePassword); - } + { + + try + { + if (_instance == null && mKeyStorePassword != null) { + File fKeyStore; + + fKeyStore = new File(context.getApplicationContext().getFilesDir(), FILE_KEYSTORE_UNENCRYPTED); + if (fKeyStore.exists()) + { + _instance = new OtrAndroidKeyManagerImpl(fKeyStore,null); + } + else + { + fKeyStore = new File(context.getApplicationContext().getFilesDir(), FILE_KEYSTORE_ENCRYPTED); + _instance = new OtrAndroidKeyManagerImpl(fKeyStore,mKeyStorePassword); + } + } - return _instance; + return _instance; + } + catch (IOException ioe) + { + Toast.makeText(context, R.string.your_keystore_is_corrupted_please_re_install_chatsecure_or_clear_data_for_the_app, Toast.LENGTH_LONG).show(); + throw new RuntimeException("Could not open keystore",ioe); + } } private OtrAndroidKeyManagerImpl(File filepath, String password) throws IOException { - this.store = new SimplePropertiesStore(filepath, password, false); - upgradeStore(); + + if (password == null) + store = new SimplePropertiesStore(filepath); + else + store = new SimplePropertiesStore(filepath, password, false); cryptoEngine = new OtrCryptoEngineImpl(); + } - + + /* private void upgradeStore() { + + LogCleaner.warn(ImApp.LOG_TAG, "upgrading keystore"); + String version = store.getPropertyString("version"); if (version == null || new Version(version).compareTo(new Version("1.0.0")) < 0) { // Add verified=false entries for TOFU sync purposes - Set keys = Sets.newHashSet(store.getKeySet()); + Set keys = Sets.newHashSet(store.getKeySet()); for (Object keyObject : keys) { String key = (String)keyObject; if (key.endsWith(".fingerprint")) { @@ -120,176 +151,235 @@ private void upgradeStore() { } } } + // This will save store.setProperty("version", CURRENT_VERSION.toString()); } + + + File fileOldKeystore = new File(FILE_KEYSTORE_UNENCRYPTED); + if (fileOldKeystore.exists()) + { + LogCleaner.warn(ImApp.LOG_TAG, "upgrading unencrypted keystore"); + try { + SimplePropertiesStore storeOldKeystore = new SimplePropertiesStore(fileOldKeystore); + + Enumeration enumKeys = storeOldKeystore.getKeys(); + + while(enumKeys.hasMoreElements()) + { + String key = (String)enumKeys.nextElement(); + LogCleaner.warn(ImApp.LOG_TAG, "importing key: " + key); + store.setProperty(key, storeOldKeystore.getPropertyString(key)); + + } + + store.save(); + + fileOldKeystore.delete(); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + else + { + LogCleaner.warn(ImApp.LOG_TAG, "unencrypted keystore not found"); + + } + } + */ static class SimplePropertiesStore implements OtrKeyManagerStore { - + private Properties mProperties = new Properties(); private File mStoreFile; private String mPassword; - /* - public SimplePropertiesStore(File storeFile) { + public SimplePropertiesStore(File storeFile) throws IOException { mStoreFile = storeFile; - mProperties.clear(); - load(); - }*/ + if (storeFile.exists()) + mProperties.load(new FileInputStream(mStoreFile)); + + + } public SimplePropertiesStore(File storeFile, final String password, boolean isImportFromKeySync) throws IOException { - + OtrDebugLogger.log("Loading store from encrypted file"); mStoreFile = storeFile; - mProperties.clear(); - + if (password == null) throw new IOException ("invalid password"); - + mPassword = password; - if (isImportFromKeySync) - loadAES(password); - else - loadOpenSSL(password); + if (mStoreFile.exists()) + if (isImportFromKeySync) + loadAES(mPassword); + else + loadOpenSSL(mPassword); + } - private void loadAES(final String password) { + private void loadAES(final String password) throws IOException + { String decoded; - try { decoded = AES_256_CBC.decrypt(mStoreFile, password); mProperties.load(new ByteArrayInputStream(decoded.getBytes())); - } catch (IOException ioe) { - OtrDebugLogger.log("Properties store error", ioe); - } } - - /* - public Properties getProperties () - { - return mProperties; - }*/ public void setProperty(String id, String value) { mProperties.setProperty(id, value); + persist(); - save(); } - + public void setProperty(String id, boolean value) { mProperties.setProperty(id, Boolean.toString(value)); + persist(); - - save(); } - + + public synchronized boolean persist () + { - public boolean save () + try { + if (mPassword != null) + saveOpenSSL(mPassword, mStoreFile); + else + savePlain(mStoreFile); + + + + return true; + } catch (IOException e) { + LogCleaner.error(ImApp.LOG_TAG, "error saving keystore", e); + return false; + } + } + + public boolean export (String password, File storeFile) { try { - saveOpenSSL (mPassword); + saveOpenSSL (password, storeFile); return true; } catch (IOException e) { LogCleaner.error(ImApp.LOG_TAG, "error saving keystore", e); return false; } } - - private void saveOpenSSL (String password) throws IOException + + private void saveOpenSSL (String password, File fileStore) throws IOException { // Encrypt these bytes ByteArrayOutputStream baos = new ByteArrayOutputStream(); - OpenSSLPBEOutputStream encOS = new OpenSSLPBEOutputStream(baos, STORE_ALGORITHM, 1, password.toCharArray()); - mProperties.store(encOS, null); - encOS.flush(); - - FileOutputStream fos = new FileOutputStream(mStoreFile); + + try + { + OpenSSLPBEOutputStream encOS = new OpenSSLPBEOutputStream(baos, STORE_ALGORITHM, 1, password.toCharArray()); + mProperties.store(encOS, null); + encOS.flush(); + } + catch (IllegalArgumentException iae) + { + + //might be a unicode character in the password + OpenSSLPBEOutputStream encOS = new OpenSSLPBEOutputStream(baos, STORE_ALGORITHM, 1, Base64.encodeToString(password.getBytes(),Base64.NO_WRAP).toCharArray()); + mProperties.store(encOS, null); + encOS.flush(); + + + } + + FileOutputStream fos = new FileOutputStream(fileStore); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } - - private void loadOpenSSL(String password) { - + + private void savePlain(File fileStore) throws IOException + { + // Encrypt these bytes + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mProperties.store(baos, null); + baos.flush(); + + FileOutputStream fos = new FileOutputStream(fileStore); + fos.write(baos.toByteArray()); + fos.flush(); + fos.close(); + + } + + private void loadOpenSSL(String password) throws IOException + { + if (!mStoreFile.exists()) return; - + + if (mStoreFile.length() == 0) + return; + FileInputStream fis = null; - + OpenSSLPBEInputStream encIS = null; try { fis = new FileInputStream(mStoreFile); // Decrypt the bytes - OpenSSLPBEInputStream encIS = new OpenSSLPBEInputStream(fis, STORE_ALGORITHM, 1, password.toCharArray()); - - mProperties.load(encIS); - + encIS = new OpenSSLPBEInputStream(fis, STORE_ALGORITHM, 1, password.toCharArray()); + mProperties.load(encIS); + } + catch (IllegalArgumentException iae) + { + //might be a unicode character in the password + encIS = new OpenSSLPBEInputStream(fis, STORE_ALGORITHM, 1, (Base64.encodeToString(password.getBytes(),Base64.NO_WRAP)).toCharArray()); + mProperties.load(encIS); + } catch (FileNotFoundException fnfe) { OtrDebugLogger.log("Properties store file not found: First time?"); mStoreFile.getParentFile().mkdirs(); - - - } catch (Exception ioe) { - OtrDebugLogger.log("Properties store error", ioe); - } - finally - { - try - { - if (fis != null) - fis.close(); - } - catch (IOException ioe) - { - OtrDebugLogger.log("Properties store error", ioe); - - } + } finally { + encIS.close(); + fis.close(); } } - - public void setProperty(String id, byte[] value) { - mProperties.setProperty(id, new String(Base64.encodeBytes(value))); - save(); + public void setProperty(String id, byte[] value) { + mProperties.setProperty(id, (Base64.encodeToString(value,Base64.NO_WRAP))); + persist(); + } - // Store as hex bytes - public void setPropertyHex(String id, byte[] value) { - mProperties.setProperty(id, new String(Hex.encode(value))); - - - save(); - } public void removeProperty(String id) { mProperties.remove(id); + persist(); + } public String getPropertyString(String id) { return mProperties.getProperty(id); } - + public byte[] getPropertyBytes(String id) { String value = mProperties.getProperty(id); if (value != null) - return Base64.decode(value); + return Base64.decode(value.getBytes(),Base64.NO_WRAP); return null; } - // Load from hex bytes - public byte[] getPropertyHexBytes(String id) { - String value = mProperties.getProperty(id); - - if (value != null) - return Hex.decode(value); - return null; + public String getProperty (String id) + { + return mProperties.getProperty(id); } public boolean getPropertyBoolean(String id, boolean defaultValue) { @@ -303,12 +393,12 @@ public boolean getPropertyBoolean(String id, boolean defaultValue) { public boolean hasProperty(String id) { return mProperties.containsKey(id); } - + public Enumeration getKeys () { return mProperties.keys(); } - + public Set getKeySet () { return mProperties.keySet(); @@ -334,12 +424,13 @@ public void generateLocalKeyPair(SessionID sessionID) { if (sessionID == null) return; - String accountID = sessionID.getAccountID(); - - generateLocalKeyPair(accountID); + generateLocalKeyPair(sessionID.getLocalUserId()); } - public void regenerateLocalPublicKey(KeyFactory factory, String accountID, DSAPrivateKey privKey) { + public void regenerateLocalPublicKey(KeyFactory factory, String fullUserId, DSAPrivateKey privKey) { + + String userId = Address.stripResource(fullUserId); + BigInteger x = privKey.getX(); DSAParams params = privKey.getParams(); BigInteger y = params.getG().modPow(x, params.getP()); @@ -347,15 +438,19 @@ public void regenerateLocalPublicKey(KeyFactory factory, String accountID, DSAPr PublicKey pubKey; try { pubKey = factory.generatePublic(keySpec); - } catch (InvalidKeySpecException e) { + storeLocalPublicKey(userId, pubKey); + + } catch (Exception e) { throw new RuntimeException(e); } - storeLocalPublicKey(accountID, pubKey); + } - - public void generateLocalKeyPair(String accountID) { - OtrDebugLogger.log("generating local key pair for: " + accountID); + public void generateLocalKeyPair(String fullUserId) { + + String userId = Address.stripResource(fullUserId); + + OtrDebugLogger.log("generating local key pair for: " + userId); KeyPair keyPair; try { @@ -369,69 +464,95 @@ public void generateLocalKeyPair(String accountID) { return; } - OtrDebugLogger.log("SUCCESS! generating local key pair for: " + accountID); + OtrDebugLogger.log("SUCCESS! generating local key pair for: " + userId); // Store Private Key. PrivateKey privKey = keyPair.getPrivate(); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privKey.getEncoded()); - this.store.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec.getEncoded()); + this.store.setProperty(userId + ".privateKey", pkcs8EncodedKeySpec.getEncoded()); - // Store Public Key. - PublicKey pubKey = keyPair.getPublic(); - storeLocalPublicKey(accountID, pubKey); + try + { + // Store Public Key. + PublicKey pubKey = keyPair.getPublic(); + storeLocalPublicKey(userId, pubKey); //this will do saving + + } + catch (Exception e) + { + throw new RuntimeException ("Error init local keypair"); + } } - private void storeLocalPublicKey(String accountID, PublicKey pubKey) { - X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded()); + private void storeLocalPublicKey(String fullUserId, PublicKey pubKey) throws OtrCryptoException { - this.store.setProperty(accountID + ".publicKey", x509EncodedKeySpec.getEncoded()); + String userId = Address.stripResource(fullUserId); + + String fingerprintString = cryptoEngine.getFingerprint(pubKey); + String fingerprintKey = userId + ".fingerprint"; + + //check if we already have this + if ((!store.hasProperty(fingerprintKey)) || + (!store.getProperty(fingerprintKey).equals(fingerprintString))) + { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded()); + this.store.setProperty(userId + ".publicKey", x509EncodedKeySpec.getEncoded()); + this.store.setProperty(fingerprintKey, fingerprintString); + - // Stash fingerprint for consistency. - try { - String fingerprintString = new OtrCryptoEngineImpl().getFingerprint(pubKey); - this.store.setPropertyHex(accountID + ".fingerprint", Hex.decode(fingerprintString)); - } catch (OtrCryptoException e) { - e.printStackTrace(); } + + Log.i(ImApp.LOG_TAG, "New public key generated: " + fingerprintString); + } - - public void importKeyStore(File filePath, String password, boolean overWriteExisting, boolean deleteImportedFile) throws IOException + + public boolean importKeyStore(String filePath, String password, boolean overWriteExisting, boolean deleteImportedFile) throws IOException { SimplePropertiesStore storeNew = null; - if (filePath.getName().endsWith(".ofcaes")) { + File fileOtrKeystore = new File(filePath); + + if (fileOtrKeystore.getName().endsWith(".ofcaes")) { //TODO implement GUI to get password via QR Code, and handle wrong password - storeNew = new SimplePropertiesStore(filePath, password, true); + storeNew = new SimplePropertiesStore(fileOtrKeystore, password, true); deleteImportedFile = true; // once its imported, its no longer needed - } - - + } + else + { + return false; + } + Enumeration enumKeys = storeNew.getKeys(); - - + + String key; - + while (enumKeys.hasMoreElements()) { key = (String)enumKeys.nextElement(); - + boolean hasKey = store.hasProperty(key); - + if (!hasKey || overWriteExisting) store.setProperty(key, storeNew.getPropertyString(key)); - + } if (deleteImportedFile) - filePath.delete(); + fileOtrKeystore.delete(); + + return true; } public String getLocalFingerprint(SessionID sessionID) { - return getLocalFingerprint(sessionID.getAccountID()); + return getLocalFingerprint(sessionID.getLocalUserId()); } - public String getLocalFingerprint(String userId) { + public String getLocalFingerprint(String fullUserId) { + + String userId = Address.stripResource(fullUserId); + KeyPair keyPair = loadLocalKeyPair(userId); if (keyPair == null) @@ -441,9 +562,7 @@ public String getLocalFingerprint(String userId) { try { String fingerprint = cryptoEngine.getFingerprint(pubKey); - - OtrDebugLogger.log("got fingerprint for: " + userId + "=" + fingerprint); - + // OtrDebugLogger.log("got fingerprint for: " + userId + "=" + fingerprint); return fingerprint; } catch (OtrCryptoException e) { @@ -453,49 +572,75 @@ public String getLocalFingerprint(String userId) { } public String getRemoteFingerprint(SessionID sessionID) { - return getRemoteFingerprint(sessionID.getFullUserID()); + return getRemoteFingerprint(sessionID.getRemoteUserId()); } - public String getRemoteFingerprint(String userId) { - if (!Address.hasResource(userId)) - return null; - byte[] fingerprint = this.store.getPropertyHexBytes(userId + ".fingerprint"); + public String getRemoteFingerprint(String fullUserId) { + + if (!Address.hasResource(fullUserId)) + return null; + + String fingerprint = this.store.getProperty(fullUserId + ".fingerprint"); if (fingerprint != null) { // If we have a fingerprint stashed, assume it is correct. - return new String(Hex.encode(fingerprint, 0, fingerprint.length)); + return fingerprint; } - PublicKey remotePublicKey = loadRemotePublicKeyFromStore(userId); + + PublicKey remotePublicKey = loadRemotePublicKeyFromStore(fullUserId); if (remotePublicKey == null) return null; + try { // Store the fingerprint, for posterity. String fingerprintString = new OtrCryptoEngineImpl().getFingerprint(remotePublicKey); - this.store.setPropertyHex(userId + ".fingerprint", Hex.decode(fingerprintString)); + this.store.setProperty(fullUserId + ".fingerprint", fingerprintString); + return fingerprintString; } catch (OtrCryptoException e) { - OtrDebugLogger.log("OtrCryptoException getting remote fingerprint",e); - return null; + throw new RuntimeException("OtrCryptoException getting remote fingerprint",e); + + } + } + + public String[] getRemoteFingerprints(String userId) { + + Enumeration keys = store.getKeys(); + + ArrayList results = new ArrayList(); + + String baseUserId = Address.stripResource(userId); + + while (keys.hasMoreElements()) + { + String key = (String)keys.nextElement(); + + if (key.startsWith(baseUserId + '/') && key.endsWith(".fingerprint")) + { + + String fingerprint = this.store.getProperty(userId + ".fingerprint"); + if (fingerprint != null) { + // If we have a fingerprint stashed, assume it is correct. + results.add(fingerprint); + } + + } + } + + String[] resultsString = new String[results.size()]; + return results.toArray(resultsString); } public boolean isVerified(SessionID sessionID) { if (sessionID == null) return false; - - String userId = sessionID.getUserID(); - String fullUserID = sessionID.getFullUserID(); - - if (Address.hasResource(userId)) - return false; - if (!Address.hasResource(fullUserID)) - return false; - - String remoteFingerprint =getRemoteFingerprint(fullUserID); - + String remoteFingerprint =getRemoteFingerprint(sessionID.getRemoteUserId()); + if (remoteFingerprint != null) { - String pubKeyVerifiedToken = buildPublicKeyVerifiedId(userId, remoteFingerprint); + String username = Address.stripResource(sessionID.getRemoteUserId()); + String pubKeyVerifiedToken = buildPublicKeyVerifiedId(username, remoteFingerprint); return this.store.getPropertyBoolean(pubKeyVerifiedToken, false); } else @@ -505,32 +650,38 @@ public boolean isVerified(SessionID sessionID) { } public boolean isVerifiedUser(String fullUserId) { - if (fullUserId == null) - return false; String userId = Address.stripResource(fullUserId); - String pubKeyVerifiedToken = buildPublicKeyVerifiedId(userId, getRemoteFingerprint(fullUserId)); + String remoteFingerprint = getRemoteFingerprint(fullUserId); + + if (remoteFingerprint != null) + { + String pubKeyVerifiedToken = buildPublicKeyVerifiedId(userId, remoteFingerprint); - return this.store.getPropertyBoolean(pubKeyVerifiedToken, false); + return this.store.getPropertyBoolean(pubKeyVerifiedToken, false); + } + else + return false; } public KeyPair loadLocalKeyPair(SessionID sessionID) { if (sessionID == null) return null; - String accountID = sessionID.getAccountID(); - return loadLocalKeyPair(accountID); + return loadLocalKeyPair(sessionID.getLocalUserId()); } - private KeyPair loadLocalKeyPair(String accountID) { + private KeyPair loadLocalKeyPair(String fullUserId) { PublicKey publicKey; PrivateKey privateKey; + String userId = Address.stripResource(fullUserId); + try { // Load Private Key. - byte[] b64PrivKey = this.store.getPropertyBytes(accountID + ".privateKey"); + byte[] b64PrivKey = this.store.getPropertyBytes(userId + ".privateKey"); if (b64PrivKey == null) return null; @@ -541,23 +692,15 @@ private KeyPair loadLocalKeyPair(String accountID) { keyFactory = KeyFactory.getInstance(KEY_ALG); privateKey = keyFactory.generatePrivate(privateKeySpec); - if (REGENERATE_LOCAL_PUBLIC_KEY) { - regenerateLocalPublicKey(keyFactory, accountID, (DSAPrivateKey)privateKey); - } - // Load Public Key. - byte[] b64PubKey = this.store.getPropertyBytes(accountID + ".publicKey"); + byte[] b64PubKey = this.store.getPropertyBytes(userId + ".publicKey"); if (b64PubKey == null) return null; X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey); publicKey = keyFactory.generatePublic(publicKeySpec); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - return null; - } catch (InvalidKeySpecException e) { - e.printStackTrace(); - return null; + } catch (Exception e) { + throw new RuntimeException(e); } return new KeyPair(publicKey, privateKey); @@ -565,14 +708,15 @@ private KeyPair loadLocalKeyPair(String accountID) { public PublicKey loadRemotePublicKey(SessionID sessionID) { - return loadRemotePublicKeyFromStore(sessionID.getFullUserID()); + return loadRemotePublicKeyFromStore(sessionID.getRemoteUserId()); } - private PublicKey loadRemotePublicKeyFromStore(String userId) { - if (!Address.hasResource(userId)) - return null; - - byte[] b64PubKey = this.store.getPropertyBytes(userId + ".publicKey"); + private PublicKey loadRemotePublicKeyFromStore(String fullUserId) { + + if (!Address.hasResource(fullUserId)) + return null; + + byte[] b64PubKey = this.store.getPropertyBytes(fullUserId + ".publicKey"); if (b64PubKey == null) { return null; @@ -601,19 +745,22 @@ public void savePublicKey(SessionID sessionID, PublicKey pubKey) { X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded()); - String userId = sessionID.getFullUserID(); - if (!Address.hasResource(userId)) + if (!Address.hasResource(sessionID.getRemoteUserId())) return; - - this.store.setProperty(userId + ".publicKey", x509EncodedKeySpec.getEncoded()); + + String fullUserId = sessionID.getRemoteUserId(); + + this.store.setProperty(fullUserId + ".publicKey", x509EncodedKeySpec.getEncoded()); // Stash the associated fingerprint. This saves calculating it in the future // and is useful for transferring rosters to other apps. try { String fingerprintString = new OtrCryptoEngineImpl().getFingerprint(pubKey); - String verifiedToken = buildPublicKeyVerifiedId(userId, fingerprintString.toLowerCase()); + String verifiedToken = buildPublicKeyVerifiedId(sessionID.getRemoteUserId(), fingerprintString); if (!this.store.hasProperty(verifiedToken)) this.store.setProperty(verifiedToken, false); - this.store.setPropertyHex(userId + ".fingerprint", Hex.decode(fingerprintString)); + + this.store.setProperty(fullUserId + ".fingerprint", fingerprintString); + } catch (OtrCryptoException e) { e.printStackTrace(); } @@ -626,26 +773,20 @@ public void unverify(SessionID sessionID) { if (!isVerified(sessionID)) return; - String userId = sessionID.getUserID(); - - this.store.setProperty(buildPublicKeyVerifiedId(userId, getRemoteFingerprint(sessionID)), false); + unverifyUser(sessionID.getRemoteUserId()); for (OtrKeyManagerListener l : listeners) l.verificationStatusChanged(sessionID); } - public void unverifyUser(String userId) { - if (userId == null) - return; + public void unverifyUser(String fullUserId) { - if (!isVerifiedUser(userId)) + if (!isVerifiedUser(fullUserId)) return; - this.store.setProperty(buildPublicKeyVerifiedId(userId, getRemoteFingerprint(userId)), false); - - // for (OtrKeyManagerListener l : listeners) - // l.verificationStatusChanged(sessionID); + store.setProperty(buildPublicKeyVerifiedId(fullUserId, getRemoteFingerprint(fullUserId)), false); + } @@ -656,13 +797,8 @@ public void verify(SessionID sessionID) { if (this.isVerified(sessionID)) return; - String userId = sessionID.getUserID(); + verifyUser(sessionID.getRemoteUserId()); - this.store - .setProperty(buildPublicKeyVerifiedId(userId, getRemoteFingerprint(sessionID)), true); - - for (OtrKeyManagerListener l : listeners) - l.verificationStatusChanged(sessionID); } public void remoteVerifiedUs(SessionID sessionID) { @@ -677,7 +813,7 @@ private static String buildPublicKeyVerifiedId(String userId, String fingerprint if (fingerprint == null) return null; - return Address.stripResource(userId) + "." + fingerprint + ".publicKey.verified"; + return (Address.stripResource(userId) + "." + fingerprint + ".publicKey.verified"); } public void verifyUser(String userId) { @@ -690,52 +826,66 @@ public void verifyUser(String userId) { this.store .setProperty(buildPublicKeyVerifiedId(userId, getRemoteFingerprint(userId)), true); - //for (OtrKeyManagerListener l : listeners) - //l.verificationStatusChanged(userId); - } - public static boolean checkForKeyImport (Intent intent, Activity activity) + public void verifyUser(String userId, String fingerprint) { + if (userId == null) + return; + + if (this.isVerifiedUser(userId)) + return; + + this.store + .setProperty(buildPublicKeyVerifiedId(userId, fingerprint), true); + } + + public boolean doKeyStoreExport (String password) + { + + + // if otr_keystore.ofcaes is in the SDCard root, import it + File otrKeystoreAES = new File(Environment.getExternalStorageDirectory(), + "otr_keystore.ofcaes"); + + + return store.export(password, otrKeystoreAES); + } + public static void checkForKeyImport (Intent intent, Activity activity) { - boolean doKeyStoreImport = false; - // if otr_keystore.ofcaes is in the SDCard root, import it File otrKeystoreAES = new File(Environment.getExternalStorageDirectory(), "otr_keystore.ofcaes"); if (otrKeystoreAES.exists()) { //Log.i(TAG, "found " + otrKeystoreAES + "to import"); - doKeyStoreImport = true; importOtrKeyStore(otrKeystoreAES, activity); + return; } - else if (intent.getData() != null) + else if (intent != null && intent.getData() != null) { Uri uriData = intent.getData(); String path = null; - + if(uriData.getScheme() != null && uriData.getScheme().equals("file")) { path = uriData.toString().replace("file://", ""); - + File file = new File(path); - - doKeyStoreImport = true; - importOtrKeyStore(file, activity); + return; } } - - return doKeyStoreImport; + Toast.makeText(activity, R.string.otr_keysync_warning_message, Toast.LENGTH_LONG).show(); } - - + + public static void importOtrKeyStore (final File fileOtrKeyStore, final Activity activity) { - + try { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); - prefs.edit().putString("keystoreimport", fileOtrKeyStore.getCanonicalPath()).commit(); + prefs.edit().putString("keystoreimport", fileOtrKeyStore.getCanonicalPath()).apply(); } catch (IOException ioe) { @@ -748,77 +898,64 @@ public static void importOtrKeyStore (final File fileOtrKeyStore, final Activity @Override public void onClick(DialogInterface dialog, int which) { - + //launch QR code intent - IntentIntegrator.initiateScan(activity); - + new IntentIntegrator(activity).initiateScan(); + } }; - + new AlertDialog.Builder(activity).setTitle(R.string.confirm) .setMessage(R.string.detected_Otr_keystore_import) .setPositiveButton(R.string.yes, ocl) // default button .setNegativeButton(R.string.no, null).setCancelable(true).show(); - - + + } - - public void importOtrKeyStoreWithPassword (final File fileOtrKeyStore, String importPassword, String keyStorePassword, Activity activity) throws IOException + + public boolean importOtrKeyStoreWithPassword (String fileOtrKeyStore, String importPassword) { - boolean overWriteExisting = true; boolean deleteImportedFile = true; - importKeyStore(fileOtrKeyStore, importPassword, overWriteExisting, deleteImportedFile); - - + try { + return importKeyStore(fileOtrKeyStore, importPassword, overWriteExisting, deleteImportedFile); + } catch (IOException e) { + OtrDebugLogger.log("error importing key store",e); + return false; + } + } - - public boolean handleKeyScanResult (int requestCode, int resultCode, Intent data, Activity activity, String keyStorePassword) - { - IntentResult scanResult = - IntentIntegrator.parseActivityResult(requestCode, resultCode, data); - - if (scanResult != null) - { - - String otrKeyPassword = scanResult.getContents(); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); - String otrKeyStorePath = prefs.getString("keystoreimport", null); - - Log.d("OTR","got password: " + otrKeyPassword + " for path: " + otrKeyStorePath); - - if (otrKeyPassword != null && otrKeyStorePath != null) - { - - otrKeyPassword = otrKeyPassword.replace("\n","").replace("\r", ""); //remove any padding, newlines, etc - - try - { - File otrKeystoreAES = new File(otrKeyStorePath); - if (otrKeystoreAES.exists()) { - importOtrKeyStoreWithPassword(otrKeystoreAES, otrKeyPassword, keyStorePassword, activity); - return true; - } - } - catch (IOException e) - { - Toast.makeText(activity, "unable to open keystore for import", Toast.LENGTH_LONG).show(); - return false; - } - } - else - { - Log.d("OTR","no key store path saved"); - return false; + public static void handleKeyScanResult (String otrKeyPassword, Activity activity) { + + if (TextUtils.isEmpty(otrKeyPassword)) { + Toast.makeText(activity, "Scanned password is empty!", Toast.LENGTH_LONG).show(); + return; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + File otrKeyStoreFile = new File(prefs.getString("keystoreimport", "/sdcard/otr_keystore.ofcaes")); + if (!otrKeyStoreFile.exists()) { + Toast.makeText(activity, otrKeyStoreFile + " does not exist!", Toast.LENGTH_LONG).show(); + return; + } + + //remove any padding, newlines, etc + otrKeyPassword = otrKeyPassword.replace("\n","").replace("\r", "").replace(" ", ""); + try { + IOtrKeyManager keyMan = ((ImApp)activity.getApplication()).getRemoteImService().getOtrKeyManager(); + if (keyMan.importOtrKeyStoreWithPassword(otrKeyStoreFile.getCanonicalPath(), otrKeyPassword)) { + Toast.makeText(activity, R.string.successfully_imported_otr_keyring, Toast.LENGTH_SHORT).show(); + return; } - - } - - return false; + } catch (Exception e) { + Toast.makeText(activity, "Exception on keystore import", Toast.LENGTH_LONG).show(); + OtrDebugLogger.log("error getting keyman",e); + } + Toast.makeText(activity, R.string.otr_keyring_not_imported_please_check_the_file_exists_in_the_proper_format_and_location, Toast.LENGTH_SHORT).show(); } + } diff --git a/src/info/guardianproject/otr/OtrChatListener.java b/src/info/guardianproject/otr/OtrChatListener.java index e4e6a230c..894ccc113 100644 --- a/src/info/guardianproject/otr/OtrChatListener.java +++ b/src/info/guardianproject/otr/OtrChatListener.java @@ -1,14 +1,25 @@ package info.guardianproject.otr; +import info.guardianproject.otr.OtrDataHandler.Transfer; +import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.ChatSession; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.Message; import info.guardianproject.otr.app.im.engine.MessageListener; +import info.guardianproject.util.Debug; + +import java.util.ArrayList; +import java.util.List; + import net.java.otr4j.OtrException; +import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionStatus; +import net.java.otr4j.session.TLV; public class OtrChatListener implements MessageListener { + public static final int TLV_DATA_REQUEST = 0x100; + public static final int TLV_DATA_RESPONSE = 0x101; private OtrChatManager mOtrChatManager; private MessageListener mMessageListener; @@ -20,40 +31,66 @@ public OtrChatListener(OtrChatManager otrChatManager, MessageListener listener) @Override public boolean onIncomingMessage(ChatSession session, Message msg) { - OtrDebugLogger.log("processing incoming message: " + msg.getID()); + // OtrDebugLogger.log("processing incoming message: " + msg.getID()); String body = msg.getBody(); String from = msg.getFrom().getAddress(); String to = msg.getTo().getAddress(); - SessionStatus otrStatus = mOtrChatManager.getSessionStatus(to, from); + body = Debug.injectErrors(body); + + SessionID sessionID = mOtrChatManager.getSessionId(to, from); + SessionStatus otrStatus = mOtrChatManager.getSessionStatus(sessionID); - OtrDebugLogger.log("session status: " + otrStatus.name()); + List tlvs = new ArrayList(); try { - body = mOtrChatManager.decryptMessage(to, from, body); + // No OTR for groups (yet) + if (!session.getParticipant().isGroup()) { + body = mOtrChatManager.decryptMessage(to, from, body, tlvs); + } if (body != null) { - msg.setBody(body); + msg.setBody(body); mMessageListener.onIncomingMessage(session, msg); } - + } catch (OtrException e) { - - OtrDebugLogger.log("error decrypting message"); - msg.setBody("error decryption message body"); - mMessageListener.onIncomingMessage(session, msg); + + // OtrDebugLogger.log("error decrypting message",e); + + // msg.setBody("[" + "You received an unreadable encrypted message" + "]"); + // mMessageListener.onIncomingMessage(session, msg); + // mOtrChatManager.injectMessage(sessionID, "[error please stop/start encryption]"); + + } + + for (TLV tlv : tlvs) { + if (tlv.getType() == TLV_DATA_REQUEST) { + mMessageListener.onIncomingDataRequest(session, msg, tlv.getValue()); + } else if (tlv.getType() == TLV_DATA_RESPONSE) { + mMessageListener.onIncomingDataResponse(session, msg, tlv.getValue()); + } } - - - - if (mOtrChatManager.getSessionStatus(to, from) != otrStatus) { - mMessageListener.onStatusChanged(session); + + SessionStatus newStatus = mOtrChatManager.getSessionStatus(to, from); + if (newStatus != otrStatus) { + mMessageListener.onStatusChanged(session, newStatus); } - + return true; } + @Override + public void onIncomingDataRequest(ChatSession session, Message msg, byte[] value) { + throw new UnsupportedOperationException(); + } + + @Override + public void onIncomingDataResponse(ChatSession session, Message msg, byte[] value) { + throw new UnsupportedOperationException(); + } + @Override public void onSendMessageError(ChatSession session, Message msg, ImErrorInfo error) { @@ -70,14 +107,19 @@ public void onIncomingReceipt(ChatSession ses, String id) { public void onMessagePostponed(ChatSession ses, String id) { mMessageListener.onMessagePostponed(ses, id); } - + @Override public void onReceiptsExpected(ChatSession ses) { mMessageListener.onReceiptsExpected(ses); } @Override - public void onStatusChanged(ChatSession session) { - mMessageListener.onStatusChanged(session); + public void onStatusChanged(ChatSession session, SessionStatus status) { + mMessageListener.onStatusChanged(session, status); + } + + @Override + public void onIncomingTransferRequest(Transfer transfer) { + mMessageListener.onIncomingTransferRequest(transfer); } } diff --git a/src/info/guardianproject/otr/OtrChatManager.java b/src/info/guardianproject/otr/OtrChatManager.java index 6c06ad7f6..651328003 100644 --- a/src/info/guardianproject/otr/OtrChatManager.java +++ b/src/info/guardianproject/otr/OtrChatManager.java @@ -2,14 +2,20 @@ // Originally: package com.zadov.beem; -import info.guardianproject.otr.app.im.ImService; +import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.otr.app.im.app.SmpResponseActivity; +import info.guardianproject.otr.app.im.engine.Address; +import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.Message; import info.guardianproject.otr.app.im.service.ImConnectionAdapter; import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.otr.app.im.service.RemoteImService; +import info.guardianproject.util.Debug; import java.security.KeyPair; import java.security.PublicKey; +import java.util.Collection; +import java.util.Enumeration; import java.util.Hashtable; import java.util.List; @@ -25,7 +31,9 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.TLV; +import android.content.Context; import android.content.Intent; +import android.util.Log; /* * OtrChatManager keeps track of the status of chats and their OTR stuff @@ -38,39 +46,71 @@ public class OtrChatManager implements OtrEngineListener, OtrSmEngineHost { private OtrEngineHostImpl mOtrEngineHost; private OtrEngineImpl mOtrEngine; private Hashtable mSessions; - private Hashtable mOtrSms; + private Hashtable mOtrSms; - private ImService mContext; + private Context mContext; + + private OtrChatManager(int otrPolicy, RemoteImService imService, OtrKeyManager otrKeyManager) throws Exception { + + mContext = (Context)imService; - private OtrChatManager(int otrPolicy, ImService context, OtrKeyManager otrKeyManager) throws Exception { - mOtrEngineHost = new OtrEngineHostImpl(new OtrPolicyImpl(otrPolicy), - context, otrKeyManager); + mContext, otrKeyManager, imService); mOtrEngine = new OtrEngineImpl(mOtrEngineHost); mOtrEngine.addOtrEngineListener(this); mSessions = new Hashtable(); - mOtrSms = new Hashtable(); + mOtrSms = new Hashtable(); + + - mContext = context; } - public static synchronized OtrChatManager getInstance(int otrPolicy, ImService context, OtrKeyManager otrKeyManager) + + public static synchronized OtrChatManager getInstance(int otrPolicy, RemoteImService imService, OtrKeyManager otrKeyManager) throws Exception { if (mInstance == null) { - mInstance = new OtrChatManager(otrPolicy, context,otrKeyManager); + mInstance = new OtrChatManager(otrPolicy, imService,otrKeyManager); } return mInstance; } - public void addConnection(ImConnectionAdapter imConnectionAdapter) { - mOtrEngineHost.addConnection(imConnectionAdapter); + public static OtrChatManager getInstance() + { + return mInstance; + } + + public static void endAllSessions() { + if (mInstance == null) { + return; + } + Collection sessionIDs = mInstance.mSessions.values(); + for (SessionID sessionId : sessionIDs) { + mInstance.endSession(sessionId); + } } - public void removeConnection(ImConnectionAdapter imConnectionAdapter) { - mOtrEngineHost.removeConnection(imConnectionAdapter); + public static void endSessionsForAccount(Contact localUserContact) { + if (mInstance == null) { + return; + } + String localUserId = localUserContact.getAddress().getBareAddress(); + + Enumeration sKeys = mInstance.mSessions.keys(); + + while (sKeys.hasMoreElements()) + { + String sKey = sKeys.nextElement(); + if (sKey.contains(localUserId)) + { + SessionID sessionId = mInstance.mSessions.get(sKey); + + if (sessionId != null) + mInstance.endSession(sessionId); + } + } } public void addOtrEngineListener(OtrEngineListener oel) { @@ -85,13 +125,6 @@ public OtrKeyManager getKeyManager() { return mOtrEngineHost.getKeyManager(); } - public static String processUserId(String userId) { - String result = userId.split(":")[0]; //remove any port indication in the username - result = userId.split("/")[0]; - - return result; - } - public static String processResource(String userId) { String[] splits = userId.split("/", 2); if (splits.length > 1) @@ -101,17 +134,28 @@ public static String processResource(String userId) { } public SessionID getSessionId(String localUserId, String remoteUserId) { - String sessionIdKey = processUserId(localUserId) + "+" + processUserId(remoteUserId); - SessionID sessionId = mSessions.get(sessionIdKey); - if (sessionId == null || - (!sessionId.getFullUserID().equals(remoteUserId) && - remoteUserId.contains("/"))) { + SessionID sIdTemp = new SessionID(localUserId, remoteUserId, "XMPP"); + SessionID sessionId = mSessions.get(sIdTemp.toString()); + + if (sessionId == null) + { + // or we didn't have a session yet. + sessionId = sIdTemp; + mSessions.put(sessionId.toString(), sessionId); + } + else if ((!sessionId.getRemoteUserId().equals(remoteUserId)) && + remoteUserId.contains("/")) { // Remote has changed (either different presence, or from generic JID to specific presence), - // or we didn't have a session yet. // Create or replace sessionId with one that is specific to the new presence. - sessionId = new SessionID(processUserId(localUserId), remoteUserId, "XMPP"); - mSessions.put(sessionIdKey, sessionId); + + //sessionId.updateRemoteUserId(remoteUserId); + sessionId = sIdTemp; + mSessions.put(sessionId.toString(), sessionId); + + if (Debug.DEBUG_ENABLED) + Log.d(ImApp.LOG_TAG,"getting new otr session id: " + sessionId); + } return sessionId; } @@ -119,7 +163,7 @@ public SessionID getSessionId(String localUserId, String remoteUserId) { /** * Tell if the session represented by a local user account and a remote user * account is currently encrypted or not. - * + * * @param localUserId * @param remoteUserId * @return state @@ -128,7 +172,7 @@ public SessionStatus getSessionStatus(String localUserId, String remoteUserId) { SessionID sessionId = getSessionId(localUserId, remoteUserId); if (sessionId == null) return null; - + return mOtrEngine.getSessionStatus(sessionId); @@ -151,59 +195,101 @@ public void refreshSession(String localUserId, String remoteUserId) { /** * Start a new OTR encryption session for the chat session represented by a * local user address and a remote user address. - * + * * @param localUserId i.e. the account of the user of this phone * @param remoteUserId i.e. the account that this user is talking to */ - public SessionID startSession(String localUserId, String remoteUserId) { + private SessionID startSession(String localUserId, String remoteUserId) { + + SessionID sessionId = getSessionId(localUserId, remoteUserId); try { - SessionID sessionId = getSessionId(localUserId, remoteUserId); + mOtrEngine.startSession(sessionId); + + return sessionId; } catch (OtrException e) { OtrDebugLogger.log("startSession", e); + showError(sessionId,"Unable to start OTR session: " + e.getLocalizedMessage()); + } return null; } - public void endSession(String localUserId, String remoteUserId) { + + /** + * Start a new OTR encryption session for the chat session represented by a + * local user address and a remote user address. + * + * @param localUserId i.e. the account of the user of this phone + * @param remoteUserId i.e. the account that this user is talking to + */ + public SessionID startSession(SessionID sessionId) { + + try { + + mOtrEngine.startSession(sessionId); + + return sessionId; + + } catch (OtrException e) { + OtrDebugLogger.log("startSession", e); + + showError(sessionId,"Unable to start OTR session: " + e.getLocalizedMessage()); + + } + + return null; + } + + + + + public void endSession(SessionID sessionId) { try { - SessionID sessionId = getSessionId(localUserId, remoteUserId); mOtrEngine.endSession(sessionId); + mSessions.remove(sessionId.toString()); } catch (OtrException e) { OtrDebugLogger.log("endSession", e); } } + public void endSession(String localUserId, String remoteUserId) { + + SessionID sessionId = getSessionId(localUserId, remoteUserId); + endSession(sessionId); + + } + public void status(String localUserId, String remoteUserId) { mOtrEngine.getSessionStatus(getSessionId(localUserId, remoteUserId)).toString(); } - public String decryptMessage(String localUserId, String remoteUserId, String msg) throws OtrException { + public String decryptMessage(String localUserId, String remoteUserId, String msg, List tlvs) throws OtrException { String plain = null; SessionID sessionId = getSessionId(localUserId, remoteUserId); - OtrDebugLogger.log("session status: " + mOtrEngine.getSessionStatus(sessionId)); + // OtrDebugLogger.log("session status: " + mOtrEngine.getSessionStatus(sessionId)); if (mOtrEngine != null && sessionId != null) { + mOtrEngineHost.putSessionResource(sessionId, processResource(remoteUserId)); - plain = mOtrEngine.transformReceiving(sessionId, msg); - OtrSm otrSm = mOtrSms.get(sessionId); + plain = mOtrEngine.transformReceiving(sessionId, msg, tlvs); + OtrSm otrSm = mOtrSms.get(sessionId.toString()); if (otrSm != null) { - List tlvs = otrSm.getPendingTlvs(); - if (tlvs != null) { - String encrypted = mOtrEngine.transformSending(sessionId, "", tlvs); + List smTlvs = otrSm.getPendingTlvs(); + if (smTlvs != null) { + String encrypted = mOtrEngine.transformSending(sessionId, "", smTlvs); mOtrEngineHost.injectMessage(sessionId, encrypted); - } } @@ -213,7 +299,11 @@ public String decryptMessage(String localUserId, String remoteUserId, String msg return plain; } - public void transformSending(Message message) { + public boolean transformSending(Message message) { + return transformSending(message, false, null); + } + + public boolean transformSending(Message message, boolean isResponse, byte[] data) { String localUserId = message.getFrom().getAddress(); String remoteUserId = message.getTo().getAddress(); String body = message.getBody(); @@ -222,13 +312,25 @@ public void transformSending(Message message) { if (mOtrEngine != null && sessionId != null) { SessionStatus sessionStatus = mOtrEngine.getSessionStatus(sessionId); + if (data != null && sessionStatus != SessionStatus.ENCRYPTED) { + // Cannot send data without OTR, so start a session and drop message. + // Message will be resent by caller when session is encrypted. + startSession(sessionId); + OtrDebugLogger.log("auto-start OTR on data send request"); + return false; + } OtrDebugLogger.log("session status: " + sessionStatus); try { OtrPolicy sessionPolicy = getSessionPolicy(sessionId); + if (sessionStatus == SessionStatus.PLAINTEXT && sessionPolicy.getRequireEncryption()) + { + startSession(sessionId); + return false; + } if (sessionStatus != SessionStatus.PLAINTEXT || sessionPolicy.getRequireEncryption()) { - body = mOtrEngine.transformSending(sessionId, body); + body = mOtrEngine.transformSending(sessionId, body, isResponse, data); message.setTo(mOtrEngineHost.appendSessionResource(sessionId, message.getTo())); } else if (sessionStatus == SessionStatus.PLAINTEXT && sessionPolicy.getAllowV2() && sessionPolicy.getSendWhitespaceTag()) { @@ -240,8 +342,10 @@ public void transformSending(Message message) { OtrDebugLogger.log("error encrypting", e); } } - + message.setBody(body); + + return true; } @Override @@ -251,9 +355,8 @@ public void sessionStatusChanged(SessionID sessionID) { OtrDebugLogger.log("session status changed: " + sStatus); final Session session = mOtrEngine.getSession(sessionID); - OtrSm otrSm = mOtrSms.get(sessionID); + OtrSm otrSm = mOtrSms.get(sessionID.toString()); - if (sStatus == SessionStatus.ENCRYPTED) { PublicKey remoteKey = mOtrEngine.getRemotePublicKey(sessionID); @@ -265,12 +368,12 @@ public void sessionStatusChanged(SessionID sessionID) { sessionID, OtrChatManager.this); session.addTlvHandler(otrSm); - mOtrSms.put(sessionID, otrSm); + mOtrSms.put(sessionID.toString(), otrSm); } } else if (sStatus == SessionStatus.PLAINTEXT) { if (otrSm != null) { session.removeTlvHandler(otrSm); - mOtrSms.remove(sessionID); + mOtrSms.remove(sessionID.toString()); } mOtrEngineHost.removeSessionResource(sessionID); } else if (sStatus == SessionStatus.FINISHED) { @@ -302,6 +405,7 @@ public void showWarning(SessionID sessionID, String warning) { public void showError(SessionID sessionID, String error) { mOtrEngineHost.showError(sessionID, error); + } @Override @@ -322,12 +426,13 @@ public void askForSecret(SessionID sessionID, String question) { dialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); dialog.putExtra("q", question); - dialog.putExtra("sid", sessionID.getUserID()); - ImConnectionAdapter connection = mOtrEngineHost.findConnection(sessionID.getAccountID()); + dialog.putExtra("sid", sessionID.getRemoteUserId());//yes "sid" = remoteUserId in this case - see SMPResponseActivity + ImConnectionAdapter connection = mOtrEngineHost.findConnection(sessionID); if (connection == null) { - OtrDebugLogger.log("Could ask for secret - no connection for " + sessionID.getAccountID()); + OtrDebugLogger.log("Could ask for secret - no connection for " + sessionID.toString()); return; } + dialog.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, connection.getProviderId()); mContext.getApplicationContext().startActivity(dialog); @@ -335,7 +440,7 @@ public void askForSecret(SessionID sessionID, String question) { } public void respondSmp(SessionID sessionID, String secret) throws OtrException { - OtrSm otrSm = mOtrSms.get(sessionID); + OtrSm otrSm = mOtrSms.get(sessionID.toString()); List tlvs; @@ -343,14 +448,14 @@ public void respondSmp(SessionID sessionID, String secret) throws OtrException { showError(sessionID, "Could not respond to verification because conversation is not encrypted"); return; } - + tlvs = otrSm.initRespondSmp(null, secret, false); String encrypted = mOtrEngine.transformSending(sessionID, "", tlvs); mOtrEngineHost.injectMessage(sessionID, encrypted); } public void initSmp(SessionID sessionID, String question, String secret) throws OtrException { - OtrSm otrSm = mOtrSms.get(sessionID); + OtrSm otrSm = mOtrSms.get(sessionID.toString()); List tlvs; @@ -358,15 +463,15 @@ public void initSmp(SessionID sessionID, String question, String secret) throws showError(sessionID, "Could not perform verification because conversation is not encrypted"); return; } - + tlvs = otrSm.initRespondSmp(question, secret, true); String encrypted = mOtrEngine.transformSending(sessionID, "", tlvs); mOtrEngineHost.injectMessage(sessionID, encrypted); } public void abortSmp(SessionID sessionID) throws OtrException { - OtrSm otrSm = mOtrSms.get(sessionID); - + OtrSm otrSm = mOtrSms.get(sessionID.toString()); + if (otrSm == null) return; @@ -375,4 +480,5 @@ public void abortSmp(SessionID sessionID) throws OtrException { mOtrEngineHost.injectMessage(sessionID, encrypted); } + } diff --git a/src/info/guardianproject/otr/OtrChatSessionAdapter.java b/src/info/guardianproject/otr/OtrChatSessionAdapter.java index bef808cf0..798a77fd8 100644 --- a/src/info/guardianproject/otr/OtrChatSessionAdapter.java +++ b/src/info/guardianproject/otr/OtrChatSessionAdapter.java @@ -1,7 +1,10 @@ package info.guardianproject.otr; import info.guardianproject.otr.IOtrChatSession.Stub; +import info.guardianproject.otr.app.im.engine.ChatSession; +import info.guardianproject.util.Debug; import net.java.otr4j.OtrException; +import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionStatus; import android.os.RemoteException; @@ -13,45 +16,78 @@ public class OtrChatSessionAdapter extends Stub { public OtrChatSessionAdapter(String localUser, String remoteUser, OtrChatManager chatManager) { - _chatManager = chatManager; _localUser = localUser; _remoteUser = remoteUser; + _chatManager = chatManager; + } + private SessionID getSessionID () + { + if (_chatManager != null) + return _chatManager.getSessionId(_localUser, _remoteUser); + else + return null; } public void startChatEncryption() throws RemoteException { - - _chatManager.startSession(_localUser, _remoteUser); + Debug.wrapExceptions(new Runnable() { + @Override + public void run() { + if (_chatManager != null) + { + _chatManager.startSession(getSessionID ()); + } + } + }); } @Override public void stopChatEncryption() throws RemoteException { + Debug.wrapExceptions(new Runnable() { + @Override + public void run() { + if (_chatManager != null) + { + + _chatManager.endSession(getSessionID ()); - _chatManager.endSession(_localUser, _remoteUser); + } + } + }); } @Override public boolean isChatEncrypted() throws RemoteException { - return _chatManager.getSessionStatus(_localUser, _remoteUser) == SessionStatus.ENCRYPTED; - + if (getSessionID () != null) + return _chatManager.getSessionStatus(getSessionID ()) == SessionStatus.ENCRYPTED; + else + return false; } @Override public int getChatStatus() throws RemoteException { - SessionStatus sessionStatus = _chatManager.getSessionStatus(_localUser, _remoteUser); - if (sessionStatus == null) - sessionStatus = SessionStatus.PLAINTEXT; - return sessionStatus.ordinal(); + + if (_chatManager != null && getSessionID () != null) + { + SessionStatus sessionStatus = _chatManager.getSessionStatus(getSessionID ()); + if (sessionStatus == null) + sessionStatus = SessionStatus.PLAINTEXT; + return sessionStatus.ordinal(); + } + else + { + return SessionStatus.PLAINTEXT.ordinal(); + } } @Override public void initSmpVerification(String question, String secret) throws RemoteException { try { - _chatManager.initSmp(_chatManager.getSessionId(_localUser, _remoteUser), question, + _chatManager.initSmp(getSessionID (), question, secret); } catch (OtrException e) { OtrDebugLogger.log("initSmp", e); @@ -63,10 +99,69 @@ public void initSmpVerification(String question, String secret) throws RemoteExc public void respondSmpVerification(String answer) throws RemoteException { try { - _chatManager.respondSmp(_chatManager.getSessionId(_localUser, _remoteUser), answer); + _chatManager.respondSmp(getSessionID (), answer); + } catch (OtrException e) { OtrDebugLogger.log("respondSmp", e); throw new RemoteException(); } } + + @Override + public void verifyKey(String address) throws RemoteException { + + _chatManager.getKeyManager().verify(getSessionID ()); + + } + + @Override + public void unverifyKey(String address) throws RemoteException { + _chatManager.getKeyManager().unverify(getSessionID ()); + } + + @Override + public boolean isKeyVerified(String address) throws RemoteException { + return _chatManager.getKeyManager().isVerified(getSessionID ()); + } + + @Override + public String getLocalFingerprint() throws RemoteException { + + SessionID sid = getSessionID (); + + if (sid != null) + return _chatManager.getKeyManager().getLocalFingerprint(sid); + else + return null; + + } + + @Override + public String getRemoteFingerprint() throws RemoteException { + return _chatManager.getKeyManager().getRemoteFingerprint(getSessionID ()); + } + + @Override + public void generateLocalKeyPair() throws RemoteException { + _chatManager.getKeyManager().generateLocalKeyPair(getSessionID ()); + } + + @Override + public String getLocalUserId() throws RemoteException { + SessionID sid = getSessionID (); + if (sid != null) + return sid.getLocalUserId(); + else + return null; + } + + @Override + public String getRemoteUserId() throws RemoteException { + SessionID sid = getSessionID (); + if (sid != null) + return sid.getRemoteUserId(); + else + return null; + } + } diff --git a/src/info/guardianproject/otr/OtrConstants.java b/src/info/guardianproject/otr/OtrConstants.java index 079b1baec..e60ea017e 100644 --- a/src/info/guardianproject/otr/OtrConstants.java +++ b/src/info/guardianproject/otr/OtrConstants.java @@ -19,8 +19,9 @@ public interface OtrConstants { public static final String QueryMessage_V12 = "?OTR?v2?"; public static final String QueryMessage_V14x = "?OTRv24x?"; public static final String QueryMessage_V124x = "?OTR?v24x?"; - public static final String QueryMessage_CommonRequest = "?OTR?v2? You are being requested to have an Off-the-Record private conversation <http://otr.cypherpunks.ca/>. Please install Gibberbot (Android), ChatSecure (iOS), Pidgin (Win/Linux), Adium (Mac) or any other OTR-enabled app."; + public static final String CommonRequest = " Your contact is requesting to start an encrypted chat. Please install ChatSecure or other encryption-capable messaging app."; + public static final String QueryMessage_CommonRequest = "?OTR?v2? " + CommonRequest; + //You are being requested to have an Off-the-Record private conversation <http://otr.cypherpunks.ca/>. Please install ChatSecure (aka Gibberbot Android/iOS), Pidgin (Win/Linux), Adium (Mac) or any other OTR-enabled app."; public static final String PlainText_V12 = "This is a plain text that has hidden support for V1 and V2! "; public static final String PlainText_V1 = "This is a plain text that has hidden support for V1! "; - public static final String CommonRequest = " You are being requested to have an Off-the-Record private conversation <http://otr.cypherpunks.ca/>. Please install Gibberbot (Android), ChatSecure (iOS), Pidgin (Win/Linux), Adium (Mac) or any other OTR-enabled app."; } diff --git a/src/info/guardianproject/otr/OtrDataHandler.java b/src/info/guardianproject/otr/OtrDataHandler.java new file mode 100644 index 000000000..c0193038d --- /dev/null +++ b/src/info/guardianproject/otr/OtrDataHandler.java @@ -0,0 +1,853 @@ +package info.guardianproject.otr; + +import info.guardianproject.iocipher.File; +import info.guardianproject.iocipher.FileInputStream; +import info.guardianproject.iocipher.RandomAccessFile; +import info.guardianproject.otr.app.im.IDataListener; +import info.guardianproject.otr.app.im.app.ImApp; +import info.guardianproject.otr.app.im.app.ChatFileStore; +import info.guardianproject.otr.app.im.engine.Address; +import info.guardianproject.otr.app.im.engine.ChatSession; +import info.guardianproject.otr.app.im.engine.DataHandler; +import info.guardianproject.otr.app.im.engine.Message; +import info.guardianproject.util.Debug; +import info.guardianproject.util.LogCleaner; +import info.guardianproject.util.SystemServices; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import net.java.otr4j.session.SessionStatus; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestFactory; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.MethodNotSupportedException; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.impl.DefaultHttpResponseFactory; +import org.apache.http.impl.io.AbstractSessionInputBuffer; +import org.apache.http.impl.io.AbstractSessionOutputBuffer; +import org.apache.http.impl.io.HttpRequestParser; +import org.apache.http.impl.io.HttpRequestWriter; +import org.apache.http.impl.io.HttpResponseParser; +import org.apache.http.impl.io.HttpResponseWriter; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.BasicHttpRequest; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicLineFormatter; +import org.apache.http.message.BasicLineParser; +import org.apache.http.message.BasicStatusLine; +import org.apache.http.message.LineFormatter; +import org.apache.http.message.LineParser; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; + +import android.net.Uri; +import android.os.RemoteException; +import android.util.Log; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +public class OtrDataHandler implements DataHandler { + public static final String URI_PREFIX_OTR_IN_BAND = "otr-in-band:/storage/"; + private static final int MAX_OUTSTANDING = 5; + + private static final int MAX_CHUNK_LENGTH = 32768/2; + + private static final int MAX_TRANSFER_LENGTH = 1024*1024*64; + + private static final byte[] EMPTY_BODY = new byte[0]; + + private static final String TAG = "GB.OtrDataHandler"; + + private static final ProtocolVersion PROTOCOL_VERSION = new ProtocolVersion("HTTP", 1, 1); + private static HttpParams params = new BasicHttpParams(); + private static HttpRequestFactory requestFactory = new MyHttpRequestFactory(); + private static HttpResponseFactory responseFactory = new DefaultHttpResponseFactory(); + + private LineParser lineParser = new BasicLineParser(PROTOCOL_VERSION); + private LineFormatter lineFormatter = new BasicLineFormatter(); + private ChatSession mChatSession; + private long mChatId; + + private IDataListener mDataListener; + private SessionStatus mOtrStatus; + + public OtrDataHandler(ChatSession chatSession) { + this.mChatSession = chatSession; + } + + public void setChatId(long chatId) { + this.mChatId = chatId; + } + + public void onOtrStatusChanged(SessionStatus status) { + mOtrStatus = status; + if (status == SessionStatus.ENCRYPTED) { + retryRequests(); + } + } + + private void retryRequests() { + // Resend all unfilled requests + for (Request request: requestCache.asMap().values()) { + if (!request.isSeen()) + sendRequest(request); + } + } + + public void setDataListener (IDataListener dataListener) + { + mDataListener = dataListener; + } + + public static class MyHttpRequestFactory implements HttpRequestFactory { + public MyHttpRequestFactory() { + super(); + } + + public HttpRequest newHttpRequest(final RequestLine requestline) + throws MethodNotSupportedException { + if (requestline == null) { + throw new IllegalArgumentException("Request line may not be null"); + } + //String method = requestline.getMethod(); + return new BasicHttpRequest(requestline); + } + + public HttpRequest newHttpRequest(final String method, final String uri) + throws MethodNotSupportedException { + return new BasicHttpRequest(method, uri); + } + } + + static class MemorySessionInputBuffer extends AbstractSessionInputBuffer { + public MemorySessionInputBuffer(byte[] value) { + init(new ByteArrayInputStream(value), 1000, params); + } + + @Override + public boolean isDataAvailable(int timeout) throws IOException { + throw new UnsupportedOperationException(); + } + } + + static class MemorySessionOutputBuffer extends AbstractSessionOutputBuffer { + ByteArrayOutputStream outputStream; + public MemorySessionOutputBuffer() { + outputStream = new ByteArrayOutputStream(1000); + init(outputStream, 1000, params); + } + + public byte[] getOutput() { + return outputStream.toByteArray(); + } + } + + public void onIncomingRequest(Address requestThem, Address requestUs, byte[] value) { + //Log.e( TAG, "onIncomingRequest:" + requestThem); + + SessionInputBuffer inBuf = new MemorySessionInputBuffer(value); + HttpRequestParser parser = new HttpRequestParser(inBuf, lineParser, requestFactory, params); + HttpRequest req; + + try { + req = (HttpRequest)parser.parse(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (HttpException e) { + e.printStackTrace(); + return; + } + + String requestMethod = req.getRequestLine().getMethod(); + String uid = req.getFirstHeader("Request-Id").getValue(); + String url = req.getRequestLine().getUri(); + + if (requestMethod.equals("OFFER")) { + debug("incoming OFFER " + url); + if (!url.startsWith(URI_PREFIX_OTR_IN_BAND)) { + debug("Unknown url scheme " + url); + sendResponse(requestUs, 400, "Unknown scheme", uid, EMPTY_BODY); + return; + } + sendResponse(requestUs, 200, "OK", uid, EMPTY_BODY); + if (!req.containsHeader("File-Length")) + { + sendResponse(requestUs, 400, "File-Length must be supplied", uid, EMPTY_BODY); + return; + } + int length = Integer.parseInt(req.getFirstHeader("File-Length").getValue()); + if (!req.containsHeader("File-Hash-SHA1")) + { + sendResponse(requestUs, 400, "File-Hash-SHA1 must be supplied", uid, EMPTY_BODY); + return; + } + String sum = req.getFirstHeader("File-Hash-SHA1").getValue(); + String type = null; + if (req.containsHeader("Mime-Type")) { + type = req.getFirstHeader("Mime-Type").getValue(); + } + debug("Incoming sha1sum " + sum); + + VfsTransfer transfer; + try { + transfer = new VfsTransfer(url, type, length, requestUs, sum); + } catch (IOException e) { + e.printStackTrace(); + return; + } + transferCache.put(url, transfer); + + // Handle offer + + // TODO ask user to confirm we want this + boolean accept = false; + + if (mDataListener != null) + { + try { + mDataListener.onTransferRequested(url, requestThem.getAddress(),requestUs.getAddress(),transfer.url); + + //callback is now async, via "acceptTransfer" method + // if (accept) + // transfer.perform(); + + } catch (RemoteException e) { + LogCleaner.error(ImApp.LOG_TAG, "error approving OTRDATA transfer request", e); + } + + } + + } else if (requestMethod.equals("GET") && url.startsWith(URI_PREFIX_OTR_IN_BAND)) { + debug("incoming GET " + url); + ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + int reqEnd; + + try { + Offer offer = offerCache.getIfPresent(url); + if (offer == null) { + sendResponse(requestUs, 400, "No such offer made", uid, EMPTY_BODY); + return; + } + + offer.seen(); // in case we don't see a response to underlying request, but peer still proceeds + + if (!req.containsHeader("Range")) + { + sendResponse(requestUs, 400, "Range must start with bytes=", uid, EMPTY_BODY); + return; + } + String rangeHeader = req.getFirstHeader("Range").getValue(); + String[] spec = rangeHeader.split("="); + if (spec.length != 2 || !spec[0].equals("bytes")) + { + sendResponse(requestUs, 400, "Range must start with bytes=", uid, EMPTY_BODY); + return; + } + String[] startEnd = spec[1].split("-"); + if (startEnd.length != 2) + { + sendResponse(requestUs, 400, "Range must be START-END", uid, EMPTY_BODY); + return; + } + + int start = Integer.parseInt(startEnd[0]); + int end = Integer.parseInt(startEnd[1]); + if (end - start + 1 > MAX_CHUNK_LENGTH) { + sendResponse(requestUs, 400, "Range must be at most " + MAX_CHUNK_LENGTH, uid, EMPTY_BODY); + return; + } + + + File fileGet = new File(offer.getUri()); + FileInputStream is = new FileInputStream(fileGet); + readIntoByteBuffer(byteBuffer, is, start, end); + is.close(); + + if (mDataListener != null) + { + float percent = ((float)end) / ((float)fileGet.length()); + + mDataListener.onTransferProgress(true, offer.getId(), requestThem.getAddress(), offer.getUri(), + percent); + + String mimeType = null; + if (req.getFirstHeader("Mime-Type") != null) + mimeType = req.getFirstHeader("Mime-Type").getValue(); + mDataListener.onTransferComplete(true, offer.getId(), requestThem.getAddress(), offer.getUri(), mimeType, offer.getUri()); + + } + + } catch (UnsupportedEncodingException e) { + // throw new RuntimeException(e); + sendResponse(requestUs, 400, "Unsupported encoding", uid, EMPTY_BODY); + return; + } catch (IOException e) { + //throw new RuntimeException(e); + sendResponse(requestUs, 400, "IOException", uid, EMPTY_BODY); + return; + } catch (NumberFormatException e) { + sendResponse(requestUs, 400, "Range is not numeric", uid, EMPTY_BODY); + return; + } catch (Exception e) { + sendResponse(requestUs, 500, "Unknown error", uid, EMPTY_BODY); + return; + } + + + byte[] body = byteBuffer.toByteArray(); + debug("Sent sha1 is " + sha1sum(body)); + sendResponse(requestUs, 200, "OK", uid, body); + + + } else { + debug("Unknown method / url " + requestMethod + " " + url); + sendResponse(requestUs, 400, "OK", uid, EMPTY_BODY); + } + } + + public void acceptTransfer (String url) + { + Transfer transfer = transferCache.getIfPresent(url); + if (transfer != null) + { + transfer.perform(); + + } + + } + + private static void readIntoByteBuffer(ByteArrayOutputStream byteBuffer, FileInputStream is, int start, int end) + throws IOException { + //Log.e( TAG, "readIntoByteBuffer:" + (end-start)); + if (start != is.skip(start)) { + return; + } + int size = end - start + 1; + int buffersize = 1024; + byte[] buffer = new byte[buffersize]; + + int len = 0; + while((len = is.read(buffer)) != -1){ + if (len > size) { + len = size; + } + byteBuffer.write(buffer, 0, len); + size -= len; + } + } + + private static void readIntoByteBuffer(ByteArrayOutputStream byteBuffer, SessionInputBuffer sib) + throws IOException { + //Log.e( TAG, "readIntoByteBuffer:"); + int buffersize = 1024; + byte[] buffer = new byte[buffersize]; + + int len = 0; + while((len = sib.read(buffer)) != -1){ + byteBuffer.write(buffer, 0, len); + } + } + + private void sendResponse(Address us, int code, String statusString, String uid, byte[] body) { + MemorySessionOutputBuffer outBuf = new MemorySessionOutputBuffer(); + HttpMessageWriter writer = new HttpResponseWriter(outBuf, lineFormatter, params); + HttpMessage response = new BasicHttpResponse(new BasicStatusLine(PROTOCOL_VERSION, code, statusString)); + response.addHeader("Request-Id", uid); + try { + writer.write(response); + outBuf.write(body); + outBuf.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (HttpException e) { + throw new RuntimeException(e); + } + byte[] data = outBuf.getOutput(); + Message message = new Message(""); + message.setFrom(us); + debug("send response " + statusString + " for " + uid); + mChatSession.sendDataAsync(message, true, data); + } + + public void onIncomingResponse(Address from, Address to, byte[] value) { + //Log.e( TAG, "onIncomingResponse:" + value.length); + SessionInputBuffer buffer = new MemorySessionInputBuffer(value); + HttpResponseParser parser = new HttpResponseParser(buffer, lineParser, responseFactory, params); + HttpResponse res; + try { + res = (HttpResponse) parser.parse(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (HttpException e) { + e.printStackTrace(); + return; + } + + String uid = res.getFirstHeader("Request-Id").getValue(); + Request request = requestCache.getIfPresent(uid); + if (request == null) { + debug("Unknown request ID " + uid); + return; + } + + if (request.isSeen()) { + debug("Already seen request ID " + uid); + return; + } + + request.seen(); + int statusCode = res.getStatusLine().getStatusCode(); + if (statusCode != 200) { + debug("got status " + statusCode + ": " + res.getStatusLine().getReasonPhrase()); + // TODO handle error + return; + } + + // TODO handle success + try { + ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + readIntoByteBuffer(byteBuffer, buffer); + debug("Received sha1 @" + request.start + " is " + sha1sum(byteBuffer.toByteArray())); + if (request.method.equals("GET")) { + VfsTransfer transfer = transferCache.getIfPresent(request.url); + if (transfer == null) { + debug("Transfer expired for url " + request.url); + return; + } + transfer.chunkReceived(request, byteBuffer.toByteArray()); + if (transfer.isDone()) { + //Log.e( TAG, "onIncomingResponse: isDone"); + debug("Transfer complete for " + request.url); + String filename = transfer.closeFile(); + Uri vfsUri = ChatFileStore.vfsUri(filename); + if (transfer.checkSum()) { + + //Log.e( TAG, "onIncomingResponse: writing"); + if (mDataListener != null) + mDataListener.onTransferComplete( + false, + null, + mChatSession.getParticipant().getAddress().getAddress(), + transfer.url, + transfer.type, + vfsUri.toString()); + } else { + if (mDataListener != null) + mDataListener.onTransferFailed( + false, + null, + mChatSession.getParticipant().getAddress().getAddress(), + transfer.url, + "checksum"); + debug( "Wrong checksum for file"); + } + } else { + if (mDataListener != null) + mDataListener.onTransferProgress(true, null, mChatSession.getParticipant().getAddress().getAddress(), transfer.url, + ((float)transfer.chunksReceived) / transfer.chunks); + transfer.perform(); + debug("Progress " + transfer.chunksReceived + " / " + transfer.chunks); + } + } + } catch (IOException e) { + debug("Could not read line from response"); + } catch (RemoteException e) { + debug("Could not read remote exception"); + } + + } + + private String getFilenameFromUrl(String url) { + String[] path = url.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + return sanitizedPath; + } + + /** + private File writeDataToStorage (String url, byte[] data) + { + debug( "writeDataToStorage:" + url + " " + data.length); + + String[] path = url.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + + File fileDownloadsDir = new File(Environment.DIRECTORY_DOWNLOADS); + fileDownloadsDir.mkdirs(); + + info.guardianproject.iocipher.File file = new info.guardianproject.iocipher.File(fileDownloadsDir, sanitizedPath); + debug( "writeDataToStorage:" + file.getAbsolutePath() ); + + try { + OutputStream output = (new info.guardianproject.iocipher.FileOutputStream(file)); + output.write(data); + output.flush(); + output.close(); + return file; + } catch (IOException e) { + OtrDebugLogger.log("error writing file", e); + return null; + } + }*/ + + @Override + public void offerData(String id, Address us, String localUri, Map headers) throws IOException { + + // TODO stash localUri and intended recipient + + + long length = new File(localUri).length(); + if (length > MAX_TRANSFER_LENGTH) { + throw new IOException("Length too large: " + length); + } + if (headers == null) + headers = Maps.newHashMap(); + headers.put("File-Length", String.valueOf(length)); + + try { + + FileInputStream is = new FileInputStream(localUri); + headers.put("File-Hash-SHA1", sha1sum(is)); + is.close(); + + String[] paths = localUri.split("/"); + String url = URI_PREFIX_OTR_IN_BAND + SystemServices.sanitize(paths[paths.length - 1]); + Request request = new Request("OFFER", us, url, headers); + offerCache.put(url, new Offer(id, localUri, request)); + sendRequest(request); + + } catch (IOException e) { + Log.e(ImApp.LOG_TAG,"error opening file",e); + } + } + + public Request performGetData(Address us, String url, Map headers, int start, int end) { + String rangeSpec = "bytes=" + start + "-" + end; + headers.put("Range", rangeSpec); + Request request = new Request("GET", us, url, start, end, headers, EMPTY_BODY); + + sendRequest(request); + return request; + } + + static class Offer { + private String mId; + private String mUri; + private Request request; + + public Offer(String id, String uri, Request request) { + this.mId = id; + this.mUri = uri; + this.request = request; + } + + public String getUri() { + return mUri; + } + + public String getId() { + return mId; + } + + public Request getRequest() { + return request; + } + + public void seen() { + request.seen(); + } + } + + static class Request { + + public Request(String method, Address us, String url, int start, int end, Map headers, byte[] body) { + this.method = method; + this.url = url; + this.start = start; + this.end = end; + this.us = us; + this.headers = headers; + this.body = body; + } + + public Request(String method, Address us, String url, Map headers) { + this(method, us, url, -1, -1, headers, null); + } + + public String method; + public String url; + public int start; + public int end; + public byte[] data; + public boolean seen = false; + public Address us; + public Map headers; + public byte[] body; + + public boolean isSeen() { + return seen; + } + + public void seen() { + seen = true; + } + } + + public class Transfer { + public final String TAG = Transfer.class.getSimpleName(); + public String url; + public String type; + public int chunks = 0; + public int chunksReceived = 0; + private int length = 0; + private int current = 0; + private Address us; + protected Set outstanding; + private byte[] buffer; + protected String sum; + + public Transfer(String url, String type, int length, Address us, String sum) { + this.url = url; + this.type = type; + this.length = length; + this.us = us; + this.sum = sum; + + //Log.e(TAG, "url:"+url + " type:"+ type + " length:"+length) ; + + if (length > MAX_TRANSFER_LENGTH || length <= 0) { + throw new RuntimeException("Invalid transfer size " + length); + } + chunks = ((length - 1) / MAX_CHUNK_LENGTH) + 1; + buffer = new byte[length]; + outstanding = Sets.newHashSet(); + } + + public boolean checkSum() { + return sum.equals(sha1sum(buffer)); + } + + public boolean perform() { + // TODO global throttle rather than this local hack + while (outstanding.size() < MAX_OUTSTANDING) { + if (current >= length) + return false; + int end = current + MAX_CHUNK_LENGTH - 1; + if (end >= length) { + end = length - 1; + } + Map headers = Maps.newHashMap(); + Request request= performGetData(us, url, headers, current, end); + outstanding.add(request); + current = end + 1; + } + return true; + } + + public boolean isDone() { + //Log.e( TAG, "isDone:" + chunksReceived + " " + chunks); + return chunksReceived == chunks; + } + + public void chunkReceived(Request request, byte[] bs) { + //Log.e( TAG, "chunkReceived:" + bs.length); + chunksReceived++; + System.arraycopy(bs, 0, buffer, request.start, bs.length); + outstanding.remove(request); + } + + public String getSum() { + return sum; + } + } + + public class VfsTransfer extends Transfer { + String localFilename; + private RandomAccessFile raf; + + public VfsTransfer(String url, String type, int length, Address us, String sum) throws FileNotFoundException { + super(url, type, length, us, sum); + } + + @Override + public void chunkReceived(Request request, byte[] bs) { + debug( "chunkReceived: start: :" + request.start + " length " + bs.length) ; + chunksReceived++; + try { + raf.seek( request.start ); + raf.write(bs) ; + } catch (IOException e) { + e.printStackTrace(); + } + outstanding.remove(request); + } + + @Override + public boolean checkSum() { + try { + File file = new File(localFilename); + return sum.equals( checkSum(file.getAbsolutePath()) ); + } catch (IOException e) { + debug("checksum IOException"); + return false; + } + } + + @Override + public boolean perform() { + boolean result = super.perform(); + try { + if (raf == null) { + raf = openFile(url); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + return result; + } + + private RandomAccessFile openFile(String url) throws FileNotFoundException { + debug( "openFile: url " + url) ; + String sessionId = ""+ mChatId; + String filename = getFilenameFromUrl(url); + localFilename = ChatFileStore.getDownloadFilename( sessionId, filename ); + debug( "openFile: localFilename " + localFilename) ; + info.guardianproject.iocipher.RandomAccessFile ras = new info.guardianproject.iocipher.RandomAccessFile(localFilename, "rw"); + return ras; + } + + public String closeFile() throws IOException { + //Log.e(TAG, "closeFile") ; + raf.close(); + File file = new File(localFilename); + String newPath = file.getCanonicalPath(); + if(true) return newPath; + + newPath = newPath.substring(0,newPath.length()-4); // remove the .tmp + //Log.e(TAG, "vfsCloseFile: rename " + newPath) ; + File newPathFile = new File(newPath); + boolean success = file.renameTo(newPathFile); + if (!success) { + throw new IOException("Rename error " + newPath ); + } + return newPath; + } + + private String checkSum(String filename) throws IOException { + FileInputStream fis = new FileInputStream(new File(filename)); + String sum = sha1sum(fis); + fis.close(); + return sum; + } + } + + Cache offerCache = CacheBuilder.newBuilder().maximumSize(100).build(); + Cache requestCache = CacheBuilder.newBuilder().maximumSize(100).build(); + Cache transferCache = CacheBuilder.newBuilder().maximumSize(100).build(); + + private void sendRequest(Request request) { + MemorySessionOutputBuffer outBuf = new MemorySessionOutputBuffer(); + HttpMessageWriter writer = new HttpRequestWriter(outBuf, lineFormatter, params); + HttpMessage req = new BasicHttpRequest(request.method, request.url, PROTOCOL_VERSION); + String uid = UUID.randomUUID().toString(); + req.addHeader("Request-Id", uid); + if (request.headers != null) { + for (Entry entry : request.headers.entrySet()) { + req.addHeader(entry.getKey(), entry.getValue()); + } + } + + try { + writer.write(req); + outBuf.write(request.body); + outBuf.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (HttpException e) { + throw new RuntimeException(e); + } + byte[] data = outBuf.getOutput(); + Message message = new Message(""); + message.setFrom(request.us); + if (req.containsHeader("Range")) + debug("send request " + request.method + " " + request.url + " " + req.getFirstHeader("Range")); + else + debug("send request " + request.method + " " + request.url); + requestCache.put(uid, request); + mChatSession.sendDataAsync(message, false, data); + } + + private static String hexChr(int b) { + return Integer.toHexString(b & 0xF); + } + + private static String toHex(int b) { + return hexChr((b & 0xF0) >> 4) + hexChr(b & 0x0F); + } + + private String sha1sum(byte[] bytes) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + digest.update(bytes, 0, bytes.length); + byte[] sha1sum = digest.digest(); + String display = ""; + for(byte b : sha1sum) + display += toHex(b); + return display; + } + + private String sha1sum(java.io.InputStream is) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA1"); + + DigestInputStream dig = new DigestInputStream(is, digest); + IOUtils.copy( dig, new NullOutputStream() ); + + byte[] sha1sum = digest.digest(); + String display = ""; + for(byte b : sha1sum) + display += toHex(b); + return display; + } + catch (Exception npe) + { + Log.e(ImApp.LOG_TAG,"unable to hash file",npe); + return null; + } + + } + + + private void debug (String msg) + { + if (Debug.DEBUG_ENABLED) + Log.d(ImApp.LOG_TAG,msg); + } +} diff --git a/src/info/guardianproject/otr/OtrDebugLogger.java b/src/info/guardianproject/otr/OtrDebugLogger.java index 06159ff96..5b895e92a 100644 --- a/src/info/guardianproject/otr/OtrDebugLogger.java +++ b/src/info/guardianproject/otr/OtrDebugLogger.java @@ -1,20 +1,18 @@ package info.guardianproject.otr; +import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.util.Debug; import info.guardianproject.util.LogCleaner; import android.util.Log; public class OtrDebugLogger { - private final static String TAG = "Gibberbot.OTR"; - public static void log(String msg) { - if (Debug.DEBUG_ENABLED && Log.isLoggable(TAG, Log.DEBUG)) - Log.d(TAG, LogCleaner.clean(msg)); + if (Debug.DEBUG_ENABLED && Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) + Log.d(ImApp.LOG_TAG, LogCleaner.clean(msg)); } public static void log(String msg, Exception e) { - if (Debug.DEBUG_ENABLED) - Log.e(TAG, LogCleaner.clean(msg), e); + Log.e(ImApp.LOG_TAG, LogCleaner.clean(msg), e); } } diff --git a/src/info/guardianproject/otr/OtrEngineHostImpl.java b/src/info/guardianproject/otr/OtrEngineHostImpl.java index b6ac8447f..0ef4fd78b 100644 --- a/src/info/guardianproject/otr/OtrEngineHostImpl.java +++ b/src/info/guardianproject/otr/OtrEngineHostImpl.java @@ -1,28 +1,28 @@ package info.guardianproject.otr; import info.guardianproject.otr.app.im.ImService; -import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.Address; -import info.guardianproject.otr.app.im.engine.ChatSessionManager; -import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.Message; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import info.guardianproject.otr.app.im.service.ChatSessionAdapter; import info.guardianproject.otr.app.im.service.ChatSessionManagerAdapter; import info.guardianproject.otr.app.im.service.ImConnectionAdapter; +import info.guardianproject.otr.app.im.service.RemoteImService; import java.io.IOException; import java.security.KeyPair; import java.security.PublicKey; -import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; -import java.util.List; import net.java.otr4j.OtrEngineHost; import net.java.otr4j.OtrKeyManager; -import net.java.otr4j.OtrKeyManagerListener; import net.java.otr4j.OtrPolicy; import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionStatus; +import android.content.Context; +import android.os.RemoteException; +import android.widget.Toast; /* * OtrEngineHostImpl is the connects this app and the OtrEngine @@ -30,16 +30,17 @@ */ public class OtrEngineHostImpl implements OtrEngineHost { - private List mConnections; private OtrPolicy mPolicy; private OtrKeyManager mOtrKeyManager; - private ImService mContext; + private Context mContext; private Hashtable mSessionResources; - public OtrEngineHostImpl(OtrPolicy policy, ImService context, OtrKeyManager otrKeyManager) throws IOException { + private RemoteImService mImService; + + public OtrEngineHostImpl(OtrPolicy policy, Context context, OtrKeyManager otrKeyManager, RemoteImService imService) throws IOException { mPolicy = policy; mContext = context; @@ -47,32 +48,9 @@ public OtrEngineHostImpl(OtrPolicy policy, ImService context, OtrKeyManager otrK mOtrKeyManager = otrKeyManager; - mOtrKeyManager.addListener(new OtrKeyManagerListener() { - public void verificationStatusChanged(SessionID session) { - String msg = session + ": verification status=" - + mOtrKeyManager.isVerified(session); - - OtrDebugLogger.log(msg); - } - - public void remoteVerifiedUs(SessionID session) { - String msg = session + ": remote verified us"; + mImService = imService; - OtrDebugLogger.log(msg); - if (!isRemoteKeyVerified(session)) - showWarning(session, mContext.getApplicationContext().getString(R.string.remote_verified_us)); - } - }); - - mConnections = new ArrayList(); - } - public void addConnection(ImConnectionAdapter connection) { - mConnections.add(connection); - } - - public void removeConnection(ImConnectionAdapter connection) { - mConnections.remove(connection); } public void putSessionResource(SessionID session, String resource) { @@ -84,19 +62,19 @@ public void removeSessionResource(SessionID session) { } public Address appendSessionResource(SessionID session, Address to) { + String resource = mSessionResources.get(session); - return to;//.appendResource(resource); + if (resource != null) + return new XmppAddress(to.getBareAddress() + '/' + resource); + else + return to; + + //return new XmppAddress(session.getRemoteUserId()); } - public ImConnectionAdapter findConnection(String localAddress) { - for (ImConnectionAdapter connection : mConnections) { - Contact user = connection.getLoginUser(); - if (user != null) { - if (user.getAddress().getAddress().equals(localAddress)) - return connection; - } - } - return null; + public ImConnectionAdapter findConnection(SessionID session) { + + return mImService.getConnection(Address.stripResource(session.getLocalUserId())); } public OtrKeyManager getKeyManager() { @@ -138,40 +116,81 @@ public void setSessionPolicy(OtrPolicy policy) { mPolicy = policy; } - private void sendMessage(SessionID sessionID, String body) { - ImConnectionAdapter connection = findConnection(sessionID.getAccountID()); - ChatSessionManagerAdapter chatSessionManagerAdapter = (ChatSessionManagerAdapter) connection - .getChatSessionManager(); - ChatSessionAdapter chatSessionAdapter = (ChatSessionAdapter) chatSessionManagerAdapter - .getChatSession(sessionID.getUserID()); - ChatSessionManager chatSessionManager = chatSessionManagerAdapter.getChatSessionManager(); + public void injectMessage(SessionID sessionID, String text) { + OtrDebugLogger.log(sessionID.toString() + ": injecting message: " + text); + + ImConnectionAdapter connection = findConnection(sessionID); + if (connection != null) + { + ChatSessionManagerAdapter chatSessionManagerAdapter = (ChatSessionManagerAdapter) connection + .getChatSessionManager(); + ChatSessionAdapter chatSessionAdapter = (ChatSessionAdapter) chatSessionManagerAdapter + .getChatSession(Address.stripResource(sessionID.getRemoteUserId())); + + if (chatSessionAdapter != null) + { + String body = text; + + if (body == null) + body = ""; //don't allow null messages, only empty ones! + + Message msg = new Message(body); + + // msg.setFrom(new XmppAddress(sessionID.getLocalUserId())); + // final Address to = chatSessionAdapter.getAdaptee().getParticipant().getAddress(); + + Address to = new XmppAddress(sessionID.getRemoteUserId()); + + try + { + SessionStatus chatStatus = SessionStatus.values()[chatSessionAdapter.getOtrChatSession().getChatStatus()]; + + if (chatStatus == SessionStatus.ENCRYPTED) + { + msg.setTo(appendSessionResource(sessionID, to)); + } + else + { + msg.setTo(new XmppAddress(chatSessionAdapter.getAdaptee().getParticipant().getAddress().getBareAddress())); + } + } + catch (RemoteException e) + { + msg.setTo(chatSessionAdapter.getAdaptee().getParticipant().getAddress()); + } + //msg.setTo(to); + // msg.setDateTime(new Date()); + + // msg ID is set by plugin + chatSessionManagerAdapter.getChatSessionManager().sendMessageAsync(chatSessionAdapter.getAdaptee(), msg); - Message msg = new Message(body); + } + else + { + OtrDebugLogger.log(sessionID.toString() + ": could not find chatSession"); - msg.setFrom(connection.getLoginUser().getAddress());sessionID.getFullUserID(); - final Address to = chatSessionAdapter.getAdaptee().getParticipant().getAddress(); - msg.setTo(appendSessionResource(sessionID, to)); - msg.setDateTime(new Date()); - msg.setID(msg.getFrom() + ":" + msg.getDateTime().getTime()); - chatSessionManager.sendMessageAsync(chatSessionAdapter.getAdaptee(), msg); - - } + } + } + else + { + OtrDebugLogger.log(sessionID.toString() + ": could not find ImConnection"); + + } - public void injectMessage(SessionID sessionID, String text) { - OtrDebugLogger.log(sessionID.toString() + ": injecting message: " + text); - sendMessage(sessionID, text); } public void showError(SessionID sessionID, String error) { OtrDebugLogger.log(sessionID.toString() + ": ERROR=" + error); + // Toast.makeText(mContext, "ERROR: " + error, Toast.LENGTH_SHORT).show(); + } public void showWarning(SessionID sessionID, String warning) { OtrDebugLogger.log(sessionID.toString() + ": WARNING=" + warning); - + } - + } diff --git a/src/info/guardianproject/otr/OtrKeyManagerAdapter.java b/src/info/guardianproject/otr/OtrKeyManagerAdapter.java deleted file mode 100644 index 3d8dc2698..000000000 --- a/src/info/guardianproject/otr/OtrKeyManagerAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * - */ -package info.guardianproject.otr; - -import net.java.otr4j.OtrKeyManager; -import net.java.otr4j.session.SessionID; -import android.os.RemoteException; - -/** @author n8fr8 */ -public class OtrKeyManagerAdapter extends IOtrKeyManager.Stub { - - private OtrKeyManager mKeyManager; - - private String mRemoteUserId; - - private String mAccountId; - private OtrChatManager mOtrChatManager; - - private SessionID mSessionId; - - public OtrKeyManagerAdapter(OtrChatManager otrChatManager, String accountId, - String remoteUserId) { - - this.mKeyManager = otrChatManager.getKeyManager(); - this.mOtrChatManager = otrChatManager; - - // The session ID can change, depending on which remote presence we are talking - // to at the moment. Therefore, keep local/remote bare JIDs and look up the session ID - // on demand. - this.mRemoteUserId = remoteUserId; - this.mAccountId = accountId; - - mSessionId = mOtrChatManager.getSessionId(mAccountId, mRemoteUserId); - } - - @Override - public void verifyKey(String address) throws RemoteException { - - SessionID sessionId = mOtrChatManager.getSessionId(mAccountId, address); - mKeyManager.verify(sessionId); - - } - - @Override - public void unverifyKey(String address) throws RemoteException { - SessionID sessionId = mOtrChatManager.getSessionId(mAccountId, address); - mKeyManager.unverify(sessionId); - } - - @Override - public boolean isKeyVerified(String address) throws RemoteException { - SessionID sessionId = mOtrChatManager.getSessionId(mAccountId, address); - return mKeyManager.isVerified(sessionId); - } - - @Override - public String getLocalFingerprint() throws RemoteException { - return mKeyManager.getLocalFingerprint(mSessionId); - - } - - @Override - public String getRemoteFingerprint() throws RemoteException { - SessionID sessionId = mOtrChatManager.getSessionId(mAccountId, mRemoteUserId); - - return mKeyManager.getRemoteFingerprint(sessionId); - } - - @Override - public void generateLocalKeyPair() throws RemoteException { - mKeyManager.generateLocalKeyPair(mSessionId); - } - -} diff --git a/src/info/guardianproject/otr/app/Broadcaster.java b/src/info/guardianproject/otr/app/Broadcaster.java index aeb275e9a..3ea4b0387 100644 --- a/src/info/guardianproject/otr/app/Broadcaster.java +++ b/src/info/guardianproject/otr/app/Broadcaster.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2006 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -26,7 +26,7 @@ public Broadcaster() { /** * Sign up for notifications about something. - * + * * When this broadcaster pushes a message with senderWhat in the what field, * target will be sent a copy of that message with targetWhat in the what * field. diff --git a/src/info/guardianproject/otr/app/im/IChatListener.aidl b/src/info/guardianproject/otr/app/im/IChatListener.aidl index 513ed2d64..7527c8f41 100755 --- a/src/info/guardianproject/otr/app/im/IChatListener.aidl +++ b/src/info/guardianproject/otr/app/im/IChatListener.aidl @@ -22,16 +22,23 @@ import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.Message; -oneway interface IChatListener { +interface IChatListener { /** * This method is called when a new message of the ChatSession has arrived. + * + * response indicates whether the user is focused on this message stream or not (for notifications) */ - void onIncomingMessage(IChatSession ses, in Message msg); + boolean onIncomingMessage(IChatSession ses, in info.guardianproject.otr.app.im.engine.Message msg); + + /** + * This method is called when a new message of the ChatSession has arrived. + */ + void onIncomingData(IChatSession ses, in byte[] data); /** * This method is called when an error is found to send a message in the ChatSession. */ - void onSendMessageError(IChatSession ses, in Message msg, in ImErrorInfo error); + void onSendMessageError(IChatSession ses, in info.guardianproject.otr.app.im.engine.Message msg, in ImErrorInfo error); /** * This method is called when the chat is converted to a group chat. @@ -61,4 +68,15 @@ oneway interface IChatListener { /** This method is called when OTR status changes */ void onStatusChanged(IChatSession ses); + + /** this is called when there is a incoming file transfer request **/ + void onIncomingFileTransfer (String from, String file); + + + /** this is called when there is a incoming file transfer request **/ + void onIncomingFileTransferProgress (String file, int percent); + + /** this is called when there is a incoming file transfer request **/ + void onIncomingFileTransferError (String file, String message); + } diff --git a/src/info/guardianproject/otr/app/im/IChatSession.aidl b/src/info/guardianproject/otr/app/im/IChatSession.aidl index 43a1558db..9825fd921 100755 --- a/src/info/guardianproject/otr/app/im/IChatSession.aidl +++ b/src/info/guardianproject/otr/app/im/IChatSession.aidl @@ -18,8 +18,8 @@ package info.guardianproject.otr.app.im; import info.guardianproject.otr.app.im.IChatListener; +import info.guardianproject.otr.app.im.IDataListener; import info.guardianproject.otr.app.im.engine.Message; -import info.guardianproject.otr.IOtrKeyManager; import info.guardianproject.otr.IOtrChatSession; @@ -60,7 +60,7 @@ interface IChatSession { * Convert a single chat to a group chat. If the chat session is already a * group chat or it's converting to group chat. */ - void convertToGroupChat(); + void convertToGroupChat(String nickname); /** * Invites a contact to join this ChatSession. The user can only invite @@ -84,6 +84,11 @@ interface IChatSession { */ void sendMessage(String text); + /** + * Sends data to all participants in this ChatSession. + */ + boolean offerData(String offerId, String localUri, String type); + /** * Mark this chat session as read. */ @@ -95,7 +100,18 @@ interface IChatSession { IOtrChatSession getOtrChatSession(); /** - * Get OTR Key Manager + * set class for handling incoming data transfers + */ + void setDataListener (IDataListener dataListener); + + /** + * respond to incoming data request */ - IOtrKeyManager getOtrKeyManager(); + void setIncomingFileResponse (boolean acceptThis, boolean acceptAll); + + /** + * reinit chatsession if we are starting a new chat + */ + void reInit(); } + diff --git a/src/info/guardianproject/otr/app/im/IChatSessionManager.aidl b/src/info/guardianproject/otr/app/im/IChatSessionManager.aidl index 2a7d13200..0f2e75920 100755 --- a/src/info/guardianproject/otr/app/im/IChatSessionManager.aidl +++ b/src/info/guardianproject/otr/app/im/IChatSessionManager.aidl @@ -30,15 +30,19 @@ interface IChatSessionManager { * * @param contactAddress the address of the contact. */ - IChatSession createChatSession(String contactAddress); + IChatSession createChatSession(String contactAddress, boolean isNewSession); /** * Create a MultiUserChatSession with the specified room. * * @param contactAddress the address of the contact. */ - IChatSession createMultiUserChatSession(String roomAddress); + IChatSession createMultiUserChatSession(String roomAddress, String nickname, boolean isNewSession); + /** + * Get the default MUC server so we can show it to the user + */ + String getDefaultMultiUserChatServer(); /** * Get the ChatSession that is associated with the specified contact or group. diff --git a/src/info/guardianproject/otr/app/im/IContactListManager.aidl b/src/info/guardianproject/otr/app/im/IContactListManager.aidl index 3ebbf416c..c19767645 100755 --- a/src/info/guardianproject/otr/app/im/IContactListManager.aidl +++ b/src/info/guardianproject/otr/app/im/IContactListManager.aidl @@ -62,15 +62,23 @@ interface IContactListManager { */ int removeContact(String address); + /** + * Set a contact's nickname + * + * @param address the address of the contact to be updates + * @param name the new name + */ + int setContactName(String address, String name); + /** * Approves a subscription request from another user. */ - void approveSubscription(String address); + void approveSubscription(in Contact address); /** * Declines a subscription request from another user. */ - void declineSubscription(String address); + void declineSubscription(in Contact address); /** * Blocks a contact. The ContactListListener will be notified when the contact is blocked diff --git a/src/info/guardianproject/otr/app/im/IDataListener.aidl b/src/info/guardianproject/otr/app/im/IDataListener.aidl new file mode 100644 index 000000000..93fb42e76 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/IDataListener.aidl @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2011 The Guardian Project + */ + +package info.guardianproject.otr.app.im; + +interface IDataListener { + + void onTransferComplete(boolean outgoing, String offerId, String from, String url, String type, String fileLocalPath); + + void onTransferFailed(boolean outgoing, String offerId, String from, String url, String reason); + + void onTransferProgress(boolean outgoing, String offerId, String from, String url, float f); + + boolean onTransferRequested(String offerId, String from, String to, String transferUrl); + +} diff --git a/src/info/guardianproject/otr/app/im/IImConnection.aidl b/src/info/guardianproject/otr/app/im/IImConnection.aidl index 91137b443..51aaf8e13 100755 --- a/src/info/guardianproject/otr/app/im/IImConnection.aidl +++ b/src/info/guardianproject/otr/app/im/IImConnection.aidl @@ -62,6 +62,12 @@ interface IImConnection { long getProviderId(); long getAccountId(); + /** + * Whether this connection is going over Tor or not. + * @return boolean + */ + boolean isUsingTor(); + void acceptInvitation(long id); void rejectInvitation(long id); void sendHeartbeat(); diff --git a/src/info/guardianproject/otr/app/im/IRemoteImService.aidl b/src/info/guardianproject/otr/app/im/IRemoteImService.aidl index aff544535..764ce02fb 100755 --- a/src/info/guardianproject/otr/app/im/IRemoteImService.aidl +++ b/src/info/guardianproject/otr/app/im/IRemoteImService.aidl @@ -61,11 +61,6 @@ interface IRemoteImService { void dismissChatNotification(long providerId, String username); - /** - * Get OTR Key Manager - */ - IOtrKeyManager getOtrKeyManager(String accountId); - /** * do it */ @@ -75,4 +70,19 @@ interface IRemoteImService { * cleaning up rpocess **/ void setKillProcessOnStop (boolean killProcess); + + /** + * get interface to keymanager/store singleton + **/ + IOtrKeyManager getOtrKeyManager (); + + /** + * use debug log to logcat out + **/ + void enableDebugLogging (boolean debugOn); + + /** + * update settings from OTR + **/ + void updateStateFromSettings (); } diff --git a/src/info/guardianproject/otr/app/im/ISubscriptionListener.aidl b/src/info/guardianproject/otr/app/im/ISubscriptionListener.aidl index 16a7277fe..ac785b243 100755 --- a/src/info/guardianproject/otr/app/im/ISubscriptionListener.aidl +++ b/src/info/guardianproject/otr/app/im/ISubscriptionListener.aidl @@ -28,19 +28,19 @@ oneway interface ISubscriptionListener { * * @see info.guardianproject.otr.app.im.engine.SubscriptionRequestListener#onSubScriptionRequest(Contact from) */ - void onSubScriptionRequest(in Contact from); + void onSubScriptionRequest(in Contact from, long providerId, long accountId); /** * Called when the request is approved by user. * * @see info.guardianproject.otr.app.im.engine.SubscriptionRequestListener#onSubscriptionApproved(String contact) */ - void onSubscriptionApproved(String contact); + void onSubscriptionApproved(in Contact from, long providerId, long accountId); /** * Called when a subscription request is declined. * * @see info.guardianproject.otr.app.im.engine.ContactListListener#onSubscriptionDeclined(String contact) */ - void onSubscriptionDeclined(String contact); + void onSubscriptionDeclined(in Contact from, long providerId, long accountId); } diff --git a/src/info/guardianproject/otr/app/im/app/AccountActivity.java b/src/info/guardianproject/otr/app/im/app/AccountActivity.java index 37e29709a..d7c95bb3c 100644 --- a/src/info/guardianproject/otr/app/im/app/AccountActivity.java +++ b/src/info/guardianproject/otr/app/im/app/AccountActivity.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -15,22 +15,8 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.onionkit.ui.OrbotHelper; -import info.guardianproject.otr.IOtrKeyManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.engine.ImConnection; -import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; -import info.guardianproject.otr.app.im.plugin.xmpp.XmppConnection; -import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.provider.Imps.AccountColumns; -import info.guardianproject.otr.app.im.provider.Imps.AccountStatusColumns; -import info.guardianproject.otr.app.im.provider.Imps.CommonPresenceColumns; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; -import android.app.AlertDialog; import android.app.Activity; +import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.ContentUris; @@ -42,9 +28,9 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.os.RemoteException; import android.provider.BaseColumns; +import android.support.v7.app.ActionBarActivity; import android.text.Editable; import android.text.SpannableString; import android.text.TextUtils; @@ -52,22 +38,44 @@ import android.text.util.Linkify; import android.util.Log; import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; -import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import com.google.zxing.integration.android.IntentIntegrator; + +import info.guardianproject.onionkit.ui.OrbotHelper; +import info.guardianproject.otr.IOtrChatSession; +import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppConnection; +import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.provider.Imps.AccountColumns; +import info.guardianproject.otr.app.im.provider.Imps.AccountStatusColumns; +import info.guardianproject.otr.app.im.provider.Imps.CommonPresenceColumns; +import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.util.LogCleaner; +import info.guardianproject.util.XmppUriHelper; -public class AccountActivity extends Activity { +import java.util.HashMap; +import java.util.Locale; + +public class AccountActivity extends ActionBarActivity { public static final String TAG = "AccountActivity"; private static final String ACCOUNT_URI_KEY = "accountUri"; @@ -82,13 +90,15 @@ public class AccountActivity extends Activity { private static final int ACCOUNT_PROVIDER_COLUMN = 1; private static final int ACCOUNT_USERNAME_COLUMN = 2; private static final int ACCOUNT_PASSWORD_COLUMN = 3; - + public final static String DEFAULT_SERVER_GOOGLE = "talk.l.google.com"; public final static String DEFAULT_SERVER_FACEBOOK = "chat.facebook.com"; - public final static String DEFAULT_SERVER_JABBERORG = "hermes.jabber.org"; + public final static String DEFAULT_SERVER_JABBERORG = "hermes2.jabber.org"; public final static String DEFAULT_SERVER_DUKGO = "dukgo.com"; public final static String ONION_JABBERCCC = "okj7xc6j2szr2y75.onion"; + public final static String ONION_CALYX = "ijeeynrc6x2uy5ob.onion"; + private static final String USERNAME_VALIDATOR = "[^a-z0-9\\.\\-_\\+]"; // private static final int ACCOUNT_KEEP_SIGNED_IN_COLUMN = 4; // private static final int ACCOUNT_LAST_LOGIN_STATE = 5; @@ -99,14 +109,14 @@ public class AccountActivity extends Activity { CheckBox mRememberPass; CheckBox mUseTor; Button mBtnSignIn; - Button mBtnDelete; - Spinner mSpinnerDomains; - + Button mBtnQrDisplay; + AutoCompleteTextView mSpinnerDomains; + Button mBtnAdvanced; TextView mTxtFingerprint; //Imps.ProviderSettings.QueryMap settings; - + boolean isEdit = false; boolean isSignedIn = false; @@ -115,84 +125,52 @@ public class AccountActivity extends Activity { int mPort = 0; private String mOriginalUserAccount = ""; - private final static int DEFAULT_PORT = 5222; - - IOtrKeyManager otrKeyManager; + IOtrChatSession mOtrChatSession; private SignInHelper mSignInHelper; private boolean mIsNewAccount = false; - + + private AsyncTask mCreateAccountTask = null; + @Override protected void onCreate(Bundle icicle) { + super.onCreate(icicle); - //getWindow().requestFeature(Window.FEATURE_LEFT_ICON); - setContentView(R.layout.account_activity); Intent i = getIntent(); - - mIsNewAccount = getIntent().getBooleanExtra("register", false); - + + mApp = (ImApp)getApplication(); + + String action = i.getAction(); + + if (i.hasExtra("isSignedIn")) + isSignedIn = i.getBooleanExtra("isSignedIn", false); + + + final ProviderDef provider; + mSignInHelper = new SignInHelper(this); - SignInHelper.Listener signInListener = new SignInHelper.Listener() { + SignInHelper.SignInListener signInListener = new SignInHelper.SignInListener() { + @Override public void connectedToService() { } + @Override public void stateChanged(int state, long accountId) { - if (state == ImConnection.LOGGING_IN || state == ImConnection.LOGGED_IN) + if (state == ImConnection.LOGGED_IN) { - mSignInHelper.goToAccount(accountId); - finish(); + // mSignInHelper.goToAccount(accountId); + // finish(); + isSignedIn = true; + } + else + { + isSignedIn = false; } - } - }; - mSignInHelper.setSignInListener(signInListener); - mEditUserAccount = (EditText) findViewById(R.id.edtName); - mEditUserAccount.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - checkUserChanged(); - } - }); - - mEditPass = (EditText) findViewById(R.id.edtPass); - - mEditPassConfirm = (EditText) findViewById(R.id.edtPassConfirm); - mSpinnerDomains = (Spinner) findViewById(R.id.spinnerDomains); - - if (mIsNewAccount) - { - mEditPassConfirm.setVisibility(View.VISIBLE); - mSpinnerDomains.setVisibility(View.VISIBLE); - mEditUserAccount.setHint(R.string.account_setup_new_username); - } - - mRememberPass = (CheckBox) findViewById(R.id.rememberPassword); - mUseTor = (CheckBox) findViewById(R.id.useTor); - - mBtnSignIn = (Button) findViewById(R.id.btnSignIn); - - if (mIsNewAccount) - mBtnSignIn.setText("Create Account"); - - mBtnAdvanced = (Button) findViewById(R.id.btnAdvanced); - mBtnDelete = (Button) findViewById(R.id.btnDelete); - - mRememberPass.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - updateWidgetState(); } - }); - - - mApp = (ImApp)getApplication(); - - String action = i.getAction(); + }; - if (i.hasExtra("isSignedIn")) - isSignedIn = i.getBooleanExtra("isSignedIn", false); - + mSignInHelper.setSignInListener(signInListener); - final ProviderDef provider; ContentResolver cr = getContentResolver(); @@ -206,24 +184,72 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { } } - if (Intent.ACTION_INSERT.equals(action)) { + if (Intent.ACTION_INSERT.equals(action) && uri.getScheme().equals("ima")) { + ImPluginHelper helper = ImPluginHelper.getInstance(this); + String authority = uri.getAuthority(); + String[] userpass_host = authority.split("@"); + String[] user_pass = userpass_host[0].split(":"); + mUserName = user_pass[0].toLowerCase(Locale.getDefault()); + String pass = user_pass[1]; + mDomain = userpass_host[1].toLowerCase(Locale.getDefault()); + mPort = 0; + final boolean regWithTor = i.getBooleanExtra("useTor", false); + + Cursor cursor = openAccountByUsernameAndDomain(cr); + boolean exists = cursor.moveToFirst(); + long accountId; + if (exists) { + accountId = cursor.getLong(0); + mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); + pass = cursor.getString(ACCOUNT_PASSWORD_COLUMN); + + setAccountKeepSignedIn(true); + mSignInHelper.activateAccount(mProviderId, accountId); + mSignInHelper.signIn(pass, mProviderId, accountId, true); + setResult(RESULT_OK); + cursor.close(); + finish(); + return; + + } else { + mProviderId = helper.createAdditionalProvider(helper.getProviderNames().get(0)); //xmpp FIXME + accountId = ImApp.insertOrUpdateAccount(cr, mProviderId, mUserName, pass); + mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); + mSignInHelper.activateAccount(mProviderId, accountId); + createNewAccount(mUserName, pass, accountId, regWithTor); + cursor.close(); + return; + } + + + + + } else if (Intent.ACTION_INSERT.equals(action)) { + + + setupUIPre(); + mOriginalUserAccount = ""; // TODO once we implement multiple IM protocols - mProviderId = ContentUris.parseId(i.getData()); + mProviderId = ContentUris.parseId(uri); provider = mApp.getProvider(mProviderId); if (provider != null) { setTitle(getResources().getString(R.string.add_account, provider.mFullName)); - + } else { finish(); } - + } else if (Intent.ACTION_EDIT.equals(action)) { + + + setupUIPre(); + if ((uri == null) || !Imps.Account.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) { LogCleaner.warn(ImApp.LOG_TAG, "Bad data"); return; @@ -251,22 +277,26 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mProviderId = cursor.getLong(ACCOUNT_PROVIDER_COLUMN); provider = mApp.getProvider(mProviderId); - Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - cr, mProviderId, false /* don't keep updated */, null /* no handler */); - - mOriginalUserAccount = cursor.getString(ACCOUNT_USERNAME_COLUMN) + "@" - + settings.getDomain(); - mEditUserAccount.setText(mOriginalUserAccount); - mEditPass.setText(cursor.getString(ACCOUNT_PASSWORD_COLUMN)); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); - mRememberPass.setChecked(!cursor.isNull(ACCOUNT_PASSWORD_COLUMN)); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, mProviderId, false /* don't keep updated */, null /* no handler */); - mUseTor.setChecked(settings.getUseTor()); - - mBtnDelete.setVisibility(View.VISIBLE); - - cursor.close(); - settings.close(); + try { + mOriginalUserAccount = cursor.getString(ACCOUNT_USERNAME_COLUMN) + "@" + + settings.getDomain(); + mEditUserAccount.setText(mOriginalUserAccount); + mEditPass.setText(cursor.getString(ACCOUNT_PASSWORD_COLUMN)); + mRememberPass.setChecked(!cursor.isNull(ACCOUNT_PASSWORD_COLUMN)); + mUseTor.setChecked(settings.getUseTor()); + mBtnQrDisplay.setVisibility(View.VISIBLE); + + mPort = settings.getPort(); + + } finally { + settings.close(); + cursor.close(); + } } else { @@ -275,6 +305,71 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { return; } + setupUIPost(); + + } + + private void setupUIPre () + { + ((ImApp)getApplication()).setAppTheme(this); + + setContentView(R.layout.account_activity); + + getSupportActionBar().setHomeButtonEnabled(true); + + mIsNewAccount = getIntent().getBooleanExtra("register", false); + + + mEditUserAccount = (EditText) findViewById(R.id.edtName); + mEditUserAccount.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + checkUserChanged(); + } + }); + + mEditPass = (EditText) findViewById(R.id.edtPass); + + mEditPassConfirm = (EditText) findViewById(R.id.edtPassConfirm); + mSpinnerDomains = (AutoCompleteTextView) findViewById(R.id.spinnerDomains); + + if (mIsNewAccount) + { + mEditPassConfirm.setVisibility(View.VISIBLE); + mSpinnerDomains.setVisibility(View.VISIBLE); + mEditUserAccount.setHint(R.string.account_setup_new_username); + + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, getResources().getStringArray(R.array.account_domains)); + mSpinnerDomains.setAdapter(adapter); + + } + + mRememberPass = (CheckBox) findViewById(R.id.rememberPassword); + mUseTor = (CheckBox) findViewById(R.id.useTor); + + + mBtnSignIn = (Button) findViewById(R.id.btnSignIn); + + if (mIsNewAccount) + mBtnSignIn.setText(R.string.btn_create_new_account); + + mBtnAdvanced = (Button) findViewById(R.id.btnAdvanced); + mBtnQrDisplay = (Button) findViewById(R.id.btnQR); + + mRememberPass.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + updateWidgetState(); + } + }); + + } + + private void setupUIPost () + { + Intent i = getIntent(); + if (isSignedIn) { mBtnSignIn.setText(getString(R.string.menu_sign_out)); mBtnSignIn.setBackgroundResource(R.drawable.btn_red); @@ -292,26 +387,27 @@ public void onClick(View v) { showAdvanced(); } }); - - mBtnDelete.setOnClickListener(new OnClickListener() + + + mBtnQrDisplay.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - - deleteAccount(); - finish(); - + + showQR(); + } - + }); + mBtnSignIn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { checkUserChanged(); - + if (mUseTor.isChecked()) { OrbotHelper oh = new OrbotHelper(AccountActivity.this); @@ -321,27 +417,32 @@ public void onClick(View v) { return; } } - + final String pass = mEditPass.getText().toString(); final String passConf = mEditPassConfirm.getText().toString(); final boolean rememberPass = mRememberPass.isChecked(); final boolean isActive = false; // TODO(miron) does this ever need to be true? ContentResolver cr = getContentResolver(); + final boolean useTor = mUseTor.isChecked(); if (mIsNewAccount) { - mDomain = (String)mSpinnerDomains.getSelectedItem(); + mDomain = mSpinnerDomains.getText().toString(); String fullUser = mEditUserAccount.getText().toString(); - + if (fullUser.indexOf("@")==-1) fullUser += '@' + mDomain; - + if (!parseAccount(fullUser)) { mEditUserAccount.selectAll(); mEditUserAccount.requestFocus(); return; } + + ImPluginHelper helper = ImPluginHelper.getInstance(AccountActivity.this); + mProviderId = helper.createAdditionalProvider(helper.getProviderNames().get(0)); //xmpp FIXME + } else { @@ -350,31 +451,31 @@ public void onClick(View v) { mEditUserAccount.requestFocus(); return; } + else + { + settingsForDomain(mDomain,mPort);//apply final settings + } } - final long accountId = ImApp.insertOrUpdateAccount(cr, mProviderId, mUserName, + + mAccountId = ImApp.insertOrUpdateAccount(cr, mProviderId, mUserName, rememberPass ? pass : null); - mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); - + mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, mAccountId); + //if remember pass is true, set the "keep signed in" property to true if (mIsNewAccount) { if (pass.equals(passConf)) { - createNewAccount(mUserName, pass); - ContentValues values = new ContentValues(); - values.put(AccountColumns.KEEP_SIGNED_IN, rememberPass ? 1 : 0); - getContentResolver().update(mAccountUri, values, null, null); - mSignInHelper.activateAccount(mProviderId, accountId); - //mSignInHelper.signIn(pass, mProviderId, accountId, isActive); - //isSignedIn = true; - //updateWidgetState(); - finish(); + setAccountKeepSignedIn(rememberPass); + + createNewAccount(mUserName, pass, mAccountId, useTor); + } else { - Toast.makeText(AccountActivity.this, "Your passwords do not match", Toast.LENGTH_SHORT).show(); + Toast.makeText(AccountActivity.this, getString(R.string.error_account_password_mismatch), Toast.LENGTH_SHORT).show(); } } else @@ -383,29 +484,35 @@ public void onClick(View v) { signOut(); isSignedIn = false; } else { - ContentValues values = new ContentValues(); - values.put(AccountColumns.KEEP_SIGNED_IN, rememberPass ? 1 : 0); - getContentResolver().update(mAccountUri, values, null, null); - + setAccountKeepSignedIn(rememberPass); + if (!mOriginalUserAccount.equals(mUserName + '@' + mDomain) && shouldShowTermOfUse(brandingRes)) { confirmTermsOfUse(brandingRes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mSignInHelper.signIn(pass, mProviderId, accountId, isActive); + mSignInHelper.signIn(pass, mProviderId, mAccountId, isActive); } }); } else { - mSignInHelper.signIn(pass, mProviderId, accountId, isActive); + + boolean hasKey = checkForKey (mUserName + '@' + mDomain); + + mSignInHelper.signIn(pass, mProviderId, mAccountId, isActive); } + isSignedIn = true; + setResult(RESULT_OK); + finish(); } updateWidgetState(); + + } - + } }); - + mUseTor.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -414,35 +521,88 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { }); updateWidgetState(); - + + if (i.hasExtra("title")) + { + String title = i.getExtras().getString("title"); + setTitle(title); + } + if (i.hasExtra("newuser")) { String newuser = i.getExtras().getString("newuser"); mEditUserAccount.setText(newuser); - + parseAccount(newuser); settingsForDomain(mDomain,mPort); - + } - + if (i.hasExtra("newpass")) { mEditPass.setText(i.getExtras().getString("newpass")); + mEditPass.setVisibility(View.GONE); mRememberPass.setChecked(true); + mRememberPass.setVisibility(View.GONE); } + if (i.getBooleanExtra("hideTor", false)) + { + mUseTor.setVisibility(View.GONE); + } } - + + private boolean checkForKey (String userid) + { + + try + { + OtrAndroidKeyManagerImpl otrKeyMan = OtrAndroidKeyManagerImpl.getInstance(AccountActivity.this); + String fp = otrKeyMan.getLocalFingerprint(userid); + + if (fp == null) + { + otrKeyMan.generateLocalKeyPair(userid); + fp = otrKeyMan.getLocalFingerprint(userid); + return true; + } + else + return true; + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"error checking for key",e); + return false; + } + + } + + private Cursor openAccountByUsernameAndDomain(ContentResolver cr) { + String clauses = Imps.Account.USERNAME + " = ? AND " + Imps.ProviderSettings.VALUE + " = ?"; + String args[] = new String[2]; + args[0] = mUserName; + args[1] = mDomain; + + String[] projection = { Imps.Account._ID }; + Cursor cursor = cr.query(Imps.Account.BY_DOMAIN_URI, projection, clauses, args, null); + return cursor; + } + @Override protected void onDestroy() { - + + if (mCreateAccountTask != null && (!mCreateAccountTask.isCancelled())) + { + mCreateAccountTask.cancel(true); + } + if (mSignInHelper != null) mSignInHelper.stop(); - + super.onDestroy(); } - + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK)) { @@ -450,20 +610,24 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { } return super.onKeyDown(keyCode, event); } - + private void updateUseTor(boolean useTor) { checkUserChanged(); - + OrbotHelper orbotHelper = new OrbotHelper(this); + + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), mProviderId, false /* don't keep updated */, null /* no handler */); + pCursor, cr, mProviderId, false /* don't keep updated */, null /* no handler */); if (useTor && (!orbotHelper.isOrbotInstalled())) { //Toast.makeText(this, "Orbot app is not installed. Please install from Google Play or from https://guardianproject.info/releases", Toast.LENGTH_LONG).show(); - + orbotHelper.promptToInstall(this); - + mUseTor.setChecked(false); settings.setUseTor(false); } @@ -471,11 +635,9 @@ private void updateUseTor(boolean useTor) { { settings.setUseTor(useTor); } - - settingsForDomain(settings.getDomain(),settings.getPort()); - + + settingsForDomain(settings.getDomain(),settings.getPort(),settings); settings.close(); - } /* private void getOTRKeyInfo() { @@ -509,59 +671,32 @@ private void getOTRKeyInfo() { }*/ private void checkUserChanged() { - String username = mEditUserAccount.getText().toString().trim(); + if (mEditUserAccount != null) + { + String username = mEditUserAccount.getText().toString().trim().toLowerCase(); - if ((!username.equals(mOriginalUserAccount)) && parseAccount(username)) { - //Log.i(TAG, "Username changed: " + mOriginalUserAccount + " != " + username); - settingsForDomain(mDomain, mPort); - mOriginalUserAccount = username; - + if ((!username.equals(mOriginalUserAccount)) && parseAccount(username)) { + //Log.i(TAG, "Username changed: " + mOriginalUserAccount + " != " + username); + settingsForDomain(mDomain, mPort); + mOriginalUserAccount = username; + + } } - - - + + } - + boolean parseAccount(String userField) { boolean isGood = true; String[] splitAt = userField.trim().split("@"); - mUserName = splitAt[0]; - mDomain = null; - mPort = 0; + mUserName = splitAt[0].toLowerCase(Locale.ENGLISH).replaceAll(USERNAME_VALIDATOR, ""); + mDomain = ""; + if (splitAt.length > 1) { - mDomain = splitAt[1].toLowerCase(); - String[] splitColon = mDomain.split(":"); - mDomain = splitColon[0]; - if (splitColon.length > 1) { - try { - mPort = Integer.parseInt(splitColon[1]); - } catch (NumberFormatException e) { - // TODO move these strings to strings.xml - isGood = false; - Toast.makeText( - AccountActivity.this, - "The port value '" + splitColon[1] - + "' after the : could not be parsed as a number!", - Toast.LENGTH_LONG).show(); - } - } + mDomain = splitAt[1].toLowerCase(Locale.ENGLISH); } - if (mDomain == null) { - isGood = false; - //Toast.makeText(AccountActivity.this, - // R.string.account_wizard_no_domain_warning, - // Toast.LENGTH_LONG).show(); - } - /*//removing requirement of a . in the domain - else if (mDomain.indexOf(".") == -1) { - isGood = false; - // Toast.makeText(AccountActivity.this, - // R.string.account_wizard_no_root_domain_warning, - // Toast.LENGTH_LONG).show(); - }*/ - return isGood; } @@ -571,53 +706,82 @@ else if (mDomain.indexOf(".") == -1) { */ void settingsForDomain(String domain,int port) { + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), mProviderId, false /* don't keep updated */, null /* no handler */); + pCursor, cr, mProviderId, false /* don't keep updated */, null /* no handler */); + + settingsForDomain(domain, port, settings); + + settings.close(); + + } + + private void settingsForDomain(String domain, int port, Imps.ProviderSettings.QueryMap settings) { + + + settings.setRequireTls(true); + settings.setTlsCertVerify(true); + settings.setAllowPlainAuth(false); + settings.setPort(port); if (domain.equals("gmail.com")) { // Google only supports a certain configuration for XMPP: // http://code.google.com/apis/talk/open_communications.html - + settings.setDoDnsSrv(false); - settings.setServer(DEFAULT_SERVER_GOOGLE); + settings.setServer(DEFAULT_SERVER_GOOGLE); //set the google connect server settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } - else if (mEditPass.getText().toString().startsWith(GTalkOAuth2.NAME)) + } + //mEditPass can be NULL if this activity is used in "headless" mode for auto account setup + else if (mEditPass != null && mEditPass.getText().toString().startsWith(GTalkOAuth2.NAME)) { - //this is not @gmail but IS a google account + // this is not @gmail but IS a google account settings.setDoDnsSrv(false); settings.setServer(DEFAULT_SERVER_GOOGLE); //set the google connect server settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); } else if (domain.equals("jabber.org")) { - settings.setDoDnsSrv(false); - settings.setServer(DEFAULT_SERVER_JABBERORG); + + if (settings.getUseTor()) + { + settings.setDoDnsSrv(false); + settings.setServer(DEFAULT_SERVER_JABBERORG); + } + settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); + } else if (domain.equals("facebook.com")) { - settings.setDoDnsSrv(false); + + if (settings.getUseTor()) + { + settings.setDoDnsSrv(false); + settings.setServer(DEFAULT_SERVER_FACEBOOK); + + } + settings.setDomain(DEFAULT_SERVER_FACEBOOK); - settings.setPort(DEFAULT_PORT); - settings.setServer(DEFAULT_SERVER_FACEBOOK); - settings.setRequireTls(true); //facebook TLS now seems to be on - settings.setTlsCertVerify(true); //but cert verify can still be funky - off by default - settings.setAllowPlainAuth(false); - } + } + else if (domain.equals("jabber.calyxinstitute.org")) { + + if (settings.getUseTor()) + { + settings.setDoDnsSrv(false); + settings.setServer(ONION_CALYX); + } + else + { + settings.setDoDnsSrv(false); + settings.setServer(""); + } + + settings.setDomain(domain); + } else if (domain.equals("jabber.ccc.de")) { - + if (settings.getUseTor()) - { + { settings.setDoDnsSrv(false); settings.setServer(ONION_JABBERCCC); } @@ -626,23 +790,19 @@ else if (domain.equals("jabber.ccc.de")) { settings.setDoDnsSrv(true); settings.setServer(""); } - + settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } + } else { - + settings.setDomain(domain); settings.setPort(port); - + //if use Tor, turn off DNS resolution, and set Server manually from Domain if (settings.getUseTor()) { settings.setDoDnsSrv(false); - + //if Tor is off, and the user has not provided any values here, set to the @domain if (settings.getServer() == null || settings.getServer().length() == 0) settings.setServer(domain); @@ -653,13 +813,10 @@ else if (settings.getServer() == null || settings.getServer().length() == 0) settings.setDoDnsSrv(true); settings.setServer(""); } - - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); + } - - + + settings.requery(); } void confirmTermsOfUse(BrandingResources res, DialogInterface.OnClickListener accept) { @@ -708,7 +865,7 @@ void signOut() { getContentResolver().update(mAccountUri, values, null, null); mApp = (ImApp)getApplication(); - + mApp.callWhenServiceConnected(mHandler, new Runnable() { @Override public void run() { @@ -742,19 +899,19 @@ void signOut(long providerId, long accountId) { Log.e(ImApp.LOG_TAG, "signout: caught ", ex); } finally { - Toast.makeText(this, - getString(R.string.signed_out_prompt, this.mEditUserAccount.getText()), - Toast.LENGTH_SHORT).show(); + // Toast.makeText(this, + // getString(R.string.signed_out_prompt, this.mEditUserAccount.getText()), + // Toast.LENGTH_SHORT).show(); isSignedIn = false; mBtnSignIn.setText(getString(R.string.sign_in)); mBtnSignIn.setBackgroundResource(R.drawable.btn_green); } } - - void createNewaccount (long accountId) + + void createNewaccount (long accountId) { - + ContentValues values = new ContentValues(2); values.put(AccountStatusColumns.PRESENCE_STATUS, CommonPresenceColumns.NEW_ACCOUNT); @@ -762,9 +919,9 @@ void createNewaccount (long accountId) String where = AccountStatusColumns.ACCOUNT + "=?"; getContentResolver().update(Imps.AccountStatus.CONTENT_URI, values, where, new String[] { Long.toString(accountId) }); - - - + + + } @Override @@ -789,13 +946,6 @@ void updateWidgetState() { mEditPass.setFocusable(goodUsername); mEditPass.setFocusableInTouchMode(goodUsername); - // enable keep sign in only when remember password is checked. - boolean rememberPass = mRememberPass.isChecked(); - if (rememberPass && !hasNameAndPassword) { - mRememberPass.setChecked(false); - rememberPass = false; - - } mRememberPass.setEnabled(hasNameAndPassword); mRememberPass.setFocusable(hasNameAndPassword); @@ -806,6 +956,10 @@ void updateWidgetState() { mBtnSignIn.setEnabled(hasNameAndPassword); mBtnSignIn.setFocusable(hasNameAndPassword); } + else + { + + } } private final TextWatcher mTextWatcher = new TextWatcher() { @@ -827,11 +981,13 @@ public void afterTextChanged(Editable s) { private void deleteAccount () { - - //need to delete + + //need to delete ((ImApp)getApplication()).deleteAccount(mAccountId, mProviderId); + + finish(); } - + private void showAdvanced() { checkUserChanged(); @@ -860,134 +1016,109 @@ public boolean onOptionsItemSelected(MenuItem item) { case android.R.id.home: finish(); return true; - - case R.id.menu_gen_key: - otrGenKey(); - return true; + case R.id.menu_account_delete: + deleteAccount(); + return true; } return super.onOptionsItemSelected(item); } - ProgressDialog pbarDialog; - - private void otrGenKey() { - - pbarDialog = new ProgressDialog(this); - - pbarDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - pbarDialog.setMessage(getString(R.string.otr_gen_key)); - pbarDialog.show(); - - KeyGenThread kgt = new KeyGenThread(); - kgt.start(); - - } - - private class KeyGenThread extends Thread { - - public KeyGenThread() { - + public void createNewAccount (final String usernameNew, final String passwordNew, final long newAccountId, final boolean useTor) + { + if (mCreateAccountTask != null && (!mCreateAccountTask.isCancelled())) + { + mCreateAccountTask.cancel(true); } - @Override - public void run() { + mCreateAccountTask = new AsyncTask() { - try { - if (otrKeyManager != null) { - otrKeyManager.generateLocalKeyPair(); + private ProgressDialog dialog; - } else { - Toast.makeText(AccountActivity.this, "OTR is not initialized yet", - Toast.LENGTH_SHORT).show(); - } - } catch (Exception e) { - Log.e("OTR", "could not gen local key pair", e); - } finally { - handler.sendEmptyMessage(0); + @Override + protected void onPreExecute() { + dialog = new ProgressDialog(AccountActivity.this); + dialog.setCancelable(true); + dialog.setMessage(getString(R.string.registering_new_account_)); + dialog.show(); } - } - - private Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { + protected String doInBackground(Void... params) { + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); - pbarDialog.dismiss(); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, mProviderId, false /* don't keep updated */, null /* no handler */); try { - if (otrKeyManager != null) { - String lFingerprint = otrKeyManager.getLocalFingerprint(); - mTxtFingerprint.setText(processFingerprint(lFingerprint)); - } - - } catch (Exception e) { - Log.e("OTR", "could not gen local key pair", e); - } - - } - }; - } - - private String processFingerprint(String fingerprint) { - StringBuffer out = new StringBuffer(); - - for (int n = 0; n < fingerprint.length(); n++) { - for (int i = n; n < i + 4; n++) { - out.append(fingerprint.charAt(n)); - } - - out.append(' '); - } + settings.setUseTor(useTor); + settingsForDomain(mDomain, mPort, settings); - return out.toString(); - } + HashMap aParams = new HashMap(); - public void createNewAccount (String usernameNew, String passwordNew) - { - - new AsyncTask() { - - @Override - protected String doInBackground(String... params) { - try { - - settingsForDomain(mDomain, mPort); - - Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), mProviderId, false /* don't keep updated */, null /* no handler */); - - XmppConnection xmppConn = new XmppConnection(AccountActivity.this); - xmppConn.registerAccount(settings, params[0], params[1]); - - settings.close(); + xmppConn.initUser(mProviderId, newAccountId); + xmppConn.registerAccount(settings, usernameNew, passwordNew, aParams); + // settings closed in registerAccount } catch (Exception e) { - LogCleaner.error(ImApp.LOG_TAG, "error registering new account", e); - + LogCleaner.error(ImApp.LOG_TAG, "error registering new account", e); + return e.getLocalizedMessage(); - + } finally { + + settings.close(); } + return null; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); - + + try + { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + dialog = null; + } + } + catch ( java.lang.IllegalArgumentException iae) + { + //dialog may not be attached to window if Activity was closed + } + if (result != null) { Toast.makeText(AccountActivity.this, "error creating account: " + result, Toast.LENGTH_LONG).show(); + //AccountActivity.this.setResult(Activity.RESULT_CANCELED); + //AccountActivity.this.finish(); + } + else + { + mSignInHelper.activateAccount(mProviderId, newAccountId); + mSignInHelper.signIn(passwordNew, mProviderId, newAccountId, true); + + AccountActivity.this.setResult(Activity.RESULT_OK); + AccountActivity.this.finish(); } - } - }.execute(usernameNew, passwordNew); - - + }.execute(); } - + public void showQR () + { + String localFingerprint = OtrAndroidKeyManagerImpl.getInstance(this).getLocalFingerprint(mOriginalUserAccount); + String uri = XmppUriHelper.getUri(mOriginalUserAccount, localFingerprint); + new IntentIntegrator(this).shareText(uri); + } + + private void setAccountKeepSignedIn(final boolean rememberPass) { + ContentValues values = new ContentValues(); + values.put(AccountColumns.KEEP_SIGNED_IN, rememberPass ? 1 : 0); + getContentResolver().update(mAccountUri, values, null, null); + } } diff --git a/src/info/guardianproject/otr/app/im/app/AccountAdapter.java b/src/info/guardianproject/otr/app/im/app/AccountAdapter.java new file mode 100644 index 000000000..69576325c --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/AccountAdapter.java @@ -0,0 +1,198 @@ +/** + * + */ +package info.guardianproject.otr.app.im.app; + +import java.util.ArrayList; +import java.util.List; + +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.provider.Imps; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.RemoteException; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class AccountAdapter extends CursorAdapter { + private LayoutInflater mInflater; + private int mResId; + private Cursor mStashCursor; + private AsyncTask> mBindTask; + private Listener mListener; + + public AccountAdapter(Activity context, + LayoutInflater.Factory factory, int resId) { + super(context, null, 0); + mInflater = LayoutInflater.from(context).cloneInContext(context); + mInflater.setFactory(factory); + mResId = resId; + } + + public void setListener(Listener listener) { + this.mListener = listener; + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + if(mBindTask != null) { + mBindTask.cancel(false); + mBindTask = null ; + } + + if (mStashCursor != null && (!mStashCursor.isClosed())) + mStashCursor.close(); + + mStashCursor = newCursor; + + if (mStashCursor != null) { + // Delay swapping in the cursor until we get the extra info + // List accountInfoList = getAccountInfoList(mStashCursor) ; + // runBindTask((Activity)mContext, accountInfoList); + } + return super.swapCursor(mStashCursor); + }; + + /** + * @param mStashCursor + * @return + */ + private List getAccountInfoList(Cursor cursor) { + List aiList = new ArrayList(); + cursor.moveToPosition(-1); + while( cursor.moveToNext() ) { + aiList.add( getAccountInfo(cursor)); + } + return aiList; + } + + static class AccountInfo { + int providerId; + String activeUserName; + int dbConnectionStatus; + int presenceStatus; + } + + static class AccountSetting { + String mProviderNameText; + String mSecondRowText; + boolean mSwitchOn; + String activeUserName; + int connectionStatus ; + + String domain; + String host; + int port; + boolean isTor; + + } + + AccountInfo getAccountInfo( Cursor cursor ) { + AccountInfo accountInfo = new AccountInfo(); + accountInfo.providerId = cursor.getInt(cursor.getColumnIndexOrThrow(Imps.Provider._ID)); + if (!cursor.isNull(cursor.getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_ID))) { + accountInfo.activeUserName = cursor.getString(cursor.getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_USERNAME)); + accountInfo.dbConnectionStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Imps.Provider.ACCOUNT_PRESENCE_STATUS)); + accountInfo.presenceStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Imps.Provider.ACCOUNT_CONNECTION_STATUS)); + } + return accountInfo; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // create a custom view, so we can manage it ourselves. Mainly, we want to + // initialize the widget views (by calling getViewById()) in newView() instead of in + // bindView(), which can be called more often. + ProviderListItem view = (ProviderListItem) mInflater.inflate(mResId, parent, false); + boolean showLongName = false; + view.init(cursor, showLongName); + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ((ProviderListItem) view).bindView(cursor); + + } + + private void runBindTask( final Activity context, final List accountInfoList ) { + final Resources resources = context.getResources(); + final ContentResolver resolver = context.getContentResolver(); + final ImApp mApp = (ImApp)context.getApplication(); + + // if called multiple times + if (mBindTask != null) + mBindTask.cancel(false); + // + + + mBindTask = new AsyncTask>() { + + @Override + protected List doInBackground(Void... params) { + List accountSettingList = new ArrayList(); + for( AccountInfo ai : accountInfoList ) { + accountSettingList.add( getAccountSettings(ai) ); + } + return accountSettingList; + } + + private AccountSetting getAccountSettings(AccountInfo ai) { + AccountSetting as = new AccountSetting(); + + + Cursor pCursor = resolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(ai.providerId)},null); + + if (pCursor != null) + { + Imps.ProviderSettings.QueryMap settings = + new Imps.ProviderSettings.QueryMap(pCursor, resolver, ai.providerId, false , null); + + as.connectionStatus = ai.dbConnectionStatus; + as.activeUserName = ai.activeUserName; + as.domain = settings.getDomain(); + as.host = settings.getServer(); + as.port = settings.getPort(); + as.isTor = settings.getUseTor(); + + IImConnection conn = mApp.getConnection(ai.providerId); + if (conn == null) { + as.connectionStatus = ImConnection.DISCONNECTED; + } else { + try { + as.connectionStatus = conn.getState(); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + settings.close(); + } + return as; + } + + @Override + protected void onPostExecute(List result) { + // store + mBindTask = null; + // swap + AccountAdapter.super.swapCursor(mStashCursor); + if (mListener != null) + mListener.onPopulate(); + } + }; + mBindTask.execute(); + } + + public interface Listener { + void onPopulate(); + } +} \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/app/AccountListActivity.java b/src/info/guardianproject/otr/app/im/app/AccountListActivity.java deleted file mode 100644 index e73f7263f..000000000 --- a/src/info/guardianproject/otr/app/im/app/AccountListActivity.java +++ /dev/null @@ -1,861 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.ICacheWordSubscriber; -import info.guardianproject.cacheword.SQLCipherOpenHelper; -import info.guardianproject.otr.OtrAndroidKeyManagerImpl; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; - -import java.io.IOException; -import java.util.List; - -import net.hockeyapp.android.CrashManager; -import net.hockeyapp.android.UpdateManager; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.ListView; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockListActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; -import com.github.espiandev.showcaseview.ShowcaseView; - -public class AccountListActivity extends SherlockListActivity implements View.OnCreateContextMenuListener, ICacheWordSubscriber, ProviderListItem.SignInManager, ShowcaseView.OnShowcaseEventListener { - - private static final String TAG = ImApp.LOG_TAG; - - private static final int ID_SIGN_IN = Menu.FIRST + 1; - private static final int ID_SIGN_OUT = Menu.FIRST + 2; - private static final int ID_EDIT_ACCOUNT = Menu.FIRST + 3; - private static final int ID_REMOVE_ACCOUNT = Menu.FIRST + 4; -// private static final int ID_SIGN_OUT_ALL = Menu.FIRST + 5; - private static final int ID_ADD_ACCOUNT = Menu.FIRST + 6; - private static final int ID_VIEW_CONTACT_LIST = Menu.FIRST + 7; - - private ProviderAdapter mAdapter; - private Cursor mProviderCursor; - private ImApp mApp; - private SimpleAlertHandler mHandler; - - private SignInHelper mSignInHelper; - - private CacheWordActivityHandler mCacheWord; - private ShowcaseView sv; - - private final static int SCAN_REQUEST_CODE = 7171; //otr key import scanning - - private static final String[] PROVIDER_PROJECTION = { - Imps.Provider._ID, - Imps.Provider.NAME, - Imps.Provider.FULLNAME, - Imps.Provider.CATEGORY, - Imps.Provider.ACTIVE_ACCOUNT_ID, - Imps.Provider.ACTIVE_ACCOUNT_USERNAME, - Imps.Provider.ACTIVE_ACCOUNT_PW, - Imps.Provider.ACTIVE_ACCOUNT_LOCKED, - Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, - Imps.Provider.ACCOUNT_PRESENCE_STATUS, - Imps.Provider.ACCOUNT_CONNECTION_STATUS - }; - - static final int PROVIDER_ID_COLUMN = 0; - static final int PROVIDER_NAME_COLUMN = 1; - static final int PROVIDER_FULLNAME_COLUMN = 2; - static final int PROVIDER_CATEGORY_COLUMN = 3; - static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; - static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; - static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; - static final int ACTIVE_ACCOUNT_LOCKED = 7; - static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; - static final int ACCOUNT_PRESENCE_STATUS = 9; - static final int ACCOUNT_CONNECTION_STATUS = 10; - - @Override - protected void onCreate(Bundle icicle) { - - ((ImApp)getApplication()).setAppTheme(this); - - super.onCreate(icicle); - - mCacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); - ((ImApp)getApplication()).setCacheWord(mCacheWord); - - ThemeableActivity.setBackgroundImage(this); - - mApp = (ImApp)getApplication(); - mHandler = new MyHandler(this); - mSignInHelper = new SignInHelper(this); - - ImPluginHelper.getInstance(this).loadAvailablePlugins(); - - ViewGroup godfatherView = (ViewGroup) this.getWindow().getDecorView(); - - // registerForContextMenu(getListView()); - - View emptyView = getLayoutInflater().inflate(R.layout.empty_account_view, godfatherView, false); - emptyView.setVisibility(View.GONE); - ((ViewGroup)getListView().getParent()).addView(emptyView); - - getListView().setEmptyView(emptyView); - emptyView.setOnClickListener(new OnClickListener() - { - - @Override - public void onClick(View arg0) { - - if (getListView().getCount() == 0) - { - showExistingAccountListDialog(); - } - - - } - - }); - - checkForUpdates(); - doShowcase(); - - getWindow().setBackgroundDrawableResource(R.drawable.bgcolor2); - } - - private void doShowcase () - { - ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions(); - co.hideOnClickOutside = true; - // sv = ShowcaseView.insertShowcaseView(getListView(), this, "Many of You!", "ChatSecure supports accounts on your favorite services, and your own hosted servers as well!", co); - - - // sv.setOnShowcaseEventListener(this); - } - - @Override - protected void onPause() { - mHandler.unregisterForBroadcastEvents(); - - mCacheWord.onPause(); - super.onPause(); - } - - @Override - protected void onDestroy() { - mSignInHelper.stop(); - - - super.onDestroy(); - } - - - - @Override - protected void onResume() { - - super.onResume(); - - mApp = (ImApp)getApplication(); - mApp.startImServiceIfNeed(); - mApp.setAppTheme(this); - - mHandler.registerForBroadcastEvents(); - mCacheWord.onResume(); - - if (!mCacheWord.isLocked()) - { - onCacheWordOpened(); - - } - - checkForCrashes(); - - } - - - - protected void openAccountAtPosition(int position) { - - mProviderCursor.moveToPosition(position); - - if (mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { - showExistingAccountListDialog(); - - } else { - - int state = mProviderCursor.getInt(ACCOUNT_CONNECTION_STATUS); - long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); - - - if (state == Imps.ConnectionStatus.OFFLINE) { - - Intent intent = getEditAccountIntent(); - startActivity(intent); - - } - else - { - gotoAccount(accountId); - } - - - } - - - } - - public void refreshAccountState () - { - mProviderCursor.moveToFirst(); - while (!mProviderCursor.isAfterLast()) - { - long cAccountId = mProviderCursor.getLong(this.ACTIVE_ACCOUNT_ID_COLUMN); - - try - { - IImConnection conn = mApp.getConnectionByAccount(cAccountId); - } - catch (Exception e){} - - mProviderCursor.moveToNext(); - } - } - - public void signIn(long accountId) { - if (accountId <= 0) { - Log.w(TAG, "signIn: account id is 0, bail"); - return; - } - - mProviderCursor.moveToFirst(); - while (!mProviderCursor.isAfterLast()) - { - long cAccountId = mProviderCursor.getLong(this.ACTIVE_ACCOUNT_ID_COLUMN); - - if (cAccountId == accountId) - break; - - mProviderCursor.moveToNext(); - } - - // Remember that the user signed in. - setKeepSignedIn(accountId, true); - - long providerId = mProviderCursor.getLong(PROVIDER_ID_COLUMN); - String password = mProviderCursor.getString(ACTIVE_ACCOUNT_PW_COLUMN); - - boolean isActive = false; // TODO(miron) - mSignInHelper.signIn(password, providerId, accountId, isActive); - - mProviderCursor.moveToPosition(-1); - } - - - protected void gotoAccount(long accountId) - { - - Intent intent = new Intent(this, NewChatActivity.class); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, accountId); - startActivity(intent); - - } - - private void handlePanic() { - - signOutAll (); - ((ImApp)getApplication()).forceStopImService(); - - } - - private void signOutAll() { - - if (mProviderCursor != null) - { - mProviderCursor.moveToPosition(-1); - - while (mProviderCursor.moveToNext()) - { - long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); - signOut(accountId); - } - - if (mCacheWord != null) - mCacheWord.manuallyLock(); - - finish(); - } - - } - - public void signOut(final long accountId) { - - - if (mProviderCursor.isAfterLast()) - { - mProviderCursor.moveToFirst(); - while (!mProviderCursor.isAfterLast()) - { - long cAccountId = mProviderCursor.getLong(this.ACTIVE_ACCOUNT_ID_COLUMN); - - if (cAccountId == accountId) - break; - - mProviderCursor.moveToNext(); - } - - - mProviderCursor.moveToPosition(-1); - } - - // Remember that the user signed out and do not auto sign in until they - // explicitly do so - setKeepSignedIn(accountId, false); - - try { - IImConnection conn = mApp.getConnectionByAccount(accountId); - if (conn != null) { - conn.logout(); - } - } catch (Exception ex) { - Log.e(TAG, "signOut failed", ex); - } - } - - private void setKeepSignedIn(final long accountId, boolean signin) { - Uri mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); - ContentValues values = new ContentValues(); - values.put(Imps.Account.KEEP_SIGNED_IN, signin); - getContentResolver().update(mAccountUri, values, null, null); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.accounts_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_sign_out_all: - signOutAll(); - return true; - case R.id.menu_existing_account: - showExistingAccountListDialog(); - return true; - case R.id.menu_create_account: - showSetupAccountForm(helper.getProviderNames().get(0), null, null, true); - return true; - case R.id.menu_settings: - Intent sintent = new Intent(this, SettingActivity.class); - startActivity(sintent); - return true; - case R.id.menu_import_keys: - importKeyStore(); - return true; - // case R.id.menu_exit: - // signOutAndKillProcess(); - - // return true; - } - return super.onOptionsItemSelected(item); - } - - private String[] mAccountList; - private String mNewUser; - - private ImPluginHelper helper = ImPluginHelper.getInstance(this); - - private void importKeyStore () - { - boolean doKeyStoreImport = OtrAndroidKeyManagerImpl.checkForKeyImport(getIntent(), this); - - } - private void showExistingAccountListDialog() { - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.account_select_type); - - List listProviders = helper.getProviderNames(); - - mAccountList = new String[listProviders.size()+1]; - - int i = 0; - mAccountList[i] = getString(R.string.google_account); - i++; - - for (String providerName : listProviders) - mAccountList[i++] = providerName; - - - builder.setItems(mAccountList, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int pos) { - - if (pos == 0) //google accounts based on xmpp - { - showGoogleAccountListDialog(); - } - else - { - //otherwise support the actual plugin-type - showSetupAccountForm(mAccountList[pos],null, null, false); - } - } - }); - AlertDialog dialog = builder.create(); - dialog.show(); - - } - - -private Handler mHandlerGoogleAuth = new Handler () -{ - - @Override - public void handleMessage(Message msg) { - - super.handleMessage(msg); - - Log.d(TAG,"Got handler callback from auth: " + msg.what); - } - -}; - - - private void showGoogleAccountListDialog() { - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.account_select_type); - - Account[] accounts = AccountManager.get(this).getAccountsByType(GTalkOAuth2.TYPE_GOOGLE_ACCT); - - mAccountList = new String[accounts.length]; - - for (int i = 0; i < mAccountList.length; i++) - mAccountList[i] = accounts[i].name; - - builder.setItems(mAccountList, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int pos) { - - mNewUser = mAccountList[pos]; - Thread thread = new Thread () - { - public void run () - { - //get the oauth token - - //don't store anything just make sure it works! - String password = GTalkOAuth2.NAME + ':' + GTalkOAuth2.getGoogleAuthTokenAllow(mNewUser, getApplicationContext(), AccountListActivity.this,mHandlerGoogleAuth); - - //use the XMPP type plugin for google accounts, and the .NAME "X-GOOGLE-TOKEN" as the password - showSetupAccountForm(helper.getProviderNames().get(0), mNewUser,password, false); - } - }; - thread.start(); - - } - }); - AlertDialog dialog = builder.create(); - dialog.show(); - - } - - public void showSetupAccountForm (String providerType, String username, String token, boolean createAccount) - { - long providerId = helper.createAdditionalProvider(providerType);//xmpp - ((ImApp)getApplication()).resetProviderSettings(); //clear cached provider list - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_INSERT); - - intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); - intent.addCategory(ImApp.IMPS_CATEGORY); - - if (username != null) - intent.putExtra("newuser", username); - - if (token != null) - intent.putExtra("newpass", token); - - intent.putExtra("register", createAccount); - - startActivity(intent); - } - - - - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - /* - AdapterView.AdapterContextMenuInfo info; - try { - info = (AdapterView.AdapterContextMenuInfo) menuInfo; - } catch (ClassCastException e) { - Log.e(TAG, "bad menuInfo", e); - return; - } - - Cursor providerCursor = (Cursor) getListAdapter().getItem(info.position); - menu.setHeaderTitle(providerCursor.getString(PROVIDER_FULLNAME_COLUMN)); - - if (providerCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { - menu.add(0, ID_ADD_ACCOUNT, 0, R.string.menu_edit_account); - menu.add(0, ID_REMOVE_ACCOUNT, 0, R.string.menu_remove_account).setIcon( - android.R.drawable.ic_menu_delete); - return; - } - - long providerId = providerCursor.getLong(PROVIDER_ID_COLUMN); - boolean isLoggingIn = isSigningIn(providerCursor); - boolean isLoggedIn = isSignedIn(providerCursor); - - BrandingResources brandingRes = mApp.getBrandingResource(providerId); - //menu.add(0, ID_VIEW_CONTACT_LIST, 0, - // brandingRes.getString(BrandingResourceIDs.STRING_MENU_CONTACT_LIST)); - - if (isLoggedIn) { - menu.add(0, ID_SIGN_OUT, 0, R.string.menu_sign_out).setIcon( - android.R.drawable.ic_menu_close_clear_cancel); - } else if (isLoggingIn) { - menu.add(0, ID_SIGN_OUT, 0, R.string.menu_cancel_signin).setIcon( - android.R.drawable.ic_menu_close_clear_cancel); - } else { - menu.add(0, ID_SIGN_IN, 0, R.string.sign_in) - // TODO .setIcon(info.guardianproject.otr.app.internal.R.drawable.ic_menu_login) - ; - } - - boolean isAccountEditable = providerCursor.getInt(ACTIVE_ACCOUNT_LOCKED) == 0; - if (isAccountEditable && !isLoggingIn && !isLoggedIn) { - menu.add(0, ID_EDIT_ACCOUNT, 0, R.string.menu_edit_account).setIcon( - android.R.drawable.ic_menu_edit); - menu.add(0, ID_REMOVE_ACCOUNT, 0, R.string.menu_remove_account).setIcon( - android.R.drawable.ic_menu_delete); - }*/ - } - - - @SuppressWarnings("deprecation") - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - /* - AdapterView.AdapterContextMenuInfo info; - try { - info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - } catch (ClassCastException e) { - Log.e(TAG, "bad menuInfo", e); - return false; - } - - long providerId = info.id; - Cursor providerCursor = (Cursor) getListAdapter().getItem(info.position); - long accountId = providerCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); - - mProviderCursor.moveToPosition(info.position); - Intent intent = new Intent(getContext(), NewChatActivity.class); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - getContext().startActivity(intent); - - switch (item.getItemId()) { - case ID_EDIT_ACCOUNT: { - startActivity(getEditAccountIntent()); - return true; - } - - case ID_REMOVE_ACCOUNT: { - Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); - getContentResolver().delete(accountUri, null, null); - Uri providerUri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId); - getContentResolver().delete(providerUri, null, null); - // Requery the cursor to force refreshing screen - providerCursor.requery(); - return true; - } - - case ID_VIEW_CONTACT_LIST: { - Intent intent = getViewContactsIntent(); - startActivity(intent); - return true; - } - case ID_ADD_ACCOUNT: { - - showNewAccountListDialog(); - - return true; - } - - case ID_SIGN_IN: { - signIn(accountId); - return true; - } - - case ID_SIGN_OUT: { - // TODO: progress bar - signOut(accountId); - return true; - } - - } - */ - - return false; - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - - - - } - - /* - Intent getCreateAccountIntent() { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_INSERT); - - long providerId = mProviderCursor.getLong(PROVIDER_ID_COLUMN); - intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); - intent.addCategory(getProviderCategory(mProviderCursor)); - return intent; - }*/ - - Intent getEditAccountIntent() { - Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( - Imps.Account.CONTENT_URI, mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN))); - intent.addCategory(ImApp.IMPS_CATEGORY); - return intent; - } - - Intent getViewContactsIntent() { - Intent intent = new Intent(this, ContactListActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN)); - return intent; - } - - /* - Intent getViewChatsIntent() { - Intent intent = new Intent(this, ChatListActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN)); - return intent; - }*/ - - /* - private String getProviderCategory(Cursor cursor) { - return cursor.getString(PROVIDER_CATEGORY_COLUMN); - }*/ - - static void log(String msg) { - Log.d(TAG, "[LandingPage]" + msg); - } - - private class ProviderListItemFactory implements LayoutInflater.Factory { - public View onCreateView(String name, Context context, AttributeSet attrs) { - if (name != null && name.equals(ProviderListItem.class.getName())) { - return new ProviderListItem(context, AccountListActivity.this, AccountListActivity.this); - } - return null; - } - } - - private final class ProviderAdapter extends CursorAdapter { - - public ProviderAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - - mInflater = LayoutInflater.from(context).cloneInContext(context); - mInflater.setFactory(new ProviderListItemFactory()); - } - - private LayoutInflater mInflater; - - - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // create a custom view, so we can manage it ourselves. Mainly, we want to - // initialize the widget views (by calling getViewById()) in newView() instead of in - // bindView(), which can be called more often. - ProviderListItem view = (ProviderListItem) mInflater.inflate(R.layout.account_view, - parent, false); - view.init(cursor, false); - return view; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ((ProviderListItem) view).bindView(cursor); - } - } - - - private final class MyHandler extends SimpleAlertHandler { - - public MyHandler(Activity activity) { - super(activity); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == ImApp.EVENT_CONNECTION_DISCONNECTED) { - promptDisconnectedEvent(msg); - } - super.handleMessage(msg); - } - } - - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == SCAN_REQUEST_CODE) - { - try - { - boolean success = OtrAndroidKeyManagerImpl.getInstance(this).handleKeyScanResult(requestCode, resultCode, data, this, null); - - if (success) - { - Toast.makeText(this, R.string.successfully_imported_otr_keyring, Toast.LENGTH_SHORT).show(); - } - else - { - Toast.makeText(this, R.string.otr_keyring_not_imported_please_check_the_file_exists_in_the_proper_format_and_location, Toast.LENGTH_SHORT).show(); - - } - } - catch (IOException ioe) - { - Toast.makeText(this, R.string.otr_keyring_not_imported_please_check_the_file_exists_in_the_proper_format_and_location, Toast.LENGTH_SHORT).show(); - - LogCleaner.error(ImApp.LOG_TAG, "problem importing key",ioe); - } - } - } - - - @Override - public void onCacheWordUninitialized() { - // this will never happen - } - - - @Override - public void onCacheWordLocked() { - - Intent intent = new Intent(getApplicationContext(), WelcomeActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - - - @Override - public void onCacheWordOpened() { - - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - - int defaultTimeout = Integer.parseInt(prefs.getString("pref_cacheword_timeout",ImApp.DEFAULT_TIMEOUT_CACHEWORD)); - - mCacheWord.setTimeoutMinutes(defaultTimeout); - - - String pkey = SQLCipherOpenHelper.encodeRawKey(mCacheWord.getEncryptionKey()); - - - initProviderCursor (pkey); - - } - - - private void initProviderCursor (String pkey) - { - Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; - - uri = uri.buildUpon().appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pkey).build(); - - mProviderCursor = managedQuery(uri, PROVIDER_PROJECTION, - Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, - new String[] { ImApp.IMPS_CATEGORY } /* selection args */, - Imps.Provider.DEFAULT_SORT_ORDER); - - mAdapter = new ProviderAdapter(this, mProviderCursor, true); - setListAdapter(mAdapter); - - refreshAccountState(); - } - - private void checkForCrashes() { - CrashManager.register(this, ImApp.HOCKEY_APP_ID); - } - - private void checkForUpdates() { - // Remove this for store builds! - UpdateManager.register(this, ImApp.HOCKEY_APP_ID); - } - - @Override - public void onShowcaseViewHide(ShowcaseView showcaseView) { - - } - - @Override - public void onShowcaseViewShow(ShowcaseView showcaseView) { - - } -} diff --git a/src/info/guardianproject/otr/app/im/app/AccountSettingsActivity.java b/src/info/guardianproject/otr/app/im/app/AccountSettingsActivity.java index 71a1dfb17..36cef0e1d 100644 --- a/src/info/guardianproject/otr/app/im/app/AccountSettingsActivity.java +++ b/src/info/guardianproject/otr/app/im/app/AccountSettingsActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,8 +17,6 @@ package info.guardianproject.otr.app.im.app; -import com.actionbarsherlock.app.SherlockPreferenceActivity; - import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.service.ImServiceConstants; @@ -26,6 +24,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; @@ -33,7 +32,7 @@ import android.util.Log; import android.widget.Toast; -public class AccountSettingsActivity extends SherlockPreferenceActivity implements +public class AccountSettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { private long mProviderId; @@ -48,7 +47,8 @@ public class AccountSettingsActivity extends SherlockPreferenceActivity implemen private void setInitialValues() { ContentResolver cr = getContentResolver(); - Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(cr, + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, cr, mProviderId, false /* keep updated */, null /* no handler */); String text; @@ -64,7 +64,7 @@ private void setInitialValues() { } text = Integer.toString(settings.getPort()); mPort.setText(text); - if (text != null && settings.getPort() != 5222 && settings.getPort() != 0) { + if (text != null && settings.getPort() != 0) { mPort.setSummary(text); } text = settings.getServer(); @@ -82,8 +82,12 @@ private void setInitialValues() { /* save the preferences in Imps so they are accessible everywhere */ @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { - final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), mProviderId, false /* don't keep updated */, null /* no handler */); + + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, mProviderId, true /* don't keep updated */, null /* no handler */); String value; if (key.equals("pref_account_xmpp_resource")) { @@ -95,13 +99,13 @@ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { mXmppResource.setText(value); // In case it was trimmed } } else if (key.equals("pref_account_xmpp_resource_prio")) { - + value = prefs.getString(key, "20"); try { settings.setXmppResourcePrio(Integer.parseInt(value)); } catch (NumberFormatException nfe) { Toast.makeText(getBaseContext(), - "Priority must be a number in the range [0 .. 127]", Toast.LENGTH_SHORT) + getString(R.string.error_account_settings_priority), Toast.LENGTH_SHORT) .show(); } mXmppResourcePrio.setSummary(value); @@ -110,10 +114,10 @@ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { try { settings.setPort(Integer.parseInt(value)); } catch (NumberFormatException nfe) { - Toast.makeText(getBaseContext(), "Port number must be a number", Toast.LENGTH_SHORT) + Toast.makeText(getBaseContext(), getString(R.string.error_account_settings_port), Toast.LENGTH_SHORT) .show(); } - if (settings.getPort() != 5222 && settings.getPort() != 0) + if (settings.getPort() != 0) mPort.setSummary(value); } else if (key.equals("pref_account_server")) { value = prefs.getString(key, null); @@ -132,9 +136,9 @@ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { } else if (key.equals("pref_security_do_dns_srv")) { settings.setDoDnsSrv(prefs.getBoolean(key, true)); } - + settings.setShowMobileIndicator(true); - + settings.close(); } @Override @@ -166,6 +170,7 @@ protected void onResume() { super.onResume(); setInitialValues(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); } diff --git a/src/info/guardianproject/otr/app/im/app/AccountWizardActivity.java b/src/info/guardianproject/otr/app/im/app/AccountWizardActivity.java new file mode 100644 index 000000000..7a56c6a41 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/AccountWizardActivity.java @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package info.guardianproject.otr.app.im.app; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ContentUris; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; +import com.viewpagerindicator.PageIndicator; + +import info.guardianproject.onionkit.ui.OrbotHelper; +import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.OtrDebugLogger; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; +import info.guardianproject.otr.app.im.provider.Imps; +import java.util.List; +import java.util.UUID; + +public class AccountWizardActivity extends ThemeableActivity { + private static final String TAG = ImApp.LOG_TAG; + + private AccountAdapter mAdapter; + private ImApp mApp; + private SimpleAlertHandler mHandler; + + private SignInHelper mSignInHelper; + + private static final int REQUEST_CREATE_ACCOUNT = RESULT_FIRST_USER + 2; + + private static final String GOOGLE_ACCOUNT = "google_account"; + private static final String EXISTING_ACCOUNT = "existing_account"; + private static final String NEW_ACCOUNT = "new_account"; + private static final String BONJOUR_ACCOUNT = "bonjour_account"; + private static final String BURNER_ACCOUNT = "secret_account"; + + /** + * The pager widget, which handles animation and allows swiping horizontally to access previous + * and next wizard steps. + */ + private ViewPager mPager; + + /** + * The pager adapter, which provides the pages to the view pager widget. + */ + private PagerAdapter mPagerAdapter; + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + protected void onCreate(Bundle icicle) { + + if(Build.VERSION.SDK_INT >= 11) + getWindow().requestFeature(Window.FEATURE_ACTION_BAR); + + super.onCreate(icicle); + + getSupportActionBar().hide(); + + mApp = (ImApp)getApplication(); + mApp.maybeInit(this); + mApp.setAppTheme(this); + + if (!Imps.isUnlocked(this)) { + onDBLocked(); + return; + } + + mHandler = new MyHandler(this); + mSignInHelper = new SignInHelper(this); + + buildAccountList(); + + setContentView(R.layout.account_list_activity); + + // Instantiate a ViewPager and a PagerAdapter. + mPager = (ViewPager) findViewById(R.id.pager); + mPagerAdapter = new WizardPagerAdapter(getSupportFragmentManager()); + mPager.setAdapter(mPagerAdapter); + + PageIndicator titleIndicator = (PageIndicator) findViewById(R.id.indicator); + titleIndicator.setViewPager(mPager); + } + + AccountAdapter getAdapter() { + return mAdapter; + } + + @Override + protected void onPause() { + mHandler.unregisterForBroadcastEvents(); + + super.onPause(); + + } + + @Override + protected void onDestroy() { + if (mSignInHelper != null) // if !Imps.isUnlocked(this) + mSignInHelper.stop(); + + if (mAdapter != null) + mAdapter.swapCursor(null); + + + unbindDrawables(findViewById(R.id.RootView)); + System.gc(); + + super.onDestroy(); + } + + private void unbindDrawables(View view) { + if (view != null) + { + if (view.getBackground() != null) { + view.getBackground().setCallback(null); + } + if (view instanceof ViewGroup) { + for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { + unbindDrawables(((ViewGroup) view).getChildAt(i)); + } + ((ViewGroup) view).removeAllViews(); + } + } + } + + + + @Override + protected void onResume() { + + super.onResume(); + + mHandler.registerForBroadcastEvents(); + + } + + + protected void gotoChats() + { + Intent intent = new Intent(this, NewChatActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra("showaccounts", true); + startActivity(intent); + finish(); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + return true; + } + + private String[][] mAccountList; + private String mNewUser; + + private ImPluginHelper helper = ImPluginHelper.getInstance(this); + + + Account[] mGoogleAccounts; + + private void buildAccountList () + { + int i = 0; + int accountProviders = 0; + + mGoogleAccounts = AccountManager.get(this).getAccountsByType(GTalkOAuth2.TYPE_GOOGLE_ACCT); + + if (mGoogleAccounts.length > 0) { + accountProviders = 5; //potentialProviders + google + create account + burner + + mAccountList = new String[accountProviders][3]; + + mAccountList[i][0] = getString(R.string.i_want_to_chat_using_my_google_account); + mAccountList[i][1] = getString(R.string.account_google_full); + mAccountList[i][2] = GOOGLE_ACCOUNT; + i++; + } else { + accountProviders = 4;//listProviders.size() + 2; //potentialProviders + create account + burner + + mAccountList = new String[accountProviders][3]; + } + + + mAccountList[i][0] = getString(R.string.i_have_an_existing_xmpp_account); + mAccountList[i][1] = getString(R.string.account_existing_full); + mAccountList[i][2] = EXISTING_ACCOUNT; + i++; + + mAccountList[i][0] = getString(R.string.i_need_a_new_account); + mAccountList[i][1] = getString(R.string.account_new_full); + mAccountList[i][2] = NEW_ACCOUNT; + i++; + + mAccountList[i][0] = getString(R.string.i_want_to_chat_on_my_local_wifi_network_bonjour_zeroconf_); + mAccountList[i][1] = getString(R.string.account_wifi_full); + mAccountList[i][2] = BONJOUR_ACCOUNT; + i++; + + mAccountList[i][0] = getString(R.string.i_need_a_burner_one_time_throwaway_account_); + mAccountList[i][1] = getString(R.string.account_burner_full); + mAccountList[i][2] = BURNER_ACCOUNT; + i++; + + } + + private Handler mHandlerGoogleAuth = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + } + }; + + private void addGoogleAccount () + { + // mNewUser = newUser; + AlertDialog.Builder builderSingle = new AlertDialog.Builder( + this); + // builderSingle.setTitle("Select One Name:-"); + final ArrayAdapter arrayAdapter = new ArrayAdapter( + this, + android.R.layout.select_dialog_singlechoice); + + for (Account gAccount : mGoogleAccounts) + arrayAdapter.add(gAccount.name); + + builderSingle.setNegativeButton(R.string.cancel, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + builderSingle.setAdapter(arrayAdapter, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + + mNewUser = arrayAdapter.getItem(which); + + Thread thread = new Thread () + { + @Override + public void run () + { + //get the oauth token + + //don't store anything just make sure it works! + String password = GTalkOAuth2.NAME + ':' + GTalkOAuth2.getGoogleAuthTokenAllow(mNewUser, getApplicationContext(), AccountWizardActivity.this,mHandlerGoogleAuth); + + //use the XMPP type plugin for google accounts, and the .NAME "X-GOOGLE-TOKEN" as the password + showSetupAccountForm(helper.getProviderNames().get(0), mNewUser,password, false, getString(R.string.google_account),false); + } + }; + thread.start(); + + } + }); + builderSingle.show(); + } + + + public void showSetupAccountForm (String providerType, String username, String token, boolean createAccount, String formTitle, boolean hideTor) + { + long providerId = helper.createAdditionalProvider(providerType);//xmpp + // mApp.resetProviderSettings(); //clear cached provider list + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_INSERT); + + intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); + intent.addCategory(ImApp.IMPS_CATEGORY); + + if (username != null) + intent.putExtra("newuser", username); + + if (token != null) + intent.putExtra("newpass", token); + + if (formTitle != null) + intent.putExtra("title", formTitle); + + intent.putExtra("hideTor", hideTor); + + intent.putExtra("register", createAccount); + + startActivityForResult(intent,REQUEST_CREATE_ACCOUNT); + } + + public void createBurnerAccount () + { + + OrbotHelper oh = new OrbotHelper(this); + if (!oh.isOrbotInstalled()) + { + oh.promptToInstall(this); + return; + } + else if (!oh.isOrbotRunning()) + { + oh.requestOrbotStart(this); + return; + } + + //need to generate proper IMA url for account setup + String regUser = java.util.UUID.randomUUID().toString().substring(0,10).replace('-','a'); + String regPass = UUID.randomUUID().toString().substring(0,16); + String regDomain = "jabber.calyxinstitute.org"; + Uri uriAccountData = Uri.parse("ima://" + regUser + ':' + regPass + '@' + regDomain); + + Intent intent = new Intent(this, AccountActivity.class); + intent.setAction(Intent.ACTION_INSERT); + intent.setData(uriAccountData); + intent.putExtra("useTor", true); + startActivityForResult(intent,REQUEST_CREATE_ACCOUNT); + + + } + + private final class MyHandler extends SimpleAlertHandler { + + public MyHandler(Activity activity) { + super(activity); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == ImApp.EVENT_CONNECTION_DISCONNECTED) { + promptDisconnectedEvent(msg); + } + super.handleMessage(msg); + } + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == IntentIntegrator.REQUEST_CODE) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, + data); + if (scanResult != null) { + OtrAndroidKeyManagerImpl.handleKeyScanResult(scanResult.getContents(), this); + } + } + else if (requestCode == REQUEST_CREATE_ACCOUNT) + { + // if (resultCode == RESULT_OK) + // { + gotoChats(); + // } + } + } + + + public void onDBLocked() { + + Intent intent = new Intent(getApplicationContext(), WelcomeActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + finish(); + } + + public static class WizardPageFragment extends Fragment { + + private TextView mAccountInfo = null; + private TextView mAccountDetail = null; + + private Button mButtonAddAccount = null; + private String mAccountInfoText = null; + private String mAccountDetailText = null; + private String mButtonText = null; + private OnClickListener mOcl = null; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup rootView = (ViewGroup) inflater.inflate( + R.layout.account_wizard_slider, container, false); + + mAccountInfo = (TextView)rootView.findViewById(R.id.lblAccountTypeInfo); + mAccountDetail = (TextView)rootView.findViewById(R.id.lblAccountTypeDetail); + + mButtonAddAccount = (Button)rootView.findViewById(R.id.btnAddAccount); + + if (mButtonText != null) + mButtonAddAccount.setText(mButtonText); + + mAccountInfo.setText(mAccountInfoText); + mAccountDetail.setText(mAccountDetailText); + mButtonAddAccount.setOnClickListener(mOcl); + + setRetainInstance(true); + + return rootView; + } + + public void setAccountInfo (String accountInfoText, String accountDetailText, String mButtonText) + { + mAccountInfoText = accountInfoText; + mAccountDetailText = accountDetailText; + } + + public void setOnClickListener(OnClickListener ocl) + { + mOcl = ocl; + } + + } + + /** + * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in + * sequence. + */ + private class WizardPagerAdapter extends FragmentStatePagerAdapter { + public WizardPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(final int pos) { + WizardPageFragment wpf = new WizardPageFragment(); + wpf.setAccountInfo(mAccountList[pos][0],mAccountList[pos][1],null); + wpf.setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + String accountType = mAccountList[pos][2]; + if (TextUtils.equals(accountType, EXISTING_ACCOUNT)) + { + //otherwise support the actual plugin-type + showSetupAccountForm(helper.getProviderNames().get(0),null, null, false,helper.getProviderNames().get(0),false); + } + else if (TextUtils.equals(accountType, BONJOUR_ACCOUNT)) + { + String username = ""; + String passwordPlaceholder = "password";//zeroconf doesn't need a password + showSetupAccountForm(helper.getProviderNames().get(1),username,passwordPlaceholder, false,helper.getProviderNames().get(1),true); + } + else if (TextUtils.equals(accountType, NEW_ACCOUNT)) + { + showSetupAccountForm(helper.getProviderNames().get(0), null, null, true, null,false); + } + else if (TextUtils.equals(accountType, BURNER_ACCOUNT)) + { + createBurnerAccount(); + } + else if (TextUtils.equals(accountType, GOOGLE_ACCOUNT)) + { + addGoogleAccount(); + } + else + throw new IllegalArgumentException("Mystery account type!"); + } + + }); + + return wpf; + } + + @Override + public int getCount() { + return mAccountList.length; + } + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + Window window = getWindow(); + window.setFormat(PixelFormat.RGBA_8888); + } +} diff --git a/src/info/guardianproject/otr/app/im/app/AccountsFragment.java b/src/info/guardianproject/otr/app/im/app/AccountsFragment.java new file mode 100644 index 000000000..8b8ecfeb3 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/AccountsFragment.java @@ -0,0 +1,201 @@ +package info.guardianproject.otr.app.im.app; + +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.provider.Imps; +import android.app.Activity; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ListView; + +public class AccountsFragment extends ListFragment implements ProviderListItem.SignInManager { + + private FragmentActivity mActivity; + private int mAccountLayoutView; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + mActivity = (FragmentActivity)activity; + + mAccountLayoutView = R.layout.account_view; + if (mActivity instanceof NewChatActivity) + mAccountLayoutView = R.layout.account_view_sidebar; + + + initProviderCursor(); + + + } + + @Override + public void onDetach() { + super.onDetach(); + mActivity = null; + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + // TODO Auto-generated method stub + super.onViewCreated(view, savedInstanceState); + } + + private void initProviderCursor() + { + final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + mAdapter = new AccountAdapter(mActivity, new ProviderListItemFactory(), mAccountLayoutView); + setListAdapter(mAdapter); + + mActivity.getSupportLoaderManager().initLoader(ACCOUNT_LOADER_ID, null, new LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + CursorLoader loader = new CursorLoader(mActivity, uri, PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + loader.setUpdateThrottle(100l); + + return loader; + } + + @Override + public void onLoadFinished(Loader loader, Cursor newCursor) { + mAdapter.swapCursor(newCursor); + } + + @Override + public void onLoaderReset(Loader loader) { + mAdapter.swapCursor(null); + + } + }); + + } + + + private class ProviderListItemFactory implements LayoutInflater.Factory { + public View onCreateView(String name, Context context, AttributeSet attrs) { + if (name != null && name.equals(ProviderListItem.class.getName())) { + return new ProviderListItem(context, mActivity, AccountsFragment.this); + } + return null; + } + } + + + public void signIn(long accountId) { + if (accountId <= 0) { + return; + } + Cursor cursor = mAdapter.getCursor(); + + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + long cAccountId = cursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + + if (cAccountId == accountId) + break; + + cursor.moveToNext(); + } + + // Remember that the user signed in. + setKeepSignedIn(accountId, true); + + long providerId = cursor.getLong(PROVIDER_ID_COLUMN); + String password = cursor.getString(ACTIVE_ACCOUNT_PW_COLUMN); + + boolean isActive = false; // TODO(miron) + + new SignInHelper(mActivity).signIn(password, providerId, accountId, isActive); + + cursor.moveToPosition(-1); + } + + + public void signOut(final long accountId) { + // Remember that the user signed out and do not auto sign in until they + // explicitly do so + setKeepSignedIn(accountId, false); + + try { + IImConnection conn = ((ImApp)mActivity.getApplication()).getConnectionByAccount(accountId); + if (conn != null) { + conn.logout(); + } + } catch (Exception ex) { + } + } + + private void setKeepSignedIn(final long accountId, boolean signin) { + Uri mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); + ContentValues values = new ContentValues(); + values.put(Imps.Account.KEEP_SIGNED_IN, signin); + mActivity.getContentResolver().update(mAccountUri, values, null, null); + } + + AccountAdapter mAdapter; + + private static final String[] PROVIDER_PROJECTION = { + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.CATEGORY, + Imps.Provider.ACTIVE_ACCOUNT_ID, + Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + Imps.Provider.ACTIVE_ACCOUNT_PW, + Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + Imps.Provider.ACCOUNT_PRESENCE_STATUS, + Imps.Provider.ACCOUNT_CONNECTION_STATUS + }; + + static final int PROVIDER_ID_COLUMN = 0; + static final int PROVIDER_NAME_COLUMN = 1; + static final int PROVIDER_FULLNAME_COLUMN = 2; + static final int PROVIDER_CATEGORY_COLUMN = 3; + static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; + static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; + static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; + static final int ACTIVE_ACCOUNT_LOCKED = 7; + static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; + static final int ACCOUNT_PRESENCE_STATUS = 9; + static final int ACCOUNT_CONNECTION_STATUS = 10; + + private static final int ACCOUNT_LOADER_ID = 1000; + + } \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/app/ActiveChatListView.java b/src/info/guardianproject/otr/app/im/app/ActiveChatListView.java deleted file mode 100644 index 3a05dea41..000000000 --- a/src/info/guardianproject/otr/app/im/app/ActiveChatListView.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open - * Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatSessionListener; -import info.guardianproject.otr.app.im.IChatSessionManager; -import info.guardianproject.otr.app.im.IContactListManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.adapter.ChatSessionListenerAdapter; -import info.guardianproject.otr.app.im.app.adapter.ConnectionListenerAdapter; -import info.guardianproject.otr.app.im.engine.ContactListManager; -import info.guardianproject.otr.app.im.engine.ImErrorInfo; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ContentUris; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.util.AttributeSet; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.LinearLayout; -import android.widget.ListView; - -public class ActiveChatListView extends LinearLayout { - - Activity mScreen; - // IImConnection mConn; - SimpleAlertHandler mHandler; - Context mContext; - private final IChatSessionListener mChatListListener; - - ListView mChatList; - private ChatListAdapter mAdapter; - private boolean mAutoRefresh = true; - private final ConnectionListenerAdapter mConnectionListener; - - public ActiveChatListView(Context screen, AttributeSet attrs) { - super(screen, attrs); - mContext = screen; - mScreen = (Activity) screen; - mHandler = new SimpleAlertHandler(mScreen); - mChatListListener = new MyChatSessionListener(mHandler); - - mConnectionListener = new ConnectionListenerAdapter(mHandler) { - @Override - public void onConnectionStateChange(IImConnection connection, int state, - ImErrorInfo error) { - - } - }; - } - - @Override - public boolean isInEditMode() { - return true; - } - - - - private class MyChatSessionListener extends ChatSessionListenerAdapter { - public MyChatSessionListener(SimpleAlertHandler handler) { - super(); - } - - @Override - public void onChatSessionCreated(IChatSession session) { - super.onChatSessionCreated(session); - mAdapter.startAutoRequery(); - } - - @Override - public void onChatSessionCreateError(String name, ImErrorInfo error) { - super.onChatSessionCreateError(name, error); - } - - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mChatList = (ListView) findViewById(R.id.chatsList); - mChatList.setOnItemClickListener(mOnClickListener); - } - - public ListView getListView() { - return mChatList; - } - - public void setConnection(IImConnection conn) { - - if (conn != null) { - unregisterListeners(conn); - } - - if (conn != null) { - - registerListeners(conn); - - if (mAdapter == null) { - mAdapter = new ChatListAdapter(mScreen); - mAdapter.changeConnection(); - mChatList.setAdapter(mAdapter); - mChatList.setOnScrollListener(mAdapter); - } - - // mAdapter.changeConnection(); - - -// try { -// IChatSessionManager listMgr = conn.getChatSessionManager(); - mAdapter.startAutoRequery(); - -// } catch (RemoteException e) { -// Log.e(ImApp.LOG_TAG, "Service died!"); -// } - } - - } - - - - void startChat(Cursor c) { - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - - long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); - IImConnection conn = ((ImApp)mScreen.getApplication()).getConnection(providerId); - - - try { - - if (conn != null) - { - IChatSessionManager manager = conn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session == null) { - manager.createChatSession(username); - } - } - - Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); - Intent intent = new Intent(Intent.ACTION_VIEW, data); - intent.addCategory(ImApp.IMPS_CATEGORY); - mScreen.startActivity(intent); - setAutoRefreshContacts(false); - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "error starting chat",e); - } - - clearFocusIfEmpty(c); - } - } - - private void clearFocusIfEmpty(Cursor c) { - // clear focus if there's only one item so that it would focus on the - // "empty" item after the contact removed. - if (c.getCount() == 1) { - clearFocus(); - } - } - - - public void endChat(Cursor c, IImConnection conn) { - if (c != null) { - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - - try { - IChatSessionManager manager = conn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session != null) { - session.leave(); - } - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - } - - clearFocusIfEmpty(c); - } - } - - - public void viewContactPresence(Cursor c) { - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - mScreen.startActivity(i); - } - } - - - public boolean isConversationAtPosition(long packedPosition) { - /* - int type = ExpandableListView.getPackedPositionType(packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); - return (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) - && mAdapter.isPosForOngoingConversation(groupPosition);*/ - return true; - } - - public boolean isConversationSelected() { - long pos = mChatList.getSelectedItemPosition(); - return isConversationAtPosition(pos); - } - - /* - public boolean isContactsLoaded() { - - if (mConn != null) - { - try { - IContactListManager manager = mConn.getContactListManager(); - return (manager.getState() == ContactListManager.LISTS_LOADED); - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(); - return false; - } - } - return false; - }*/ - - /* - void removeContact(Cursor c) { - if (c == null) { - mHandler.showAlert(R.string.error, R.string.select_contact); - } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - try { - IContactListManager manager = mConn.getContactListManager(); - int res = manager.removeContact(address); - if (res != ImErrorInfo.NO_ERROR) { - mHandler.showAlert(R.string.error, - ErrorResUtils.getErrorRes(getResources(), res, address)); - } - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(); - } - } - }; - Resources r = getResources(); - - new AlertDialog.Builder(mContext).setTitle(R.string.confirm) - .setMessage(r.getString(R.string.confirm_delete_contact, nickname)) - .setPositiveButton(R.string.yes, confirmListener) // default button - .setNegativeButton(R.string.no, null).setCancelable(false).show(); - - clearFocusIfEmpty(c); - } - }*/ - - - /* - void blockContact(Cursor c) { - if (c == null) { - mHandler.showAlert(R.string.error, R.string.select_contact); - } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - try { - IContactListManager manager = mConn.getContactListManager(); - int res = manager.blockContact(address); - if (res != ImErrorInfo.NO_ERROR) { - mHandler.showAlert(R.string.error, - ErrorResUtils.getErrorRes(getResources(), res, address)); - } - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(); - } - } - }; - - Resources r = getResources(); - - new AlertDialog.Builder(mContext).setTitle(R.string.confirm) - .setMessage(r.getString(R.string.confirm_block_contact, nickname)) - .setPositiveButton(R.string.yes, confirmListener) // default button - .setNegativeButton(R.string.no, null).setCancelable(false).show(); - clearFocusIfEmpty(c); - } - }*/ - - - private void registerListeners(IImConnection conn) { - - - try { - IChatSessionManager chatManager = conn.getChatSessionManager(); - chatManager.registerChatSessionListener(mChatListListener); - conn.registerConnectionListener(mConnectionListener); - - - } - catch (RemoteException e) { - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "error registering listeners",e); - } - - } - - private void unregisterListeners(IImConnection conn) { - - try { - IChatSessionManager chatManager = conn.getChatSessionManager(); - chatManager.unregisterChatSessionListener(mChatListListener); - } - catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "error unreg listener",e); - } - - } - - private final OnItemClickListener mOnClickListener = new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView view, View itemView, int position, long id) { - ListView chatList = (ListView) view; - - Cursor cursor = (Cursor) chatList.getAdapter().getItem(position); - - int subscriptionType = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_TYPE); - int subscriptionStatus = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_STATUS); - if ((subscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM) - && (subscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)) { - long providerId = cursor.getLong(ContactView.COLUMN_CONTACT_PROVIDER); - String username = cursor.getString(ContactView.COLUMN_CONTACT_USERNAME); - Intent intent = new Intent(ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION, - ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id)); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); - mScreen.startActivity(intent); - } else { - startChat(cursor); - } - - } - }; - - static class SavedState extends BaseSavedState { - int[] mExpandedGroups; - - SavedState(Parcelable superState, int[] expandedGroups) { - super(superState); - mExpandedGroups = expandedGroups; - } - - private SavedState(Parcel in) { - super(in); - mExpandedGroups = in.createIntArray(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeIntArray(mExpandedGroups); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return superState; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - super.onRestoreInstanceState(state); - } - - protected void setAutoRefreshContacts(boolean isRefresh) { - mAutoRefresh = isRefresh; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - if (mAutoRefresh) { - super.onLayout(changed, l, t, r, b); - } - } -} diff --git a/src/info/guardianproject/otr/app/im/app/AddContactActivity.java b/src/info/guardianproject/otr/app/im/app/AddContactActivity.java index 4660d97f2..01df2873a 100644 --- a/src/info/guardianproject/otr/app/im/app/AddContactActivity.java +++ b/src/info/guardianproject/otr/app/im/app/AddContactActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,48 +17,51 @@ package info.guardianproject.otr.app.im.app; -import static android.provider.Contacts.ContactMethods.CONTENT_EMAIL_URI; - +import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.app.im.IContactList; +import info.guardianproject.otr.app.im.IContactListManager; +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; -import info.guardianproject.otr.app.im.plugin.ImpsConfigNames; import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.util.XmppUriHelper; import java.util.List; +import java.util.Map; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.IContactList; -import info.guardianproject.otr.app.im.IContactListManager; -import info.guardianproject.otr.app.im.IImConnection; - -import android.app.Activity; import android.content.ContentResolver; -import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Contacts.ContactMethods; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v7.app.ActionBarActivity; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.util.Rfc822Token; import android.text.util.Rfc822Tokenizer; +import android.util.AttributeSet; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; import android.widget.Button; import android.widget.MultiAutoCompleteTextView; -import android.widget.ResourceCursorAdapter; -import android.widget.SimpleCursorAdapter; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; -public class AddContactActivity extends Activity { +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +public class AddContactActivity extends ActionBarActivity { + private static final String TAG = "AddContactActivity"; private static final String[] CONTACT_LIST_PROJECTION = { Imps.ContactList._ID, Imps.ContactList.NAME, }; @@ -67,58 +70,122 @@ public class AddContactActivity extends Activity { private MultiAutoCompleteTextView mAddressList; private Spinner mListSpinner; Button mInviteButton; + Button mScanButton; ImApp mApp; SimpleAlertHandler mHandler; - private long mProviderId; - private long mAccountId; - private String mDefaultDomain; + private Cursor mCursorProviders; + private long mProviderId, mAccountId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mApp = (ImApp)getApplication(); + mApp.setAppTheme(this); mHandler = new SimpleAlertHandler(this); - resolveIntent(getIntent()); setContentView(R.layout.add_contact_activity); - BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); + BrandingResources brandingRes = mApp.getBrandingResource(0); setTitle(brandingRes.getString(BrandingResourceIDs.STRING_ADD_CONTACT_TITLE)); TextView label = (TextView) findViewById(R.id.input_contact_label); label.setText(brandingRes.getString(BrandingResourceIDs.STRING_LABEL_INPUT_CONTACT)); mAddressList = (MultiAutoCompleteTextView) findViewById(R.id.email); - mAddressList.setAdapter(new EmailAddressAdapter(this)); mAddressList.setTokenizer(new Rfc822Tokenizer()); mAddressList.addTextChangedListener(mTextWatcher); mListSpinner = (Spinner) findViewById(R.id.choose_list); - Cursor c = queryContactLists(); - int initSelection = searchInitListPos(c, - getIntent().getStringExtra(ImServiceConstants.EXTRA_INTENT_LIST_NAME)); - SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, - android.R.layout.simple_spinner_item, c, new String[] { Imps.ContactList.NAME }, - new int[] { android.R.id.text1 }); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mListSpinner.setAdapter(adapter); - mListSpinner.setSelection(initSelection); + setupAccountSpinner(); mInviteButton = (Button) findViewById(R.id.invite); mInviteButton.setText(brandingRes.getString(BrandingResourceIDs.STRING_BUTTON_ADD_CONTACT)); mInviteButton.setOnClickListener(mButtonHandler); mInviteButton.setEnabled(false); + + mScanButton = (Button) findViewById(R.id.scan); + mScanButton.setOnClickListener(mScanHandler); + + Intent intent = getIntent(); + String scheme = intent.getScheme(); + if (TextUtils.equals(scheme, "xmpp")) + { + addContactFromUri(intent.getData()); + } + } + + + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mCursorProviders != null && (!mCursorProviders.isClosed())) + mCursorProviders.close(); + } - private Cursor queryContactLists() { - Uri uri = Imps.ContactList.CONTENT_URI; - uri = ContentUris.withAppendedId(uri, mProviderId); - uri = ContentUris.withAppendedId(uri, mAccountId); - Cursor c = managedQuery(uri, CONTACT_LIST_PROJECTION, null, null, null); - return c; + + + private void setupAccountSpinner () + { + final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + mCursorProviders = managedQuery(uri, PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + + SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, + android.R.layout.simple_spinner_item, mCursorProviders, + new String[] { + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + }, + new int[] { android.R.id.text1 }); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + // TODO Something is causing the managedQuery() to return null, use null guard for now + if (mCursorProviders != null && mCursorProviders.getCount() > 0) + { + mCursorProviders.moveToFirst(); + mProviderId = mCursorProviders.getLong(PROVIDER_ID_COLUMN); + mAccountId = mCursorProviders.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + } + + mListSpinner.setAdapter(adapter); + mListSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView arg0, View arg1, + int arg2, long arg3) { + if (mCursorProviders == null) + return; + mCursorProviders.moveToPosition(arg2); + mProviderId = mCursorProviders.getLong(PROVIDER_ID_COLUMN); + mAccountId = mCursorProviders.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + } + + @Override + public void onNothingSelected(AdapterView arg0) { + // TODO Auto-generated method stub + + } + }); + + } + + public class ProviderListItemFactory implements LayoutInflater.Factory { + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + if (name != null && name.equals(ProviderListItem.class.getName())) { + return new ProviderListItem(context, AddContactActivity.this, null); + } + return null; + } + } private int searchInitListPos(Cursor c, String listName) { @@ -134,11 +201,22 @@ private int searchInitListPos(Cursor c, String listName) { return 0; } - private void resolveIntent(Intent intent) { - mProviderId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1); - mAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, -1); - mDefaultDomain = Imps.ProviderSettings.getStringValue(getContentResolver(), mProviderId, - ImpsConfigNames.DEFAULT_DOMAIN); + private String getDomain (long providerId) + { + //mDefaultDomain = Imps.ProviderSettings.getStringValue(getContentResolver(), mProviderId, + // ImpsConfigNames.DEFAULT_DOMAIN); + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(providerId)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, providerId, false /* don't keep updated */, null /* no handler */); + + String domain = settings.getDomain();//get domain of current user + + settings.close(); + pCursor.close(); + + return domain; } void inviteBuddies() { @@ -147,29 +225,43 @@ void inviteBuddies() { IImConnection conn = mApp.getConnection(mProviderId); IContactList list = getContactList(conn); if (list == null) { - Log.e(ImApp.LOG_TAG, " can't find given contact list:" - + getSelectedListName()); + // Log.e(ImApp.LOG_TAG, " can't find given contact list:" + // + getSelectedListName()); finish(); } else { boolean fail = false; + String username = null; + for (Rfc822Token recipient : recipients) { - String username = recipient.getAddress(); - if (mDefaultDomain != null && username.indexOf('@') == -1) { - username = username + "@" + mDefaultDomain; + username = recipient.getAddress(); + if (username.indexOf('@') == -1) { + username = username + "@" + getDomain(mProviderId); } if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("addContact:" + username); } + int res = list.addContact(username); if (res != ImErrorInfo.NO_ERROR) { fail = true; mHandler.showAlert(R.string.error, ErrorResUtils.getErrorRes(getResources(), res, username)); } + } // close the screen if there's no error. if (!fail) { - finish(); + + if (username != null) + { + Intent intent=new Intent(); + intent.putExtra(ContactsPickerActivity.EXTRA_RESULT_USERNAME, username); + intent.putExtra(ContactsPickerActivity.EXTRA_RESULT_PROVIDER, mProviderId); + setResult(RESULT_OK, intent); + finish(); + } + + } } } catch (RemoteException ex) { @@ -184,7 +276,8 @@ private IContactList getContactList(IImConnection conn) { try { IContactListManager contactListMgr = conn.getContactListManager(); - String listName = getSelectedListName(); + String listName = "";//getSelectedListName(); + if (!TextUtils.isEmpty(listName)) { return contactListMgr.getContactList(listName); } else { @@ -208,10 +301,11 @@ private IContactList getContactList(IImConnection conn) { } } + /** private String getSelectedListName() { Cursor c = (Cursor) mListSpinner.getSelectedItem(); return (c == null) ? null : c.getString(CONTACT_LIST_NAME_COLUMN); - } + }*/ private View.OnClickListener mButtonHandler = new View.OnClickListener() { public void onClick(View v) { @@ -223,6 +317,14 @@ public void run() { } }; + + private View.OnClickListener mScanHandler = new View.OnClickListener() { + public void onClick(View v) { + new IntentIntegrator(AddContactActivity.this).initiateScan(); + + } + }; + private TextWatcher mTextWatcher = new TextWatcher() { public void afterTextChanged(Editable s) { mInviteButton.setEnabled(s.length() != 0); @@ -241,49 +343,67 @@ private static void log(String msg) { Log.d(ImApp.LOG_TAG, " " + msg); } - static class EmailAddressAdapter extends ResourceCursorAdapter { - public static final int DATA_INDEX = 1; - - private static final String SORT_ORDER = "people.name, contact_methods.data"; - private ContentResolver mContentResolver; - - private static final String[] PROJECTION = { ContactMethods._ID, // 0 - ContactMethods.DATA // 1 - }; - - public EmailAddressAdapter(Context context) { - super(context, android.R.layout.simple_dropdown_item_1line, null); - mContentResolver = context.getContentResolver(); - } - - @Override - public final String convertToString(Cursor cursor) { - return cursor.getString(DATA_INDEX); + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { + if (resultCode == RESULT_OK) { + + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, + resultIntent); + if (scanResult != null) { + String qrContents = scanResult.getContents(); + if (!TextUtils.isEmpty(qrContents)) + addContactFromUri(Uri.parse(qrContents)); + } } + } - @Override - public final void bindView(View view, Context context, Cursor cursor) { - ((TextView) view).setText(cursor.getString(DATA_INDEX)); + /** + * Implement {@code xmpp:} URI parsing according to the RFC: http://tools.ietf.org/html/rfc5122 + * @param uri the URI to be parsed + */ + private void addContactFromUri(Uri uri) { + Log.i(TAG, "addContactFromUri: " + uri + " scheme: " + uri.getScheme()); + Map parsedUri = XmppUriHelper.parse(uri); + if (!parsedUri.containsKey(XmppUriHelper.KEY_ADDRESS)) { + Toast.makeText(this, "error parsing address: " + uri, Toast.LENGTH_LONG).show(); + return; } - - @Override - public Cursor runQueryOnBackgroundThread(CharSequence constraint) { - String where = null; - - if (constraint != null) { - String filter = DatabaseUtils.sqlEscapeString(constraint.toString() + '%'); - - StringBuilder s = new StringBuilder(); - s.append("(people.name LIKE "); - s.append(filter); - s.append(") OR (contact_methods.data LIKE "); - s.append(filter); - s.append(")"); - - where = s.toString(); - } - - return mContentResolver.query(CONTENT_EMAIL_URI, PROJECTION, where, null, SORT_ORDER); + String address = parsedUri.get(XmppUriHelper.KEY_ADDRESS); + this.mAddressList.setText(address); + this.mInviteButton.setBackgroundColor(R.drawable.btn_green); + + //store this for future use... ideally the user comes up as verified the first time! + String fingerprint = parsedUri.get(XmppUriHelper.KEY_OTR_FINGERPRINT); + if (!TextUtils.isEmpty(fingerprint)) { + Log.i(TAG, "fingerprint: " + fingerprint); + OtrAndroidKeyManagerImpl.getInstance(this).verifyUser(address, fingerprint); } } + + private static final String[] PROVIDER_PROJECTION = { + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.CATEGORY, + Imps.Provider.ACTIVE_ACCOUNT_ID, + Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + Imps.Provider.ACTIVE_ACCOUNT_PW, + Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + Imps.Provider.ACCOUNT_PRESENCE_STATUS, + Imps.Provider.ACCOUNT_CONNECTION_STATUS + + }; + + static final int PROVIDER_ID_COLUMN = 0; + static final int PROVIDER_NAME_COLUMN = 1; + static final int PROVIDER_FULLNAME_COLUMN = 2; + static final int PROVIDER_CATEGORY_COLUMN = 3; + static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; + static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; + static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; + static final int ACTIVE_ACCOUNT_LOCKED = 7; + static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; + static final int ACCOUNT_PRESENCE_STATUS = 9; + static final int ACCOUNT_CONNECTION_STATUS = 10; } diff --git a/src/info/guardianproject/otr/app/im/app/BlockedContactView.java b/src/info/guardianproject/otr/app/im/app/BlockedContactView.java index c4e2a92c8..2d9cc2a5f 100644 --- a/src/info/guardianproject/otr/app/im/app/BlockedContactView.java +++ b/src/info/guardianproject/otr/app/im/app/BlockedContactView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -45,7 +45,7 @@ public BlockedContactView(Context context, AttributeSet attrs) { protected void onFinishInflate() { super.onFinishInflate(); - mAvatar = (ImageView) findViewById(R.id.expandable_toggle_button); + mAvatar = (ImageView) findViewById(R.id.avatar); mBlockedIcon = (ImageView) findViewById(R.id.blocked); mLine1 = (TextView) findViewById(R.id.line1); mLine2 = (TextView) findViewById(R.id.line2); @@ -56,18 +56,21 @@ public void bind(Cursor cursor, Context mContext) { String username = cursor.getString(BlockedContactsActivity.USERNAME_COLUMN); String nickname = cursor.getString(BlockedContactsActivity.NICKNAME_COLUMN); - Drawable avatar = DatabaseUtils.getAvatarFromCursor(cursor, - BlockedContactsActivity.AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT); + Drawable avatar = null; + + try { avatar = DatabaseUtils.getAvatarFromCursor(cursor, + BlockedContactsActivity.AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT);} + catch(Exception e){} if (avatar != null) { mAvatar.setImageDrawable(avatar); } else { mAvatar.setImageResource(R.drawable.avatar_unknown); } - + /* ImApp app = ImApp.getApplication((Activity) mContext); - + BrandingResources brandingRes = app.getBrandingResource(providerId); mBlockedIcon.setImageDrawable(brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_BLOCK)); */ diff --git a/src/info/guardianproject/otr/app/im/app/BlockedContactsActivity.java b/src/info/guardianproject/otr/app/im/app/BlockedContactsActivity.java index c5428f3a0..737681027 100644 --- a/src/info/guardianproject/otr/app/im/app/BlockedContactsActivity.java +++ b/src/info/guardianproject/otr/app/im/app/BlockedContactsActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -69,7 +69,7 @@ protected void onCreate(Bundle icicle) { mApp = (ImApp)getApplication(); mApp.startImServiceIfNeed(); - + if (!resolveIntent()) { finish(); return; @@ -111,25 +111,25 @@ private boolean resolveIntent() { accountCursor.close(); return false; } - + long providerId = accountCursor.getLong(accountCursor .getColumnIndexOrThrow(Imps.Account.PROVIDER)); String username = accountCursor.getString(accountCursor .getColumnIndexOrThrow(Imps.Account.USERNAME)); - + BrandingResources brandingRes = mApp.getBrandingResource(providerId); getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_LOGO)); - + setTitle(getResources().getString(R.string.blocked_list_title, username)); accountCursor.close(); - + Cursor c = managedQuery(uri, PROJECTION, null, null, Imps.BlockedList.DEFAULT_SORT_ORDER); if (c == null) { warning("Database error when query " + uri); return false; } - + ListAdapter adapter = new BlockedContactsAdapter(c, this); setListAdapter(adapter); } @@ -138,7 +138,7 @@ private boolean resolveIntent() { //error parsing input Log.e(ImApp.LOG_TAG,"error parsing intent input",e); } - + return true; } diff --git a/src/info/guardianproject/otr/app/im/app/BootCompletedListener.java b/src/info/guardianproject/otr/app/im/app/BootCompletedListener.java index eb19dde11..caed08bb3 100644 --- a/src/info/guardianproject/otr/app/im/app/BootCompletedListener.java +++ b/src/info/guardianproject/otr/app/im/app/BootCompletedListener.java @@ -1,10 +1,18 @@ package info.guardianproject.otr.app.im.app; +import java.util.Date; + +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.otr.app.im.service.StatusBarNotifier; +import info.guardianproject.util.Debug; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.net.ConnectivityManager; +import android.database.Cursor; +import android.net.Uri; +import android.net.Uri.Builder; import android.preference.PreferenceManager; import android.util.Log; @@ -14,35 +22,48 @@ * including on boot. */ public class BootCompletedListener extends BroadcastReceiver { - + + private static final String LAST_BOOT_TRAIL_TAG = "last_boot"; public final static String BOOTFLAG = "BOOTFLAG"; - + @Override public synchronized void onReceive(Context context, Intent intent) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - boolean prefStartOnBoot = prefs.getBoolean("pref_start_on_boot", true); - + + Debug.recordTrail(context, LAST_BOOT_TRAIL_TAG, new Date()); + boolean prefStartOnBoot = prefs.getBoolean("pref_start_on_boot", true); + if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { + Debug.onServiceStart(); if (prefStartOnBoot) { - // ImApp.getApplication().startImServiceIfNeed(true); - } - else - { - /* - Log.d(ImApp.LOG_TAG,"killing auto-connect process"); - android.os.Process.killProcess(android.os.Process.myPid()); - System.exit(0); - */ + if (Imps.isUnencrypted(context)) + { + Log.d(ImApp.LOG_TAG, "autostart"); + + Intent serviceIntent = new Intent(); + serviceIntent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT); + serviceIntent.putExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, true); + context.startService(serviceIntent); + + Log.d(ImApp.LOG_TAG, "autostart done"); + } + else + { + //show unlock notification + StatusBarNotifier sbn = new StatusBarNotifier(context); + sbn.notifyLocked(); + } } } - - - + + + } - - + + + + } diff --git a/src/info/guardianproject/otr/app/im/app/BrandingResources.java b/src/info/guardianproject/otr/app/im/app/BrandingResources.java index 1a10b9469..50622a23d 100644 --- a/src/info/guardianproject/otr/app/im/app/BrandingResources.java +++ b/src/info/guardianproject/otr/app/im/app/BrandingResources.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -38,13 +38,13 @@ public class BrandingResources { private Map mResMapping; private Resources mPackageRes; - + private BrandingResources mDefaultRes; /** * Creates a new BrandingResource of a specific plug-in. The resources will * be retrieved from the plug-in package. - * + * * @param context The current application context. * @param pluginInfo The info about the plug-in. * @param defaultRes The default branding resources. If the resource is not @@ -68,7 +68,7 @@ public BrandingResources(Context context, ImPluginInfo pluginInfo, BrandingResou Method m = cls.getMethod("onBind", Intent.class); ImPlugin plugin = (ImPlugin) m.invoke(cls.newInstance(), new Object[] { null }); mResMapping = plugin.getResourceMap(); - + } catch (ClassNotFoundException e) { Log.e(TAG, "Failed load the plugin resource map", e); } catch (IllegalAccessException e) { @@ -90,7 +90,7 @@ public BrandingResources(Context context, ImPluginInfo pluginInfo, BrandingResou * Creates a BrandingResource with application context and the resource ID * map. The resource will be retrieved from the context directly instead * from the plug-in package. - * + * * @param context * @param resMapping */ @@ -109,7 +109,7 @@ public BrandingResources(Resources packageRes, Map resMapping, /** * Gets a drawable object associated with a particular resource ID defined * in {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} - * + * * @param id The ID defined in * {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} * @return Drawable An object that can be used to draw this resource. @@ -130,7 +130,7 @@ public Drawable getDrawable(int id) { /** * Gets the string value associated with a particular resource ID defined in * {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} - * + * * @param id The ID of the string resource defined in * {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} * @param formatArgs The format arguments that will be used for @@ -151,7 +151,7 @@ public String getString(int id, Object... formatArgs) { /** * Gets the string array associated with a particular resource ID defined in * {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} - * + * * @param id The ID of the string resource defined in * {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs} * @return The string array associated with the resource. diff --git a/src/info/guardianproject/otr/app/im/app/CertDisplayActivity.java b/src/info/guardianproject/otr/app/im/app/CertDisplayActivity.java index 8c77c1468..2abb458de 100644 --- a/src/info/guardianproject/otr/app/im/app/CertDisplayActivity.java +++ b/src/info/guardianproject/otr/app/im/app/CertDisplayActivity.java @@ -5,6 +5,7 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; import android.os.Bundle; +import info.guardianproject.otr.app.im.R; public class CertDisplayActivity extends Activity { @@ -20,33 +21,33 @@ protected void onCreate(Bundle savedInstanceState) { String issuedOn = getIntent().getStringExtra("issued"); String expiresOn = getIntent().getStringExtra("expires"); String msg = getIntent().getStringExtra("msg"); - + StringBuilder sb = new StringBuilder(); - + if (msg != null) sb.append(msg).append("\n\n"); - + if (subject != null) - sb.append("Certificate: ").append(subject).append("\n\n"); - + sb.append(getString(R.string.dialog_cert_subject)).append(subject).append("\n\n"); + if (issuer != null) - sb.append("Issued by: ").append(issuer).append("\n\n"); - + sb.append(getString(R.string.dialog_cert_issuer)).append(issuer).append("\n\n"); + if (fingerprint != null) - sb.append("SHA1 Fingerprint: ").append(fingerprint).append("\n\n"); - + sb.append(getString(R.string.dialog_cert_fingerprint)).append(fingerprint).append("\n\n"); + if (issuedOn != null) - sb.append("Issued: ").append(issuedOn).append("\n\n"); - + sb.append(getString(R.string.dialog_cert_issue_date)).append(issuedOn).append("\n\n"); + if (expiresOn != null) - sb.append("Expires: ").append(expiresOn).append("\n\n"); - + sb.append(getString(R.string.dialog_cert_expires)).append(expiresOn).append("\n\n"); + showDialog(sb.toString()); } private void showDialog(String msg) { - ad = new AlertDialog.Builder(this).setTitle("Certificate Info").setMessage(msg).show(); + ad = new AlertDialog.Builder(this).setTitle(R.string.dialog_cert_title).setMessage(msg).show(); ad.setOnDismissListener(new OnDismissListener() { diff --git a/src/info/guardianproject/otr/app/im/app/ChatBackgroundMaker.java b/src/info/guardianproject/otr/app/im/app/ChatBackgroundMaker.java index 2bf021230..5eb730e63 100644 --- a/src/info/guardianproject/otr/app/im/app/ChatBackgroundMaker.java +++ b/src/info/guardianproject/otr/app/im/app/ChatBackgroundMaker.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -40,6 +40,6 @@ public ChatBackgroundMaker(Context context) { } public void setBackground(MessageView view, String contact, int type) { - + } } diff --git a/src/info/guardianproject/otr/app/im/app/ChatFileStore.java b/src/info/guardianproject/otr/app/im/app/ChatFileStore.java new file mode 100644 index 000000000..cb0830c87 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/ChatFileStore.java @@ -0,0 +1,465 @@ +/** + * + */ +package info.guardianproject.otr.app.im.app; + +import info.guardianproject.iocipher.File; +import info.guardianproject.iocipher.FileInputStream; +import info.guardianproject.iocipher.FileOutputStream; +import info.guardianproject.iocipher.VirtualFileSystem; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.util.LogCleaner; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Log; + +/** + * Copyright (C) 2014 Guardian Project. All rights reserved. + * + * @author liorsaar + * + */ +public class ChatFileStore { + public static final String TAG = ChatFileStore.class.getName(); + private static String dbFilePath; + private static final String BLOB_NAME = "media.db"; + + public static void unmount() { + VirtualFileSystem.get().unmount(); + } + + public static void list(String parent) { + File file = new File(parent); + String[] list = file.list(); + // Log.e(TAG, file.getAbsolutePath()); + for (int i = 0 ; i < list.length ; i++) { + String fullname = parent + list[i]; + File child = new File(fullname); + if (child.isDirectory()) { + list(fullname+"/"); + } else { + File full = new File(fullname); + // Log.e(TAG, fullname + " " + full.length()); + } + } + } + + public static void deleteSession( String sessionId ) throws IOException { + String dirName = "/" + sessionId; + File file = new File(dirName); + // if the session doesnt have any ul/dl files - bail + if (!file.exists()) { + return; + } + // delete recursive + delete( dirName ); + } + + private static void delete(String parentName) throws IOException { + File parent = new File(parentName); + // if a file or an empty directory - delete it + if (!parent.isDirectory() || parent.list().length == 0 ) { + // Log.e(TAG, "delete:" + parent ); + if (!parent.delete()) { + throw new IOException("Error deleting " + parent); + } + return; + } + // directory - recurse + String[] list = parent.list(); + for (int i = 0 ; i < list.length ; i++) { + String childName = parentName + "/" + list[i]; + delete( childName ); + } + delete( parentName ); + } + + private static final String VFS_SCHEME = "vfs"; + + public static Uri vfsUri(String filename) { + return Uri.parse(VFS_SCHEME + ":" + filename); + } + + public static boolean isVfsUri(Uri uri) { + return TextUtils.equals(VFS_SCHEME, uri.getScheme()); + } + + public static boolean isVfsUri(String uriString) { + if (TextUtils.isEmpty(uriString)) + return false; + else + return uriString.startsWith(VFS_SCHEME + ":/"); + } + + public static Bitmap getThumbnailVfs(Uri uri, int thumbnailSize) { + + if (!VirtualFileSystem.get().isMounted()) + return null; + + File image = new File(uri.getPath()); + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inInputShareable = true; + options.inPurgeable = true; + + try { + FileInputStream fis = new FileInputStream(new File(image.getPath())); + BitmapFactory.decodeStream(fis, null, options); + } catch (Exception e) { + Log.e(ImApp.LOG_TAG,"unable to read vfs thumbnail",e); + return null; + } + + if ((options.outWidth == -1) || (options.outHeight == -1)) + return null; + + int originalSize = (options.outHeight > options.outWidth) ? options.outHeight + : options.outWidth; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = originalSize / thumbnailSize; + + try { + FileInputStream fis = new FileInputStream(new File(image.getPath())); + Bitmap scaledBitmap = BitmapFactory.decodeStream(fis, null, opts); + return scaledBitmap; + } catch (FileNotFoundException e) { + LogCleaner.error(ImApp.LOG_TAG, "can't find IOcipher file: " + image.getPath(), e); + return null; + } + catch (OutOfMemoryError oe) + { + LogCleaner.error(ImApp.LOG_TAG, "out of memory loading thumbnail: " + image.getPath(), oe); + + return null; + } + } + + /** + * Setup IOCipher VirtualFileSystem without a user-provided password. + * @param context + */ + public static void initWithoutPassword(Context context) { + init(context, null); + } + + /** + * Careful! All of the {@code File}s in this method are {@link java.io.File} + * not {@link info.guardianproject.iocipher.File}s + * + * @param context + * @param key + * @throws IllegalArgumentException + */ + public static void init(Context context, byte[] key) throws IllegalArgumentException { + // there is only one VFS, so if its already mounted, nothing to do + VirtualFileSystem vfs = VirtualFileSystem.get(); + if (vfs.isMounted()) { + Log.w(TAG, "VFS " + vfs.getContainerPath() + " is already mounted, skipping init()"); + return; + } + + /* TODO None of these key/keyText transformations are necessary since IOCipher/SQLCipher + * will handle long strings and blank strings, but changing it might break existing + * DBs. These transformations where gathered from a couple places to be centralized here. + */ + String keyText; + if (key == null) + keyText = ""; + else + keyText = new String(Hex.encodeHex(key)); + if (keyText.length() > 32) + keyText = keyText.substring(0, 32); + + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); + if (settings.getBoolean( + context.getString(R.string.key_store_media_on_external_storage_pref), false)) + dbFilePath = getExternalDbFilePath(context); + else + dbFilePath = getInternalDbFilePath(context); + + if (!new java.io.File(dbFilePath).exists()) { + vfs.createNewContainer(dbFilePath, keyText); + } + vfs.mount(dbFilePath, keyText); + } + + /** + * get the external storage path for the chat media file storage file. + */ + public static String getExternalDbFilePath(Context c) { + java.io.File externalFilesDir = c.getExternalFilesDir(null); + if (externalFilesDir == null) + return null; + else + return externalFilesDir.getAbsolutePath() + "/" + BLOB_NAME; + } + + /** + * get the internal storage path for the chat media file storage file. + */ + public static String getInternalDbFilePath(Context c) { + return c.getFilesDir() + "/" + BLOB_NAME; + } + + /** + * Copy device content into vfs. + * All imported content is stored under /SESSION_NAME/ + * The original full path is retained to facilitate browsing + * The session content can be deleted when the session is over + * @param sourcePath + * @return vfs uri + * @throws IOException + */ + public static Uri importContent(String sessionId, String sourcePath) throws IOException { + list("/"); + File sourceFile = new File(sourcePath); + String targetPath = "/" + sessionId + "/upload/" + sourceFile.getName(); + targetPath = createUniqueFilename(targetPath); + copyToVfs( sourcePath, targetPath ); + list("/"); + return vfsUri(targetPath); + } + + /** + * Copy device content into vfs. + * All imported content is stored under /SESSION_NAME/ + * The original full path is retained to facilitate browsing + * The session content can be deleted when the session is over + * @param sourcePath + * @return vfs uri + * @throws IOException + */ + public static Uri importContent(String sessionId, String fileName, InputStream sourceStream) throws IOException { + list("/"); + String targetPath = "/" + sessionId + "/upload/" + fileName; + targetPath = createUniqueFilename(targetPath); + copyToVfs( sourceStream, targetPath ); + list("/"); + return vfsUri(targetPath); + } + + + /** + * Resize an image to an efficient size for sending via OTRDATA, then copy + * that resized version into vfs. All imported content is stored under + * /SESSION_NAME/ The original full path is retained to facilitate browsing + * The session content can be deleted when the session is over + * + * @param imagePath + * @return vfs uri + * @throws IOException + */ + public static Uri resizeAndImportImage(Context context, String sessionId, Uri uri, String mimeType) + throws IOException { + String imagePath = uri.getPath(); + String targetPath = "/" + sessionId + "/upload/" + imagePath; + targetPath = createUniqueFilename(targetPath); + + int defaultImageWidth = 600; + //load lower-res bitmap + Bitmap bmp = getThumbnailFile(context, uri, defaultImageWidth); + + File file = new File(targetPath); + FileOutputStream out = new FileOutputStream(file); + + if (imagePath.endsWith(".png") || mimeType.contains("png")) //preserve alpha channel + bmp.compress(Bitmap.CompressFormat.PNG, 100, out); + else + bmp.compress(Bitmap.CompressFormat.JPEG, 90, out); + + out.flush(); + out.close(); + bmp.recycle(); + + return vfsUri(targetPath); + } + + public static Bitmap getThumbnailFile(Context context, Uri uri, int thumbnailSize) throws IOException { + + InputStream is = context.getContentResolver().openInputStream(uri); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inInputShareable = true; + options.inPurgeable = true; + + BitmapFactory.decodeStream(is, null, options); + + if ((options.outWidth == -1) || (options.outHeight == -1)) + return null; + + int originalSize = (options.outHeight > options.outWidth) ? options.outHeight + : options.outWidth; + + is.close(); + is = context.getContentResolver().openInputStream(uri); + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = originalSize / thumbnailSize; + + Bitmap scaledBitmap = BitmapFactory.decodeStream(is, null, opts); + + is.close(); + return scaledBitmap; + } + + public static void exportAll(String sessionId ) throws IOException { + } + + public static void exportContent(String mimeType, Uri mediaUri, java.io.File exportPath) throws IOException { + String sourcePath = mediaUri.getPath(); + copyToExternal( sourcePath, exportPath); + } + + public static java.io.File exportPath(String mimeType, Uri mediaUri) { + java.io.File targetFilename; + if (mimeType.startsWith("image")) { + targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),mediaUri.getLastPathSegment()); + } else if (mimeType.startsWith("audio")) { + targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),mediaUri.getLastPathSegment()); + } else { + targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),mediaUri.getLastPathSegment()); + } + java.io.File targetUniqueFilename = createUniqueFilenameExternal(targetFilename); + return targetFilename; + } + + public static void copyToVfs(String sourcePath, String targetPath) throws IOException { + // create the target directories tree + mkdirs( targetPath ); + // copy + java.io.FileInputStream fis = new java.io.FileInputStream(new java.io.File(sourcePath)); + FileOutputStream fos = new FileOutputStream(new File(targetPath), false); + + IOUtils.copyLarge(fis, fos); + + fos.close(); + fis.close(); + } + + public static void copyToVfs(InputStream sourceIS, String targetPath) throws IOException { + // create the target directories tree + mkdirs( targetPath ); + // copy + FileOutputStream fos = new FileOutputStream(new File(targetPath), false); + + IOUtils.copyLarge(sourceIS, fos); + + fos.close(); + sourceIS.close(); + } + + /** + * Write a {@link byte[]} into an IOCipher File + * @param sessionId + * @param buf + * @return + * @throws IOException + */ + public static void copyToVfs(byte buf[], String targetPath) throws IOException { + File file = new File(targetPath); + FileOutputStream out = new FileOutputStream(file); + out.write(buf); + out.close(); + } + + + public static void copyToExternal(String sourcePath, java.io.File targetPath) throws IOException { + // copy + FileInputStream fis = new FileInputStream(new File(sourcePath)); + java.io.FileOutputStream fos = new java.io.FileOutputStream(targetPath, false); + + IOUtils.copyLarge(fis, fos); + + fos.close(); + fis.close(); + } + + private static void mkdirs(String targetPath) throws IOException { + File targetFile = new File(targetPath); + if (!targetFile.exists()) { + File dirFile = targetFile.getParentFile(); + if (!dirFile.exists()) { + boolean created = dirFile.mkdirs(); + if (!created) { + throw new IOException("Error creating " + targetPath); + } + } + } + } + + public static boolean exists(String path) { + return new File(path).exists(); + } + + public static boolean sessionExists(String sessionId) { + return exists( "/" + sessionId ); + } + + private static String createUniqueFilename( String filename ) { + if (!exists(filename)) { + return filename; + } + int count = 1; + String uniqueName; + File file; + do { + uniqueName = formatUnique(filename, count++); + file = new File(uniqueName); + } while(file.exists()); + + return uniqueName; + } + + private static String formatUnique(String filename, int counter) { + int lastDot = filename.lastIndexOf("."); + if (lastDot != -1) + { + String name = filename.substring(0,lastDot); + String ext = filename.substring(lastDot); + return name + "-" + counter + "." + ext; + } + else + { + return filename + counter; + } + } + + public static String getDownloadFilename(String sessionId, String filenameFromUrl) { + String filename = "/" + sessionId + "/download/" + filenameFromUrl; + String uniqueFilename = createUniqueFilename(filename); + return uniqueFilename; + } + + private static java.io.File createUniqueFilenameExternal(java.io.File filename ) { + if (!filename.exists()) { + return filename; + } + int count = 1; + String uniqueName; + java.io.File file; + do { + uniqueName = formatUnique(filename.getName(), count++); + file = new java.io.File(filename.getParentFile(),uniqueName); + } while(file.exists()); + + return file; + } +} diff --git a/src/info/guardianproject/otr/app/im/app/ChatListAdapter.java b/src/info/guardianproject/otr/app/im/app/ChatListAdapter.java deleted file mode 100644 index 162946308..000000000 --- a/src/info/guardianproject/otr/app/im/app/ChatListAdapter.java +++ /dev/null @@ -1,618 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.provider.Imps; - -import java.util.List; -import java.util.Observable; -import java.util.Observer; - -import android.app.Activity; -import android.content.AsyncQueryHandler; -import android.content.ContentQueryMap; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.net.Uri; -import android.os.RemoteException; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.CursorTreeAdapter; -import android.widget.TextView; -import android.widget.ListAdapter; - -public class ChatListAdapter implements ListAdapter, AbsListView.OnScrollListener { - - private static final String[] CONTACT_LIST_PROJECTION = { Imps.ContactList._ID, - Imps.ContactList.NAME, }; - - private static final int COLUMN_CONTACT_LIST_ID = 0; - private static final int COLUMN_CONTACT_LIST_NAME = 1; - - Activity mActivity; - SimpleAlertHandler mHandler; - private LayoutInflater mInflate; - private long mProviderId = -1; - long mAccountId = -1; - Cursor mOngoingConversations; - boolean mDataValid; - ListTreeAdapter mAdapter; - - final MyContentObserver mContentObserver; - final MyDataSetObserver mDataSetObserver; - - // private static final int TOKEN_CONTACT_LISTS = -1; - private static final int TOKEN_ONGOING_CONVERSATION = -2; - // private static final int TOKEN_SUBSCRIPTION = -3; - - /* - private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "(" - + Imps.Contacts.LAST_MESSAGE_DATE - + " IS NULL) AND (" - + Imps.Contacts.TYPE + "!=" - + Imps.Contacts.TYPE_BLOCKED + ")"; - - - private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST + "=? AND " - + NON_CHAT_AND_BLOCKED_CONTACTS; - - - private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION + " AND " - + Imps.Contacts.PRESENCE_STATUS + " != " - + Imps.Presence.OFFLINE; -*/ - - static final void log(String msg) { - Log.d(ImApp.LOG_TAG, "" + msg); - } - - static final String[] CONTACT_COUNT_PROJECTION = { Imps.Contacts.CONTACTLIST, - Imps.Contacts._COUNT, }; - - ContentQueryMap mOnlineContactsCountMap; - - // Async QueryHandler - private final class QueryHandler extends AsyncQueryHandler { - public QueryHandler(Context context) { - super(context.getContentResolver()); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor c) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("onQueryComplete:token=" + token); - } - - if (token == TOKEN_ONGOING_CONVERSATION) { - setOngoingConversations(c); - mAdapter.notifyDataSetChanged(); - - } else { - int count = mAdapter.getGroupCount(); - for (int pos = 0; pos < count; pos++) { - long listId = mAdapter.getGroupId(pos); - if (listId == token) { - mAdapter.setChildrenCursor(pos, c); - break; - } - } - } - } - } - - private QueryHandler mQueryHandler; - - private int mScrollState; - - private boolean mAutoRequery; - private boolean mRequeryPending; - - public ChatListAdapter(Activity activity) { - mActivity = activity; - mInflate = activity.getLayoutInflater(); - mHandler = new SimpleAlertHandler(activity); - - mAdapter = new ListTreeAdapter(null); - - mContentObserver = new MyContentObserver(); - mDataSetObserver = new MyDataSetObserver(); - mQueryHandler = new QueryHandler(activity); - - } - - public void changeConnection(IImConnection conn) { - mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION); - - synchronized (this) { - if (mOngoingConversations != null) { - mOngoingConversations.close(); - mOngoingConversations = null; - } - if (mOnlineContactsCountMap != null) { - mOnlineContactsCountMap.close(); - } - } - - mAdapter.notifyDataSetChanged(); - if (conn != null) { - try { - mProviderId = conn.getProviderId(); - mAccountId = conn.getAccountId(); - startQueryOngoingConversations(); - } catch (RemoteException e) { - // Service died! - } - } - } - - public void changeConnection() { - mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION); - - synchronized (this) { - if (mOngoingConversations != null) { - mOngoingConversations.close(); - mOngoingConversations = null; - } - if (mOnlineContactsCountMap != null) { - mOnlineContactsCountMap.close(); - } - } - - mAdapter.notifyDataSetChanged(); - - mProviderId = -1; - mAccountId = -1; - startQueryOngoingConversations(); - - - } - - public void startAutoRequery() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startAutoRequery()"); - } - mAutoRequery = true; - if (mRequeryPending) { - mRequeryPending = false; - startQueryOngoingConversations(); - } - } - - void startQueryOngoingConversations() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQueryOngoingConversations()"); - } - - Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; - - if (mProviderId != -1) - uri = ContentUris.withAppendedId(uri, mProviderId); - - if (mAccountId != -1) - uri = ContentUris.withAppendedId(uri, mAccountId); - - - mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri, - ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER); - } - - - - /* - void startQuerySubscriptions() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQuerySubscriptions()"); - } - - Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY; - uri = ContentUris.withAppendedId(uri, mProviderId); - uri = ContentUris.withAppendedId(uri, mAccountId); - } - */ - - public long getChildId(int groupPosition, int childPosition) { - if (isPosForOngoingConversation(groupPosition)) { - // No cursor id exists for the "Empty" TextView item - if (getOngoingConversationCount() == 0) - return 0; - return getId(getOngoingConversations(), childPosition); - } - - return -1; - } - - - public int getChildrenCount(int groupPosition) { - return 0; - } - - public Object getGroup(int groupPosition) { - if (isPosForOngoingConversation(groupPosition)) { - return null; - } else { - return mAdapter.getGroup(getChildAdapterPosition(groupPosition)); - } - } - - - public boolean isChildSelectable(int groupPosition, int childPosition) { - if (isPosForOngoingConversation(groupPosition)) { - // "Empty" TextView is not selectable - if (getOngoingConversationCount() == 0) - return false; - return true; - } - - return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition); - } - - public boolean stableIds() { - return true; - } - - View newChildView(ViewGroup parent) { - return mInflate.inflate(R.layout.contact_view, parent, false); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false); - } - - View newGroupView(ViewGroup parent) { - return mInflate.inflate(R.layout.group_view, parent, false); - } - - private synchronized Cursor getOngoingConversations() { - if (mOngoingConversations == null) { - startQueryOngoingConversations(); - } - return mOngoingConversations; - } - - synchronized void setOngoingConversations(Cursor c) { - if (mOngoingConversations != null) { - mOngoingConversations.unregisterContentObserver(mContentObserver); - mOngoingConversations.unregisterDataSetObserver(mDataSetObserver); - mOngoingConversations.close(); - } - - if (c != null) { - c.registerContentObserver(mContentObserver); - c.registerDataSetObserver(mDataSetObserver); - } - - mOngoingConversations = c; - - } - - private int getOngoingConversationCount() { - Cursor c = getOngoingConversations(); - return c == null ? 0 : c.getCount(); - } - - public boolean isPosForOngoingConversation(int groupPosition) { - return groupPosition == 0; - } - - private int getChildAdapterPosition(int groupPosition) { - return groupPosition - 1; - - } - - private Cursor moveTo(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor; - } - return null; - } - - private long getId(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor.getLong(ContactView.COLUMN_CONTACT_ID); - } - return 0; - } - - class ListTreeAdapter extends CursorTreeAdapter { - - public ListTreeAdapter(Cursor cursor) { - super(cursor, mActivity); - } - - @Override - protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { - // binding when child is text view for an empty group - if (view instanceof TextView) { - ((TextView) view).setText(mActivity.getText(R.string.empty_contact_group)); - } else { - ((ContactView) view).bind(cursor, null, isScrolling()); - } - } - - @Override - protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { - TextView text1 = (TextView) view.findViewById(R.id.text1); - TextView text2 = (TextView) view.findViewById(R.id.text2); - Resources r = view.getResources(); - - text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME)); - text2.setVisibility(View.VISIBLE); - text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor))); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_contact_group_view, parent, false); - } - - // if the group is empty, provide a text view. The infrastructure provides a "convertView" - // as a possible suggestion to reuse an existing view's data. It may be null, it may be a - // TextView, or it may be a ContactView, so we need to test the possible cases. - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - // Provide a TextView if the group is empty - if (super.getChildrenCount(groupPosition) == 0) { - if (convertView != null) { - if (convertView instanceof TextView) { - ((TextView) convertView).setText(mActivity - .getText(R.string.empty_contact_group)); - return convertView; - } - } - return newEmptyView(parent); - } - if (!(convertView instanceof ContactView)) { - convertView = null; - } - return super.getChildView(groupPosition, childPosition, isLastChild, convertView, - parent); - } - - @Override - protected Cursor getChildrenCursor(Cursor groupCursor) { - return null; - } - - // return a TextView for empty groups - @Override - protected View newChildView(Context context, Cursor cursor, boolean isLastChild, - ViewGroup parent) { - if (cursor.getCount() == 0) { - return newEmptyView(parent); - } else { - return ChatListAdapter.this.newChildView(parent); - } - } - - @Override - protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, - ViewGroup parent) { - return ChatListAdapter.this.newGroupView(parent); - } - - private int getOnlineChildCount(Cursor groupCursor) { - long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); - if (mOnlineContactsCountMap == null) { - String where = Imps.Contacts.ACCOUNT + "=" + mAccountId; - ContentResolver cr = mActivity.getContentResolver(); - - Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT, - CONTACT_COUNT_PROJECTION, where, null, null); - mOnlineContactsCountMap = new ContentQueryMap(c, Imps.Contacts.CONTACTLIST, true, - mHandler); - mOnlineContactsCountMap.addObserver(new Observer() { - public void update(Observable observable, Object data) { - notifyDataSetChanged(); - } - }); - } - ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId)); - return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT); - } - - @Override - public int getChildrenCount(int groupPosition) { - int children = super.getChildrenCount(groupPosition); - if (children == 0) { - // Count the empty group text item as a child - return 1; - } - return children; - } - - // Don't allow the empty group text item to be selected - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return (super.getChildrenCount(groupPosition) > 0); - } - } - - private class MyContentObserver extends ContentObserver { - - public MyContentObserver() { - super(mHandler); - } - - @Override - public void onChange(boolean selfChange) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyContentObserver.onChange() autoRequery=" + mAutoRequery); - } - // Don't requery when fling. We will schedule a requery when the fling is complete. - if (isScrolling()) { - return; - } - if (mAutoRequery) { - startQueryOngoingConversations(); - } else { - mRequeryPending = true; - } - } - } - - private class MyDataSetObserver extends DataSetObserver { - public MyDataSetObserver() { - } - - @Override - public void onChanged() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onChanged()"); - } - mDataValid = true; - } - - @Override - public void onInvalidated() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onInvalidated()"); - } - mDataValid = false; - } - } - - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - // no op - } - - public void onScrollStateChanged(AbsListView view, int scrollState) { - int oldState = mScrollState; - - mScrollState = scrollState; - // If we just finished a fling then some items may not have an icon - // So force a full redraw now that the fling is complete - if (oldState == OnScrollListener.SCROLL_STATE_FLING) { - } - } - - public boolean isScrolling() { - return mScrollState == OnScrollListener.SCROLL_STATE_FLING; - } - - @Override - public int getCount() { - return this.getOngoingConversationCount(); - } - - @Override - public Object getItem(int position) { - - if (getOngoingConversationCount() == 0) - return null; - return moveTo(getOngoingConversations(), position); - } - - @Override - public long getItemId(int position) { - // No cursor id exists for the "Empty" TextView item - if (getOngoingConversationCount() == 0) - return 0; - return getId(getOngoingConversations(), position); - - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - // boolean isOngoingConversation = isPosForOngoingConversation(0); - boolean displayEmpty = (getOngoingConversationCount() == 0); - View view = null; - if (convertView != null) { - // use the convert view if it matches the type required by displayEmpty - if (displayEmpty && (convertView instanceof TextView)) { - view = convertView; - ((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group)); - } else if (!displayEmpty && (convertView instanceof ContactView)) { - view = convertView; - } - } - if (view == null) { - if (displayEmpty) { - view = newEmptyView(parent); - } else { - view = newChildView(parent); - } - } - if (!displayEmpty) { - Cursor cursor = getOngoingConversations(); - cursor.moveToPosition(position); - - String[] myColumnString = cursor.getColumnNames(); - for (int i = 0; i < myColumnString.length; i++) { - } - - ((ContactView) view).bind(cursor, null, isScrolling()); - } - return view; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean hasStableIds() { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isEmpty() { - - return (this.getOngoingConversationCount() == 0); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - mAdapter.registerDataSetObserver(observer); - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - mAdapter.unregisterDataSetObserver(observer); - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } -} diff --git a/src/info/guardianproject/otr/app/im/app/ChatListOldActivity.java b/src/info/guardianproject/otr/app/im/app/ChatListOldActivity.java deleted file mode 100644 index faad1b611..000000000 --- a/src/info/guardianproject/otr/app/im/app/ChatListOldActivity.java +++ /dev/null @@ -1,833 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open - * Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatSessionManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.ContactListFilterView.ContactListListener; -import info.guardianproject.otr.app.im.app.adapter.ConnectionListenerAdapter; -import info.guardianproject.otr.app.im.engine.ImConnection; -import info.guardianproject.otr.app.im.engine.ImErrorInfo; -import info.guardianproject.otr.app.im.provider.Imps; - -import java.util.Observable; -import java.util.Observer; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.CursorAdapter; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; -import android.widget.ListView; -import android.widget.Spinner; -import android.widget.TextView; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; -import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; -// mScreen.finish(); -//mContactListView.setAutoRefreshContacts(false); - -public class ChatListOldActivity extends ThemeableActivity implements View.OnCreateContextMenuListener, ContactListListener { - - private static final int MENU_START_CONVERSATION = Menu.FIRST; - private static final int MENU_VIEW_PROFILE = Menu.FIRST + 1; - private static final int MENU_BLOCK_CONTACT = Menu.FIRST + 2; - private static final int MENU_DELETE_CONTACT = Menu.FIRST + 3; - private static final int MENU_END_CONVERSATION = Menu.FIRST + 4; - - private static final String FILTER_STATE_KEY = "Filtering"; - - ImApp mApp; - - // long mProviderId; - // long mAccountId; - // IImConnection mConn; - ActiveChatListView mActiveChatListView; - ContactListFilterView mFilterView; - - SlidingMenu menu; - - ContextMenuHandler mContextMenuHandler; - - boolean mIsFiltering; - UserPresenceView mPresenceView; - Imps.ProviderSettings.QueryMap mGlobalSettingMap; - boolean mDestroyed; - - private ConnectionListenerAdapter mConnectionListener; - - long[] mAccountIds; - private long mLastProviderId = -1; - - Handler mHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - /* - long providerId = ((long) msg.arg1 << 32) | msg.arg2; - if (providerId != mProviderId) { - return; - } - */ - - switch (msg.what) { - - case ImApp.EVENT_CONNECTION_LOGGED_IN: - log("Connection resumed"); - //updateWarningView(); - return; - case ImApp.EVENT_CONNECTION_SUSPENDED: - log("Connection suspended"); - // updateWarningView(); - return; - case ImApp.EVENT_CONNECTION_DISCONNECTED: - log("Handle event connection disconnected."); - // updateWarningView(); - // promptDisconnectedEvent(msg); - return; - } - - super.handleMessage(msg); - } - }; - - - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - LayoutInflater inflate = getLayoutInflater(); - mActiveChatListView = (ActiveChatListView) inflate.inflate(R.layout.chat_list_view, null); - setContentView(mActiveChatListView); - - getSherlock().getActionBar().setHomeButtonEnabled(true); - getSherlock().getActionBar().setDisplayHomeAsUpEnabled(true); - - - mApp = (ImApp)getApplication(); - - mGlobalSettingMap = new Imps.ProviderSettings.QueryMap(getContentResolver(), true, mHandler); - - mContextMenuHandler = new ContextMenuHandler(); - mActiveChatListView.getListView().setOnCreateContextMenuListener(this); - - // setupSideBar(); - - } - - /* - private void setupSideBar () - { - - menu = new SlidingMenu(this); - menu.setMode(SlidingMenu.LEFT); - menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); - menu.setShadowWidthRes(R.dimen.shadow_width); - menu.setShadowDrawable(R.drawable.shadow); - menu.setBehindOffsetRes(R.dimen.slidingmenu_offset); - menu.setFadeDegree(0.35f); - menu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); - - mFilterView = (ContactListFilterView) getLayoutInflater().inflate( - R.layout.contact_list_filter_view, null); - - mFilterView.setListener(this); - - mPresenceView = (UserPresenceView) mFilterView.findViewById(R.id.userPresence); - - mConnectionListener = new ConnectionListenerAdapter(mHandler) { - @Override - public void onConnectionStateChange(IImConnection connection, int state, - ImErrorInfo error) { - mPresenceView.loggingIn(state == ImConnection.LOGGING_IN); - } - }; - - mGlobalSettingMap.addObserver(new Observer() { - public void update(Observable observed, Object updateData) { - if (!mDestroyed) { - mConnectionListener = new ConnectionListenerAdapter(mHandler) { - @Override - public void onConnectionStateChange(IImConnection connection, int state, - ImErrorInfo error) { - mPresenceView.loggingIn(state == ImConnection.LOGGING_IN); - } - }; - - } - } - }); - - - - menu.setMenu(mFilterView); - - setupActionBarList(); - - mApp.registerForConnEvents(mHandler); - - } - */ - - private void initAccount (long accountId) - { - - ContentResolver cr = getContentResolver(); - Cursor c = cr.query(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId), null, - null, null, null); - - if (c == null) { - // finish(); - return; - } - if (!c.moveToFirst()) { - c.close(); - // finish(); - return; - } - - mLastProviderId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); - - mHandler = new MyHandler(this); - - initConnection (accountId, mLastProviderId); - - c.close(); - } - - private void initConnection (long accountId, long providerId) - { - mApp.dismissNotifications(providerId); - IImConnection conn = mApp.getConnection(providerId); - - if (conn == null) - { - try { - conn = mApp.createConnection(providerId, accountId); - } catch (RemoteException e) { - Log.e(ImApp.LOG_TAG,"error creating connection",e); - } - } - - if (conn != null) - { - mActiveChatListView.setConnection(conn); - mPresenceView.setConnection(conn); - - try { - mPresenceView.loggingIn(conn.getState() == ImConnection.LOGGING_IN); - } catch (RemoteException e) { - - mPresenceView.loggingIn(false); - // mHandler.showServiceErrorAlert(); - } - - Uri uri = mGlobalSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - : Imps.Contacts.CONTENT_URI_CONTACTS_BY; - uri = ContentUris.withAppendedId(uri, providerId); - uri = ContentUris.withAppendedId(uri, accountId); - mFilterView.doFilter(uri, null); - - - - } - - - } - - private void setupActionBarList () - { - - Cursor providerCursor = managedQuery(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, PROVIDER_PROJECTION, - Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL", - - new String[] { ImApp.IMPS_CATEGORY } /* selection args */, - Imps.Provider.DEFAULT_SORT_ORDER); - - - // + " AND " + Imps.Provider.ACCOUNT_CONNECTION_STATUS + " != 0" - - /* selection */ - mAccountIds = new long[providerCursor.getCount()]; - - providerCursor.moveToFirst(); - int activeAccountIdColumn = providerCursor.getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_ID); - - // int currentAccountIndex = -1; - - for (int i = 0; i < mAccountIds.length; i++) - { - mAccountIds[i] = providerCursor.getLong(activeAccountIdColumn); - providerCursor.moveToNext(); - - } - - providerCursor.moveToFirst(); - - ProviderAdapter pAdapter = new ProviderAdapter(this, providerCursor); - - Spinner spinnerAccounts = (Spinner)mFilterView.findViewById(R.id.spinnerAccounts); - spinnerAccounts.setAdapter(pAdapter); - spinnerAccounts.setOnItemSelectedListener(new OnItemSelectedListener () - { - - @Override - public void onItemSelected(AdapterView parent, View view, int itemPosition, long id) { - - // mAccountId = mAccountIds[itemPosition]; - //update account list - initAccount(mAccountIds[itemPosition]); - - - } - - @Override - public void onNothingSelected(AdapterView arg0) { - // TODO Auto-generated method stub - - } - - }); - - - - } - - private class ProviderAdapter extends CursorAdapter { - private LayoutInflater mInflater; - - @SuppressWarnings("deprecation") - public ProviderAdapter(Context context, Cursor c) { - super(context, c); - mInflater = LayoutInflater.from(context).cloneInContext(context); - mInflater.setFactory(new ProviderListItemFactory()); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // create a custom view, so we can manage it ourselves. Mainly, we want to - // initialize the widget views (by calling getViewById()) in newView() instead of in - // bindView(), which can be called more often. - ProviderListItem view = (ProviderListItem) mInflater.inflate(R.layout.account_view_small, - parent, false); - view.init(cursor,false); - return view; - } - - - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ((ProviderListItem) view).bindView(cursor); - } - } - - private class ProviderListItemFactory implements LayoutInflater.Factory { - public View onCreateView(String name, Context context, AttributeSet attrs) { - if (name != null && name.equals(ProviderListItem.class.getName())) { - return new ProviderListItem(context, ChatListOldActivity.this, null); - } - return null; - } - } - - - private void signOut (long providerId) - { - IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); - - try { - if (conn != null) - conn.logout(); - - } catch (RemoteException e) { - - Log.e("ChatList","error signing out",e); - } - } - - private void showContactsList () - { - // Intent intent = new Intent (this, ContactListActivity.class); - // intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - // startActivity(intent); - if (menu != null) - menu.showMenu(true); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.chat_list_menu, menu); - return true; - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case R.id.menu_new_chat: - - showContactsList (); - - return true; - case R.id.menu_new_group_chat: - - // showGroupChatDialog(); - - return true; - - case R.id.menu_view_accounts: - startActivity(new Intent(getBaseContext(), ChooseAccountActivity.class)); - // finish(); - return true; - - case R.id.menu_settings: - Intent intent = new Intent(this, SettingActivity.class); - startActivityForResult(intent,1); - return true; - -// case R.id.menu_sign_out: - // signOut(); - // return true; - - } - - return super.onOptionsItemSelected(item); - } - - Intent getEditAccountIntent() { - - Cursor mProviderCursor = managedQuery(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, - PROVIDER_PROJECTION, Imps.Provider.CATEGORY + "=?" /* selection */, - new String[] { ImApp.IMPS_CATEGORY } /* selection args */, - Imps.Provider.DEFAULT_SORT_ORDER); - mProviderCursor.moveToFirst(); - - Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( - Imps.Account.CONTENT_URI, mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN))); - intent.addCategory(mProviderCursor.getString(PROVIDER_CATEGORY_COLUMN)); - intent.putExtra("isSignedIn", true); - - return intent; - } - - private static final String[] PROVIDER_PROJECTION = { - Imps.Provider._ID, - Imps.Provider.NAME, - Imps.Provider.FULLNAME, - Imps.Provider.CATEGORY, - Imps.Provider.ACTIVE_ACCOUNT_ID, - Imps.Provider.ACTIVE_ACCOUNT_USERNAME, - Imps.Provider.ACTIVE_ACCOUNT_PW, - Imps.Provider.ACTIVE_ACCOUNT_LOCKED, - Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, - Imps.Provider.ACCOUNT_PRESENCE_STATUS, - Imps.Provider.ACCOUNT_CONNECTION_STATUS, }; - - static final int PROVIDER_CATEGORY_COLUMN = 3; - static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; - - - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - - - //this is bad code for weird locale switching stuff - /** - - if (requestCode == 1 && resultCode == 2) - { - Intent intent = getIntent(); - finish(); - startActivity(intent); - - }*/ - - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(FILTER_STATE_KEY, mIsFiltering); - - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - - boolean isFiltering = savedInstanceState.getBoolean(FILTER_STATE_KEY); - if (isFiltering) { - showFilterView(); - } - - super.onRestoreInstanceState(savedInstanceState); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - - boolean handled = false; - - if (mIsFiltering) { - handled = mFilterView.dispatchKeyEvent(event); - if (!handled && (KeyEvent.KEYCODE_BACK == keyCode) - && (KeyEvent.ACTION_DOWN == event.getAction())) { - showChatListView(); - handled = true; - } - } else { - handled = mActiveChatListView.dispatchKeyEvent(event); - if (!handled && isReadable(keyCode, event) - && (KeyEvent.ACTION_DOWN == event.getAction())) { - showFilterView(); - handled = mFilterView.dispatchKeyEvent(event); - } - } - - if (!handled) { - handled = super.dispatchKeyEvent(event); - } - - return handled; - } - - private static boolean isReadable(int keyCode, KeyEvent event) { - if (KeyEvent.isModifierKey(keyCode) || event.isSystem()) { - return false; - } - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_ENTER: - return false; - } - - return true; - } - - private void showFilterView() { - - if (mFilterView == null) { - mFilterView = (ContactListFilterView) getLayoutInflater().inflate( - R.layout.contact_list_filter_view, null); - mFilterView.getListView().setOnCreateContextMenuListener(this); - } - Uri uri = mGlobalSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - : Imps.Contacts.CONTENT_URI_CONTACTS_BY; - //uri = ContentUris.withAppendedId(uri, mProviderId); - //uri = ContentUris.withAppendedId(uri, mAccountId); - - mFilterView.doFilter(uri, null); - - try - { - setContentView(mFilterView); - mFilterView.requestFocus(); - } - catch (Exception e) { - Log.d(ImApp.LOG_TAG,"error switching view",e); - } - - mIsFiltering = true; - } - - void showChatListView() { - if (mIsFiltering) { - setContentView(mActiveChatListView); - mActiveChatListView.requestFocus(); - mActiveChatListView.invalidate(); - mIsFiltering = false; - } - } - - @Override - protected void onPause() { - super.onPause(); - mApp.unregisterForConnEvents(mHandler); - } - - @Override - protected void onResume() { - super.onResume(); - - ((ImApp)getApplication()).startImServiceIfNeed(true); - - mApp.registerForConnEvents(mHandler); - //mActiveChatListView.setAutoRefreshContacts(true); - - } - - @Override - protected void onDestroy() { - mDestroyed = true; - // set connection to null to unregister listeners. - mActiveChatListView.setConnection(null); - if (mGlobalSettingMap != null) { - mGlobalSettingMap.close(); - } - super.onDestroy(); - } - - static void log(String msg) { - Log.d(ImApp.LOG_TAG, " " + msg); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - boolean chatSelected = false; - boolean contactSelected = false; - Cursor contactCursor; - - if (mIsFiltering) { - AdapterView.AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - mContextMenuHandler.mPosition = info.position; - contactSelected = true; - contactCursor = mFilterView.getContactAtPosition(info.position); - chatSelected = true; - } else { - - if (menuInfo instanceof ExpandableListContextMenuInfo) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo; - mContextMenuHandler.mPosition = info.packedPosition; - contactSelected = false; - chatSelected = mActiveChatListView.isConversationAtPosition(info.packedPosition); - contactCursor = null; - } else if (menuInfo instanceof AdapterContextMenuInfo) { - AdapterView.AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - mContextMenuHandler.mPosition = info.position; - contactSelected = false; - contactCursor = null; - chatSelected = mActiveChatListView.isConversationAtPosition(info.position); - - } else - contactCursor = null; - } - - /* - boolean allowBlock = true; - if (contactCursor != null) { - //XXX HACK: Yahoo! doesn't allow to block a friend. We can only block a temporary contact. - ProviderDef provider = mApp.getProvider(mProviderId); - if (Imps.ProviderNames.YAHOO.equals(provider.mName)) { - int type = contactCursor.getInt(contactCursor - .getColumnIndexOrThrow(Imps.Contacts.TYPE)); - allowBlock = (type == Imps.Contacts.TYPE_TEMPORARY); - } - - int nickNameIndex = contactCursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME); - - menu.setHeaderTitle(contactCursor.getString(nickNameIndex)); - } -*/ - - /* - BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); - String menu_end_conversation = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_END_CHAT); - String menu_view_profile = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_VIEW_PROFILE); - String menu_block_contact = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_BLOCK_CONTACT); - String menu_start_conversation = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_START_CHAT); - String menu_delete_contact = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_DELETE_CONTACT); - - if (chatSelected) { - menu.add(0, MENU_END_CONVERSATION, 0, menu_end_conversation) - .setOnMenuItemClickListener(mContextMenuHandler); - - menu.add(0, MENU_VIEW_PROFILE, 0, menu_view_profile) - .setIcon(R.drawable.ic_menu_my_profile) - .setOnMenuItemClickListener(mContextMenuHandler); - if (allowBlock) { - menu.add(0, MENU_BLOCK_CONTACT, 0, menu_block_contact) - .setOnMenuItemClickListener(mContextMenuHandler); - - } - } else if (contactSelected) { - menu.add(0, MENU_START_CONVERSATION, 0, menu_start_conversation) - .setOnMenuItemClickListener(mContextMenuHandler); - menu.add(0, MENU_VIEW_PROFILE, 0, menu_view_profile) - .setIcon(R.drawable.ic_menu_view_profile) - .setOnMenuItemClickListener(mContextMenuHandler); - if (allowBlock) { - menu.add(0, MENU_BLOCK_CONTACT, 0, menu_block_contact) - .setOnMenuItemClickListener(mContextMenuHandler); - } - menu.add(0, MENU_DELETE_CONTACT, 0, menu_delete_contact) - .setIcon(android.R.drawable.ic_menu_delete) - .setOnMenuItemClickListener(mContextMenuHandler); - }*/ - } - - /* - void clearConnectionStatus() { - ContentResolver cr = getContentResolver(); - ContentValues values = new ContentValues(3); - - values.put(Imps.AccountStatus.ACCOUNT, mAccountId); - values.put(Imps.AccountStatus.PRESENCE_STATUS, Imps.Presence.OFFLINE); - values.put(Imps.AccountStatus.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); - // insert on the "account_status" uri actually replaces the existing value - cr.insert(Imps.AccountStatus.CONTENT_URI, values); - }*/ - - final class ContextMenuHandler implements android.view.MenuItem.OnMenuItemClickListener { - long mPosition; - - @Override - public boolean onMenuItemClick(android.view.MenuItem item) { - - if (item.getItemId() == MENU_END_CONVERSATION) - { - - Cursor c = (Cursor)mActiveChatListView.getListView().getAdapter().getItem((int) mPosition); - - long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); - IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); - - mActiveChatListView.endChat(c, conn); - - } - return false; - } - - - - } - - final class MyHandler extends SimpleAlertHandler { - public MyHandler(Activity activity) { - super(activity); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == ImApp.EVENT_CONNECTION_DISCONNECTED) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("Handle event connection disconnected."); - } - promptDisconnectedEvent(msg); - } - super.handleMessage(msg); - } - } - - - @Override - public void onContentChanged() { - super.onContentChanged(); - - if (mActiveChatListView != null && mActiveChatListView.getListView().getCount() == 0) - { - - View empty = findViewById(R.id.empty); - - if (empty != null) - { - empty.setOnClickListener(new OnClickListener (){ - - @Override - public void onClick(View arg0) { - showContactsList (); - - } - - }); - - ListView list = (ListView) findViewById(R.id.chatsList); - list.setEmptyView(empty); - } - } - } - - - @Override - public void startChat(Cursor c) { - - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - - long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); - IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); - - try { - IChatSessionManager manager = conn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session == null) { - manager.createChatSession(username); - } - - Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - i.addCategory(ImApp.IMPS_CATEGORY); - - if (menu.isShown()) - menu.toggle(); - - startActivity(i); - - } catch (RemoteException e) { - // mHandler.showServiceErrorAlert(); - } - - } - - - } - - - public void showProfile (Cursor c){} -} diff --git a/src/info/guardianproject/otr/app/im/app/ChatPagerAdapter.java b/src/info/guardianproject/otr/app/im/app/ChatPagerAdapter.java deleted file mode 100644 index 41a161096..000000000 --- a/src/info/guardianproject/otr/app/im/app/ChatPagerAdapter.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.NewChatActivity.ChatViewFragment; -import info.guardianproject.otr.app.im.provider.Imps; - -import java.util.Observable; -import java.util.Observer; - -import android.app.Activity; -import android.content.AsyncQueryHandler; -import android.content.ContentQueryMap; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.CursorTreeAdapter; -import android.widget.TextView; - -public class ChatPagerAdapter extends FragmentStatePagerAdapter { - - private static final String[] CONTACT_LIST_PROJECTION = { Imps.ContactList._ID, - Imps.ContactList.NAME, }; - - private static final int COLUMN_CONTACT_LIST_ID = 0; - private static final int COLUMN_CONTACT_LIST_NAME = 1; - - Activity mActivity; - SimpleAlertHandler mHandler; - private LayoutInflater mInflate; - private long mProviderId = -1; - long mAccountId = -1; - Cursor mOngoingConversations; - boolean mDataValid; - ListTreeAdapter mAdapter; - - final MyContentObserver mContentObserver; - final MyDataSetObserver mDataSetObserver; - - // private static final int TOKEN_CONTACT_LISTS = -1; - private static final int TOKEN_ONGOING_CONVERSATION = -2; - // private static final int TOKEN_SUBSCRIPTION = -3; - - /* - private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "(" - + Imps.Contacts.LAST_MESSAGE_DATE - + " IS NULL) AND (" - + Imps.Contacts.TYPE + "!=" - + Imps.Contacts.TYPE_BLOCKED + ")"; - - - private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST + "=? AND " - + NON_CHAT_AND_BLOCKED_CONTACTS; - - - private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION + " AND " - + Imps.Contacts.PRESENCE_STATUS + " != " - + Imps.Presence.OFFLINE; -*/ - - static final void log(String msg) { - Log.d(ImApp.LOG_TAG, "" + msg); - } - - static final String[] CONTACT_COUNT_PROJECTION = { Imps.Contacts.CONTACTLIST, - Imps.Contacts._COUNT, }; - - ContentQueryMap mOnlineContactsCountMap; - - // Async QueryHandler - private final class QueryHandler extends AsyncQueryHandler { - public QueryHandler(Context context) { - super(context.getContentResolver()); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor c) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("onQueryComplete:token=" + token); - } - - if (token == TOKEN_ONGOING_CONVERSATION) { - setOngoingConversations(c); - mAdapter.notifyDataSetChanged(); - - } else { - int count = mAdapter.getGroupCount(); - for (int pos = 0; pos < count; pos++) { - long listId = mAdapter.getGroupId(pos); - if (listId == token) { - mAdapter.setChildrenCursor(pos, c); - break; - } - } - } - } - } - - private QueryHandler mQueryHandler; - - private int mScrollState; - - private boolean mAutoRequery; - private boolean mRequeryPending; - - public ChatPagerAdapter(Activity activity, FragmentManager fm) { - - super(fm); - - mActivity = activity; - mInflate = activity.getLayoutInflater(); - mHandler = new SimpleAlertHandler(activity); - - mAdapter = new ListTreeAdapter(null); - - mContentObserver = new MyContentObserver(); - mDataSetObserver = new MyDataSetObserver(); - mQueryHandler = new QueryHandler(activity); - - } - - public void changeConnection(IImConnection conn) { - mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION); - - synchronized (this) { - if (mOngoingConversations != null) { - mOngoingConversations.close(); - mOngoingConversations = null; - } - if (mOnlineContactsCountMap != null) { - mOnlineContactsCountMap.close(); - } - } - - mAdapter.notifyDataSetChanged(); - if (conn != null) { - try { - mProviderId = conn.getProviderId(); - mAccountId = conn.getAccountId(); - startQueryOngoingConversations(); - } catch (RemoteException e) { - // Service died! - } - } - } - - public void changeConnection() { - mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION); - - synchronized (this) { - if (mOngoingConversations != null) { - mOngoingConversations.close(); - mOngoingConversations = null; - } - if (mOnlineContactsCountMap != null) { - mOnlineContactsCountMap.close(); - } - } - - mAdapter.notifyDataSetChanged(); - - //mProviderId = -1; - //mAccountId = -1; - - startQueryOngoingConversations(); - - - } - - public void startAutoRequery() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startAutoRequery()"); - } - mAutoRequery = true; - if (mRequeryPending) { - mRequeryPending = false; - startQueryOngoingConversations(); - } - } - - void startQueryOngoingConversations() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQueryOngoingConversations()"); - } - - Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; - - if (mProviderId != -1) - uri = ContentUris.withAppendedId(uri, mProviderId); - - if (mAccountId != -1) - uri = ContentUris.withAppendedId(uri, mAccountId); - - - mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri, - ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER); - } - - - - /* - void startQuerySubscriptions() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQuerySubscriptions()"); - } - - Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY; - uri = ContentUris.withAppendedId(uri, mProviderId); - uri = ContentUris.withAppendedId(uri, mAccountId); - }*/ - - - public long getChildId(int groupPosition, int childPosition) { - if (isPosForOngoingConversation(groupPosition)) { - // No cursor id exists for the "Empty" TextView item - if (getOngoingConversationCount() == 0) - return 0; - return getId(getOngoingConversations(), childPosition); - } - - return -1; - } - - - public int getChildrenCount(int groupPosition) { - return 0; - } - - public Object getGroup(int groupPosition) { - if (isPosForOngoingConversation(groupPosition)) { - return null; - } else { - return mAdapter.getGroup(getChildAdapterPosition(groupPosition)); - } - } - - - public boolean isChildSelectable(int groupPosition, int childPosition) { - if (isPosForOngoingConversation(groupPosition)) { - // "Empty" TextView is not selectable - if (getOngoingConversationCount() == 0) - return false; - return true; - } - - return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition); - } - - public boolean stableIds() { - return true; - } - - View newChildView(ViewGroup parent) { - return mInflate.inflate(R.layout.contact_view, parent, false); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false); - } - - View newGroupView(ViewGroup parent) { - return mInflate.inflate(R.layout.group_view, parent, false); - } - - private synchronized Cursor getOngoingConversations() { - if (mOngoingConversations == null) { - startQueryOngoingConversations(); - } - return mOngoingConversations; - } - - synchronized void setOngoingConversations(Cursor c) { - if (mOngoingConversations != null) { - mOngoingConversations.unregisterContentObserver(mContentObserver); - mOngoingConversations.unregisterDataSetObserver(mDataSetObserver); - mOngoingConversations.close(); - } - - if (c != null) { - c.registerContentObserver(mContentObserver); - c.registerDataSetObserver(mDataSetObserver); - } - - mOngoingConversations = c; - - } - - private int getOngoingConversationCount() { - Cursor c = getOngoingConversations(); - return c == null ? 0 : c.getCount(); - } - - public boolean isPosForOngoingConversation(int groupPosition) { - return groupPosition == 0; - } - - private int getChildAdapterPosition(int groupPosition) { - return groupPosition - 1; - - } - - private Cursor moveTo(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor; - } - return null; - } - - private long getId(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor.getLong(ContactView.COLUMN_CONTACT_ID); - } - return 0; - } - - class ListTreeAdapter extends CursorTreeAdapter { - - public ListTreeAdapter(Cursor cursor) { - super(cursor, mActivity); - } - - @Override - protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { - // binding when child is text view for an empty group - if (view instanceof TextView) { - ((TextView) view).setText(mActivity.getText(R.string.empty_contact_group)); - } else { - ((ContactView) view).bind(cursor, null, false); - } - } - - @Override - protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { - TextView text1 = (TextView) view.findViewById(R.id.text1); - TextView text2 = (TextView) view.findViewById(R.id.text2); - Resources r = view.getResources(); - - text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME)); - text2.setVisibility(View.VISIBLE); - text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor))); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_contact_group_view, parent, false); - } - - // if the group is empty, provide a text view. The infrastructure provides a "convertView" - // as a possible suggestion to reuse an existing view's data. It may be null, it may be a - // TextView, or it may be a ContactView, so we need to test the possible cases. - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - // Provide a TextView if the group is empty - if (super.getChildrenCount(groupPosition) == 0) { - if (convertView != null) { - if (convertView instanceof TextView) { - ((TextView) convertView).setText(mActivity - .getText(R.string.empty_contact_group)); - return convertView; - } - } - return newEmptyView(parent); - } - if (!(convertView instanceof ContactView)) { - convertView = null; - } - return super.getChildView(groupPosition, childPosition, isLastChild, convertView, - parent); - } - - @Override - protected Cursor getChildrenCursor(Cursor groupCursor) { - return null; - } - - // return a TextView for empty groups - @Override - protected View newChildView(Context context, Cursor cursor, boolean isLastChild, - ViewGroup parent) { - if (cursor.getCount() == 0) { - return newEmptyView(parent); - } else { - return ChatPagerAdapter.this.newChildView(parent); - } - } - - @Override - protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, - ViewGroup parent) { - return ChatPagerAdapter.this.newGroupView(parent); - } - - private int getOnlineChildCount(Cursor groupCursor) { - long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); - if (mOnlineContactsCountMap == null) { - String where = Imps.Contacts.ACCOUNT + "=" + mAccountId; - ContentResolver cr = mActivity.getContentResolver(); - - Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT, - CONTACT_COUNT_PROJECTION, where, null, null); - mOnlineContactsCountMap = new ContentQueryMap(c, Imps.Contacts.CONTACTLIST, true, - mHandler); - mOnlineContactsCountMap.addObserver(new Observer() { - public void update(Observable observable, Object data) { - notifyDataSetChanged(); - } - }); - } - ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId)); - return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT); - } - - @Override - public int getChildrenCount(int groupPosition) { - int children = super.getChildrenCount(groupPosition); - if (children == 0) { - // Count the empty group text item as a child - return 1; - } - return children; - } - - // Don't allow the empty group text item to be selected - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return (super.getChildrenCount(groupPosition) > 0); - } - } - - private class MyContentObserver extends ContentObserver { - - public MyContentObserver() { - super(mHandler); - } - - @Override - public void onChange(boolean selfChange) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyContentObserver.onChange() autoRequery=" + mAutoRequery); - } - if (mAutoRequery) { - startQueryOngoingConversations(); - } else { - mRequeryPending = true; - } - } - } - - private class MyDataSetObserver extends DataSetObserver { - public MyDataSetObserver() { - } - - @Override - public void onChanged() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onChanged()"); - } - mDataValid = true; - } - - @Override - public void onInvalidated() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onInvalidated()"); - } - mDataValid = false; - } - } - - @Override - public int getCount() { - return this.getOngoingConversationCount(); - } - - @Override - public Fragment getItem(int position) { - - if (getOngoingConversationCount() > 0) - { - // return moveTo(getOngoingConversations(), position); - return null; - } - else - return null; - } - - /* - @Override - public long getItemId(int position) { - // No cursor id exists for the "Empty" TextView item - if (getOngoingConversationCount() == 0) - return 0; - return getId(getOngoingConversations(), position); - - } - - @Override - public int getItemViewType(int position) { - return 0; - } - */ - - /* - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - // boolean isOngoingConversation = isPosForOngoingConversation(0); - boolean displayEmpty = (getOngoingConversationCount() == 0); - View view = null; - if (convertView != null) { - // use the convert view if it matches the type required by displayEmpty - if (displayEmpty && (convertView instanceof TextView)) { - view = convertView; - ((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group)); - } else if (!displayEmpty && (convertView instanceof ContactView)) { - view = convertView; - } - } - if (view == null) { - if (displayEmpty) { - view = newEmptyView(parent); - } else { - view = newChildView(parent); - } - } - if (!displayEmpty) { - Cursor cursor = getOngoingConversations(); - cursor.moveToPosition(position); - - String[] myColumnString = cursor.getColumnNames(); - for (int i = 0; i < myColumnString.length; i++) { - } - - ((ContactView) view).bind(cursor, null, isScrolling()); - } - return view; - } - - */ - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - mAdapter.registerDataSetObserver(observer); - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - mAdapter.unregisterDataSetObserver(observer); - } - - - public static class ChatViewFragment extends Fragment { - long mChatId; - ChatView mChatView; - /** - * When creating, retrieve this instance's number from its arguments. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mChatId = getArguments() != null ? getArguments().getLong("chatId") : 1; - - } - - /** - * The Fragment's UI is just a simple text view showing its - * instance number. - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - mChatView = (ChatView)inflater.inflate(R.layout.chat_view, container, false);; - mChatView.bindChat(mChatId); - return mChatView; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - - } - - } -} diff --git a/src/info/guardianproject/otr/app/im/app/ChatSwitcher.java b/src/info/guardianproject/otr/app/im/app/ChatSwitcher.java index b2cec59c2..ec881776e 100644 --- a/src/info/guardianproject/otr/app/im/app/ChatSwitcher.java +++ b/src/info/guardianproject/otr/app/im/app/ChatSwitcher.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2008 Google Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -328,7 +328,7 @@ public void onChange(boolean selfChange) { public SwitcherAdapter(Cursor c, Activity a) { // use false as the third parameter to the CursorAdapter constructor - // to indicate that we should not auto-requery the cursor + // to indicate that we should not auto-requery the cursor super(a, c, false); mLayout = R.layout.chat_switcher_item; mActivity = a; @@ -403,7 +403,10 @@ public void bindView(View view, Context context, Cursor c) { } ImageView avatarView = (ImageView) view.findViewById(R.id.avatar); - Drawable avatar = DatabaseUtils.getAvatarFromCursor(c, mAvatarDataColumn, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT); + Drawable avatar = null; + + try { avatar = DatabaseUtils.getAvatarFromCursor(c, mAvatarDataColumn, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT);} + catch(Exception e){} if (avatar == null) { avatarView.setImageResource(R.drawable.avatar_unknown); diff --git a/src/info/guardianproject/otr/app/im/app/ChatView.java b/src/info/guardianproject/otr/app/im/app/ChatView.java index f3ab0c9d3..916336115 100644 --- a/src/info/guardianproject/otr/app/im/app/ChatView.java +++ b/src/info/guardianproject/otr/app/im/app/ChatView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -21,10 +21,8 @@ import info.guardianproject.emoji.EmojiManager; import info.guardianproject.emoji.EmojiPagerAdapter; import info.guardianproject.otr.IOtrChatSession; -import info.guardianproject.otr.IOtrKeyManager; import info.guardianproject.otr.app.im.IChatListener; import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatSessionListener; import info.guardianproject.otr.app.im.IChatSessionManager; import info.guardianproject.otr.app.im.IContactList; import info.guardianproject.otr.app.im.IContactListListener; @@ -34,28 +32,30 @@ import info.guardianproject.otr.app.im.app.MessageView.DeliveryState; import info.guardianproject.otr.app.im.app.MessageView.EncryptionState; import info.guardianproject.otr.app.im.app.adapter.ChatListenerAdapter; -import info.guardianproject.otr.app.im.app.adapter.ChatSessionListenerAdapter; +import info.guardianproject.otr.app.im.engine.Address; import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.engine.ImErrorInfo; +import info.guardianproject.otr.app.im.engine.Presence; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.provider.ImpsAddressUtils; import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; import info.guardianproject.util.LogCleaner; +import info.guardianproject.util.SystemServices; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import net.java.otr4j.OtrPolicy; import net.java.otr4j.session.SessionStatus; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.AsyncQueryHandler; -import android.content.ClipData; -import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; @@ -70,8 +70,6 @@ import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -85,7 +83,6 @@ import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.AttributeSet; -import android.util.Base64; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -100,18 +97,52 @@ import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.google.gson.JsonSyntaxException; +import com.google.zxing.integration.android.IntentIntegrator; + +import info.guardianproject.emoji.EmojiGroup; +import info.guardianproject.emoji.EmojiManager; +import info.guardianproject.emoji.EmojiPagerAdapter; +import info.guardianproject.otr.IOtrChatSession; +import info.guardianproject.otr.app.im.IChatListener; +import info.guardianproject.otr.app.im.IChatSession; +import info.guardianproject.otr.app.im.IChatSessionManager; +import info.guardianproject.otr.app.im.IContactList; +import info.guardianproject.otr.app.im.IContactListListener; +import info.guardianproject.otr.app.im.IContactListManager; +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.app.MessageView.DeliveryState; +import info.guardianproject.otr.app.im.app.MessageView.EncryptionState; +import info.guardianproject.otr.app.im.app.adapter.ChatListenerAdapter; +import info.guardianproject.otr.app.im.engine.Address; +import info.guardianproject.otr.app.im.engine.Contact; +import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.engine.ImErrorInfo; +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.provider.ImpsAddressUtils; +import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; +import info.guardianproject.util.LogCleaner; +import info.guardianproject.util.SystemServices; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.session.SessionStatus; public class ChatView extends LinearLayout { // This projection and index are set for the query of active chats @@ -119,10 +150,14 @@ public class ChatView extends LinearLayout { Imps.Contacts.PROVIDER, Imps.Contacts.USERNAME, Imps.Contacts.NICKNAME, Imps.Contacts.TYPE, Imps.Presence.PRESENCE_STATUS, - Imps.Chats.LAST_UNREAD_MESSAGE, - Imps.Chats._ID + Imps.Chats.LAST_UNREAD_MESSAGE, + Imps.Chats._ID, + Imps.Contacts.SUBSCRIPTION_TYPE, + Imps.Contacts.SUBSCRIPTION_STATUS, + Imps.Contacts.AVATAR_DATA + }; - + static final int CONTACT_ID_COLUMN = 0; static final int ACCOUNT_COLUMN = 1; static final int PROVIDER_COLUMN = 2; @@ -132,6 +167,11 @@ public class ChatView extends LinearLayout { static final int PRESENCE_STATUS_COLUMN = 6; static final int LAST_UNREAD_MESSAGE_COLUMN = 7; static final int CHAT_ID_COLUMN = 8; + static final int SUBSCRIPTION_TYPE_COLUMN = 9; + static final int SUBSCRIPTION_STATUS_COLUMN = 10; + static final int AVATAR_COLUMN = 11; + + //static final int MIME_TYPE_COLUMN = 9; static final String[] INVITATION_PROJECT = { Imps.Invitation._ID, Imps.Invitation.PROVIDER, Imps.Invitation.SENDER, }; @@ -144,52 +184,167 @@ public class ChatView extends LinearLayout { Markup mMarkup; - NewChatActivity mActivity; + NewChatActivity mNewChatActivity; ImApp mApp; SimpleAlertHandler mHandler; - Cursor mCursor; + IImConnection mConn; //private ImageView mStatusIcon; // private TextView mTitle; /*package*/ListView mHistory; EditText mComposeMessage; private ImageButton mSendButton; + + private ImageButton mButtonAttach; + private View mViewAttach; + private View mStatusWarningView; - private ImageView mWarningIcon; private TextView mWarningText; - + private ProgressBar mProgressTransfer; + private ViewPager mEmojiPager; - private View mActionBox; + // private View mActionBox; private ImageView mDeliveryIcon; private boolean mExpectingDelivery; - - private CompoundButton mOtrSwitch; - private boolean mOtrSwitchTouched = false; - private OnCheckedChangeListener mOtrListener = new OnCheckedChangeListener () + + private boolean mIsSelected = false; + + private SessionStatus mLastSessionStatus = null; + private boolean mIsStartingOtr = false; + private boolean mIsVerified = false; + + public void setSelected (boolean isSelected) { + mIsSelected = isSelected; - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mActivity.setOTRState(ChatView.this, ChatView.this.getOtrChatSession(), isChecked); - - mOtrSwitchTouched = true; + if (mIsSelected) + { + bindChat(mLastChatId); + setTitle(); + updateWarningView(); + mComposeMessage.requestFocus(); + userActionDetected(); + + try + { + boolean isConnected = (mConn == null) ? false : mConn.getState() != ImConnection.SUSPENDED; + + + if (mLastSessionStatus == SessionStatus.PLAINTEXT && isConnected) { + + + boolean otrPolicyAuto = mNewChatActivity.getOtrPolicy() == OtrPolicy.OTRL_POLICY_ALWAYS + || this.mNewChatActivity.getOtrPolicy() == OtrPolicy.OPPORTUNISTIC; + + if (mCurrentChatSession == null) + mCurrentChatSession = getChatSession(); + if (mCurrentChatSession == null) + return; + IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession(); + + if (otrChatSession != null) + { + String remoteJID = otrChatSession.getRemoteUserId(); + + boolean isChatSecure = (remoteJID != null && remoteJID.contains("ChatSecure")); + + if (otrPolicyAuto && isChatSecure) //if set to auto, and is chatsecure, then start encryption + { + //automatically attempt to turn on OTR after 1 second + mHandler.postAtTime(new Runnable (){ + public void run (){ setOTRState(true);} + },1000); + } + } + + } + } + catch (RemoteException re){} } - - }; - - private MessageAdapter mMessageAdapter; - private IChatSessionManager mChatSessionManager; - private IChatSessionListener mChatSessionListener; + + } + + + private boolean checkConnection () throws RemoteException + { + if (mConn == null) + { + mConn = mApp.createConnection(mProviderId,mAccountId); + + if (mConn != null) + return false; + + } + + return true; + + + } + + public void setOTRState(boolean otrEnabled) { + + + try { + + boolean isConnected = (mConn == null) ? false : mConn.getState() != ImConnection.SUSPENDED; + + if (isConnected) + { + if (mCurrentChatSession == null) + mCurrentChatSession = getChatSession(); + + if (mCurrentChatSession != null) + { + IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession(); + + if (otrChatSession != null) + { + + if (otrEnabled) { + + otrChatSession.startChatEncryption(); + mIsStartingOtr = true; + mProgressBarOtr.setVisibility(View.VISIBLE); + + // Toast.makeText(getContext(),getResources().getString(R.string.starting_otr_chat), Toast.LENGTH_LONG).show(); + } + else + { + otrChatSession.stopChatEncryption(); + // Toast.makeText(getContext(),getResources().getString(R.string.stopping_otr_chat), Toast.LENGTH_LONG).show(); + + } + + + } + } + + } + + + updateWarningView(); + + } + catch (RemoteException e) { + Log.d(ImApp.LOG_TAG, "error getting remote activity", e); + } + + + } + + private MessageAdapter mMessageAdapter; + private boolean isServiceUp; private IChatSession mCurrentChatSession; - private IOtrKeyManager mOtrKeyManager; - private IOtrChatSession mOtrChatSession; long mLastChatId=-1; - int mType; - String mNickName; - String mUserName; + String mRemoteNickname; + String mRemoteAddress; + RoundedAvatarDrawable mRemoteAvatar = null; + int mSubscriptionType; + int mSubscriptionStatus; + long mProviderId; long mAccountId; long mInvitationId; @@ -202,13 +357,18 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { private static final int VIEW_TYPE_INVITATION = 2; private static final int VIEW_TYPE_SUBSCRIPTION = 3; - private static final long SHOW_TIME_STAMP_INTERVAL = 30 * 1000; // 1 minute - private static final long SHOW_DELIVERY_INTERVAL = 5 * 1000; // 10 seconds - private static final long DEFAULT_QUERY_INTERVAL = 1000; + private static final long SHOW_TIME_STAMP_INTERVAL = 30 * 1000; // 15 seconds + private static final long SHOW_DELIVERY_INTERVAL = 5 * 1000; // 5 seconds + private static final long SHOW_MEDIA_DELIVERY_INTERVAL = 120 * 1000; // 2 minutes + private static final long DEFAULT_QUERY_INTERVAL = 2000; + private static final long FAST_QUERY_INTERVAL = 200; private static final int QUERY_TOKEN = 10; // Async QueryHandler private final class QueryHandler extends AsyncQueryHandler { + + private Cursor mLastCursor = null; + public QueryHandler(Context context) { super(context.getContentResolver()); } @@ -216,20 +376,30 @@ public QueryHandler(Context context) { @Override protected void onQueryComplete(int token, Object cookie, Cursor c) { mExpectingDelivery = false; - setDeliveryIcon(); - + + if (c != null) { - Cursor cursor = new DeltaCursor(c); - + + closeCursor (); + + mLastCursor = new DeltaCursor(c); + if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("onQueryComplete: cursor.count=" + cursor.getCount()); + log("onQueryComplete: cursor.count=" + mLastCursor.getCount()); } - - if (mMessageAdapter != null && cursor != null) - mMessageAdapter.changeCursor(cursor); + + if (mMessageAdapter != null) + mMessageAdapter.changeCursor(mLastCursor); } } + + public void closeCursor () + { + if (mLastCursor != null && (!mLastCursor.isClosed())) + mLastCursor.close(); + + } } private QueryHandler mQueryHandler; @@ -258,50 +428,54 @@ public void onItemClick(AdapterView parent, View view, int position, long id) if (!(view instanceof MessageView)) { return; } - + URLSpan[] links = ((MessageView) view).getMessageLinks(); if (links.length > 0) { - final ArrayList linkUrls = new ArrayList(links.length); for (URLSpan u : links) { linkUrls.add(u.getURL()); } - ArrayAdapter a = new ArrayAdapter(mActivity, + ArrayAdapter a = new ArrayAdapter(mNewChatActivity, android.R.layout.select_dialog_item, linkUrls); - AlertDialog.Builder b = new AlertDialog.Builder(mActivity); + AlertDialog.Builder b = new AlertDialog.Builder(mNewChatActivity); b.setTitle(R.string.select_link_title); b.setCancelable(true); b.setAdapter(a, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { Uri uri = Uri.parse(linkUrls.get(which)); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName()); - mActivity.startActivity(intent); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, mNewChatActivity.getPackageName()); + mNewChatActivity.startActivity(intent); } }); b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); b.show(); } - else - { - viewProfile(); - } } }; + private final static int PROMPT_FOR_DATA_TRANSFER = 9999; + private final static int SHOW_DATA_PROGRESS = 9998; + private final static int SHOW_DATA_ERROR = 9997; + + private IChatListener mChatListener = new ChatListenerAdapter() { @Override - public void onIncomingMessage(IChatSession ses, + public boolean onIncomingMessage(IChatSession ses, info.guardianproject.otr.app.im.engine.Message msg) { - scheduleRequery(DEFAULT_QUERY_INTERVAL); - + scheduleRequery(FAST_QUERY_INTERVAL); + updatePresenceDisplay(); + + return mIsSelected; } @Override @@ -317,34 +491,147 @@ public void onContactLeft(IChatSession ses, Contact contact) { @Override public void onSendMessageError(IChatSession ses, info.guardianproject.otr.app.im.engine.Message msg, ImErrorInfo error) { - scheduleRequery(DEFAULT_QUERY_INTERVAL); + scheduleRequery(FAST_QUERY_INTERVAL); } + @Override public void onIncomingReceipt(IChatSession ses, String packetId) throws RemoteException { - scheduleRequery(DEFAULT_QUERY_INTERVAL); + scheduleRequery(FAST_QUERY_INTERVAL); } + @Override public void onStatusChanged(IChatSession ses) throws RemoteException { scheduleRequery(DEFAULT_QUERY_INTERVAL); - - + updatePresenceDisplay(); + }; + + + @Override + public void onIncomingFileTransfer(String transferFrom, String transferUrl) throws RemoteException { + + String[] path = transferUrl.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + + android.os.Message message = android.os.Message.obtain(null, PROMPT_FOR_DATA_TRANSFER, (int) (mProviderId >> 32), + (int) mProviderId, -1); + message.getData().putString("from", transferFrom); + message.getData().putString("file", sanitizedPath); + mHandler.sendMessage(message); + + + } + + @Override + public void onIncomingFileTransferProgress(String file, int percent) + throws RemoteException { + + android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_PROGRESS, (int) (mProviderId >> 32), + (int) mProviderId, -1); + message.getData().putString("file", file); + message.getData().putInt("progress", percent); + + scheduleRequery(FAST_QUERY_INTERVAL); + + mHandler.sendMessage(message); + + + } + + @Override + public void onIncomingFileTransferError(String file, String err) throws RemoteException { + + + android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_ERROR, (int) (mProviderId >> 32), + (int) mProviderId, -1); + message.getData().putString("file", file); + message.getData().putString("err", err); + + mHandler.sendMessage(message); + } + + }; + private void showPromptForData (String transferFrom, String filePath) + { + AlertDialog.Builder builder = new AlertDialog.Builder(mNewChatActivity); + + builder.setTitle(mContext.getString(R.string.file_transfer)); + builder.setMessage(transferFrom + ' ' + mNewChatActivity.getString(R.string.wants_to_send_you_the_file) + + " '" + filePath + "'. " + mNewChatActivity.getString(R.string.accept_transfer_)); + + builder.setNeutralButton(R.string.button_yes_accept_all,new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + + try { + mCurrentChatSession.setIncomingFileResponse(true, true); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + dialog.dismiss(); + } + + }); + + builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + try { + mCurrentChatSession.setIncomingFileResponse(true, false); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + dialog.dismiss(); + } + + }); + + builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + + try { + mCurrentChatSession.setIncomingFileResponse(false, false); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + // Do nothing + dialog.dismiss(); + } + }); + + AlertDialog alert = builder.create(); + alert.show(); + + } private Runnable mUpdateChatCallback = new Runnable() { public void run() { - if (mCursor.requery() && mCursor.moveToFirst()) { + // if (mCursor != null && mCursor.requery() && mCursor.moveToFirst()) { updateChat(); - } + // } } }; - + private IContactListListener mContactListListener = new IContactListListener.Stub() { public void onAllContactListsLoaded() { } public void onContactChange(int type, IContactList list, Contact contact) { + + if (contact != null && contact.getPresence() != null) + mPresenceStatus = contact.getPresence().getStatus(); + } public void onContactError(int errorType, ImErrorInfo error, String listName, @@ -352,12 +639,20 @@ public void onContactError(int errorType, ImErrorInfo error, String listName, } public void onContactsPresenceUpdate(Contact[] contacts) { - + if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("onContactsPresenceUpdate()"); } + for (Contact c : contacts) { - if (c.getAddress().getAddress().equals(mUserName)) { + if (c.getAddress().getBareAddress().equals(Address.stripResource(mRemoteAddress))) { + + if (c != null && c.getPresence() != null) + { + mPresenceStatus = c.getPresence().getStatus(); + updatePresenceDisplay(); + } + mHandler.post(mUpdateChatCallback); scheduleRequery(DEFAULT_QUERY_INTERVAL); break; @@ -366,17 +661,20 @@ public void onContactsPresenceUpdate(Contact[] contacts) { } }; + private boolean mIsListening; + static final void log(String msg) { Log.d(ImApp.LOG_TAG, " " + msg); } public ChatView(Context context, AttributeSet attrs) { super(context, attrs); - mActivity = (NewChatActivity) context; - mApp = (ImApp)mActivity.getApplication(); - mHandler = new ChatViewHandler(mActivity); + mNewChatActivity = (NewChatActivity) context; + mApp = (ImApp)mNewChatActivity.getApplication(); + mHandler = new ChatViewHandler(mNewChatActivity); mContext = context; + ThemeableActivity.setBackgroundImage(this, mNewChatActivity); } void registerForConnEvents() { @@ -386,73 +684,129 @@ void registerForConnEvents() { void unregisterForConnEvents() { mApp.unregisterForConnEvents(mHandler); } + + ProgressBar mProgressBarOtr; @Override protected void onFinishInflate() { // mStatusIcon = (ImageView) findViewById(R.id.statusIcon); - mDeliveryIcon = (ImageView) findViewById(R.id.deliveryIcon); + // mDeliveryIcon = (ImageView) findViewById(R.id.deliveryIcon); // mTitle = (TextView) findViewById(R.id.title); mHistory = (ListView) findViewById(R.id.history); mComposeMessage = (EditText) findViewById(R.id.composeMessage); mSendButton = (ImageButton) findViewById(R.id.btnSend); mHistory.setOnItemClickListener(mOnItemClickListener); - + mButtonAttach = (ImageButton) findViewById(R.id.btnAttach); + mViewAttach = findViewById(R.id.attachPanel); + mStatusWarningView = findViewById(R.id.warning); - mWarningIcon = (ImageView) findViewById(R.id.warningIcon); mWarningText = (TextView) findViewById(R.id.warningText); - - mOtrSwitch = (CompoundButton)findViewById(R.id.otrSwitch); - + + mProgressTransfer = (ProgressBar)findViewById(R.id.progressTransfer); + // mOtrSwitch = (CompoundButton)findViewById(R.id.otrSwitch); + mProgressBarOtr = (ProgressBar)findViewById(R.id.progressBarOtr); + + mButtonAttach.setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + if (mViewAttach.getVisibility() == View.GONE) + mViewAttach.setVisibility(View.VISIBLE); + else + mViewAttach.setVisibility(View.GONE); + } + + }); + + ((ImageButton) findViewById(R.id.btnAttachAudio)).setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + mNewChatActivity.startAudioPicker(); + } + + }); + + ((ImageButton) findViewById(R.id.btnAttachPicture)).setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + mNewChatActivity.startImagePicker(); + } + + }); + + ((ImageButton) findViewById(R.id.btnTakePicture)).setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + mNewChatActivity.startPhotoTaker(); + } + + }); + + ((ImageButton) findViewById(R.id.btnAttachFile)).setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + mNewChatActivity.startFilePicker(); + } + + }); + + mHistory.setOnItemLongClickListener(new OnItemLongClickListener () { @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) { - - + + if (arg1 instanceof MessageView) { - // Gets a handle to the clipboard service. - ClipboardManager clipboard = (ClipboardManager) - mActivity.getSystemService(Context.CLIPBOARD_SERVICE); - - String textToCopy = ((MessageView)arg1).getLastMessage(); - - ClipData clip = ClipData.newPlainText("chat",textToCopy); - - clipboard.setPrimaryClip(clip); - - Toast.makeText(mActivity, "message copied to the clipboard", Toast.LENGTH_SHORT).show(); - + + int sdk = android.os.Build.VERSION.SDK_INT; + if(sdk < android.os.Build.VERSION_CODES.HONEYCOMB) { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) mNewChatActivity.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setText(textToCopy); // + } else { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) mNewChatActivity.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("chat",textToCopy); + clipboard.setPrimaryClip(clip); // + } + + Toast.makeText(mNewChatActivity, mContext.getString(R.string.toast_chat_copied_to_clipboard), Toast.LENGTH_SHORT).show(); + return true; - + } - + return false; } - + }); - + mWarningText.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - viewProfile(); - + showVerifyDialog(); } + }); - + //mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - }); - - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - - - mComposeMessage.setOnKeyListener(new OnKeyListener() { + @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { @@ -508,8 +862,44 @@ public void onClick(View v) { sendMessage(); } }); - + Button btnApproveSubscription = (Button)findViewById(R.id.btnApproveSubscription); + btnApproveSubscription.setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) { + + mNewChatActivity.approveSubscription(mProviderId, mRemoteAddress); + + mHandler.postDelayed(new Runnable () { public void run () {bindChat(mLastChatId); } }, 2000); + + + } + + }); + + Button btnDeclineSubscription = (Button)findViewById(R.id.btnDeclineSubscription); + btnDeclineSubscription.setOnClickListener(new OnClickListener() + { + + @Override + + public void onClick(View v) { + + mHandler.postDelayed(new Runnable () { public void run () { + mNewChatActivity.declineSubscription(mProviderId, mRemoteAddress); + + } }, 500); + + + + + } + + }); + + /* mActionBox = (View)findViewById(R.id.actionBox); ImageButton btnActionBox = (ImageButton)findViewById(R.id.btnActionBox); btnActionBox.setOnClickListener(new OnClickListener () @@ -517,50 +907,91 @@ public void onClick(View v) { @Override public void onClick(View v) { - + + mEmojiPager.setVisibility(View.GONE); + + if (mActionBox.getVisibility() == View.GONE) mActionBox.setVisibility(View.VISIBLE); else mActionBox.setVisibility(View.GONE); } - + }); - - ImageButton btnEndChat = (ImageButton)findViewById(R.id.btnEndChat); + + View btnEndChat = findViewById(R.id.btnEndChat); btnEndChat.setOnClickListener(new OnClickListener () { @Override public void onClick(View v) { - + ChatView.this.closeChatSession(); - mActivity.refreshChatViews(); } - + }); - - ImageButton btnProfile = (ImageButton)findViewById(R.id.btnProfile); + + View btnProfile = findViewById(R.id.btnProfile); btnProfile.setOnClickListener(new OnClickListener () { @Override public void onClick(View v) { - + viewProfile(); } - + }); - - - + + View btnSharePicture = findViewById(R.id.btnSendPicture); + btnSharePicture.setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + if (mLastSessionStatus != null && mLastSessionStatus == SessionStatus.ENCRYPTED) + { + mNewChatActivity.startImagePicker(); + } + else + { + mHandler.showServiceErrorAlert(getContext().getString(R.string.please_enable_chat_encryption_to_share_files)); + } + } + + }); + + View btnShareFile = findViewById(R.id.btnSendFile); + btnShareFile.setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + if (mLastSessionStatus != null && mLastSessionStatus == SessionStatus.ENCRYPTED) + { + mNewChatActivity.startFilePicker(); + } + else + { + mHandler.showServiceErrorAlert(getContext().getString(R.string.please_enable_chat_encryption_to_share_files)); + + } + } + + }); + */ + initEmoji(); - - - + + + mMessageAdapter = new MessageAdapter(mNewChatActivity, null); + mHistory.setAdapter(mMessageAdapter); } private static EmojiManager emojiManager = null; - + private synchronized void initEmoji () { if (emojiManager == null) @@ -569,10 +1000,9 @@ private synchronized void initEmoji () try { - emojiManager.addJsonDefinitions("emoji/phantom.json", "emoji/phantom", "png"); - + emojiManager.addJsonPlugins(); - + } catch (JsonSyntaxException jse) { @@ -581,46 +1011,74 @@ private synchronized void initEmoji () catch (IOException fe) { Log.e(ImApp.LOG_TAG,"could not load emoji definition",fe); - } + } catch (Exception fe) { Log.e(ImApp.LOG_TAG,"could not load emoji definition",fe); - } + } + } - - + + mEmojiPager = (ViewPager)this.findViewById(R.id.emojiPager); - - Collection emojiGroups = emojiManager.getEmojiGroups(); - - EmojiPagerAdapter emojiPagerAdapter = new EmojiPagerAdapter(mActivity, mComposeMessage, new ArrayList(emojiGroups)); - - mEmojiPager.setAdapter(emojiPagerAdapter); - ImageView btnEmoji = (ImageView)findViewById(R.id.btnEmoji); - btnEmoji.setOnClickListener(new OnClickListener () + + Collection emojiGroups = emojiManager.getEmojiGroups(); + + if (emojiGroups.size() > 0) { + btnEmoji.setVisibility(View.VISIBLE); + + EmojiPagerAdapter emojiPagerAdapter = new EmojiPagerAdapter(mNewChatActivity, mComposeMessage, new ArrayList(emojiGroups)); - @Override - public void onClick(View v) { - - if (mEmojiPager.getVisibility() == View.GONE) - mEmojiPager.setVisibility(View.VISIBLE); - else - mEmojiPager.setVisibility(View.GONE); - } + mEmojiPager.setAdapter(emojiPagerAdapter); + + btnEmoji.setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + + // mActionBox.setVisibility(View.GONE); + + if (mEmojiPager.getVisibility() == View.GONE) + mEmojiPager.setVisibility(View.VISIBLE); + else + mEmojiPager.setVisibility(View.GONE); + } + + }); + } + else + { + btnEmoji.setVisibility(View.GONE); - }); + btnEmoji.setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + //what? prompt to install? + } + + }); + } + - - } - + public void startListening() { + if (!isServiceUp) + return; + mIsListening = true; if (mViewType == VIEW_TYPE_CHAT) { Cursor cursor = getMessageCursor(); if (cursor == null) { - startQuery(getChatId()); + long chatId = getChatId(); + if (chatId != -1) + startQuery(chatId); } else { requeryCursor(); } @@ -632,71 +1090,134 @@ public void startListening() { } public void stopListening() { - Cursor cursor = getMessageCursor(); - if (cursor != null) { - cursor.deactivate(); - } + //Cursor cursor = getMessageCursor(); + //if (cursor != null && (!cursor.isClosed())) { + // cursor.close(); + // } + cancelRequery(); - if (mViewType == VIEW_TYPE_CHAT && mCurrentChatSession != null) { - try { - mCurrentChatSession.markAsRead(); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } - } unregisterChatListener(); unregisterForConnEvents(); - unregisterChatSessionListener(); + mIsListening = false; } - - + public void unbind() { + + if (mQueryHandler != null) + mQueryHandler.closeCursor(); + + } + + void updateChat() { setViewType(VIEW_TYPE_CHAT); - updateContactInfo(); +// updateSessionInfo(); setStatusIcon(); - + + //n8fr8 + devrandom: commented out on 15 Oct 2013: we really do want the chat to update w/o a connection + //so we can show message history in offline mode + /* + * + if (!isServiceUp) + return; + IImConnection conn = mApp.getConnection(mProviderId); if (conn == null) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) log("Connection has been signed out"); - + + return; + }*/ + + mHistory.invalidate(); + + startQuery(getChatId()); + // This is not needed, now that there is a ChatView per fragment. It also causes a spurious detection of user action + // on fragments adjacent to the current one, when they get initialized. + //mComposeMessage.setText(""); + + updateWarningView(); + } + + int mContactType = -1; + + private void updateSessionInfo(Cursor c) { + + if (c != null && (!c.isClosed())) + { + mProviderId = c.getLong(PROVIDER_COLUMN); + mAccountId = c.getLong(ACCOUNT_COLUMN); + mPresenceStatus = c.getInt(PRESENCE_STATUS_COLUMN); + mContactType = c.getInt(TYPE_COLUMN); + + mRemoteNickname = c.getString(NICKNAME_COLUMN); + mRemoteAddress = c.getString(USERNAME_COLUMN); + + mSubscriptionType = c.getInt(SUBSCRIPTION_TYPE_COLUMN); + + mSubscriptionStatus = c.getInt(SUBSCRIPTION_STATUS_COLUMN); + if ((mSubscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM) + && (mSubscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)) { + bindSubscription(mProviderId, mRemoteAddress); + } + } + + } + + public void setTitle () + { + if (mIsSelected) + { + mNewChatActivity.setTitle(mRemoteNickname,mRemoteAvatar); + + } + } + + private void updatePresenceDisplay () + { + if (mRemoteAvatar == null) return; - } - - mMessageAdapter = new MessageAdapter(mActivity, null); - mHistory.setAdapter(mMessageAdapter); - mHistory.invalidate(); - startQuery(getChatId()); - mComposeMessage.setText(""); - mOtrChatSession = null; - - updateWarningView(); - setDeliveryIcon(); - } + switch (mPresenceStatus) { + case Presence.AVAILABLE: + mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_green_light)); + mRemoteAvatar.setAlpha(255); + break; - private void updateContactInfo() { - // mChatId = mCursor.getLong(CONTACT_ID_COLUMN); - mProviderId = mCursor.getLong(PROVIDER_COLUMN); - mAccountId = mCursor.getLong(ACCOUNT_COLUMN); - mPresenceStatus = mCursor.getInt(PRESENCE_STATUS_COLUMN); - mType = mCursor.getInt(TYPE_COLUMN); - mUserName = mCursor.getString(USERNAME_COLUMN); - mNickName = mCursor.getString(NICKNAME_COLUMN); + case Presence.IDLE: + mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_green_dark)); + mRemoteAvatar.setAlpha(255); + break; + + case Presence.AWAY: + mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_orange_light)); + mRemoteAvatar.setAlpha(255); + break; + + case Presence.DO_NOT_DISTURB: + mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_red_dark)); + mRemoteAvatar.setAlpha(255); + break; + + case Presence.OFFLINE: + mRemoteAvatar.setBorderColor(getResources().getColor(R.color.holo_grey_light)); + mRemoteAvatar.setAlpha(100); + break; + + + default: + } } /* private void setTitle() { - + if (mType == Imps.Contacts.TYPE_GROUP) { final String[] projection = { Imps.GroupMembers.NICKNAME }; Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, mChatId); - ContentResolver cr = mActivity.getContentResolver(); + ContentResolver cr = mNewChatActivity.getContentResolver(); Cursor c = cr.query(memberUri, projection, null, null, null); StringBuilder buf = new StringBuilder(); BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); @@ -714,58 +1235,36 @@ private void setTitle() { buf.append(','); } } - + } - - mActivity.setTitle(buf.toString()); - + + mNewChatActivity.setTitle(buf.toString()); + } else { - - + + StringBuilder buf = new StringBuilder(); - + BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); - + buf.append(this.mNickName); buf.append(" ("); buf.append(brandingRes.getString(PresenceUtils.getStatusStringRes(this.mPresenceStatus))); buf.append(")"); - - mActivity.setTitle(buf.toString()); - + + mNewChatActivity.setTitle(buf.toString()); + Drawable avatar = loadAvatar(mUserName); - + // if (avatar != null) - // mActivity.setHomeIcon(avatar); - + // mNewChatActivity.setHomeIcon(avatar); + // } }*/ - - private Drawable loadAvatar (String jid) - { - try - { - //String filename = Base64.encodeBase64String(jid.getBytes()) + ".jpg"; - String fileName = Base64.encodeToString(jid.getBytes(), Base64.NO_WRAP) + ".jpg"; - File sdCard = new File(mActivity.getCacheDir(),"avatars"); - File fileAvatar = new File(sdCard, fileName); - - if (fileAvatar.exists()) - { - return new BitmapDrawable(BitmapFactory.decodeFile(fileAvatar.getCanonicalPath())); - } - else - return null; - } - catch (IOException ioe) - { - Log.e("Contacts","error loading avatar",ioe); - return null; - } - } + private void setStatusIcon() { - if (mType == Imps.Contacts.TYPE_GROUP) { + if (mContactType == Imps.Contacts.TYPE_GROUP) { // hide the status icon for group chat. // mStatusIcon.setVisibility(GONE); } else { @@ -773,145 +1272,117 @@ private void setStatusIcon() { BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); int presenceResId = PresenceUtils.getStatusIconId(mPresenceStatus); //mStatusIcon.setImageDrawable(brandingRes.getDrawable(presenceResId)); - - } - } - private void setDeliveryIcon() { - if (mExpectingDelivery) { - mDeliveryIcon.setVisibility(VISIBLE); - } else { - mDeliveryIcon.setVisibility(GONE); } } - public void rebind () - { - bindChat(mLastChatId); - } - private void deleteChat () { Uri chatUri = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mLastChatId); - mActivity.getContentResolver().delete(chatUri,null,null); + mNewChatActivity.getContentResolver().delete(chatUri,null,null); + } - - public void bindChat(long contactId) { - - mLastChatId = contactId; - - if (mCursor != null) { - mCursor.deactivate(); - } - - Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, contactId); - mCursor = mActivity.managedQuery(contactUri, CHAT_PROJECTION, null, null, null); - - if (mCursor == null || !mCursor.moveToFirst()) { + + public void bindChat(long chatId) { + log("bind " + this + " " + chatId); + mLastChatId = chatId; + + Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId); + Cursor c = mNewChatActivity.getContentResolver().query(contactUri, CHAT_PROJECTION, null, null, null); + + if (c == null) + return; + + if (!c.moveToFirst()) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("Failed to query chat: " + contactId); + log("Failed to query chat: " + chatId); } mLastChatId = -1; + + c.close(); + } else { - - mCurrentChatSession = getChatSession(mCursor); - updateChat(); - - if (mCurrentChatSession != null) + updateSessionInfo(c); + + if (mRemoteAvatar == null) { - // This will save the current chatId and providerId in the relevant fields. - // getChatSessionManager depends on mProviderId getting the cursor value of providerId. - - registerChatListener(); - } - } - - updateWarningView(); - - - } - - private IChatSession getChatSession () - { - return getChatSession(false); - } - - private IChatSession getChatSession (boolean autoInit) - { - if (mCurrentChatSession == null && autoInit) - bindChat(mLastChatId); - - return mCurrentChatSession; - } + try {mRemoteAvatar =DatabaseUtils.getAvatarFromCursor(c, AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT);} + catch (Exception e){} - private void initOtr() { + if (mRemoteAvatar == null) + { + mRemoteAvatar = new RoundedAvatarDrawable(BitmapFactory.decodeResource(getResources(), + R.drawable.avatar_unknown)); + + } + + updatePresenceDisplay(); - if (mOtrChatSession == null) - { - try - { - //if (mOtrChatSession == null && getChatSession () != null) { - - if (getChatSession() != null) - mOtrChatSession = getChatSession ().getOtrChatSession(); - else - mOtrChatSession = null; - - if (mOtrChatSession != null) { - - mOtrKeyManager = getChatSession ().getOtrKeyManager(); - - - } } - catch (Exception e) - { - Log.e(ImApp.LOG_TAG,"error setting up OTR session",e); + + + c.close(); + + mCurrentChatSession = getChatSession(); + + if (mCurrentChatSession == null) + mCurrentChatSession = createChatSession(); + + if (mCurrentChatSession != null) { + isServiceUp = true; + } + + updateChat(); } } public void bindInvitation(long invitationId) { Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId); - ContentResolver cr = mActivity.getContentResolver(); + ContentResolver cr = mNewChatActivity.getContentResolver(); Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null); - if (cursor == null || !cursor.moveToFirst()) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("Failed to query invitation: " + invitationId); - } - // mActivity.finish(); - } else { - setViewType(VIEW_TYPE_INVITATION); + try { + if (!cursor.moveToFirst()) { + if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { + log("Failed to query invitation: " + invitationId); + } + // mNewChatActivity.finish(); + } else { + setViewType(VIEW_TYPE_INVITATION); - mInvitationId = cursor.getLong(INVITATION_ID_COLUMN); - mProviderId = cursor.getLong(INVITATION_PROVIDER_COLUMN); - String sender = cursor.getString(INVITATION_SENDER_COLUMN); + mInvitationId = cursor.getLong(INVITATION_ID_COLUMN); + mProviderId = cursor.getLong(INVITATION_PROVIDER_COLUMN); + String sender = cursor.getString(INVITATION_SENDER_COLUMN); - TextView mInvitationText = (TextView) findViewById(R.id.txtInvitation); - mInvitationText.setText(mContext.getString(R.string.invitation_prompt, sender)); - mActivity.setTitle(mContext.getString(R.string.chat_with, sender)); + TextView mInvitationText = (TextView) findViewById(R.id.txtInvitation); + mInvitationText.setText(mContext.getString(R.string.invitation_prompt, sender)); + // mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, sender)); + } + } finally { + cursor.close(); } - + } + public void bindSubscription(long providerId, String from) { mProviderId = providerId; - mUserName = from; + + // mRemoteAddressString = from; setViewType(VIEW_TYPE_SUBSCRIPTION); TextView text = (TextView) findViewById(R.id.txtSubscription); String displayableAddr = ImpsAddressUtils.getDisplayableAddress(from); text.setText(mContext.getString(R.string.subscription_prompt, displayableAddr)); - mActivity.setTitle(mContext.getString(R.string.chat_with, displayableAddr)); + //.displayableAdd mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, displayableAddr)); mApp.dismissChatNotification(providerId, from); } - private void setViewType(int type) { mViewType = type; @@ -920,12 +1391,14 @@ private void setViewType(int type) { findViewById(R.id.subscription).setVisibility(GONE); setChatViewEnabled(true); } else if (type == VIEW_TYPE_INVITATION) { - setChatViewEnabled(false); + //setChatViewEnabled(false); + findViewById(R.id.invitationPanel).setVisibility(VISIBLE); findViewById(R.id.btnAccept).requestFocus(); } else if (type == VIEW_TYPE_SUBSCRIPTION) { - setChatViewEnabled(false); + //setChatViewEnabled(false); findViewById(R.id.subscription).setVisibility(VISIBLE); + findViewById(R.id.btnApproveSubscription).requestFocus(); } } @@ -934,7 +1407,8 @@ private void setChatViewEnabled(boolean enabled) { mComposeMessage.setEnabled(enabled); mSendButton.setEnabled(enabled); if (enabled) { - mComposeMessage.requestFocus(); + // This can steal focus from the fragment that's i n front of the user + //mComposeMessage.requestFocus(); } else { mHistory.setAdapter(null); } @@ -945,7 +1419,7 @@ ListView getHistoryView() { return mHistory; } - private void startQuery(long chatId) { + private synchronized void startQuery(long chatId) { if (mQueryHandler == null) { mQueryHandler = new QueryHandler(mContext); } else { @@ -994,7 +1468,7 @@ void requeryCursor() { // This is redundant if there are messages in view, because the cursor requery will update everything. // However, if there are no messages, no update will trigger below, and we still want this to update. - updateWarningView(true); + updateWarningView(); // TODO: async query? Cursor cursor = getMessageCursor(); @@ -1007,75 +1481,168 @@ private Cursor getMessageCursor() { return mMessageAdapter == null ? null : mMessageAdapter.getCursor(); } - public void closeChatSession() { + public void closeChatSession(boolean doDelete) { if (getChatSession() != null) { try { + + if (doDelete) + setOTRState(false); + + updateWarningView(); getChatSession().leave(); - + } catch (RemoteException e) { - + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } - } - - deleteChat(); - + } + + if (doDelete) + deleteChat(); + } - public void closeChatSessionIfInactive() { - if (getChatSession() != null) { - try { - getChatSession().leaveIfInactive(); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + public void verifyScannedFingerprint (String scannedFingerprint) + { + try + { + IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession(); + + if (scannedFingerprint != null && scannedFingerprint.equalsIgnoreCase(otrChatSession.getRemoteFingerprint())) { + verifyRemoteFingerprint(); } } - - deleteChat(); - + catch (RemoteException e) + { + LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e); + } } - public void viewProfile() { - String remoteFingerprint = null; - String localFingerprint = null; - boolean isVerified = false; - + public void showVerifyDialog() { if (getChatId() == -1) return; - - Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, getChatId()); - Intent intent = new Intent(Intent.ACTION_VIEW, data); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); + try { + IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession(); + if (otrChatSession == null) { + return; + } + + String localFingerprint = otrChatSession.getLocalFingerprint(); + String remoteFingerprint = otrChatSession.getRemoteFingerprint(); + if (TextUtils.isEmpty(localFingerprint) || TextUtils.isEmpty(remoteFingerprint)) { + return; + } - if (mOtrKeyManager != null) { - try { + StringBuffer message = new StringBuffer(); + message.append(mContext.getString(R.string.fingerprint_for_you)).append("\n") + .append(prettyPrintFingerprint(localFingerprint)).append("\n\n"); + message.append(mContext.getString(R.string.fingerprint_for_)) + .append(otrChatSession.getRemoteUserId()).append("\n") + .append(prettyPrintFingerprint(remoteFingerprint)).append("\n\n"); + + message.append(mContext.getString(R.string.are_you_sure_you_want_to_confirm_this_key_)); + + new AlertDialog.Builder(mContext) + .setTitle(R.string.verify_key_) + .setMessage(message.toString()) + .setPositiveButton(R.string.menu_verify_fingerprint, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + verifyRemoteFingerprint(); + } + }) + .setNegativeButton(R.string.menu_verify_secret, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + initSmpUI(); + } + }) + .setNeutralButton(R.string.menu_scan, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + new IntentIntegrator(mNewChatActivity).initiateScan(); - - localFingerprint = mOtrKeyManager.getLocalFingerprint(); - - remoteFingerprint = mOtrKeyManager.getRemoteFingerprint(); - - if (remoteFingerprint != null) - isVerified = mOtrKeyManager.isKeyVerified(mUserName); - else - isVerified = false; - - } catch (RemoteException e) { - e.printStackTrace(); + } + }).show(); + } catch (RemoteException e) { + LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e); + } + } + + private void initSmpUI() { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View viewSmp = inflater.inflate(R.layout.smp_question_dialog, null, false); + + if (viewSmp != null) + { + new AlertDialog.Builder(mContext).setTitle(mContext.getString(R.string.otr_qa_title)).setView(viewSmp) + .setPositiveButton(mContext.getString(R.string.otr_qa_send), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + + EditText eiQuestion = (EditText) viewSmp.findViewById(R.id.editSmpQuestion); + EditText eiAnswer = (EditText) viewSmp.findViewById(R.id.editSmpAnswer); + String question = eiQuestion.getText().toString(); + String answer = eiAnswer.getText().toString(); + initSmp(question, answer); + } + }).setNegativeButton(mContext.getString(R.string.otr_qa_cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Do nothing. + } + }).show(); + } + } + + private void initSmp(String question, String answer) { + try { + + if (mCurrentChatSession != null) + { + IOtrChatSession iOtrSession = mCurrentChatSession.getOtrChatSession(); + iOtrSession.initSmpVerification(question, answer); } - // TODO define these in ImServiceConstants - intent.putExtra("remoteFingerprint", remoteFingerprint); - intent.putExtra("localFingerprint", localFingerprint); - intent.putExtra("remoteVerified", isVerified); + + } catch (RemoteException e) { + Log.e(ImApp.LOG_TAG, "error init SMP", e); + } + } + + private void verifyRemoteFingerprint() { + + + try { + + IOtrChatSession otrChatSession = mCurrentChatSession.getOtrChatSession(); + otrChatSession.verifyKey(otrChatSession.getRemoteUserId()); + + + } catch (RemoteException e) { + Log.e(ImApp.LOG_TAG, "error init otr", e); + + } + + updateWarningView(); + + + } - mActivity.startActivity(intent); + private static String prettyPrintFingerprint (String fingerprint) + { + StringBuffer spacedFingerprint = new StringBuffer(); + + for (int i = 0; i + 8 <= fingerprint.length(); i+=8) + { + spacedFingerprint.append(fingerprint.subSequence(i,i+8)); + spacedFingerprint.append(' '); + } + + return spacedFingerprint.toString(); } public void blockContact() { @@ -1083,14 +1650,15 @@ public void blockContact() { DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { try { - IImConnection conn = mApp.getConnection(mProviderId); - IContactListManager manager = conn.getContactListManager(); - manager.blockContact(mUserName); - // mActivity.finish(); - } catch (RemoteException e) { + checkConnection(); + mConn = mApp.getConnection(mProviderId); + IContactListManager manager = mConn.getContactListManager(); + manager.blockContact(Address.stripResource(mRemoteAddress)); + // mNewChatActivity.finish(); + } catch (Exception e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } }; @@ -1100,7 +1668,7 @@ public void onClick(DialogInterface dialog, int whichButton) { // The positive button is deliberately set as no so that // the no is the default value new AlertDialog.Builder(mContext).setTitle(R.string.confirm) - .setMessage(r.getString(R.string.confirm_block_contact, mNickName)) + .setMessage(r.getString(R.string.confirm_block_contact, mRemoteNickname)) .setPositiveButton(R.string.yes, confirmListener) // default button .setNegativeButton(R.string.no, null).setCancelable(false).show(); } @@ -1113,94 +1681,94 @@ public long getAccountId() { return mAccountId; } - public String getUserName() { - return mUserName; - } - public long getChatId() { - try { - return getChatSession() == null ? -1 : getChatSession().getId(); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - return -1; - } + return mLastChatId; } - private IChatSessionManager getChatSessionManager(long providerId) { - if (mChatSessionManager == null || mProviderId != providerId) { + private IChatSession createChatSession() { + + try + { + checkConnection (); + + if (mConn != null) { + IChatSessionManager sessionMgr = mConn.getChatSessionManager(); + if (sessionMgr != null) { + + String remoteAddress = mRemoteAddress; + IChatSession session = null; + + if (mContactType == Imps.Contacts.TYPE_GROUP) + { + session = sessionMgr.createMultiUserChatSession(remoteAddress,null, false); + } + else + { + remoteAddress = Address.stripResource(mRemoteAddress); + + session = sessionMgr.createChatSession(remoteAddress,false); + } - IImConnection conn = mApp.getConnection(providerId); + return session; - if (conn != null) { - try { - mChatSessionManager = conn.getChatSessionManager(); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } + } } + + } catch (Exception e) { + + //mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "issue getting chat session",e); } - return mChatSessionManager; + return null; } - public IOtrKeyManager getOtrKeyManager() { - initOtr(); + private IChatSession getChatSession() { - return mOtrKeyManager; - } + try { - public IOtrChatSession getOtrChatSession() { - initOtr(); + if ( checkConnection ()) { - return mOtrChatSession; - } + if (mConn != null) + { + IChatSessionManager sessionMgr = mConn.getChatSessionManager(); + if (sessionMgr != null) { - private IChatSession getChatSession(Cursor cursor) { - long providerId = cursor.getLong(PROVIDER_COLUMN); - String username = cursor.getString(USERNAME_COLUMN); + IChatSession session = sessionMgr.getChatSession(Address.stripResource(mRemoteAddress)); - IChatSessionManager sessionMgr = getChatSessionManager(providerId); - if (sessionMgr != null) { - try { - return sessionMgr.getChatSession(username); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + return session; + + } + } } + + } catch (Exception e) { + + //mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "error getting chat session",e); } return null; } boolean isGroupChat() { - - boolean isGroupChat = false; - - if (mCurrentChatSession != null) - { - try { - isGroupChat = mCurrentChatSession.isGroupChatSession(); - } - catch (Exception e){} - - } - - return isGroupChat; + return this.mContactType == Imps.Contacts.TYPE_GROUP; } void sendMessage() { + + mEmojiPager.setVisibility(View.GONE); + String msg = mComposeMessage.getText().toString(); if (TextUtils.isEmpty(msg.trim())) { return; } - - IChatSession session = getChatSession(true); + + IChatSession session = getChatSession(); + + if (session == null) + session = createChatSession(); if (session != null) { try { @@ -1209,281 +1777,213 @@ void sendMessage() { mComposeMessage.requestFocus(); requeryCursor(); } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } catch (Exception e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } - } - } - - void sendMessage(String msg) { - - if (TextUtils.isEmpty(msg.trim())) { - return; - } - if (getChatSession() != null) { - try { - getChatSession().sendMessage(msg); - requeryCursor(); + // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } catch (Exception e) { - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + + // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } } void registerChatListener() { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("registerChatListener"); + log("registerChatListener " + mLastChatId); } try { if (getChatSession() != null) { getChatSession().registerChatListener(mChatListener); } - IImConnection conn = mApp.getConnection(mProviderId); - if (conn != null) { - IContactListManager listMgr = conn.getContactListManager(); + + checkConnection(); + + if (mConn != null) + { + IContactListManager listMgr = mConn.getContactListManager(); listMgr.registerContactListListener(mContactListListener); } - mApp.dismissChatNotification(mProviderId, mUserName); - } catch (RemoteException e) { + + } catch (Exception e) { Log.w(ImApp.LOG_TAG, " registerChatListener fail:" + e.getMessage()); } } void unregisterChatListener() { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("unregisterChatListener"); + log("unregisterChatListener " + mLastChatId); } try { if (getChatSession() != null) { getChatSession().unregisterChatListener(mChatListener); } - IImConnection conn = mApp.getConnection(mProviderId); - if (conn != null) { - IContactListManager listMgr = conn.getContactListManager(); + checkConnection (); + + if (mConn != null) { + IContactListManager listMgr = mConn.getContactListManager(); listMgr.unregisterContactListListener(mContactListListener); } - } catch (RemoteException e) { + } catch (Exception e) { Log.w(ImApp.LOG_TAG, " unregisterChatListener fail:" + e.getMessage()); } } - void registerChatSessionListener() { - IChatSessionManager sessionMgr = getChatSessionManager(mProviderId); - if (sessionMgr != null) { - mChatSessionListener = new ChatSessionListener(); - try { - sessionMgr.registerChatSessionListener(mChatSessionListener); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } - } - } - - void unregisterChatSessionListener() { - if (mChatSessionListener != null) { - try { - IChatSessionManager sessionMgr = getChatSessionManager(mProviderId); - sessionMgr.unregisterChatSessionListener(mChatSessionListener); - // We unregister the listener when the chat session we are - // waiting for has been created or the activity is stopped. - // Clear the listener so that we won't unregister the listener - // twice. - mChatSessionListener = null; - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); - } - } - } + void updateWarningView() { - void updateWarningView() - { - updateWarningView(false); - } - - void updateWarningView(boolean overrideUserTouch) { int visibility = View.GONE; int iconVisibility = View.GONE; String message = null; boolean isConnected; - SessionStatus sessionStatus = null; - - if (overrideUserTouch) - mOtrSwitchTouched = false; + + try { + checkConnection(); + isConnected = (mConn == null) ? false : mConn.getState() == ImConnection.LOGGED_IN; + + } catch (Exception e) { + + isConnected = false; + } if (this.isGroupChat()) { - //no OTR in group chat - mStatusWarningView.setVisibility(View.GONE); - return; + //anything to do here? + /* + visibility = View.VISIBLE; + message = getContext().getString(R.string.this_is_a_group_chat); + mWarningText.setTextColor(Color.WHITE); + mStatusWarningView.setBackgroundColor(Color.LTGRAY); + */ + + mButtonAttach.setVisibility(View.GONE); + + mSendButton.setImageResource(R.drawable.ic_send_holo_light); + + mComposeMessage.setHint(R.string.this_is_a_group_chat); + + } - - initOtr(); + else if (mCurrentChatSession != null) { + IOtrChatSession otrChatSession = null; - //check if the chat is otr or not - if (mOtrChatSession != null) { try { - sessionStatus = SessionStatus.values()[mOtrChatSession.getChatStatus()]; + otrChatSession = mCurrentChatSession.getOtrChatSession(); + + //check if the chat is otr or not + if (otrChatSession != null) { + try { + mLastSessionStatus = SessionStatus.values()[otrChatSession.getChatStatus()]; + } catch (RemoteException e) { + Log.w("Gibber", "Unable to call remote OtrChatSession from ChatView", e); + } + } + + } catch (RemoteException e) { - Log.w("Gibber", "Unable to call remote OtrChatSession from ChatView", e); + LogCleaner.error(ImApp.LOG_TAG, "error getting OTR session in ChatView", e); } - } - try { - IImConnection conn = mApp.getConnection(mProviderId); - isConnected = (conn == null) ? false : conn.getState() != ImConnection.SUSPENDED; - } catch (RemoteException e) { - - isConnected = false; - } + if (mContactType == Imps.Contacts.TYPE_GROUP) { + message = ""; + } + else if ((mSubscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM)) { + bindSubscription(mProviderId, mRemoteAddress); + visibility = View.VISIBLE; + //message = mContext.getString(R.string.contact_not_in_list_warning, mRemoteNickname); + //mWarningText.setTextColor(Color.WHITE); + //mStatusWarningView.setBackgroundColor(Color.DKGRAY); - if (isConnected) { + } else { - if (mType == Imps.Contacts.TYPE_GROUP) { visibility = View.GONE; - message = ""; + } - else if (mType == Imps.Contacts.TYPE_TEMPORARY) { - visibility = View.VISIBLE; - message = mContext.getString(R.string.contact_not_in_list_warning, mNickName); - } else if (mPresenceStatus == Imps.Presence.OFFLINE) { - visibility = View.VISIBLE; - message = mContext.getString(R.string.contact_offline_warning, mNickName); - } else { - visibility = View.VISIBLE; + + if (mLastSessionStatus == SessionStatus.PLAINTEXT) { + + mSendButton.setImageResource(R.drawable.ic_send_holo_light); + mComposeMessage.setHint(R.string.compose_hint); + } + else if (mLastSessionStatus == SessionStatus.ENCRYPTED) { - if (mPresenceStatus == Imps.Presence.OFFLINE) - { - mWarningText.setTextColor(Color.WHITE); - mStatusWarningView.setBackgroundColor(Color.DKGRAY); - message = mContext.getString(R.string.presence_offline); - - /* - if (!mOtrSwitchTouched) + if (mIsStartingOtr) { - mOtrSwitch.setOnCheckedChangeListener(null); - mOtrSwitch.setChecked(false); - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); + mIsStartingOtr = false; //it's started! + mProgressBarOtr.setVisibility(View.GONE); } - */ - } - else if (sessionStatus == SessionStatus.ENCRYPTED) { - try { + mComposeMessage.setHint(R.string.compose_hint_secure); + mSendButton.setImageResource(R.drawable.ic_send_secure); - if (mOtrKeyManager == null) - initOtr(); - - if (!mOtrSwitchTouched) - { - mOtrSwitch.setOnCheckedChangeListener(null); - mOtrSwitch.setChecked(true); - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - } - - String rFingerprint = mOtrKeyManager.getRemoteFingerprint(); - boolean rVerified = mOtrKeyManager.isKeyVerified(mUserName); + try + { + String rFingerprint = otrChatSession.getRemoteFingerprint(); + mIsVerified = otrChatSession.isKeyVerified(mRemoteAddress); - if (rFingerprint != null) { - if (!rVerified) { - message = mContext.getString(R.string.otr_session_status_encrypted); + } + catch (RemoteException re){} - mWarningText.setTextColor(Color.BLACK); - mStatusWarningView.setBackgroundResource(R.color.otr_yellow); - } else { - message = mContext.getString(R.string.otr_session_status_verified); - mWarningText.setTextColor(Color.BLACK); - mStatusWarningView.setBackgroundResource(R.color.otr_green); - } - } else { - mWarningText.setTextColor(Color.WHITE); - mStatusWarningView.setBackgroundResource(R.color.otr_red); - message = mContext.getString(R.string.otr_session_status_plaintext); - } + } else if (mLastSessionStatus == SessionStatus.FINISHED) { - // ImageView imgSec = (ImageView) findViewById(R.id.composeSecureIcon); -// imgSec.setImageResource(R.drawable.ic_menu_encrypt); + mSendButton.setImageResource(R.drawable.ic_send_holo_light); + mComposeMessage.setHint(R.string.compose_hint); - - // mSendButton.setCompoundDrawablesWithIntrinsicBounds( getContext().getResources().getDrawable(R.drawable.ic_menu_encrypt ), null, null, null ); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else if (sessionStatus == SessionStatus.FINISHED) { - // mSendButton.setCompoundDrawablesWithIntrinsicBounds( getContext().getResources().getDrawable(R.drawable.ic_menu_unencrypt ), null, null, null ); - - if (!mOtrSwitchTouched) - { - mOtrSwitch.setOnCheckedChangeListener(null); - mOtrSwitch.setChecked(true); - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - } - mWarningText.setTextColor(Color.WHITE); mStatusWarningView.setBackgroundColor(Color.DKGRAY); message = mContext.getString(R.string.otr_session_status_finished); - - mOtrChatSession = null; - } - else if (sessionStatus == SessionStatus.PLAINTEXT) { - - // mOtrSwitch.setChecked(false); - - if (!mOtrSwitchTouched) - { - mOtrSwitch.setOnCheckedChangeListener(null); - mOtrSwitch.setChecked(false); - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - } - -// ImageView imgSec = (ImageView) findViewById(R.id.composeSecureIcon); - // imgSec.setImageResource(R.drawable.ic_menu_unencrypt); - // mSendButton.setCompoundDrawablesWithIntrinsicBounds( getContext().getResources().getDrawable(R.drawable.ic_menu_unencrypt ), null, null, null ); - - mWarningText.setTextColor(Color.WHITE); - mStatusWarningView.setBackgroundResource(R.color.otr_red); - message = mContext.getString(R.string.otr_session_status_plaintext); + visibility = View.VISIBLE; } - } else { - + } + + if (!isConnected) + { + // visibility = View.VISIBLE; + // iconVisibility = View.VISIBLE; + // mWarningText.setTextColor(Color.WHITE); + // mStatusWarningView.setBackgroundColor(Color.DKGRAY); + // message = mContext.getString(R.string.disconnected_warning); + mComposeMessage.setHint(R.string.error_suspended_connection); - mOtrSwitch.setOnCheckedChangeListener(null); - mOtrSwitch.setChecked(false); - mOtrSwitch.setOnCheckedChangeListener(mOtrListener); - - - visibility = View.VISIBLE; - iconVisibility = View.VISIBLE; - mWarningText.setTextColor(Color.WHITE); - mWarningText.setBackgroundColor(Color.DKGRAY); - message = mContext.getString(R.string.disconnected_warning); - } - + mStatusWarningView.setVisibility(visibility); + if (visibility == View.VISIBLE) { - mWarningIcon.setVisibility(iconVisibility); - mWarningText.setText(message); + if (message != null && message.length() > 0) + { + mWarningText.setText(message); + mWarningText.setVisibility(View.VISIBLE); + } + else + { + mWarningText.setVisibility(View.GONE); + } } - + mNewChatActivity.updateEncryptionMenuState(); + + } + + public SessionStatus getOtrSessionStatus () + { + return mLastSessionStatus; + } + + public boolean isOtrSessionVerified () + { + return mIsVerified; + } + + public int getRemotePresence () + { + return mPresenceStatus; } @Override @@ -1494,8 +1994,16 @@ public boolean dispatchKeyEvent(KeyEvent event) { @Override public boolean dispatchTouchEvent(MotionEvent ev) { - userActionDetected(); - return super.dispatchTouchEvent(ev); + try { + userActionDetected(); + return super.dispatchTouchEvent(ev); + } catch (ActivityNotFoundException e) { + /* if the user clicked a link, e.g. geo:60.17,24.829, and there is + * no app to handle that kind of link, catch the exception */ + Toast.makeText(getContext(), R.string.error_no_app_to_handle_url, Toast.LENGTH_SHORT) + .show(); + return true; + } } @Override @@ -1505,22 +2013,25 @@ public boolean dispatchTrackballEvent(MotionEvent ev) { } private void userActionDetected() { - if (getChatSession() != null) { + // Check that we have a chat session and that our fragment is resumed + // The latter filters out bogus TextWatcher events on restore from saved + if (getChatSession() != null && mIsListening) { try { getChatSession().markAsRead(); - + // updateWarningView(); } catch (RemoteException e) { - + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } } + private final class ChatViewHandler extends SimpleAlertHandler { - + public ChatViewHandler(Activity activity) { super(activity); @@ -1540,36 +2051,39 @@ public void handleMessage(Message msg) { updateWarningView(); promptDisconnectedEvent(msg); return; - default: - updateWarningView(); - } - - super.handleMessage(msg); - } - } + case PROMPT_FOR_DATA_TRANSFER: + showPromptForData(msg.getData().getString("from"),msg.getData().getString("file")); + break; + case SHOW_DATA_ERROR: - class ChatSessionListener extends ChatSessionListenerAdapter { - @Override - public void onChatSessionCreated(IChatSession session) { - try { + String fileName = msg.getData().getString("file"); + String error = msg.getData().getString("err"); - if (session.isGroupChatSession()) { - final long id = session.getId(); - unregisterChatSessionListener(); - mHandler.post(new Runnable() { - public void run() { - bindChat(id); - } - }); - } + Toast.makeText(mContext, "Error transferring file: " + error, Toast.LENGTH_LONG).show(); + mProgressTransfer.setVisibility(View.GONE); + break; + case SHOW_DATA_PROGRESS: - updateWarningView(); + int percent = msg.getData().getInt("progress"); + + mProgressTransfer.setVisibility(View.VISIBLE); + mProgressTransfer.setProgress(percent); + mProgressTransfer.setMax(100); - } catch (RemoteException e) { + if (percent > 95) + { + mProgressTransfer.setVisibility(View.GONE); + requeryCursor(); + mMessageAdapter.notifyDataSetChanged(); + + } - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "on chat session created error",e); + break; + default: + updateWarningView(); } + + super.handleMessage(msg); } } @@ -1861,10 +2375,17 @@ private long getDeltaValue() { return t2 - t1; } - public int getType(int arg0) { - // TODO Auto-generated method stub - return 0; + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public int getType(int arg0) { + return mInnerCursor.getType(arg0); + } + + @TargetApi(19) + @Override + public Uri getNotificationUri() { + return mInnerCursor.getNotificationUri(); } + } private class MessageAdapter extends CursorAdapter implements AbsListView.OnScrollListener { @@ -1878,113 +2399,195 @@ private class MessageAdapter extends CursorAdapter implements AbsListView.OnScro private int mErrCodeColumn; private int mDeltaColumn; private int mDeliveredColumn; + private int mMimeTypeColumn; + private int mIdColumn; + private LayoutInflater mInflater; public MessageAdapter(Activity context, Cursor c) { super(context, c, false); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - + if (c != null) { resolveColumnIndex(c); } } private void resolveColumnIndex(Cursor c) { - - mNicknameColumn = c.getColumnIndexOrThrow(Imps.Messages.NICKNAME); - + mBodyColumn = c.getColumnIndexOrThrow(Imps.Messages.BODY); mDateColumn = c.getColumnIndexOrThrow(Imps.Messages.DATE); mTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.TYPE); mErrCodeColumn = c.getColumnIndexOrThrow(Imps.Messages.ERROR_CODE); mDeltaColumn = c.getColumnIndexOrThrow(DeltaCursor.DELTA_COLUMN_NAME); mDeliveredColumn = c.getColumnIndexOrThrow(Imps.Messages.IS_DELIVERED); + mMimeTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.MIME_TYPE); + mIdColumn = c.getColumnIndexOrThrow(Imps.Messages._ID); } @Override public void changeCursor(Cursor cursor) { + super.changeCursor(cursor); if (cursor != null) { resolveColumnIndex(cursor); } } + @Override + public int getItemViewType(int position) { + + Cursor c = getCursor(); + c.moveToPosition(position); + int type = c.getInt(mTypeColumn); + boolean isLeft = (type == Imps.MessageType.INCOMING_ENCRYPTED)||(type == Imps.MessageType.INCOMING)||(type == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED); + + if (isLeft) + return 0; + else + return 1; + + } + + @Override + public int getViewTypeCount() { + return 2; + } + + void setLinkifyForMessageView(MessageView messageView) { + try { + + if (messageView == null) + return; + + ContentResolver cr = getContext().getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI, + new String[] { Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE }, + Imps.ProviderSettings.PROVIDER + "=?", new String[] { Long + .toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS) }, + null); + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, + false /* keep updated */, null /* no handler */); + + if (settings != null) + { + if (mConn !=null) + messageView.setLinkify(!mConn.isUsingTor() || settings.getLinkifyOnTor()); + + settings.close(); + } + + if (pCursor != null) + pCursor.close(); + + } catch (RemoteException e) { + e.printStackTrace(); + messageView.setLinkify(false); + } + } + @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.new_message_item, parent, false); + + View result; + + int type = getItemViewType(cursor.getPosition()); + + if (type == 0) + result = mInflater.inflate(R.layout.message_view_left, null); + else + result = mInflater.inflate(R.layout.message_view_right, null); + + return result; } @Override public void bindView(View view, Context context, Cursor cursor) { MessageView messageView = (MessageView) view; - mType = cursor.getInt(mTypeColumn); - String address = mUserName; - String nickname = isGroupChat() ? cursor.getString(mNicknameColumn) : mNickName; + setLinkifyForMessageView(messageView); + + if (mApp.isThemeDark()) + { + messageView.setMessageBackground(getResources().getDrawable(R.drawable.message_view_rounded_dark)); + } + else + { + messageView.setMessageBackground(getResources().getDrawable(R.drawable.message_view_rounded_light)); + + } + + int messageType = cursor.getInt(mTypeColumn); + + String nickname = isGroupChat() ? cursor.getString(mNicknameColumn) : mRemoteNickname; + String mimeType = cursor.getString(mMimeTypeColumn); + int id = cursor.getInt(mIdColumn); String body = cursor.getString(mBodyColumn); long delta = cursor.getLong(mDeltaColumn); - boolean showTimeStamp = (delta > SHOW_TIME_STAMP_INTERVAL); + boolean showTimeStamp = true;//(delta > SHOW_TIME_STAMP_INTERVAL); long timestamp = cursor.getLong(mDateColumn); - + Date date = showTimeStamp ? new Date(timestamp) : null; boolean isDelivered = cursor.getLong(mDeliveredColumn) > 0; - boolean showDelivery = ((System.currentTimeMillis() - timestamp) > SHOW_DELIVERY_INTERVAL); - + long showDeliveryInterval = (mimeType == null) ? SHOW_DELIVERY_INTERVAL : SHOW_MEDIA_DELIVERY_INTERVAL; + boolean showDelivery = ((System.currentTimeMillis() - timestamp) > showDeliveryInterval); + DeliveryState deliveryState = DeliveryState.NEUTRAL; + if (showDelivery && !isDelivered && mExpectingDelivery) { deliveryState = DeliveryState.UNDELIVERED; } - + else if (isDelivered) + { + deliveryState = DeliveryState.DELIVERED; + } + EncryptionState encState = EncryptionState.NONE; - if (mType == Imps.MessageType.INCOMING_ENCRYPTED) + if (messageType == Imps.MessageType.INCOMING_ENCRYPTED) { - mType = Imps.MessageType.INCOMING; + messageType = Imps.MessageType.INCOMING; encState = EncryptionState.ENCRYPTED; } - else if (mType == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED) + else if (messageType == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED) { - mType = Imps.MessageType.INCOMING; + messageType = Imps.MessageType.INCOMING; encState = EncryptionState.ENCRYPTED_AND_VERIFIED; } - else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED) + else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED) { - mType = Imps.MessageType.OUTGOING; + messageType = Imps.MessageType.OUTGOING; encState = EncryptionState.ENCRYPTED; } - else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED) + else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED) { - mType = Imps.MessageType.OUTGOING; + messageType = Imps.MessageType.OUTGOING; encState = EncryptionState.ENCRYPTED_AND_VERIFIED; } - - switch (mType) { + + switch (messageType) { case Imps.MessageType.INCOMING: - if (body != null) - { - messageView.bindIncomingMessage(address, nickname, body, date, mMarkup, isScrolling(), encState, isGroupChat()); - } + messageView.bindIncomingMessage(id, messageType, mRemoteAddress, nickname, mimeType, body, date, mMarkup, isScrolling(), encState, isGroupChat(), mPresenceStatus); break; case Imps.MessageType.OUTGOING: case Imps.MessageType.POSTPONED: - - if (!isGroupChat()) - { - int errCode = cursor.getInt(mErrCodeColumn); - if (errCode != 0) { - messageView.bindErrorMessage(errCode); - } else { - messageView.bindOutgoingMessage(null, body, date, mMarkup, isScrolling(), - deliveryState, encState); - } + + int errCode = cursor.getInt(mErrCodeColumn); + if (errCode != 0) { + messageView.bindErrorMessage(errCode); + } else { + messageView.bindOutgoingMessage(id, messageType, null, mimeType, body, date, mMarkup, isScrolling(), + deliveryState, encState); } + break; default: - messageView.bindPresenceMessage(address, mType, isGroupChat(), isScrolling()); + messageView.bindPresenceMessage(mRemoteAddress, messageType, isGroupChat(), isScrolling()); } // updateWarningView(); @@ -1992,7 +2595,6 @@ else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED) if (!mExpectingDelivery && isDelivered) { log("Setting delivery icon"); mExpectingDelivery = true; - setDeliveryIcon(); scheduleRequery(DEFAULT_QUERY_INTERVAL); // FIXME workaround to no refresh } else if (cursor.getPosition() == cursor.getCount() - 1) { // if showTimeStamp is false for the latest message, then set a timer to query the @@ -2010,8 +2612,8 @@ else if (mType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED) } } } - - + + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { @@ -2026,9 +2628,9 @@ public void onScrollStateChanged(AbsListView view, int scrollState) { try { getChatSession().markAsRead(); } catch (RemoteException e) { - + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } @@ -2059,4 +2661,12 @@ EditText getComposedMessage() { return mComposeMessage; } + public void onServiceConnected() { + if (!isServiceUp) { + bindChat(mLastChatId); + startListening(); + } + + } + } diff --git a/src/info/guardianproject/otr/app/im/app/ChatViewPager.java b/src/info/guardianproject/otr/app/im/app/ChatViewPager.java index 7e818f2b9..2df82832d 100644 --- a/src/info/guardianproject/otr/app/im/app/ChatViewPager.java +++ b/src/info/guardianproject/otr/app/im/app/ChatViewPager.java @@ -10,10 +10,10 @@ public class ChatViewPager extends ViewPager { public ChatViewPager(Context context) { super(context); - + } - + public ChatViewPager(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/src/info/guardianproject/otr/app/im/app/ChooseAccountActivity.java b/src/info/guardianproject/otr/app/im/app/ChooseAccountActivity.java index af4a6f5b8..8d2021d62 100644 --- a/src/info/guardianproject/otr/app/im/app/ChooseAccountActivity.java +++ b/src/info/guardianproject/otr/app/im/app/ChooseAccountActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/ContactListActivity.java b/src/info/guardianproject/otr/app/im/app/ContactListActivity.java deleted file mode 100644 index dcd1b0922..000000000 --- a/src/info/guardianproject/otr/app/im/app/ContactListActivity.java +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open - * Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatSessionManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.ContactListFilterView.ContactListListener; -import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; - -import java.util.Observable; -import java.util.Observer; - -import android.app.Activity; -import android.app.SearchManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.os.RemoteException; -import android.support.v4.widget.SearchViewCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.CursorAdapter; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -public class ContactListActivity extends ThemeableActivity implements View.OnCreateContextMenuListener, ContactListListener { - - private static final int MENU_START_CONVERSATION = Menu.FIRST; - private static final int MENU_VIEW_PROFILE = Menu.FIRST + 1; - private static final int MENU_BLOCK_CONTACT = Menu.FIRST + 2; - private static final int MENU_DELETE_CONTACT = Menu.FIRST + 3; - private static final int MENU_END_CONVERSATION = Menu.FIRST + 4; - - private static final String FILTER_STATE_KEY = "Filtering"; - - ImApp mApp; - - long mProviderId; - long mAccountId; - IImConnection mConn; - ContactListView mContactListView; - ContactListFilterView mFilterView; - SimpleAlertHandler mHandler; - - ContextMenuHandler mContextMenuHandler; - - boolean mIsFiltering = true; - - Imps.ProviderSettings.QueryMap mGlobalSettingMap; - boolean mDestroyed; - - View mSearchView; - - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - LayoutInflater inflate = getLayoutInflater(); - - mContactListView = (ContactListView) inflate.inflate(R.layout.contact_list_view, null); - - mFilterView = (ContactListFilterView) getLayoutInflater().inflate( - R.layout.contact_list_filter_view, null); - - mFilterView.setListener(this); - - mFilterView.getListView().setOnCreateContextMenuListener(this); - - getSherlock().getActionBar().setHomeButtonEnabled(true); - getSherlock().getActionBar().setDisplayHomeAsUpEnabled(true); - - Intent intent = getIntent(); - mAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, -1); - if (mAccountId == -1) { - finish(); - return; - } - - mApp = (ImApp)getApplication(); - - } - - private void initAccount () - { - - - - ContentResolver cr = getContentResolver(); - Cursor c = cr.query(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, mAccountId), null, - null, null, null); - if (c == null) { - //finish(); - return; - } - if (!c.moveToFirst()) { - // c.close(); - //finish(); - return; - } - - mProviderId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); - mHandler = new MyHandler(this); - - String username = c.getString(c.getColumnIndexOrThrow(Imps.Account.USERNAME)); - - //c.close(); - - // BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); - // setTitle(brandingRes.getString(BrandingResourceIDs.STRING_BUDDY_LIST_TITLE, username)); - setTitle(username); - // getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, - // brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_LOGO)); - - mGlobalSettingMap = new Imps.ProviderSettings.QueryMap(getContentResolver(), true, null); - - mApp.callWhenServiceConnected(mHandler, new Runnable() { - public void run() { - if (!mDestroyed) { - mApp.dismissNotifications(mProviderId); - mConn = mApp.getConnection(mProviderId); - if (mConn == null) { - clearConnectionStatus(); - try { - mConn = mApp.createConnection(mProviderId, mAccountId); - } catch (RemoteException e) { - Log.e(ImApp.LOG_TAG, "The connection cannot be created"); - // finish(); - } - } - // mFilterView.mPresenceView.setConnection(mConn); - mFilterView.setConnection(mConn); - mContactListView.setConnection(mConn); - mContactListView.setHideOfflineContacts(mGlobalSettingMap - .getHideOfflineContacts()); - } - } - }); - - mContextMenuHandler = new ContextMenuHandler(); - mContactListView.getListView().setOnCreateContextMenuListener(this); - - mGlobalSettingMap.addObserver(new Observer() { - public void update(Observable observed, Object updateData) { - if (!mDestroyed) { - mContactListView.setHideOfflineContacts(mGlobalSettingMap - .getHideOfflineContacts()); - } - } - }); - - showFilterView(); - - } - - - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.contact_list_menu, menu); - - mSearchView = SearchViewCompat.newSearchView(this); - - if (mSearchView != null) - { - MenuItem item = menu.add("Search") - .setIcon(android.R.drawable.ic_menu_search) - .setActionView(mSearchView); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - - SearchViewCompat.setOnQueryTextListener(mSearchView, new SearchViewCompat.OnQueryTextListenerCompat() { - - @Override - public boolean onQueryTextChange(String newText) { - mFilterView.doFilter(newText); - return true; - } - - @Override - public boolean onQueryTextSubmit(String query) { - mFilterView.doFilter(query); - return true; - } - - - }); - - - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - //TODO make sure this works - - case R.id.menu_invite_user: - Intent i = new Intent(ContactListActivity.this, AddContactActivity.class); - i.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); - i.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - i.putExtra(ImServiceConstants.EXTRA_INTENT_LIST_NAME, - mContactListView.getSelectedContactList()); - startActivity(i); - return true; - - case android.R.id.home: - case R.id.menu_view_accounts: - startActivity(new Intent(getBaseContext(), ChooseAccountActivity.class)); - // finish(); - return true; - - case R.id.menu_settings: - Intent sintent = new Intent(this, SettingActivity.class); - startActivity(sintent); - return true; - - case R.id.menu_view_groups: - if (mIsFiltering) - showContactListView(); - else - showFilterView(); - - return true; - } - return super.onOptionsItemSelected(item); - } - - Intent getEditAccountIntent(boolean isSignedIn) { - Uri uri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, mProviderId); - - @SuppressWarnings("deprecation") - Cursor cursor = managedQuery(uri, - new String[] { Imps.Provider.CATEGORY }, null, null, null); - cursor.moveToFirst(); - - Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( - Imps.Account.CONTENT_URI, mAccountId)); - intent.addCategory(cursor.getString(0)); - // cursor.close(); - intent.putExtra("isSignedIn", isSignedIn); - - return intent; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(FILTER_STATE_KEY, mIsFiltering); - - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - boolean isFiltering = savedInstanceState.getBoolean(FILTER_STATE_KEY); - if (isFiltering) { - showFilterView(); - } - super.onRestoreInstanceState(savedInstanceState); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - - boolean handled = false; - - if (!mIsFiltering) { - handled = mFilterView.dispatchKeyEvent(event); - if (!handled && (KeyEvent.KEYCODE_BACK == keyCode) - && (KeyEvent.ACTION_DOWN == event.getAction())) { - showFilterView(); - handled = true; - } - } else { - - handled = mContactListView.dispatchKeyEvent(event); - - if (!handled && KeyEvent.KEYCODE_SEARCH == keyCode - && (KeyEvent.ACTION_DOWN == event.getAction())) { - InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMgr.toggleSoftInput(0, 0); - - if (!mIsFiltering) - showFilterView(); - - onSearchRequested(); - } else if (!handled && isReadable(keyCode, event) - && (KeyEvent.ACTION_DOWN == event.getAction())) { - - if (!mIsFiltering) - showFilterView(); - - handled = mFilterView.dispatchKeyEvent(event); - } - - } - - if (!handled) { - handled = super.dispatchKeyEvent(event); - } - - return handled; - } - - - @Override - protected void onNewIntent(Intent intent) { - - // The user has probably entered a URL into "Go" - - String action = intent.getAction(); - if (Intent.ACTION_SEARCH.equals(action)) { - - if (mIsFiltering) { - String filterText = intent.getStringExtra(SearchManager.QUERY); - - mFilterView.doFilter(filterText); - } - } - } - - private static boolean isReadable(int keyCode, KeyEvent event) { - if (KeyEvent.isModifierKey(keyCode) || event.isSystem()) { - return false; - } - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_ENTER: - return false; - } - - return true; - } - - private void showFilterView() { - - if (mGlobalSettingMap == null) - return; - - Uri uri = mGlobalSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - : Imps.Contacts.CONTENT_URI_CONTACTS_BY; - uri = ContentUris.withAppendedId(uri, mProviderId); - uri = ContentUris.withAppendedId(uri, mAccountId); - mFilterView.doFilter(uri, null); - - setContentView(mFilterView); - mFilterView.requestFocus(); - mIsFiltering = true; - } - - void showContactListView() { - - setContentView(mContactListView); - mContactListView.requestFocus(); - mContactListView.invalidate(); - mIsFiltering = false; - - } - - @Override - protected void onPause() { - super.onPause(); - mApp.unregisterForConnEvents(mHandler); - } - - @Override - protected void onResume() { - super.onResume(); - - mApp = (ImApp)getApplication(); - mApp.startImServiceIfNeed(); - mApp.setAppTheme(this); - - - initAccount (); - - mApp.registerForConnEvents(mHandler); - mContactListView.setAutoRefreshContacts(true); - - // Get the intent, verify the action and get the query - - - showFilterView(); - - Intent intent = getIntent(); - - if (intent.getAction() != null && Intent.ACTION_SEARCH.equals(intent.getAction())) { - if (mIsFiltering) { - String filterText = intent.getStringExtra(SearchManager.QUERY); - mFilterView.doFilter(filterText); - } - } - } - - @Override - protected void onDestroy() { - mDestroyed = true; - // set connection to null to unregister listeners. - mContactListView.setConnection(null); - mFilterView.setConnection(null); - if (mGlobalSettingMap != null) { - mGlobalSettingMap.close(); - } - super.onDestroy(); - } - - static void log(String msg) { - Log.v(ImApp.LOG_TAG, " " + msg); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - boolean chatSelected = false; - boolean contactSelected = false; - Cursor contactCursor; - if (mIsFiltering) { - AdapterView.AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - mContextMenuHandler.mPosition = info.position; - contactSelected = true; - contactCursor = mFilterView.getContactAtPosition(info.position); - } else { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo; - mContextMenuHandler.mPosition = info.packedPosition; - contactSelected = mContactListView.isContactAtPosition(info.packedPosition); - contactCursor = mContactListView.getContactAtPosition(info.packedPosition); - } - - boolean allowBlock = true; - if (contactCursor != null) { - //XXX HACK: Yahoo! doesn't allow to block a friend. We can only block a temporary contact. - ProviderDef provider = mApp.getProvider(mProviderId); - if (Imps.ProviderNames.YAHOO.equals(provider.mName)) { - int type = contactCursor.getInt(contactCursor - .getColumnIndexOrThrow(Imps.Contacts.TYPE)); - allowBlock = (type == Imps.Contacts.TYPE_TEMPORARY); - } - - int nickNameIndex = contactCursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME); - - menu.setHeaderTitle(contactCursor.getString(nickNameIndex)); - } - - BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); - String menu_end_conversation = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_END_CHAT); - String menu_view_profile = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_VIEW_PROFILE); - String menu_block_contact = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_BLOCK_CONTACT); - String menu_start_conversation = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_START_CHAT); - String menu_delete_contact = brandingRes - .getString(BrandingResourceIDs.STRING_MENU_DELETE_CONTACT); - - if (chatSelected) { - menu.add(0, MENU_END_CONVERSATION, 0, menu_end_conversation) - .setOnMenuItemClickListener(mContextMenuHandler); - menu.add(0, MENU_VIEW_PROFILE, 0, menu_view_profile) - .setIcon(R.drawable.ic_menu_my_profile) - .setOnMenuItemClickListener(mContextMenuHandler); - if (allowBlock) { - menu.add(0, MENU_BLOCK_CONTACT, 0, menu_block_contact) - .setOnMenuItemClickListener(mContextMenuHandler); - } - } else if (contactSelected) { - menu.add(0, MENU_START_CONVERSATION, 0, menu_start_conversation) - .setOnMenuItemClickListener(mContextMenuHandler); - menu.add(0, MENU_VIEW_PROFILE, 0, menu_view_profile) - .setIcon(R.drawable.ic_menu_view_profile) - .setOnMenuItemClickListener(mContextMenuHandler); - if (allowBlock) { - menu.add(0, MENU_BLOCK_CONTACT, 0, menu_block_contact) - .setOnMenuItemClickListener(mContextMenuHandler); - } - menu.add(0, MENU_DELETE_CONTACT, 0, menu_delete_contact) - .setIcon(android.R.drawable.ic_menu_delete) - .setOnMenuItemClickListener(mContextMenuHandler); - } - - // contactCursor.close(); - } - - void clearConnectionStatus() { - ContentResolver cr = getContentResolver(); - ContentValues values = new ContentValues(3); - - values.put(Imps.AccountStatus.ACCOUNT, mAccountId); - values.put(Imps.AccountStatus.PRESENCE_STATUS, Imps.Presence.OFFLINE); - values.put(Imps.AccountStatus.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); - // insert on the "account_status" uri actually replaces the existing value - cr.insert(Imps.AccountStatus.CONTENT_URI, values); - } - - final class ContextMenuHandler implements MenuItem.OnMenuItemClickListener, OnMenuItemClickListener { - long mPosition; - - public boolean onMenuItemClick(MenuItem item) { - Cursor c; - if (mIsFiltering) { - c = mFilterView.getContactAtPosition((int) mPosition); - } else { - c = mContactListView.getContactAtPosition(mPosition); - } - - switch (item.getItemId()) { - case MENU_START_CONVERSATION: - mContactListView.startChat(c); - break; - case MENU_VIEW_PROFILE: - mContactListView.viewContactPresence(c); - break; - case MENU_BLOCK_CONTACT: - mContactListView.blockContact(c); - break; - case MENU_DELETE_CONTACT: - mContactListView.removeContact(c); - break; - case MENU_END_CONVERSATION: - mContactListView.endChat(c); - break; - default: - return false; - } - - if (mIsFiltering) { - showContactListView(); - } - return true; - } - - @Override - public boolean onMenuItemClick(android.view.MenuItem item) { - Cursor c; - if (mIsFiltering) { - c = mFilterView.getContactAtPosition((int) mPosition); - } else { - c = mContactListView.getContactAtPosition(mPosition); - } - - switch (item.getItemId()) { - case MENU_START_CONVERSATION: - mContactListView.startChat(c); - break; - case MENU_VIEW_PROFILE: - mContactListView.viewContactPresence(c); - break; - case MENU_BLOCK_CONTACT: - mContactListView.blockContact(c); - break; - case MENU_DELETE_CONTACT: - mContactListView.removeContact(c); - break; - case MENU_END_CONVERSATION: - mContactListView.endChat(c); - break; - default: - return false; - } - - if (mIsFiltering) { - showContactListView(); - } - return true; - } - } - - final class MyHandler extends SimpleAlertHandler { - public MyHandler(Activity activity) { - super(activity); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == ImApp.EVENT_CONNECTION_DISCONNECTED) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("Handle event connection disconnected."); - } - promptDisconnectedEvent(msg); - long providerId = ((long) msg.arg1 << 32) | msg.arg2; - if (providerId == mProviderId) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("Current connection disconnected, finish"); - } - - startActivity(getEditAccountIntent(false)); - - // finish(); - } - return; - } - super.handleMessage(msg); - } - } - - - - - - - - public boolean onClose() { - - return false; - } - protected boolean isAlwaysExpanded() { - return false; - } - - private static final String[] PROVIDER_PROJECTION = { - Imps.Provider._ID, - Imps.Provider.NAME, - Imps.Provider.FULLNAME, - Imps.Provider.CATEGORY, - Imps.Provider.ACTIVE_ACCOUNT_ID, - Imps.Provider.ACTIVE_ACCOUNT_USERNAME, - Imps.Provider.ACTIVE_ACCOUNT_PW, - Imps.Provider.ACTIVE_ACCOUNT_LOCKED, - Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, - Imps.Provider.ACCOUNT_PRESENCE_STATUS, - Imps.Provider.ACCOUNT_CONNECTION_STATUS, }; - - static final int PROVIDER_ID_COLUMN = 0; - static final int PROVIDER_NAME_COLUMN = 1; - static final int PROVIDER_FULLNAME_COLUMN = 2; - static final int PROVIDER_CATEGORY_COLUMN = 3; - static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; - static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; - static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; - static final int ACTIVE_ACCOUNT_LOCKED = 7; - static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; - static final int ACCOUNT_PRESENCE_STATUS = 9; - static final int ACCOUNT_CONNECTION_STATUS = 10; - - - @Override - public void startChat(Cursor c) { - - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - try { - IChatSessionManager manager = mConn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session == null) { - manager.createChatSession(username); - } - - Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - i.addCategory(ImApp.IMPS_CATEGORY); - - startActivity(i); - // mScreen.finish(); - - //mContactListView.setAutoRefreshContacts(false); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - - } - - - } - - public void showProfile (Cursor c){} -} diff --git a/src/info/guardianproject/otr/app/im/app/ContactListFilterView.java b/src/info/guardianproject/otr/app/im/app/ContactListFilterView.java index 3787c2714..d5408af9c 100644 --- a/src/info/guardianproject/otr/app/im/app/ContactListFilterView.java +++ b/src/info/guardianproject/otr/app/im/app/ContactListFilterView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,96 +17,166 @@ package info.guardianproject.otr.app.im.app; +import info.guardianproject.otr.app.im.IContactListManager; import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.adapter.ConnectionListenerAdapter; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.util.LogCleaner; import android.app.Activity; -import android.content.ContentResolver; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.ResourceCursorAdapter; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.Filter; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.EditText; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.ResourceCursorAdapter; - -import com.tjerkw.slideexpandable.library.ActionSlideExpandableListView; +import android.widget.TextView; +import android.widget.Toast; public class ContactListFilterView extends LinearLayout { - - private ActionSlideExpandableListView mFilterList; - private Filter mFilter; + private AbsListView mFilterList; private ContactAdapter mContactAdapter; + private String mSearchString; + + private TextView mEmptyView = null; + private Uri mUri; private final Context mContext; private final SimpleAlertHandler mHandler; - private final ConnectionListenerAdapter mConnectionListener; - private IImConnection mConn; + private MyLoaderCallbacks mLoaderCallbacks; + + private ContactListListener mListener = null; + private LoaderManager mLoaderManager; + private int mLoaderId; public ContactListFilterView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mHandler = new SimpleAlertHandler((Activity)context); - mConnectionListener = new ConnectionListenerAdapter(mHandler) { - @Override - public void onConnectionStateChange(IImConnection connection, int state, - ImErrorInfo error) { - - } - - @Override - public void onUpdateSelfPresenceError(IImConnection connection, ImErrorInfo error) { - super.onUpdateSelfPresenceError(connection, error); - } - @Override - public void onSelfPresenceUpdated(IImConnection connection) { - super.onSelfPresenceUpdated(connection); - } - - - }; - } - - - @Override - public boolean isInEditMode() { - return true; } - @Override protected void onFinishInflate() { - mFilterList = (ActionSlideExpandableListView) findViewById(R.id.filteredList); + mFilterList = (AbsListView) findViewById(R.id.filteredList); mFilterList.setTextFilterEnabled(true); - + mEmptyView = (TextView) findViewById(R.id.empty); + mFilterList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent, View view, int position, long id) { Cursor c = (Cursor) mFilterList.getItemAtPosition(position); - + if (mListener != null) - mListener.startChat(c); + mListener.openChat(c); } }); - - mFilterList.setItemActionListener(new ActionSlideExpandableListView.OnActionClickListener() { + + mFilterList.setOnItemLongClickListener(new OnItemLongClickListener() + { + + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, final int position, long arg3) { + + String[] contactOptions = { + // mContext.getString(R.string.menu_verify), + mContext.getString(R.string.menu_contact_nickname), + mContext.getString(R.string.menu_remove_contact)}; + // mContext.getString(R.string.menu_block_contact)}; + + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setItems(contactOptions, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // The 'which' argument contains the index position + // of the selected item + + if (which == 0) + setContactNickname(position); + else if (which == 1) + removeContactAtPosition(position); + else if (which == 2) + blockContactAtPosition(position); + } + + }); + + builder.create().show(); + + return true; + + + } + + }); + + /** + mEtSearch = (EditText)findViewById(R.id.contactSearch); + + mEtSearch.addTextChangedListener(new TextWatcher() + { + + @Override + public void afterTextChanged(Editable s) { + + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + ContactListFilterView.this.doFilter(mEtSearch.getText().toString()); + + } + + }); + + + mEtSearch.setOnKeyListener(new OnKeyListener () + { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + + ContactListFilterView.this.doFilter(mEtSearch.getText().toString()); + return false; + } + + }); + */ + + /* + mFilterList.setItemActionListener(new ListView.OnActionClickListener() { @Override public void onClick(View listView, View buttonview, int position) { @@ -117,18 +187,19 @@ public void onClick(View listView, View buttonview, int position) { mListener.startChat(c); else if (buttonview.getId() == R.id.btnExListProfile) mListener.showProfile(c); - + } }, R.id.btnExListChat, R.id.btnExListProfile); + */ + + // - // - //if (!isInEditMode()) // mPresenceView = (UserPresenceView) findViewById(R.id.userPresence); } - public ListView getListView() { + public AbsListView getListView() { return mFilterList; } @@ -136,158 +207,300 @@ public Cursor getContactAtPosition(int position) { return (Cursor) mContactAdapter.getItem(position); } - public void setConnection(IImConnection conn) { - if (mConn != conn) { - if (mConn != null) { - unregisterListeners(); - } - - mConn = conn; - if (conn != null) { - /* - try { - //mPresenceView.loggingIn(mConn.getState() == ImConnection.LOGGING_IN); - } catch (RemoteException e) { - //mPresenceView.loggingIn(false); - mHandler.showServiceErrorAlert(); - } - */ - - registerListeners(); - } + + public void doFilter(Uri uri, String filterString) { + if (uri != null && !uri.equals(mUri)) { + mUri = uri; } + doFilter(filterString); } - private void registerListeners() { - try { - mConn.registerConnectionListener(mConnectionListener); - } catch (RemoteException e) { + boolean mAwaitingUpdate = false; - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } + public synchronized void doFilter(String filterString) { - private void unregisterListeners() { - try { - mConn.unregisterConnectionListener(mConnectionListener); - } catch (RemoteException e) { + mSearchString = filterString; - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } + if (mContactAdapter == null) { - - public void doFilter(Uri uri, String filterString) { - if (!uri.equals(mUri)) { - mUri = uri; + mContactAdapter = new ContactAdapter(mContext, R.layout.contact_view); - if (mContactAdapter != null && mContactAdapter.getCursor() != null) - mContactAdapter.getCursor() .close(); - - Cursor contactCursor = runQuery(filterString); - - if (mContactAdapter == null) { - mContactAdapter = new ContactAdapter(mContext, contactCursor); - mFilter = mContactAdapter.getFilter(); - mFilterList.setAdapter(mContactAdapter); - } else { - mContactAdapter.changeCursor(contactCursor); - } - - - } else { - mFilter.filter(filterString); - } - } + ((ListView)mFilterList).setAdapter(mContactAdapter); - public void doFilter(String filterString) { - mFilter.filter(filterString); + mLoaderCallbacks = new MyLoaderCallbacks(); + mLoaderManager.initLoader(mLoaderId, null, mLoaderCallbacks); + } else { - } + if (!mAwaitingUpdate) + { + mAwaitingUpdate = true; + mHandler.postDelayed(new Runnable () + { - Cursor runQuery(CharSequence constraint) { - StringBuilder buf = new StringBuilder(); + public void run () + { - if (constraint != null) { + mLoaderManager.restartLoader(mLoaderId, null, mLoaderCallbacks); + mAwaitingUpdate = false; + } + },1000); + } - buf.append(Imps.Contacts.NICKNAME); - buf.append(" LIKE "); - DatabaseUtils.appendValueToSql(buf, "%" + constraint + "%"); } - - ContentResolver cr = mContext.getContentResolver(); - - Cursor cursor = cr.query(mUri, ContactView.CONTACT_PROJECTION, - buf == null ? null : buf.toString(), null, Imps.Contacts.DEFAULT_SORT_ORDER); - - - - return cursor; } private class ContactAdapter extends ResourceCursorAdapter { - private String mSearchString; - @SuppressWarnings("deprecation") - public ContactAdapter(Context context, Cursor cursor) { - super(context, R.layout.contact_view, cursor); + public ContactAdapter(Context context, int view) { + super(context, view, null,0); + } + @Override - public View getView(int position, View convertView, ViewGroup parent) { - + public View newView(Context context, Cursor cursor, ViewGroup parent) { - return super.getView(position, convertView, parent); - } + View view = super.newView(context, cursor, parent); + + ContactView.ViewHolder holder = null; + + holder = new ContactView.ViewHolder(); + + holder.mLine1 = (TextView) view.findViewById(R.id.line1); + holder.mLine2 = (TextView) view.findViewById(R.id.line2); + + holder.mAvatar = (ImageView)view.findViewById(R.id.avatar); + holder.mStatusIcon = (ImageView)view.findViewById(R.id.statusIcon); + //holder.mEncryptionIcon = (ImageView)view.findViewById(R.id.encryptionIcon); + + holder.mContainer = view.findViewById(R.id.message_container); + + holder.mMediaThumb = (ImageView)view.findViewById(R.id.media_thumbnail); + + view.setTag(holder); + return view; + } + @Override public void bindView(View view, Context context, Cursor cursor) { ContactView v = (ContactView) view; - v.bind(cursor, mSearchString, false); - } + v.bind(cursor, mSearchString, true); - @Override - public Cursor runQueryOnBackgroundThread(CharSequence constraint) { - if (constraint != null) { - mSearchString = constraint.toString(); - } - return ContactListFilterView.this.runQuery(constraint); } + } public interface ContactListListener { - - public void startChat (Cursor c); + + public void openChat (Cursor c); public void showProfile (Cursor c); } - - - private ContactListListener mListener = null; - + + public void setListener (ContactListListener listener) { mListener = listener; } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - + + private void setContactNickname(int aPosition) { + Cursor cursor = (Cursor)mFilterList.getItemAtPosition(aPosition); + final IImConnection conn = getConnection (cursor); + + final String address = cursor.getString(cursor.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + final String nickname = cursor.getString(cursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + final View view = LayoutInflater.from(mContext).inflate(R.layout.alert_dialog_contact_nickname, null); + ((TextView)view.findViewById(R.id.contact_address_textview)).setText( address ); + ((EditText)view.findViewById(R.id.contact_nickname_edittext)).setText( nickname ); + + new AlertDialog.Builder(mContext) + .setTitle(mContext.getString(R.string.menu_contact_nickname, nickname)) + .setView(view) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + final String newNickname = ((EditText)view.findViewById(R.id.contact_nickname_edittext)).getText().toString(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setContactNickname( address, newNickname, conn) ; + } + }, 500); + } + }) + .setNegativeButton(R.string.cancel, null).show(); } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - - if (mContactAdapter != null && mContactAdapter.getCursor() != null) - { - mContactAdapter.getCursor().close(); - mContactAdapter = null; + /** + * @param value + */ + protected void setContactNickname(String aAddress, String aNickname, IImConnection conn) { + try { + + IContactListManager listManager = conn.getContactListManager(); + int result = listManager.setContactName(aAddress, aNickname); + if( result != ImErrorInfo.NO_ERROR ) { + Toast.makeText(mContext, mContext.getString(R.string.error_prefix) + result, Toast.LENGTH_LONG).show(); // TODO -LS error handling + } + } catch( Exception e ) { + Toast.makeText(mContext, mContext.getString(R.string.error_prefix) + e.getMessage(), Toast.LENGTH_LONG).show(); // TODO -LS error handling } - + mFilterList.invalidate(); + final InputMethodManager imm = (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getWindowToken(), 0); } + + + public void removeContactAtPosition(int packedPosition) { + removeContact((Cursor)mFilterList.getItemAtPosition(packedPosition)); + } + + void removeContact(Cursor c) { + final IImConnection conn = getConnection (c); + + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + try { + if (conn != null) + { + IContactListManager manager = conn.getContactListManager(); + int res = manager.removeContact(address); + if (res != ImErrorInfo.NO_ERROR) { + mHandler.showAlert(R.string.error, + ErrorResUtils.getErrorRes(getResources(), res, address)); + } + } + } catch (RemoteException e) { + + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "remote error",e); + } + } + }; + + Resources r = getResources(); + + new AlertDialog.Builder(mContext).setTitle(R.string.confirm) + .setMessage(r.getString(R.string.confirm_delete_contact, nickname)) + .setPositiveButton(R.string.yes, confirmListener) // default button + .setNegativeButton(R.string.no, null).setCancelable(false).show(); + + + + } + + private IImConnection getConnection (Cursor c) + { + return ImApp.sImApp.getConnection(c.getLong(ContactView.COLUMN_CONTACT_PROVIDER)); + + } + + public void blockContactAtPosition(int packedPosition) { + blockContact((Cursor)mFilterList.getItemAtPosition(packedPosition)); + } + + void blockContact(Cursor c) { + + final IImConnection conn = getConnection (c); + + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + try { + IContactListManager manager = conn.getContactListManager(); + + int res = -1; + + if (manager.isBlocked(address)) + res = manager.unBlockContact(address); + else + { + res = manager.blockContact(address); + + if (res != ImErrorInfo.NO_ERROR) { + mHandler.showAlert(R.string.error, + ErrorResUtils.getErrorRes(getResources(), res, address)); + } + } + } catch (RemoteException e) { + + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "remote error",e); + } + } + }; + + Resources r = getResources(); + + new AlertDialog.Builder(mContext).setTitle(R.string.confirm) + .setMessage(r.getString(R.string.confirm_block_contact, nickname)) + .setPositiveButton(R.string.yes, confirmListener) // default button + .setNegativeButton(R.string.no, null).setCancelable(false).show(); + + + } + + + public void setLoaderManager(LoaderManager loaderManager, int loaderId) { + mLoaderManager = loaderManager; + mLoaderId = loaderId; + } + + class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { + @Override + public Loader onCreateLoader(int id, Bundle args) { + StringBuilder buf = new StringBuilder(); + + if (mSearchString != null) { + + buf.append(Imps.Contacts.NICKNAME); + buf.append(" LIKE "); + DatabaseUtils.appendValueToSql(buf, "%" + mSearchString + "%"); + buf.append(" OR "); + buf.append(Imps.Contacts.USERNAME); + buf.append(" LIKE "); + DatabaseUtils.appendValueToSql(buf, "%" + mSearchString + "%"); + } + + CursorLoader loader = new CursorLoader(getContext(), mUri, ContactView.CONTACT_PROJECTION, + buf == null ? null : buf.toString(), null, Imps.Contacts.DEFAULT_SORT_ORDER); + + // loader.setUpdateThrottle(10L); + return loader; + } + + @Override + public void onLoadFinished(Loader loader, Cursor newCursor) { + if (newCursor == null) + return; // the app was quit or something while this was working + newCursor.setNotificationUri(getContext().getContentResolver(), mUri); + + mContactAdapter.changeCursor(newCursor); + + if (newCursor != null && newCursor.getCount() == 0) + { + if (mUri.getPath().contains("/contacts/chatting")) + mEmptyView.setText(R.string.empty_conversation_group); + else + mEmptyView.setText(R.string.empty_contact_list); + } + + } + + @Override + public void onLoaderReset(Loader loader) { + + mContactAdapter.swapCursor(null); + + } + + } + + } diff --git a/src/info/guardianproject/otr/app/im/app/ContactListTreeAdapter.java b/src/info/guardianproject/otr/app/im/app/ContactListTreeAdapter.java deleted file mode 100644 index 1945dd855..000000000 --- a/src/info/guardianproject/otr/app/im/app/ContactListTreeAdapter.java +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.provider.Imps; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.Observable; -import java.util.Observer; - -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.IImConnection; - -import android.app.Activity; -import android.content.AsyncQueryHandler; -import android.content.ContentQueryMap; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.net.Uri; -import android.os.RemoteException; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.BaseExpandableListAdapter; -import android.widget.CursorTreeAdapter; -import android.widget.TextView; -import android.widget.AbsListView.OnScrollListener; - -public class ContactListTreeAdapter extends BaseExpandableListAdapter implements - AbsListView.OnScrollListener { - - private static final String[] CONTACT_LIST_PROJECTION = { Imps.ContactList._ID, - Imps.ContactList.NAME, }; - - private static final int COLUMN_CONTACT_LIST_ID = 0; - private static final int COLUMN_CONTACT_LIST_NAME = 1; - - Activity mActivity; - SimpleAlertHandler mHandler; - private LayoutInflater mInflate; - private long mProviderId; - long mAccountId; - Cursor mSubscriptions; - boolean mDataValid; - ListTreeAdapter mAdapter; - private boolean mHideOfflineContacts; - - final MyContentObserver mContentObserver; - final MyDataSetObserver mDataSetObserver; - - private ArrayList mExpandedGroups; - - private static final int TOKEN_CONTACT_LISTS = -1; - // private static final int TOKEN_ONGOING_CONVERSATION = -2; - private static final int TOKEN_SUBSCRIPTION = -3; - -// private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "(" -// + Imps.Contacts.LAST_MESSAGE_DATE -// + " IS NULL) AND (" -// + Imps.Contacts.TYPE + "!=" -// + Imps.Contacts.TYPE_BLOCKED + ")"; - - private static final String BLOCKED_CONTACTS = "(" + Imps.Contacts.TYPE + "!=" - + Imps.Contacts.TYPE_BLOCKED + ")"; - - private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST + "=? AND " - + BLOCKED_CONTACTS; - - private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION + " AND " - + Imps.Contacts.PRESENCE_STATUS + " != " - + Imps.Presence.OFFLINE; - - static final void log(String msg) { - Log.d(ImApp.LOG_TAG, "" + msg); - } - - static final String[] CONTACT_COUNT_PROJECTION = { Imps.Contacts.CONTACTLIST, - Imps.Contacts._COUNT, }; - - ContentQueryMap mOnlineContactsCountMap; - - // Async QueryHandler - private final class QueryHandler extends AsyncQueryHandler { - public QueryHandler(Context context) { - super(context.getContentResolver()); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor c) { - - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("onQueryComplete:token=" + token); - } - - if (token == TOKEN_CONTACT_LISTS) { - mDataValid = true; - mAdapter.setGroupCursor(c); - } else if (token == TOKEN_SUBSCRIPTION) { - setSubscriptions(c); - notifyDataSetChanged(); - } else { - int count = mAdapter.getGroupCount(); - for (int pos = 0; pos < count; pos++) { - long listId = mAdapter.getGroupId(pos); - if (listId == token) { - mAdapter.setChildrenCursor(pos, c); - break; - } - } - } - } - } - - private QueryHandler mQueryHandler; - - private int mScrollState; - - private boolean mAutoRequery; - private boolean mRequeryPending; - - public ContactListTreeAdapter(IImConnection conn, Activity activity) { - mActivity = activity; - mInflate = activity.getLayoutInflater(); - mHandler = new SimpleAlertHandler(activity); - - mAdapter = new ListTreeAdapter(null); - - mContentObserver = new MyContentObserver(); - mDataSetObserver = new MyDataSetObserver(); - mExpandedGroups = new ArrayList(); - mQueryHandler = new QueryHandler(activity); - - changeConnection(conn); - } - - public void changeConnection(IImConnection conn) { - mQueryHandler.cancelOperation(TOKEN_SUBSCRIPTION); - mQueryHandler.cancelOperation(TOKEN_CONTACT_LISTS); - - synchronized (this) { - - if (mSubscriptions != null) { - mSubscriptions.close(); - mSubscriptions = null; - } - if (mOnlineContactsCountMap != null) { - mOnlineContactsCountMap.close(); - } - } - - mAdapter.notifyDataSetChanged(); - if (conn != null) { - try { - mProviderId = conn.getProviderId(); - mAccountId = conn.getAccountId(); - startQueryContactLists(); - startQuerySubscriptions(); - } catch (RemoteException e) { - // Service died! - } - } - } - - public void setHideOfflineContacts(boolean hide) { - if (mHideOfflineContacts != hide) { - mHideOfflineContacts = hide; - mAdapter.notifyDataSetChanged(); - } - } - - public void startAutoRequery() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startAutoRequery()"); - } - mAutoRequery = true; - if (mRequeryPending) { - mRequeryPending = false; - // startQueryOngoingConversations(); - } - } - - private void startQueryContactLists() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQueryContactLists()"); - } - - Uri uri = Imps.ContactList.CONTENT_URI; - uri = ContentUris.withAppendedId(uri, mProviderId); - uri = ContentUris.withAppendedId(uri, mAccountId); - - mQueryHandler.startQuery(TOKEN_CONTACT_LISTS, null, uri, CONTACT_LIST_PROJECTION, null, - null, Imps.ContactList.DEFAULT_SORT_ORDER); - } - - void startQuerySubscriptions() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQuerySubscriptions()"); - } - - Uri.Builder builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); - ContentUris.appendId(builder, mProviderId); - ContentUris.appendId(builder, mAccountId); - - Uri uri = builder.build(); - - mQueryHandler.startQuery(TOKEN_SUBSCRIPTION, null, uri, ContactView.CONTACT_PROJECTION, - String.format(Locale.US, "%s=%d AND %s=%d", Imps.Contacts.SUBSCRIPTION_STATUS, - Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING, - Imps.Contacts.SUBSCRIPTION_TYPE, Imps.Contacts.SUBSCRIPTION_TYPE_FROM), - null, Imps.Contacts.DEFAULT_SORT_ORDER); - } - - void startQueryContacts(long listId) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("startQueryContacts - listId=" + listId); - } - - String selection = mHideOfflineContacts ? ONLINE_CONTACT_SELECTION : CONTACTS_SELECTION; - String[] args = { Long.toString(listId) }; - int token = (int) listId; - mQueryHandler.startQuery(token, null, Imps.Contacts.CONTENT_URI, - ContactView.CONTACT_PROJECTION, selection, args, Imps.Contacts.DEFAULT_SORT_ORDER); - } - - public Object getChild(int groupPosition, int childPosition) { - if (isPosForSubscription(groupPosition)) { - return moveTo(getSubscriptions(), childPosition); - } else { - return mAdapter.getChild(getChildAdapterPosition(groupPosition), childPosition); - } - } - - public long getChildId(int groupPosition, int childPosition) { - if (isPosForSubscription(groupPosition)) { - return getId(getSubscriptions(), childPosition); - } else { - return mAdapter.getChildId(getChildAdapterPosition(groupPosition), childPosition); - } - } - - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - - if (isPosForSubscription(groupPosition)) { - View view = null; - - if (convertView != null) { - // use the convert view if it matches the type required by displayEmpty - if ((convertView instanceof TextView)) { - view = convertView; - ((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group)); - } else if ((convertView instanceof ContactView)) { - view = convertView; - } - } - if (view == null) { - view = newChildView(parent); - - } - - Cursor cursor = getSubscriptions(); - cursor.moveToPosition(childPosition); - - if (view instanceof ContactView) - ((ContactView) view).bind(cursor, null, isScrolling()); - - return view; - } else { - return mAdapter.getChildView(getChildAdapterPosition(groupPosition), childPosition, - isLastChild, convertView, parent); - } - } - - public int getChildrenCount(int groupPosition) { - if (!mDataValid) { - return 0; - } - if (isPosForSubscription(groupPosition)) { - return getSubscriptionCount(); - } else { - // XXX getChildrenCount() may be called with an invalid groupPosition that is larger - // than the total number of all groups. - int position = getChildAdapterPosition(groupPosition); - if (position >= mAdapter.getGroupCount()) { - Log.w(ImApp.LOG_TAG, "getChildrenCount out of range"); - return 0; - } - return mAdapter.getChildrenCount(position); - } - } - - public Object getGroup(int groupPosition) { - if (isPosForSubscription(groupPosition)) { - return null; - } else { - return mAdapter.getGroup(getChildAdapterPosition(groupPosition)); - } - } - - public int getGroupCount() { - if (!mDataValid) { - return 0; - } - int count = mAdapter.getGroupCount(); - - if (getSubscriptionCount() > 0) { - count++; - } - - return count; - } - - public long getGroupId(int groupPosition) { - if (isPosForSubscription(groupPosition)) { - return 0; - } else { - return mAdapter.getGroupId(getChildAdapterPosition(groupPosition)); - } - } - - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, - ViewGroup parent) { - if (isPosForSubscription(groupPosition)) { - View v; - if (convertView != null) { - v = convertView; - } else { - v = newGroupView(parent); - } - - TextView text1 = (TextView) v.findViewById(R.id.text1); - TextView text2 = (TextView) v.findViewById(R.id.text2); - - Resources r = v.getResources(); - String text = r.getString(R.string.subscriptions); - text1.setText(text); - text2.setVisibility(View.GONE); - return v; - } else { - - return mAdapter.getGroupView(getChildAdapterPosition(groupPosition), isExpanded, - convertView, parent); - - } - } - - public boolean isChildSelectable(int groupPosition, int childPosition) { - - if (isPosForSubscription(groupPosition)) - return true; - return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition); - } - - public boolean stableIds() { - return true; - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - mAdapter.registerDataSetObserver(observer); - super.registerDataSetObserver(observer); - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - mAdapter.unregisterDataSetObserver(observer); - super.unregisterDataSetObserver(observer); - } - - public boolean hasStableIds() { - return true; - } - - @Override - public void onGroupCollapsed(int groupPosition) { - super.onGroupCollapsed(groupPosition); - mExpandedGroups.remove(Integer.valueOf(groupPosition)); - int pos = getChildAdapterPosition(groupPosition); - if (pos >= 0) { - mAdapter.onGroupCollapsed(pos); - } - } - - @Override - public void onGroupExpanded(int groupPosition) { - super.onGroupExpanded(groupPosition); - mExpandedGroups.add(groupPosition); - int pos = getChildAdapterPosition(groupPosition); - if (pos >= 0) { - mAdapter.onGroupExpanded(pos); - } - } - - public int[] getExpandedGroups() { - - ArrayList expandedGroups = mExpandedGroups; - int size = expandedGroups.size(); - int[] res = new int[size]; - for (int i = 0; i < size; i++) { - res[i] = expandedGroups.get(i); - } - return res; - } - - View newChildView(ViewGroup parent) { - return mInflate.inflate(R.layout.contact_view, parent, false); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false); - } - - View newGroupView(ViewGroup parent) { - return mInflate.inflate(R.layout.group_view, parent, false); - } - - private synchronized Cursor getSubscriptions() { - if (mSubscriptions == null) { - startQuerySubscriptions(); - } - return mSubscriptions; - } - - synchronized void setSubscriptions(Cursor c) { - if (mSubscriptions != null) { - mSubscriptions.unregisterContentObserver(mContentObserver); - mSubscriptions.unregisterDataSetObserver(mDataSetObserver); - mSubscriptions.close(); - } - - c.registerContentObserver(mContentObserver); - c.registerDataSetObserver(mDataSetObserver); - - mSubscriptions = c; - - } - - private int getSubscriptionCount() { - Cursor c = getSubscriptions(); - return c == null ? 0 : c.getCount(); - } - - public boolean isPosForSubscription(int groupPosition) { - return groupPosition == 0 && getSubscriptionCount() > 0; - } - - private int getChildAdapterPosition(int groupPosition) { - - if (getSubscriptionCount() > 0) { - return groupPosition - 1; - } else { - return groupPosition; - } - } - - private Cursor moveTo(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor; - } - return null; - } - - private long getId(Cursor cursor, int position) { - if (cursor.moveToPosition(position)) { - return cursor.getLong(ContactView.COLUMN_CONTACT_ID); - } - return 0; - } - - class ListTreeAdapter extends CursorTreeAdapter { - - public ListTreeAdapter(Cursor cursor) { - super(cursor, mActivity); - } - - @Override - protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { - // binding when child is text view for an empty group - if (view instanceof TextView) { - ((TextView) view).setText(mActivity.getText(R.string.empty_contact_group)); - } else { - ((ContactView) view).bind(cursor, null, isScrolling()); - } - } - - @Override - protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { - TextView text1 = (TextView) view.findViewById(R.id.text1); - TextView text2 = (TextView) view.findViewById(R.id.text2); - Resources r = view.getResources(); - - text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME)); - text2.setVisibility(View.VISIBLE); - text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor))); - } - - View newEmptyView(ViewGroup parent) { - return mInflate.inflate(R.layout.empty_contact_group_view, parent, false); - } - - // if the group is empty, provide a text view. The infrastructure provides a "convertView" - // as a possible suggestion to reuse an existing view's data. It may be null, it may be a - // TextView, or it may be a ContactView, so we need to test the possible cases. - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - // Provide a TextView if the group is empty - if (super.getChildrenCount(groupPosition) == 0) { - if (convertView != null) { - if (convertView instanceof TextView) { - ((TextView) convertView).setText(mActivity - .getText(R.string.empty_contact_group)); - return convertView; - } - } - return newEmptyView(parent); - } - if (!(convertView instanceof ContactView)) { - convertView = null; - } - return super.getChildView(groupPosition, childPosition, isLastChild, convertView, - parent); - } - - @Override - protected Cursor getChildrenCursor(Cursor groupCursor) { - long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); - startQueryContacts(listId); - return null; - } - - // return a TextView for empty groups - @Override - protected View newChildView(Context context, Cursor cursor, boolean isLastChild, - ViewGroup parent) { - if (cursor.getCount() == 0) { - return newEmptyView(parent); - } else { - return ContactListTreeAdapter.this.newChildView(parent); - } - } - - @Override - protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, - ViewGroup parent) { - return ContactListTreeAdapter.this.newGroupView(parent); - } - - private int getOnlineChildCount(Cursor groupCursor) { - long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); - if (mOnlineContactsCountMap == null) { - String where = Imps.Contacts.ACCOUNT + "=" + mAccountId; - ContentResolver cr = mActivity.getContentResolver(); - - Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT, - CONTACT_COUNT_PROJECTION, where, null, null); - mOnlineContactsCountMap = new ContentQueryMap(c, Imps.Contacts.CONTACTLIST, true, - mHandler); - mOnlineContactsCountMap.addObserver(new Observer() { - public void update(Observable observable, Object data) { - notifyDataSetChanged(); - } - }); - } - ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId)); - return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT); - } - - @Override - public int getChildrenCount(int groupPosition) { - int children = super.getChildrenCount(groupPosition); - if (children == 0) { - // Count the empty group text item as a child - return 1; - } - return children; - } - - // Don't allow the empty group text item to be selected - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return (super.getChildrenCount(groupPosition) > 0); - } - } - - private class MyContentObserver extends ContentObserver { - - public MyContentObserver() { - super(mHandler); - } - - @Override - public void onChange(boolean selfChange) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyContentObserver.onChange() autoRequery=" + mAutoRequery); - } - // Don't requery when fling. We will schedule a requery when the fling is complete. - if (isScrolling()) { - return; - } - if (mAutoRequery) { - // startQueryOngoingConversations(); - } else { - mRequeryPending = true; - } - } - } - - private class MyDataSetObserver extends DataSetObserver { - public MyDataSetObserver() { - } - - @Override - public void onChanged() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onChanged()"); - } - - mDataValid = true; - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("MyDataSetObserver.onInvalidated()"); - } - - mDataValid = false; - notifyDataSetInvalidated(); - } - } - - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - // no op - } - - public void onScrollStateChanged(AbsListView view, int scrollState) { - int oldState = mScrollState; - - mScrollState = scrollState; - // If we just finished a fling then some items may not have an icon - // So force a full redraw now that the fling is complete - if (oldState == OnScrollListener.SCROLL_STATE_FLING) { - notifyDataSetChanged(); - } - } - - public boolean isScrolling() { - return mScrollState == OnScrollListener.SCROLL_STATE_FLING; - } -} \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/app/ContactListView.java b/src/info/guardianproject/otr/app/im/app/ContactListView.java deleted file mode 100644 index f73acfb6d..000000000 --- a/src/info/guardianproject/otr/app/im/app/ContactListView.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open - * Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatSessionManager; -import info.guardianproject.otr.app.im.IContactListListener; -import info.guardianproject.otr.app.im.IContactListManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.ISubscriptionListener; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.adapter.ContactListListenerAdapter; -import info.guardianproject.otr.app.im.engine.Contact; -import info.guardianproject.otr.app.im.engine.ContactListManager; -import info.guardianproject.otr.app.im.engine.ImErrorInfo; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ContentUris; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.OnChildClickListener; -import android.widget.LinearLayout; - -public class ContactListView extends LinearLayout { - - Activity mScreen; - IImConnection mConn; - SimpleAlertHandler mHandler; - Context mContext; - private final IContactListListener mContactListListener; - - ExpandableListView mContactsList; - private ContactListTreeAdapter mAdapter; - private boolean mHideOfflineContacts; - private SavedState mSavedState; - private boolean mAutoRefresh = true; - - public ContactListView(Context screen, AttributeSet attrs) { - super(screen, attrs); - mContext = screen; - mScreen = (Activity) screen; - mHandler = new SimpleAlertHandler(mScreen); - mContactListListener = new MyContactListListener(mHandler); - } - - private class MyContactListListener extends ContactListListenerAdapter { - public MyContactListListener(SimpleAlertHandler handler) { - super(handler); - } - - @Override - public void onAllContactListsLoaded() { - if (mAdapter != null) { - mAdapter.startAutoRequery(); - } - } - } - - private final ISubscriptionListener.Stub mSubscriptionListener = new ISubscriptionListener.Stub() { - - public void onSubScriptionRequest(Contact from) { - querySubscription(); - } - - public void onSubscriptionApproved(String contact) { - querySubscription(); - } - - public void onSubscriptionDeclined(String contact) { - querySubscription(); - } - - private void querySubscription() { - if (mAdapter != null) { - mAdapter.startQuerySubscriptions(); - } - } - }; - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContactsList = (ExpandableListView) findViewById(R.id.contactsList); - mContactsList.setOnChildClickListener(mOnChildClickListener); - } - - public ExpandableListView getListView() { - return mContactsList; - } - - public void setConnection(IImConnection conn) { - if (mConn != conn) { - if (mConn != null) { - unregisterListeners(); - } - mConn = conn; - - if (conn != null) { - registerListeners(); - - if (mAdapter == null) { - mAdapter = new ContactListTreeAdapter(conn, mScreen); - mAdapter.setHideOfflineContacts(mHideOfflineContacts); - mContactsList.setAdapter(mAdapter); - mContactsList.setOnScrollListener(mAdapter); - if (mSavedState != null) { - int[] expandedGroups = mSavedState.mExpandedGroups; - if (expandedGroups != null) { - for (int group : expandedGroups) { - mContactsList.expandGroup(group); - } - } - } - } else { - mAdapter.changeConnection(conn); - } - try { - IContactListManager listMgr = conn.getContactListManager(); - if (listMgr.getState() == ContactListManager.LISTS_LOADED) { - mAdapter.startAutoRequery(); - } - } catch (RemoteException e) { - Log.e(ImApp.LOG_TAG, "Service died!"); - } - } - } else { - mContactsList.invalidateViews(); - } - } - - public void setHideOfflineContacts(boolean hide) { - if (mAdapter != null) { - mAdapter.setHideOfflineContacts(hide); - } else { - mHideOfflineContacts = hide; - } - } - - public void startChat() { - startChat(getSelectedContact()); - } - - public void startChatAtPosition(long packedPosition) { - startChat(getContactAtPosition(packedPosition)); - } - - void startChat(Cursor c) { - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - try { - IChatSessionManager manager = mConn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session == null) { - manager.createChatSession(username); - } - - Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - i.addCategory(ImApp.IMPS_CATEGORY); - - mScreen.startActivity(i); - // mScreen.finish(); - - setAutoRefreshContacts(false); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - clearFocusIfEmpty(c); - } - } - - private void clearFocusIfEmpty(Cursor c) { - // clear focus if there's only one item so that it would focus on the - // "empty" item after the contact removed. - if (c.getCount() == 1) { - clearFocus(); - } - } - - public void endChat() { - endChat(getSelectedContact()); - } - - public void endChatAtPosition(long packedPosition) { - endChat(getContactAtPosition(packedPosition)); - } - - void endChat(Cursor c) { - if (c != null) { - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - try { - IChatSessionManager manager = mConn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session != null) { - session.leave(); - } - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - clearFocusIfEmpty(c); - } - } - - public void viewContactPresence() { - viewContactPresence(getSelectedContact()); - } - - public void viewContactPresenceAtPostion(long packedPosition) { - viewContactPresence(getContactAtPosition(packedPosition)); - } - - public void viewContactPresence(Cursor c) { - if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - mScreen.startActivity(i); - } - } - - public boolean isContactAtPosition(long packedPosition) { - int type = ExpandableListView.getPackedPositionType(packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); - return (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) - && !mAdapter.isPosForSubscription(groupPosition); - } - - public boolean isContactSelected() { - long pos = mContactsList.getSelectedPosition(); - return isContactAtPosition(pos); - } - - public boolean isContactsLoaded() { - try { - IContactListManager manager = mConn.getContactListManager(); - return (manager.getState() == ContactListManager.LISTS_LOADED); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - return false; - } - } - - public void removeContact() { - removeContact(getSelectedContact()); - } - - public void removeContactAtPosition(long packedPosition) { - removeContact(getContactAtPosition(packedPosition)); - } - - void removeContact(Cursor c) { - if (c == null) { - mHandler.showAlert(R.string.error, R.string.select_contact); - } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - try { - IContactListManager manager = mConn.getContactListManager(); - int res = manager.removeContact(address); - if (res != ImErrorInfo.NO_ERROR) { - mHandler.showAlert(R.string.error, - ErrorResUtils.getErrorRes(getResources(), res, address)); - } - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } - }; - Resources r = getResources(); - - new AlertDialog.Builder(mContext).setTitle(R.string.confirm) - .setMessage(r.getString(R.string.confirm_delete_contact, nickname)) - .setPositiveButton(R.string.yes, confirmListener) // default button - .setNegativeButton(R.string.no, null).setCancelable(false).show(); - - clearFocusIfEmpty(c); - } - } - - public void blockContact() { - blockContact(getSelectedContact()); - } - - public void blockContactAtPosition(long packedPosition) { - blockContact(getContactAtPosition(packedPosition)); - } - - void blockContact(Cursor c) { - if (c == null) { - mHandler.showAlert(R.string.error, R.string.select_contact); - } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - try { - IContactListManager manager = mConn.getContactListManager(); - int res = manager.blockContact(address); - if (res != ImErrorInfo.NO_ERROR) { - mHandler.showAlert(R.string.error, - ErrorResUtils.getErrorRes(getResources(), res, address)); - } - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } - }; - - Resources r = getResources(); - - new AlertDialog.Builder(mContext).setTitle(R.string.confirm) - .setMessage(r.getString(R.string.confirm_block_contact, nickname)) - .setPositiveButton(R.string.yes, confirmListener) // default button - .setNegativeButton(R.string.no, null).setCancelable(false).show(); - clearFocusIfEmpty(c); - } - } - - public Cursor getContactAtPosition(long packedPosition) { - int type = ExpandableListView.getPackedPositionType(packedPosition); - if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { - int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); - int childPosition = ExpandableListView.getPackedPositionChild(packedPosition); - return (Cursor) mAdapter.getChild(groupPosition, childPosition); - } - return null; - } - - public Cursor getSelectedContact() { - long pos = mContactsList.getSelectedPosition(); - if (ExpandableListView.getPackedPositionType(pos) == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { - return (Cursor) mContactsList.getSelectedItem(); - } - return null; - } - - public String getSelectedContactList() { - long pos = mContactsList.getSelectedPosition(); - int groupPos = ExpandableListView.getPackedPositionGroup(pos); - if (groupPos == -1) { - return null; - } - - Cursor cursor = (Cursor) mAdapter.getGroup(groupPos); - if (cursor == null) { - return null; - } - return cursor.getString(cursor.getColumnIndexOrThrow(Imps.ContactList.NAME)); - } - - private void registerListeners() { - try { - IContactListManager listManager = mConn.getContactListManager(); - listManager.registerContactListListener(mContactListListener); - listManager.registerSubscriptionListener(mSubscriptionListener); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } - - private void unregisterListeners() { - try { - IContactListManager listManager = mConn.getContactListManager(); - listManager.unregisterContactListListener(mContactListListener); - listManager.unregisterSubscriptionListener(mSubscriptionListener); - } catch (RemoteException e) { - - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "remote error",e); - } - } - - private final OnChildClickListener mOnChildClickListener = new OnChildClickListener() { - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - Cursor cursor = (Cursor) parent.getExpandableListAdapter().getChild(groupPosition, - childPosition); - if (cursor == null) { - Log.w(ImApp.LOG_TAG, - "[ContactListView.OnChildClickListener.onChildClick] cursor null! groupPos=" - + groupPosition + ", childPos=" + childPosition, - new RuntimeException()); - return false; - } - - int subscriptionType = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_TYPE); - int subscriptionStatus = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_STATUS); - if ((subscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM) - && (subscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)) { - long providerId = cursor.getLong(ContactView.COLUMN_CONTACT_PROVIDER); - String username = cursor.getString(ContactView.COLUMN_CONTACT_USERNAME); - Intent intent = new Intent(ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION, - ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id)); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); - mScreen.startActivity(intent); - } else { - startChat(cursor); - } - return true; - } - }; - - static class SavedState extends BaseSavedState { - int[] mExpandedGroups; - - SavedState(Parcelable superState, int[] expandedGroups) { - super(superState); - mExpandedGroups = expandedGroups; - } - - private SavedState(Parcel in) { - super(in); - mExpandedGroups = in.createIntArray(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeIntArray(mExpandedGroups); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - int[] expandedGroups = mAdapter == null ? null : mAdapter.getExpandedGroups(); - return new SavedState(superState, expandedGroups); - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - mSavedState = ss; - } - - protected void setAutoRefreshContacts(boolean isRefresh) { - mAutoRefresh = isRefresh; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - if (mAutoRefresh) { - super.onLayout(changed, l, t, r, b); - } - } -} diff --git a/src/info/guardianproject/otr/app/im/app/ContactPresenceActivity.java b/src/info/guardianproject/otr/app/im/app/ContactPresenceActivity.java index 5ad5e70dc..35af6fbd7 100644 --- a/src/info/guardianproject/otr/app/im/app/ContactPresenceActivity.java +++ b/src/info/guardianproject/otr/app/im/app/ContactPresenceActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,17 +18,15 @@ package info.guardianproject.otr.app.im.app; import info.guardianproject.otr.IOtrChatSession; -import info.guardianproject.otr.IOtrKeyManager; -import info.guardianproject.otr.OtrAndroidKeyManagerImpl; import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.provider.ImpsAddressUtils; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; +import info.guardianproject.util.LogCleaner; -import java.io.IOException; -import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; import android.app.AlertDialog; import android.content.ContentResolver; @@ -37,200 +35,305 @@ import android.content.Intent; import android.content.res.Configuration; import android.database.Cursor; -import android.graphics.Color; -import android.graphics.drawable.Drawable; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; -import android.text.SpannableString; import android.text.TextUtils; -import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; public class ContactPresenceActivity extends ThemeableActivity { - private String remoteFingerprint; - private boolean remoteFingerprintVerified = false; + private IChatSession mChatSession; + private IOtrChatSession mOtrSession; private String remoteAddress; - private String localFingerprint; private long providerId; private ImApp mApp; private final static String TAG = ImApp.LOG_TAG; - public ContactPresenceActivity() { - } + private Timer timer; - @Override + int DELAY_INTERVAL = 500; + int UPDATE_INTERVAL = 1000; + + Uri mUri = null; + + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + + timer = new Timer(); + mApp = (ImApp)getApplication(); setContentView(R.layout.contact_presence_activity); - ImageView imgAvatar = (ImageView) findViewById(R.id.imgAvatar); - TextView txtName = (TextView) findViewById(R.id.txtName); - TextView txtStatus = (TextView) findViewById(R.id.txtStatus); - TextView txtCustomStatus = (TextView) findViewById(R.id.txtStatusText); - Intent i = getIntent(); - Uri uri = i.getData(); - if (uri == null) { + mUri = i.getData(); + if (mUri == null) { warning("No data to show"); finish(); return; } - - + updateUI(); + + timer.scheduleAtFixedRate( + new TimerTask() { + public void run() { + mHandlerUI.sendEmptyMessage(0); + } + }, + DELAY_INTERVAL, + UPDATE_INTERVAL + ); + + } + + Handler mHandlerUI = new Handler () + { + + @Override + public void handleMessage(Message msg) { + updateUI(); + } + + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + + timer.cancel(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.contact_info_menu, menu); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_scan: + startScan(); + break; + + case R.id.menu_verify_secret: + initSmpUI(); + break; + + case R.id.menu_verify_fingerprint: + confirmVerify(); + break; + + default: + return false; + } + + return true; + } + + private void updateOtrStatus () + { + + if (remoteAddress != null) + { + try { + + try { + mChatSession = mApp.getChatSession(providerId, remoteAddress); + + if (mChatSession != null) + { + mOtrSession = mChatSession.getOtrChatSession(); + } + + } catch (RemoteException e) { + Log.e(TAG, "error init otr", e); + + } + + + } catch (Exception e) { + Log.e(TAG,"error reading key data",e); + } + } + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + + super.onConfigurationChanged(newConfig); + } + + private void updateUI() { + + + + + TextView txtAddress = (TextView) findViewById(R.id.txtAddress); + ImageView imgAvatar = (ImageView) findViewById(R.id.avatar); + TextView txtStatus = (TextView) findViewById(R.id.txtStatus); ContentResolver cr = getContentResolver(); - Cursor c = cr.query(uri, null, null, null, null); + Cursor c = cr.query(mUri, null, null, null, null); if (c == null) { - warning("Database error when query " + uri); + warning("Database error when query " + mUri); finish(); return; } if (c.moveToFirst()) { + providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); - remoteAddress = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + + if (remoteAddress == null) + remoteAddress = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + + + int status = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_STATUS)); // int clientType = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.CLIENT_TYPE)); String customStatus = c.getString(c .getColumnIndexOrThrow(Imps.Contacts.PRESENCE_CUSTOM_STATUS)); - - - BrandingResources brandingRes = mApp.getBrandingResource(providerId); - setTitle(brandingRes.getString(BrandingResourceIDs.STRING_CONTACT_INFO_TITLE)); + RoundedAvatarDrawable avatar = null; + + try + { + avatar = DatabaseUtils.getAvatarFromCursor(c, + c.getColumnIndexOrThrow(Imps.Contacts.AVATAR_DATA),ImApp.DEFAULT_AVATAR_WIDTH*2,ImApp.DEFAULT_AVATAR_HEIGHT*2); + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"error decoding avatar",e); + } + + if (avatar == null) + { + avatar = new RoundedAvatarDrawable(BitmapFactory.decodeResource(getResources(), + R.drawable.avatar_unknown)); + } + + setAvatarBorder(status, avatar); + + getSupportActionBar().setIcon(avatar); - Drawable avatar = DatabaseUtils.getAvatarFromCursor(c, - c.getColumnIndexOrThrow(Imps.Contacts.AVATAR_DATA),ImApp.DEFAULT_AVATAR_WIDTH*2,ImApp.DEFAULT_AVATAR_HEIGHT*2); - - imgAvatar.setImageDrawable(avatar); String address = ImpsAddressUtils.getDisplayableAddress(remoteAddress); - String displayName = nickname; - - if (!nickname.equals(address)) - displayName = nickname + "\n" + address; - - txtName.setText(displayName); - - String statusString = brandingRes.getString(PresenceUtils.getStatusStringRes(status)); - SpannableString s = new SpannableString("+ " + statusString); - Drawable statusIcon = brandingRes.getDrawable(PresenceUtils.getStatusIconId(status)); - statusIcon.setBounds(0, 0, statusIcon.getIntrinsicWidth(), - statusIcon.getIntrinsicHeight()); - s.setSpan(new ImageSpan(statusIcon), 0, 1, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - txtStatus.setText(s); + + if (nickname == null) + nickname = address; + + getSupportActionBar().setTitle(nickname); + + + if (address != null && (!nickname.equals(address))) + txtAddress.setText(address); + + String statusString = null; if (!TextUtils.isEmpty(customStatus)) { - txtCustomStatus.setVisibility(View.VISIBLE); - txtCustomStatus.setText("\"" + customStatus + "\""); - } else { - txtCustomStatus.setVisibility(View.GONE); + statusString = "\"" + customStatus + "\""; } - - updateOtrStatus(); + + + if (statusString != null) + txtStatus.setText(statusString); + + + } c.close(); - - updateUI(); - } + TextView lblFingerprintRemote = (TextView) findViewById(R.id.labelFingerprintRemote); + TextView txtFingerprintRemote = (TextView) findViewById(R.id.txtFingerprintRemote); - private void updateOtrStatus () - { + updateOtrStatus (); - IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); - - - - try { - - IOtrKeyManager keyManager = conn.getChatSessionManager().getChatSession(remoteAddress).getOtrKeyManager(); + getSupportActionBar().setBackgroundDrawable(getResources().getDrawable(R.color.holo_red_dark)); + + + try + { + if (mOtrSession != null && mOtrSession.getRemoteFingerprint() != null) { + + txtFingerprintRemote.setVisibility(View.VISIBLE); + lblFingerprintRemote.setVisibility(View.VISIBLE); + + String remoteFingerprint = mOtrSession.getRemoteFingerprint(); + boolean remoteFingerprintVerified = mOtrSession.isKeyVerified(remoteAddress); + + + txtFingerprintRemote.setText(prettyPrintFingerprint(remoteFingerprint)); - remoteFingerprint = keyManager.getRemoteFingerprint(); + if (remoteFingerprintVerified) { + lblFingerprintRemote.setText(R.string.their_fingerprint_verified_); - if (remoteFingerprint != null) { - remoteFingerprint = remoteFingerprint.toUpperCase(Locale.ENGLISH); + getSupportActionBar().setBackgroundDrawable(getResources().getDrawable(R.color.holo_green_dark)); + + } else + { + getSupportActionBar().setBackgroundDrawable(getResources().getDrawable(R.color.holo_orange_light)); - remoteFingerprintVerified = keyManager.isKeyVerified(remoteAddress); - - } - - localFingerprint = keyManager.getLocalFingerprint(); - - if (localFingerprint != null) - localFingerprint = localFingerprint.toUpperCase(Locale.ENGLISH); - - - - } catch (Exception e) { - Log.e(TAG,"error reading key data",e); - } - - - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - - super.onConfigurationChanged(newConfig); - } - private void updateUI() { - TextView lblFingerprintLocal = (TextView) findViewById(R.id.labelFingerprintLocal); - TextView lblFingerprintRemote = (TextView) findViewById(R.id.labelFingerprintRemote); - TextView txtFingerprintRemote = (TextView) findViewById(R.id.txtFingerprintRemote); - TextView txtFingerprintLocal = (TextView) findViewById(R.id.txtFingerprintLocal); - updateOtrStatus (); - - if (remoteFingerprint != null) { - - - txtFingerprintRemote.setText(remoteFingerprint); - - - if (remoteFingerprintVerified) { - lblFingerprintRemote.setText(R.string.their_fingerprint_verified_); - txtFingerprintRemote.setBackgroundColor(getResources().getColor(R.color.otr_green)); - } else - txtFingerprintRemote.setBackgroundColor(getResources().getColor(R.color.otr_yellow)); - - txtFingerprintRemote.setTextColor(Color.BLACK); - - if (localFingerprint != null) - txtFingerprintLocal.setText(localFingerprint); - - } else { + + } else { + txtFingerprintRemote.setVisibility(View.GONE); + lblFingerprintRemote.setVisibility(View.GONE); + + } + } + catch (RemoteException re) + { txtFingerprintRemote.setVisibility(View.GONE); - txtFingerprintLocal.setVisibility(View.GONE); lblFingerprintRemote.setVisibility(View.GONE); - lblFingerprintLocal.setVisibility(View.GONE); + } + + } + + private String prettyPrintFingerprint (String fingerprint) + { + StringBuffer spacedFingerprint = new StringBuffer(); + + for (int i = 0; i + 8 <= fingerprint.length(); i+=8) + { + spacedFingerprint.append(fingerprint.subSequence(i,i+8)); + spacedFingerprint.append(' '); + } + + return spacedFingerprint.toString(); } // private String getClientTypeString(int clientType) { @@ -249,99 +352,84 @@ private static void warning(String msg) { } private void confirmVerify() { - String message = getString(R.string.are_you_sure_you_want_to_confirm_this_key_); - new AlertDialog.Builder(this).setTitle(R.string.verify_key_).setMessage(message) - .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - verifyRemoteFingerprint(); - } - }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - // Do nothing. - } - }).show(); + try + { + StringBuffer message = new StringBuffer(); + message.append(getString(R.string.fingerprint_for_you)).append("\n").append(prettyPrintFingerprint(mOtrSession.getLocalFingerprint())).append("\n\n"); + message.append(getString(R.string.fingerprint_for_)).append(remoteAddress).append("\n").append(prettyPrintFingerprint(mOtrSession.getRemoteFingerprint())).append("\n\n"); + + message.append(getString(R.string.are_you_sure_you_want_to_confirm_this_key_)); + + new AlertDialog.Builder(this).setTitle(R.string.verify_key_).setMessage(message.toString()) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + verifyRemoteFingerprint(); + } + }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Do nothing. + } + }).show(); + } + catch (RemoteException e) + { + LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e); + } } private void verifyRemoteFingerprint() { - try { - IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); + try { + IChatSession session = mApp.getChatSession(providerId, remoteAddress); - IOtrKeyManager keyManager = conn.getChatSessionManager().getChatSession(remoteAddress).getOtrKeyManager(); + if (session != null) + { + IOtrChatSession iOtrSession = session.getOtrChatSession(); + iOtrSession.verifyKey(remoteAddress); - keyManager.verifyKey(remoteAddress); + } - updateUI(); - } catch (RemoteException e) { - Log.e(TAG, "error verifying remote fingerprint", e); + Log.e(TAG, "error init otr", e); + } - } - public void startScan() { - IntentIntegrator.initiateScan(this); + updateUI(); + } - public void displayQRCode(String text) { - IntentIntegrator.shareText(this, text); - - + public void startScan() { + new IntentIntegrator(this).initiateScan(); } + + public void onActivityResult(int requestCode, int resultCode, Intent intent) { IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); - if (scanResult != null) { + if (scanResult != null && mOtrSession != null) { - String otherFingerprint = scanResult.getContents(); + try + { + String otherFingerprint = scanResult.getContents(); - if (otherFingerprint != null && otherFingerprint.equalsIgnoreCase(remoteFingerprint)) { - verifyRemoteFingerprint(); + if (otherFingerprint != null && otherFingerprint.equalsIgnoreCase(mOtrSession.getRemoteFingerprint())) { + verifyRemoteFingerprint(); + } + } + catch (RemoteException re) + { + LogCleaner.error(ImApp.LOG_TAG, "error validating QR code response", re); } - } } - public boolean onCreateOptionsMenu(Menu menu) { - - if (remoteFingerprint != null) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.contact_info_menu, menu); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_scan: - startScan(); - return true; - - case R.id.menu_fingerprint: - if (remoteFingerprint != null) - displayQRCode(localFingerprint); - return true; - - case R.id.menu_verify_fingerprint: - if (remoteFingerprint != null) - confirmVerify(); - return true; - - case R.id.menu_verify_secret: - if (remoteFingerprint != null) - initSmpUI(); - return true; - } - return super.onOptionsItemSelected(item); - } private void initSmpUI() { LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -349,17 +437,17 @@ private void initSmpUI() { if (viewSmp != null) { - new AlertDialog.Builder(this).setTitle("OTR Q&A Verification").setView(viewSmp) - .setPositiveButton("Send", new DialogInterface.OnClickListener() { + new AlertDialog.Builder(this).setTitle(getString(R.string.otr_qa_title)).setView(viewSmp) + .setPositiveButton(getString(R.string.otr_qa_send), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - + EditText eiQuestion = (EditText) viewSmp.findViewById(R.id.editSmpQuestion); EditText eiAnswer = (EditText) viewSmp.findViewById(R.id.editSmpAnswer); String question = eiQuestion.getText().toString(); String answer = eiAnswer.getText().toString(); initSmp(question, answer); } - }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + }).setNegativeButton(getString(R.string.otr_qa_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } @@ -368,14 +456,52 @@ public void onClick(DialogInterface dialog, int whichButton) { } private void initSmp(String question, String answer) { - IOtrChatSession iOtrSession; try { - iOtrSession = mApp.getChatSession(providerId, remoteAddress).getOtrChatSession(); - iOtrSession.initSmpVerification(question, answer); + IChatSession session = mApp.getChatSession(providerId, remoteAddress); + + if (session != null) + { + IOtrChatSession iOtrSession = session.getOtrChatSession(); + iOtrSession.initSmpVerification(question, answer); + } } catch (RemoteException e) { Log.e(TAG, "error init SMP", e); } } + + public void setAvatarBorder(int status, RoundedAvatarDrawable avatar) { + switch (status) { + case Imps.Presence.AVAILABLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_light)); + avatar.setAlpha(255); + break; + + case Imps.Presence.IDLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_dark)); + avatar.setAlpha(255); + + break; + + case Imps.Presence.AWAY: + avatar.setBorderColor(getResources().getColor(R.color.holo_orange_light)); + avatar.setAlpha(255); + break; + + case Imps.Presence.DO_NOT_DISTURB: + avatar.setBorderColor(getResources().getColor(R.color.holo_red_dark)); + avatar.setAlpha(255); + + break; + + case Imps.Presence.OFFLINE: + avatar.setBorderColor(getResources().getColor(R.color.holo_grey_light)); + avatar.setAlpha(100); + break; + + + default: + } + } } diff --git a/src/info/guardianproject/otr/app/im/app/ContactView.java b/src/info/guardianproject/otr/app/im/app/ContactView.java index 9f6bc9c95..fd79a9b01 100644 --- a/src/info/guardianproject/otr/app/im/app/ContactView.java +++ b/src/info/guardianproject/otr/app/im/app/ContactView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,33 +17,37 @@ package info.guardianproject.otr.app.im.app; +import info.guardianproject.otr.IOtrChatSession; +import info.guardianproject.otr.app.im.IChatSession; +import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.engine.Presence; import info.guardianproject.otr.app.im.provider.Imps; - -import java.text.DateFormat; -import java.util.Calendar; - +import info.guardianproject.otr.app.im.ui.LetterAvatar; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; +import info.guardianproject.util.SystemServices; +import info.guardianproject.util.SystemServices.FileInfo; +import net.java.otr4j.session.SessionStatus; import android.app.Activity; -import android.content.ContentResolver; -import android.content.ContentUris; import android.content.Context; -import android.content.res.Resources; import android.database.Cursor; -import android.graphics.Typeface; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.support.v4.util.LruCache; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.UnderlineSpan; import android.util.AttributeSet; +import android.util.Log; import android.view.View; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; -public class ContactView extends LinearLayout { +public class ContactView extends FrameLayout { static final String[] CONTACT_PROJECTION = { Imps.Contacts._ID, Imps.Contacts.PROVIDER, Imps.Contacts.ACCOUNT, Imps.Contacts.USERNAME, Imps.Contacts.NICKNAME, Imps.Contacts.TYPE, @@ -53,10 +57,12 @@ public class ContactView extends LinearLayout { Imps.Presence.PRESENCE_CUSTOM_STATUS, Imps.Chats.LAST_MESSAGE_DATE, Imps.Chats.LAST_UNREAD_MESSAGE, + Imps.Contacts.AVATAR_HASH, Imps.Contacts.AVATAR_DATA - + }; + static final int COLUMN_CONTACT_ID = 0; static final int COLUMN_CONTACT_PROVIDER = 1; static final int COLUMN_CONTACT_ACCOUNT = 2; @@ -69,57 +75,32 @@ public class ContactView extends LinearLayout { static final int COLUMN_CONTACT_CUSTOM_STATUS = 9; static final int COLUMN_LAST_MESSAGE_DATE = 10; static final int COLUMN_LAST_MESSAGE = 11; - static final int COLUMN_AVATAR_DATA = 12; - - - private Drawable mAvatarUnknown; - - private Context mContext; + static final int COLUMN_AVATAR_HASH = 12; + static final int COLUMN_AVATAR_DATA = 13; - private static final int cacheSize = 100; // 4MiB - private static LruCache bitmapCache = new LruCache(cacheSize); - + + private ImApp app = null; + static Drawable AVATAR_DEFAULT_GROUP = null; + public ContactView(Context context, AttributeSet attrs) { super(context, attrs); - - mContext = context; - - mAvatarUnknown = context.getResources().getDrawable(R.drawable.avatar_unknown); - + + app = ((ImApp)((Activity) getContext()).getApplication()); + + } - private ViewHolder mHolder = null; - - class ViewHolder + static class ViewHolder { - //ImageView mPresence; + TextView mLine1; TextView mLine2; - TextView mTimeStamp; ImageView mAvatar; - - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mHolder = (ViewHolder)getTag(); - - if (mHolder == null) - { - mHolder = new ViewHolder(); - - //mPresence = (ImageView) findViewById(R.id.presence); - mHolder.mLine1 = (TextView) findViewById(R.id.line1); - mHolder.mLine2 = (TextView) findViewById(R.id.line2); - - mHolder.mTimeStamp = (TextView) findViewById(R.id.timestamp); - mHolder.mAvatar = (ImageView)findViewById(R.id.expandable_toggle_button); - - setTag(mHolder); - } + ImageView mStatusIcon; + ImageView mEncryptionIcon; + View mContainer; + ImageView mMediaThumb; } public void bind(Cursor cursor, String underLineText, boolean scrolling) { @@ -127,150 +108,337 @@ public void bind(Cursor cursor, String underLineText, boolean scrolling) { } public void bind(Cursor cursor, String underLineText, boolean showChatMsg, boolean scrolling) { + + /* + if (Debug.DEBUG_ENABLED) + { + StringBuffer debug = new StringBuffer(); + for (int i = 0; i < cursor.getColumnCount();i++) + { + String name = cursor.getColumnName(i); + String value = cursor.getString(i); + if (value != null && value.length() < 100) + debug.append(name+":" + value+","); + else if (value == null) + debug.append(name+":(null)"); + } + + Log.d(ImApp.LOG_TAG,"contact:" + debug.toString()); + + }*/ - mHolder = (ViewHolder)getTag(); - - Resources r = getResources(); - long providerId = cursor.getLong(COLUMN_CONTACT_PROVIDER); - - mHolder.mLine2.setCompoundDrawablePadding(5); + ViewHolder holder = (ViewHolder)getTag(); - String address = cursor.getString(COLUMN_CONTACT_USERNAME); - String nickname = cursor.getString(COLUMN_CONTACT_NICKNAME); - int type = cursor.getInt(COLUMN_CONTACT_TYPE); + final long providerId = cursor.getLong(COLUMN_CONTACT_PROVIDER); + final String address = cursor.getString(COLUMN_CONTACT_USERNAME); + + final String displayName = cursor.getString(COLUMN_CONTACT_NICKNAME); + + final int type = cursor.getInt(COLUMN_CONTACT_TYPE); + final String lastMsg = cursor.getString(COLUMN_LAST_MESSAGE); + + final int presence = cursor.getInt(COLUMN_CONTACT_PRESENCE_STATUS); + + final int subType = cursor.getInt(COLUMN_SUBSCRIPTION_TYPE); + final int subStatus = cursor.getInt(COLUMN_SUBSCRIPTION_STATUS); + String statusText = cursor.getString(COLUMN_CONTACT_CUSTOM_STATUS); - String lastMsg = cursor.getString(COLUMN_LAST_MESSAGE); - boolean hasChat = !cursor.isNull(COLUMN_LAST_MESSAGE); + String nickname = displayName; - ImApp app = (ImApp)((Activity)mContext).getApplication(); - - BrandingResources brandingRes = app.getBrandingResource(providerId); + if (nickname == null) + { + nickname = address.split("@")[0]; + } + else if (nickname.indexOf('@')!=-1) + { + nickname = nickname.split("@")[0]; + } - int presence = cursor.getInt(COLUMN_CONTACT_PRESENCE_STATUS); - - - - //mPresence.setImageDrawable(brandingRes.getDrawable(iconId)); - // Drawable presenceIcon = brandingRes.getDrawable(iconId); - // line1 - CharSequence contact; - /* - if (Imps.Contacts.TYPE_GROUP == type) { - ContentResolver resolver = getContext().getContentResolver(); - long id = cursor.getLong(ContactView.COLUMN_CONTACT_ID); - contact = queryGroupMembers(resolver, id); - } else { - */ - - //contact = TextUtils.isEmpty(nickname) ? ImpsAddressUtils.getDisplayableAddress(username) - // String address = ImpsAddressUtils.getDisplayableAddress(username); - contact = nickname; - - if (address.indexOf('/')!=-1) - { - contact = nickname + " (" + address.substring(address.indexOf('/')+1) + ")"; - } - - if (!TextUtils.isEmpty(underLineText)) { - // highlight/underline the word being searched - String lowercase = contact.toString().toLowerCase(); - int start = lowercase.indexOf(underLineText.toLowerCase()); - if (start >= 0) { - int end = start + underLineText.length(); - SpannableString str = new SpannableString(contact); - str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); - contact = str; - } + + if (!TextUtils.isEmpty(underLineText)) { + // highlight/underline the word being searched + String lowercase = nickname.toLowerCase(); + int start = lowercase.indexOf(underLineText.toLowerCase()); + if (start >= 0) { + int end = start + underLineText.length(); + SpannableString str = new SpannableString(nickname); + str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + + holder.mLine1.setText(str); + } - + else + holder.mLine1.setText(nickname); + + } + else + holder.mLine1.setText(nickname); + + /* + if (holder.mStatusIcon != null) + { + Drawable statusIcon = brandingRes.getDrawable(PresenceUtils.getStatusIconId(presence)); + //statusIcon.setBounds(0, 0, statusIcon.getIntrinsicWidth(), + // statusIcon.getIntrinsicHeight()); + holder.mStatusIcon.setImageDrawable(statusIcon);address + }*/ + + + holder.mStatusIcon.setVisibility(View.GONE); + + if (holder.mAvatar != null) + { if (Imps.Contacts.TYPE_GROUP == type) { - mHolder.mAvatar.setImageResource(R.drawable.group_chat); - + + holder.mAvatar.setVisibility(View.VISIBLE); + + if (AVATAR_DEFAULT_GROUP == null) + AVATAR_DEFAULT_GROUP = new RoundedAvatarDrawable(BitmapFactory.decodeResource(getResources(), + R.drawable.group_chat)); + + + holder.mAvatar.setImageDrawable(AVATAR_DEFAULT_GROUP); + + } - else + else if (cursor.getColumnIndex(Imps.Contacts.AVATAR_DATA)!=-1) { - - Drawable avatar = (Drawable)bitmapCache.get(address); - - if (avatar == null) +// holder.mAvatar.setVisibility(View.GONE); + + RoundedAvatarDrawable avatar = null; + + try + { + // avatar = DatabaseUtils.getAvatarFromAddress(this.getContext().getContentResolver(),address, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT); + avatar = DatabaseUtils.getAvatarFromCursor(cursor, COLUMN_AVATAR_DATA, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT); + } + catch (Exception e) + { + //problem decoding avatar + Log.e(ImApp.LOG_TAG,"error decoding avatar",e); + } + + try { - avatar = DatabaseUtils.getAvatarFromCursor(cursor, COLUMN_AVATAR_DATA, ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT); - if (avatar != null) - bitmapCache.put(address, avatar); + { + setAvatarBorder(presence,avatar); + holder.mAvatar.setImageDrawable(avatar); + } + else + { + String letterString = null; + + if (nickname.length() > 0) + letterString = nickname.substring(0,1).toUpperCase(); + else + letterString = "?"; //the unknown name! + + int color = getAvatarBorder(presence); + int padding = 24; + LetterAvatar lavatar = new LetterAvatar(getContext(), color, letterString, padding); + + holder.mAvatar.setImageDrawable(lavatar); + + } + + holder.mAvatar.setVisibility(View.VISIBLE); + } + catch (OutOfMemoryError ome) + { + //this seems to happen now and then even on tiny images; let's catch it and just not set an avatar + } + + } + else + { + //holder.mAvatar.setImageDrawable(getContext().getResources().getDrawable(R.drawable.avatar_unknown)); + holder.mAvatar.setVisibility(View.GONE); + + + + } + } + + if (showChatMsg && lastMsg != null) { + + + if (holder.mLine2 != null) + { + if (ChatFileStore.isVfsUri(lastMsg)) + { + FileInfo fInfo = SystemServices.getFileInfoFromURI(getContext(), Uri.parse(lastMsg)); + if (fInfo.type == null || fInfo.type.startsWith("image")) + { + + if (holder.mMediaThumb != null) + { + holder.mMediaThumb.setVisibility(View.VISIBLE); + + Bitmap b = MessageView.getThumbnail(getContext().getContentResolver(), Uri.parse(lastMsg)); + holder.mMediaThumb.setImageBitmap(b); + + holder.mLine2.setVisibility(View.GONE); + + } + } + else + { + holder.mLine2.setText(""); + } + } - - if (avatar != null) - mHolder.mAvatar.setImageDrawable(avatar); else - mHolder.mAvatar.setImageDrawable(mAvatarUnknown); + { + if (holder.mMediaThumb != null) + holder.mMediaThumb.setVisibility(View.GONE); + + holder.mLine2.setVisibility(View.VISIBLE); + holder.mLine2.setText(android.text.Html.fromHtml(lastMsg).toString()); + } } - // } - mHolder.mLine1.setText(contact); - - // time stamp - if (showChatMsg && hasChat) { - mHolder.mTimeStamp.setVisibility(VISIBLE); - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(cursor.getLong(COLUMN_LAST_MESSAGE_DATE)); - DateFormat formatter = DateFormat.getTimeInstance(DateFormat.SHORT); - mHolder. mTimeStamp.setText(formatter.format(cal.getTime())); - } else { - mHolder.mTimeStamp.setVisibility(GONE); } + else if (holder.mLine2 != null) + { - // line2 - String status = null; - if (showChatMsg && lastMsg != null) { + /* + if (statusText == null || statusText.length() == 0) + { + if (Imps.Contacts.TYPE_GROUP == type) + { + statusText = getContext().getString(R.string.menu_new_group_chat); + } + else + { + statusText = address;//brandingRes.getString(PresenceUtils.getStatusStringRes(presence)); + } + } + + holder.mLine2.setText(statusText); + */ - //remove HTML tags since we can't display HTML - status = lastMsg.replaceAll("\\<.*?\\>", ""); - setBackgroundResource(R.color.incoming_message_bg_plaintext); - - + statusText = address; + holder.mLine2.setText(statusText); } - else + + + + + if (subType == Imps.ContactsColumns.SUBSCRIPTION_TYPE_INVITATIONS) { - mHolder.mLine2.setVisibility(View.VISIBLE); - mHolder.mLine2.setTextAppearance(mContext, Typeface.NORMAL); - setBackgroundResource(android.R.color.transparent); + // if (holder.mLine2 != null) + // holder.mLine2.setText("Contact List Request"); } - - if (TextUtils.isEmpty(status)) { - if (Imps.Contacts.TYPE_GROUP == type) { - // Show nothing in line2 if it's a group and don't - // have any unread message. - status = null; - } else { - // Show the custom status text if there's no new message. - status = statusText; + + holder.mLine1.setVisibility(View.VISIBLE); + + getEncryptionState (providerId, address, holder); + } + + private void getEncryptionState (long providerId, String address, ViewHolder holder) + { + + try { + IImConnection conn = app.getConnection(providerId); + if (conn == null || conn.getChatSessionManager() == null) + return; + + IChatSession chatSession = conn.getChatSessionManager().getChatSession(address); + + if (chatSession != null) + { + IOtrChatSession otrChatSession = chatSession.getOtrChatSession(); + if (otrChatSession != null) + { + SessionStatus chatStatus = SessionStatus.values()[otrChatSession.getChatStatus()]; + + if (chatStatus == SessionStatus.ENCRYPTED) + { + boolean isVerified = otrChatSession.isKeyVerified(address); + + if (isVerified) + holder.mStatusIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_black_encrypted_and_verified)); + else + holder.mStatusIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_black_encrypted_not_verified)); + + holder.mStatusIcon.setVisibility(View.VISIBLE); + } + } } + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - if (TextUtils.isEmpty(status)) { - // Show a string of presence if there is neither new message nor - // custom status text. - status = brandingRes.getString(PresenceUtils.getStatusStringRes(presence)); + + + //mCurrentChatSession.getOtrChatSession(); + + } + + public void setAvatarBorder(int status, RoundedAvatarDrawable avatar) { + switch (status) { + case Presence.AVAILABLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_light)); + avatar.setAlpha(255); + break; + + case Presence.IDLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_dark)); + avatar.setAlpha(255); + + break; + + case Presence.AWAY: + avatar.setBorderColor(getResources().getColor(R.color.holo_orange_light)); + avatar.setAlpha(255); + break; + + case Presence.DO_NOT_DISTURB: + avatar.setBorderColor(getResources().getColor(R.color.holo_red_dark)); + avatar.setAlpha(255); + + break; + + case Presence.OFFLINE: + avatar.setBorderColor(getResources().getColor(android.R.color.transparent)); + avatar.setAlpha(100); + break; + + + default: } + } + + public int getAvatarBorder(int status) { + switch (status) { + case Presence.AVAILABLE: + return (getResources().getColor(R.color.holo_green_light)); + + case Presence.IDLE: + return (getResources().getColor(R.color.holo_green_dark)); + case Presence.AWAY: + return (getResources().getColor(R.color.holo_orange_light)); + + case Presence.DO_NOT_DISTURB: + return(getResources().getColor(R.color.holo_red_dark)); + + case Presence.OFFLINE: + return(getResources().getColor(R.color.holo_grey_dark)); - mHolder.mLine2.setText(status); - // mLine2.setCompoundDrawablesWithIntrinsicBounds(null, null, presenceIcon, null); - - View contactInfoPanel = findViewById(R.id.contactInfo); - if (hasChat && showChatMsg) { // HERE the bubble is set - // contactInfoPanel.setBackgroundResource(R.drawable.bubble); - // mLine1.setTextColor(r.getColor(R.color.chat_contact)); - } else { - // contactInfoPanel.setBackgroundDrawable(null); - // contactInfoPanel.setPadding(4, 0, 0, 0); - // mLine1.setTextColor(r.getColor(R.color.nonchat_contact)); + default: } + + return Color.TRANSPARENT; } + + /* private String queryGroupMembers(ContentResolver resolver, long groupId) { String[] projection = { Imps.GroupMembers.NICKNAME }; Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); @@ -279,19 +447,16 @@ private String queryGroupMembers(ContentResolver resolver, long groupId) { if (c != null) { while (c.moveToNext()) { buf.append(c.getString(0)); + Imps.Avatars.DATA if (!c.isLast()) { buf.append(','); } } } c.close(); - + return buf.toString(); - } - - public static Drawable getAvatar (String address) - { - return (Drawable) bitmapCache.get(address); - } + }*/ + } diff --git a/src/info/guardianproject/otr/app/im/app/ContactsPickerActivity.java b/src/info/guardianproject/otr/app/im/app/ContactsPickerActivity.java index deacd7118..2839af506 100644 --- a/src/info/guardianproject/otr/app/im/app/ContactsPickerActivity.java +++ b/src/info/guardianproject/otr/app/im/app/ContactsPickerActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,171 +17,366 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.otr.app.im.provider.Imps; - import info.guardianproject.otr.app.im.R; - -import android.app.ListActivity; +import info.guardianproject.otr.app.im.app.ContactListFilterView.ContactListListener; +import info.guardianproject.otr.app.im.provider.Imps; +import android.app.SearchManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.Uri; import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; +import android.os.Handler; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.MenuItemCompat; +import android.support.v4.widget.ResourceCursorAdapter; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; -import android.widget.EditText; -import android.widget.Filter; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageView; import android.widget.ListView; -import android.widget.ResourceCursorAdapter; +import android.widget.TextView; /** Activity used to pick a contact. */ -public class ContactsPickerActivity extends ListActivity { +public class ContactsPickerActivity extends ActionBarActivity { public final static String EXTRA_EXCLUDED_CONTACTS = "excludes"; public final static String EXTRA_RESULT_USERNAME = "result"; + public final static String EXTRA_RESULT_PROVIDER = "provider"; + public final static String EXTRA_RESULT_ACCOUNT = "account"; + public final static String EXTRA_RESULT_MESSAGE = "message"; + + + private int REQUEST_CODE_ADD_CONTACT = 9999; + + private ContactAdapter mAdapter; + + private MyLoaderCallbacks mLoaderCallbacks; + + private ContactListListener mListener = null; + private Uri mUri = Imps.Contacts.CONTENT_URI_CONTACTS_BY; + + private Handler mHandler = new Handler(); - private ContactsAdapter mAdapter; private String mExcludeClause; Uri mData; - Filter mFilter; - Cursor mCursor; // TODO - private static final void log(String msg) { - Log.d(ImApp.LOG_TAG, " " + msg); - } + private String mSearchString; + + SearchView mSearchView = null; + ListView mListView = null; + // The loader's unique id. Loader ids are specific to the Activity or + // Fragment in which they reside. + private static final int LOADER_ID = 1; + + // The callbacks through which we will interact with the LoaderManager. + private LoaderManager.LoaderCallbacks mCallbacks; + + private boolean mHideOffline = false; + private boolean mShowInvitations = false; + @Override - protected void onCreate(Bundle icicle) { + public void onCreate(Bundle icicle) { super.onCreate(icicle); + ((ImApp)getApplication()).setAppTheme(this); + setContentView(R.layout.contacts_picker_activity); - if (!resolveIntent()) { - if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("no data, finish"); - } - finish(); - return; - } + + if (getIntent().getData() != null) + mUri = getIntent().getData(); - EditText filter = (EditText) findViewById(R.id.filter); - filter.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + mListView = (ListView)findViewById(R.id.contactsList); - public void onTextChanged(CharSequence s, int start, int before, int count) { - mFilter.filter(s); - } + mListView.setOnItemClickListener(new OnItemClickListener () + { + + @Override + public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) { - public void afterTextChanged(Editable s) { + Cursor cursor = (Cursor) mAdapter.getItem(position); + Intent data = new Intent(); + data.putExtra(EXTRA_RESULT_USERNAME, cursor.getString(ContactView.COLUMN_CONTACT_USERNAME)); + data.putExtra(EXTRA_RESULT_PROVIDER, cursor.getLong(ContactView.COLUMN_CONTACT_PROVIDER)); + data.putExtra(EXTRA_RESULT_ACCOUNT, cursor.getLong(ContactView.COLUMN_CONTACT_ACCOUNT)); + + setResult(RESULT_OK, data); + finish(); } + }); - } - private boolean resolveIntent() { - Intent intent = getIntent(); - mData = intent.getData(); - if (mData == null) { - return false; - } - -// mExcludeClause = buildExcludeClause(intent.getStringArrayExtra(EXTRA_EXCLUDED_CONTACTS)); + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + Imps.ProviderSettings.QueryMap globalSettings = new Imps.ProviderSettings.QueryMap(pCursor, cr, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, true, null); + mHideOffline = globalSettings.getHideOfflineContacts(); - StringBuilder clause = new StringBuilder(); - clause.append(Imps.Contacts.USERNAME); - clause.append(" NOT IN ("); - - String[] excluded = intent.getStringArrayExtra(EXTRA_EXCLUDED_CONTACTS); - String[] excludedVals = new String[excluded.length]; + globalSettings.close(); - int len = excluded.length; - - for (int i = 0; i < len; i++) { - clause.append('?'); - - excludedVals[i] = DatabaseUtils.sqlEscapeString(excluded[i]); - - if (i+1 < len) - clause.append(','); + if (getIntent() != null && getIntent().hasExtra("invitations")) + { + mShowInvitations = getIntent().getBooleanExtra("invitations", false); } - clause.append(')'); - Cursor cursor = managedQuery(mData, ContactView.CONTACT_PROJECTION, mExcludeClause, excludedVals, - Imps.Contacts.DEFAULT_SORT_ORDER); - if (cursor == null) { - return false; + + + doFilterAsync(""); + } + + + + @Override + protected void onActivityResult(int request, int response, Intent data) { + super.onActivityResult(request, response, data); + + if (response == RESULT_OK) + if (request == REQUEST_CODE_ADD_CONTACT) + { + String newContact = data.getExtras().getString(ContactsPickerActivity.EXTRA_RESULT_USERNAME); + + if (newContact != null) + { + Intent dataNew = new Intent(); + + long providerId = data.getExtras().getLong(ContactsPickerActivity.EXTRA_RESULT_PROVIDER); + + dataNew.putExtra(EXTRA_RESULT_USERNAME, newContact); + dataNew.putExtra(EXTRA_RESULT_PROVIDER, providerId); + setResult(RESULT_OK, dataNew); + + finish(); + + } + } + + + } + + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.contact_list_menu, menu); + + SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); + mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search)); + + if (mSearchView != null ) + { + mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + mSearchView.setIconifiedByDefault(false); + + SearchView.OnQueryTextListener queryTextListener = new SearchView.OnQueryTextListener() + { + public boolean onQueryTextChange(String newText) + { + mSearchString = newText; + doFilterAsync(mSearchString); + return true; + } + + public boolean onQueryTextSubmit(String query) + { + mSearchString = query; + doFilterAsync(mSearchString); + + return true; + } + }; + + mSearchView.setOnQueryTextListener(queryTextListener); } - mAdapter = new ContactsAdapter(this, cursor); - mFilter = mAdapter.getFilter(); - setListAdapter(mAdapter); + + return true; } @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - Cursor cursor = (Cursor) mAdapter.getItem(position); - Intent data = new Intent(); - data.putExtra(EXTRA_RESULT_USERNAME, cursor.getString(ContactView.COLUMN_CONTACT_USERNAME)); - setResult(RESULT_OK, data); - // finish(); + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_invite_user: + Intent i = new Intent(ContactsPickerActivity.this, AddContactActivity.class); + + this.startActivityForResult(i, REQUEST_CODE_ADD_CONTACT); + return true; + + + } + return super.onOptionsItemSelected(item); } - - Cursor runQuery(CharSequence constraint) { - String where; - if (constraint == null) { - where = mExcludeClause; - return managedQuery(mData, ContactView.CONTACT_PROJECTION, where, null, - Imps.Contacts.DEFAULT_SORT_ORDER); + + public void doFilterAsync (final String query) + { + + doFilter(query); + } + + boolean mAwaitingUpdate = false; + + public synchronized void doFilter(String filterString) { + + mSearchString = filterString; + + if (mAdapter == null) { + + mAdapter = new ContactAdapter(ContactsPickerActivity.this, R.layout.contact_view); + + mListView.setAdapter(mAdapter); + + mLoaderCallbacks = new MyLoaderCallbacks(); + getSupportLoaderManager().initLoader(LOADER_ID, null, mLoaderCallbacks); } else { - StringBuilder buf = new StringBuilder(); - if (mExcludeClause != null) { - buf.append(mExcludeClause).append(" AND "); - } - buf.append(Imps.Contacts.NICKNAME); - buf.append(" LIKE ?"); - - String[] whereVal = { DatabaseUtils.sqlEscapeString("%" + constraint + "%")}; - where = buf.toString(); + if (!mAwaitingUpdate) + { + mAwaitingUpdate = true; + mHandler.postDelayed(new Runnable () + { + + public void run () + { + + getSupportLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks); + mAwaitingUpdate = false; + } + },1000); + } - return managedQuery(mData, ContactView.CONTACT_PROJECTION, where, whereVal, - Imps.Contacts.DEFAULT_SORT_ORDER); } } - private class ContactsAdapter extends ResourceCursorAdapter { - private String mConstraints; + private Cursor mCursor; + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mCursor != null && (!mCursor.isClosed())) + mCursor.close(); + + + } + + private class ContactAdapter extends ResourceCursorAdapter { + + + public ContactAdapter(Context context, int view) { + super(context, view, null,0); + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + + View view = super.newView(context, cursor, parent); + + ContactView.ViewHolder holder = null; + + holder = new ContactView.ViewHolder(); + + holder.mLine1 = (TextView) view.findViewById(R.id.line1); + holder.mLine2 = (TextView) view.findViewById(R.id.line2); + + holder.mAvatar = (ImageView)view.findViewById(R.id.avatar); + holder.mStatusIcon = (ImageView)view.findViewById(R.id.statusIcon); + + holder.mContainer = view.findViewById(R.id.message_container); + + holder.mMediaThumb = (ImageView)view.findViewById(R.id.media_thumbnail); + + view.setTag(holder); + + return view; + + - public ContactsAdapter(Context context, Cursor c) { - super(context, R.layout.contact_view, c); } + @Override public void bindView(View view, Context context, Cursor cursor) { ContactView v = (ContactView) view; - v.setPadding(0, 0, 0, 0); - v.bind(cursor, mConstraints, false); + v.bind(cursor, mSearchString, true); + } + } + class MyLoaderCallbacks implements LoaderCallbacks { @Override - public void changeCursor(Cursor cursor) { - if (mCursor != null && mCursor != cursor) { - mCursor.deactivate(); + public Loader onCreateLoader(int id, Bundle args) { + StringBuilder buf = new StringBuilder(); + + if (mSearchString != null) { + + buf.append('('); + buf.append(Imps.Contacts.NICKNAME); + buf.append(" LIKE "); + android.database.DatabaseUtils.appendValueToSql(buf, "%" + mSearchString + "%"); + buf.append(" OR "); + buf.append(Imps.Contacts.USERNAME); + buf.append(" LIKE "); + android.database.DatabaseUtils.appendValueToSql(buf, "%" + mSearchString + "%"); + buf.append(')'); + } - super.changeCursor(cursor); + +// normal types not temporary + buf.append(" AND "); + buf.append(Imps.Contacts.TYPE).append('=').append(Imps.Contacts.TYPE_NORMAL); + + if (mShowInvitations) + { + buf.append(" AND ("); + buf.append(Imps.Contacts.SUBSCRIPTION_TYPE).append('=').append(Imps.Contacts.SUBSCRIPTION_TYPE_FROM); + buf.append(" )"); + } + + if(mHideOffline) + { + buf.append(" AND "); + buf.append(Imps.Contacts.PRESENCE_STATUS).append("!=").append(Imps.Presence.OFFLINE); + } + + CursorLoader loader = new CursorLoader(ContactsPickerActivity.this, mUri, ContactView.CONTACT_PROJECTION, + buf == null ? null : buf.toString(), null, Imps.Contacts.MODE_AND_ALPHA_SORT_ORDER); + loader.setUpdateThrottle(50L); + return loader; } @Override - public Cursor runQueryOnBackgroundThread(CharSequence constraint) { - mConstraints = constraint.toString(); + public void onLoadFinished(Loader loader, Cursor newCursor) { + mAdapter.swapCursor(newCursor); + + - return ContactsPickerActivity.this.runQuery(constraint); } + + @Override + public void onLoaderReset(Loader loader) { + + mAdapter.swapCursor(null); + + + + } + } + } diff --git a/src/info/guardianproject/otr/app/im/app/CreateAccountActivity.java b/src/info/guardianproject/otr/app/im/app/CreateAccountActivity.java deleted file mode 100644 index 2a05491d9..000000000 --- a/src/info/guardianproject/otr/app/im/app/CreateAccountActivity.java +++ /dev/null @@ -1,892 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package info.guardianproject.otr.app.im.app; - -import info.guardianproject.onionkit.ui.OrbotHelper; -import info.guardianproject.otr.IOtrKeyManager; -import info.guardianproject.otr.app.im.IImConnection; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.AccountSettingsActivity; -import info.guardianproject.otr.app.im.app.BrandingResources; -import info.guardianproject.otr.app.im.app.ImApp; -import info.guardianproject.otr.app.im.app.ProviderDef; -import info.guardianproject.otr.app.im.app.SignInHelper; -import info.guardianproject.otr.app.im.app.SignoutActivity; -import info.guardianproject.otr.app.im.app.ThemeableActivity; -import info.guardianproject.otr.app.im.engine.ImConnection; -import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; -import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.provider.Imps.AccountColumns; -import info.guardianproject.otr.app.im.provider.Imps.AccountStatusColumns; -import info.guardianproject.otr.app.im.provider.Imps.CommonPresenceColumns; -import info.guardianproject.otr.app.im.service.ImServiceConstants; -import info.guardianproject.util.LogCleaner; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.provider.BaseColumns; -import android.text.Editable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.util.Linkify; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -public class CreateAccountActivity extends ThemeableActivity { - - public static final String TAG = "CreateAccountActivity"; - private static final String ACCOUNT_URI_KEY = "accountUri"; - private long mProviderId = 0; - private long mAccountId = 0; - static final int REQUEST_SIGN_IN = RESULT_FIRST_USER + 1; - private static final String[] ACCOUNT_PROJECTION = { Imps.Account._ID, Imps.Account.PROVIDER, - Imps.Account.USERNAME, - Imps.Account.PASSWORD, - Imps.Account.KEEP_SIGNED_IN, - Imps.Account.LAST_LOGIN_STATE }; - private static final int ACCOUNT_PROVIDER_COLUMN = 1; - private static final int ACCOUNT_USERNAME_COLUMN = 2; - private static final int ACCOUNT_PASSWORD_COLUMN = 3; - // private static final int ACCOUNT_KEEP_SIGNED_IN_COLUMN = 4; - // private static final int ACCOUNT_LAST_LOGIN_STATE = 5; - - Uri mAccountUri; - EditText mEditUserAccount; - EditText mEditPass; - CheckBox mRememberPass; - CheckBox mUseTor; - Button mBtnSignIn; - Button mBtnDelete; - - Button mBtnAdvanced; - TextView mTxtFingerprint; - - Imps.ProviderSettings.QueryMap settings; - - boolean isEdit = false; - boolean isSignedIn = false; - - String mUserName = ""; - String mDomain = ""; - int mPort = 0; - private String mOriginalUserAccount = ""; - - private final static int DEFAULT_PORT = 5222; - - IOtrKeyManager otrKeyManager; - private SignInHelper mSignInHelper; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - //getWindow().requestFeature(Window.FEATURE_LEFT_ICON); - setContentView(R.layout.account_activity); - Intent i = getIntent(); - - getSherlock().getActionBar().setHomeButtonEnabled(true); - getSherlock().getActionBar().setDisplayHomeAsUpEnabled(true); - - mSignInHelper = new SignInHelper(this); - SignInHelper.Listener signInListener = new SignInHelper.Listener() { - public void connectedToService() { - } - public void stateChanged(int state, long accountId) { - if (state == ImConnection.LOGGING_IN || state == ImConnection.LOGGED_IN) - { - mSignInHelper.goToAccount(accountId); - finish(); - } - } - }; - mSignInHelper.setSignInListener(signInListener); - mEditUserAccount = (EditText) findViewById(R.id.edtName); - mEditUserAccount.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - checkUserChanged(); - } - }); - - mEditPass = (EditText) findViewById(R.id.edtPass); - mRememberPass = (CheckBox) findViewById(R.id.rememberPassword); - mUseTor = (CheckBox) findViewById(R.id.useTor); - - - mBtnSignIn = (Button) findViewById(R.id.btnSignIn); - mBtnAdvanced = (Button) findViewById(R.id.btnAdvanced); - mBtnDelete = (Button) findViewById(R.id.btnDelete); - - mRememberPass.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - updateWidgetState(); - } - }); - - - mApp = (ImApp)getApplication(); - - String action = i.getAction(); - - if (i.hasExtra("isSignedIn")) - isSignedIn = i.getBooleanExtra("isSignedIn", false); - - - final ProviderDef provider; - - ContentResolver cr = getContentResolver(); - - Uri uri = i.getData(); - // check if there is account information and direct accordingly - if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) { - if ((uri == null) || !Imps.Account.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) { - action = Intent.ACTION_INSERT; - } else { - action = Intent.ACTION_EDIT; - } - } - - if (Intent.ACTION_INSERT.equals(action)) { - mOriginalUserAccount = ""; - // TODO once we implement multiple IM protocols - mProviderId = ContentUris.parseId(i.getData()); - provider = mApp.getProvider(mProviderId); - - if (provider != null) - { - setTitle(getResources().getString(R.string.add_account, provider.mFullName)); - - settings = new Imps.ProviderSettings.QueryMap( - cr, mProviderId, false /* don't keep updated */, null /* no handler */); - } - else - { - finish(); - } - - - } else if (Intent.ACTION_EDIT.equals(action)) { - if ((uri == null) || !Imps.Account.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) { - LogCleaner.warn(ImApp.LOG_TAG, "Bad data"); - return; - } - - isEdit = true; - - Cursor cursor = cr.query(uri, ACCOUNT_PROJECTION, null, null, null); - - if (cursor == null) { - finish(); - return; - } - - if (!cursor.moveToFirst()) { - cursor.close(); - finish(); - return; - } - - setTitle(R.string.sign_in); - - mAccountId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)); - - mProviderId = cursor.getLong(ACCOUNT_PROVIDER_COLUMN); - provider = mApp.getProvider(mProviderId); - - settings = new Imps.ProviderSettings.QueryMap( - cr, mProviderId, false /* don't keep updated */, null /* no handler */); - - mOriginalUserAccount = cursor.getString(ACCOUNT_USERNAME_COLUMN) + "@" - + settings.getDomain(); - mEditUserAccount.setText(mOriginalUserAccount); - mEditPass.setText(cursor.getString(ACCOUNT_PASSWORD_COLUMN)); - - mRememberPass.setChecked(!cursor.isNull(ACCOUNT_PASSWORD_COLUMN)); - - mUseTor.setChecked(settings.getUseTor()); - - mBtnDelete.setVisibility(View.VISIBLE); - - - } else { - LogCleaner.warn(ImApp.LOG_TAG, " unknown intent action " + action); - finish(); - return; - } - - if (isSignedIn) { - mBtnSignIn.setText(getString(R.string.menu_sign_out)); - mBtnSignIn.setBackgroundResource(R.drawable.btn_red); - } - - final BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); - - mRememberPass.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - - CheckBox mRememberPass = (CheckBox) v; - - if (mRememberPass.isChecked()) { - String msg = brandingRes - .getString(BrandingResourceIDs.STRING_TOAST_CHECK_SAVE_PASSWORD); - Toast.makeText(CreateAccountActivity.this, msg, Toast.LENGTH_LONG).show(); - } - } - }); - - mEditUserAccount.addTextChangedListener(mTextWatcher); - mEditPass.addTextChangedListener(mTextWatcher); - - mBtnAdvanced.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - showAdvanced(); - } - }); - - mBtnDelete.setOnClickListener(new OnClickListener() - { - - @Override - public void onClick(View v) { - - deleteAccount(); - finish(); - - } - - }); - - mBtnSignIn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - - checkUserChanged(); - - if (mUseTor.isChecked()) - { - OrbotHelper oh = new OrbotHelper(CreateAccountActivity.this); - if (!oh.isOrbotRunning()) - { - oh.requestOrbotStart(CreateAccountActivity.this); - return; - } - } - - - final String pass = mEditPass.getText().toString(); - final boolean rememberPass = mRememberPass.isChecked(); - final boolean isActive = false; // TODO(miron) does this ever need to be true? - ContentResolver cr = getContentResolver(); - - if (!parseAccount(mEditUserAccount.getText().toString())) { - mEditUserAccount.selectAll(); - mEditUserAccount.requestFocus(); - return; - } - - final long accountId = ImApp.insertOrUpdateAccount(cr, mProviderId, mUserName, - rememberPass ? pass : null); - - mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); - - //if remember pass is true, set the "keep signed in" property to true - - if (isSignedIn) { - signOut(); - isSignedIn = false; - } else { - ContentValues values = new ContentValues(); - values.put(AccountColumns.KEEP_SIGNED_IN, rememberPass ? 1 : 0); - getContentResolver().update(mAccountUri, values, null, null); - - if (!mOriginalUserAccount.equals(mUserName + '@' + mDomain) - && shouldShowTermOfUse(brandingRes)) { - confirmTermsOfUse(brandingRes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mSignInHelper.signIn(pass, mProviderId, accountId, isActive); - } - }); - } else { - mSignInHelper.signIn(pass, mProviderId, accountId, isActive); - } - isSignedIn = true; - } - updateWidgetState(); - - } - }); - - mUseTor.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - updateUseTor(isChecked); - } - }); - - updateWidgetState(); - - - if (i.hasExtra("newuser")) - { - String newuser = i.getExtras().getString("newuser"); - mEditUserAccount.setText(newuser); - - parseAccount(newuser); - settingsForDomain(mDomain,mPort); - - } - - if (i.hasExtra("newpass")) - { - mEditPass.setText(i.getExtras().getString("newpass")); - mRememberPass.setChecked(true); - } - - - } - - @Override - protected void onDestroy() { - - if (mSignInHelper != null) - mSignInHelper.stop(); - - if (settings != null) - settings.close(); - - super.onDestroy(); - } - - private void updateUseTor(boolean useTor) { - checkUserChanged(); - - final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), mProviderId, false /* don't keep updated */, null /* no handler */); - - OrbotHelper orbotHelper = new OrbotHelper(this); - - if (useTor && (!orbotHelper.isOrbotInstalled())) - { - //Toast.makeText(this, "Orbot app is not installed. Please install from Google Play or from https://guardianproject.info/releases", Toast.LENGTH_LONG).show(); - - orbotHelper.promptToInstall(this); - - mUseTor.setChecked(false); - settings.setUseTor(false); - settings.setServer(""); - } - else - { - - // if using Tor, disable DNS SRV to reduce anonymity leaks - settings.setDoDnsSrv(!useTor); - - String server = settings.getServer(); - - if (useTor && (server == null || server.length() == 0)) { - server = settings.getDomain(); - String domain = settings.getDomain().toLowerCase(); - - // a little bit of custom handling here - if (domain.equals("gmail.com")) { - server = "talk.l.google.com"; - } else if (domain.equals("jabber.ccc.de")) { - server = "okj7xc6j2szr2y75.onion"; - } else if (domain.equals("jabber.org")) { - server = "hermes.jabber.org"; - } else if (domain.equals("chat.facebook.com")) { - server = "chat.facebook.com"; - } else if (domain.equals("dukgo.com")) { - server = "dukgo.com"; - //settings.setTlsCertVerify(false); //remove this - MemorizingTrustManager will now prompt - } - else - { - Toast.makeText(this, getString(R.string.warning_tor_connect), Toast.LENGTH_LONG).show(); - } - - } - else - { - - } - - settings.setServer(server); - settings.setUseTor(useTor); - } - - settings.close(); - - } - - private void getOTRKeyInfo() { - - if (mApp != null && mApp.getRemoteImService() != null) { - try { - otrKeyManager = mApp.getRemoteImService().getOtrKeyManager(mOriginalUserAccount); - - if (otrKeyManager == null) { - mTxtFingerprint = ((TextView) findViewById(R.id.txtFingerprint)); - - String localFingerprint = otrKeyManager.getLocalFingerprint(); - if (localFingerprint != null) { - ((TextView) findViewById(R.id.lblFingerprint)).setVisibility(View.VISIBLE); - mTxtFingerprint.setText(processFingerprint(localFingerprint)); - } else { - ((TextView) findViewById(R.id.lblFingerprint)).setVisibility(View.GONE); - mTxtFingerprint.setText(""); - } - } else { - //don't need to notify people if there is nothing to show here -// Toast.makeText(this, "OTR is not initialized yet", Toast.LENGTH_SHORT).show(); - } - - } catch (Exception e) { - Log.e(ImApp.LOG_TAG, "error on create", e); - - } - } - - } - - private void checkUserChanged() { - String username = mEditUserAccount.getText().toString().trim(); - - if ((!username.equals(mOriginalUserAccount)) && parseAccount(username)) { - //Log.i(TAG, "Username changed: " + mOriginalUserAccount + " != " + username); - settingsForDomain(mDomain, mPort); - mOriginalUserAccount = username; - - } - - } - - - - boolean parseAccount(String userField) { - boolean isGood = true; - String[] splitAt = userField.trim().split("@"); - mUserName = splitAt[0]; - mDomain = null; - mPort = 0; - - if (splitAt.length > 1) { - mDomain = splitAt[1].toLowerCase(); - String[] splitColon = mDomain.split(":"); - mDomain = splitColon[0]; - if (splitColon.length > 1) { - try { - mPort = Integer.parseInt(splitColon[1]); - } catch (NumberFormatException e) { - // TODO move these strings to strings.xml - isGood = false; - Toast.makeText( - CreateAccountActivity.this, - "The port value '" + splitColon[1] - + "' after the : could not be parsed as a number!", - Toast.LENGTH_LONG).show(); - } - } - } - - if (mDomain == null) { - isGood = false; - //Toast.makeText(AccountActivity.this, - // R.string.account_wizard_no_domain_warning, - // Toast.LENGTH_LONG).show(); - } - /*//removing requirement of a . in the domain - else if (mDomain.indexOf(".") == -1) { - isGood = false; - // Toast.makeText(AccountActivity.this, - // R.string.account_wizard_no_root_domain_warning, - // Toast.LENGTH_LONG).show(); - }*/ - - return isGood; - } - - void settingsForDomain(String domain, int port) { - - - if (domain.equals("gmail.com")) { - // Google only supports a certain configuration for XMPP: - // http://code.google.com/apis/talk/open_communications.html - settings.setDoDnsSrv(true); - settings.setServer(""); - settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } - else if (mEditPass.getText().toString().startsWith(GTalkOAuth2.NAME)) - { - //this is not @gmail but IS a google account - settings.setDoDnsSrv(false); - settings.setServer("talk.google.com"); //set the google connect server - settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } - else if (domain.equals("jabber.org")) { - settings.setDoDnsSrv(true); - settings.setDomain(domain); - settings.setPort(DEFAULT_PORT); - settings.setServer(""); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } else if (domain.equals("facebook.com")) { - settings.setDoDnsSrv(false); - settings.setDomain("chat.facebook.com"); - settings.setPort(DEFAULT_PORT); - settings.setServer("chat.facebook.com"); - settings.setRequireTls(true); //facebook TLS now seems to be on - settings.setTlsCertVerify(false); //but cert verify can still be funky - off by default - settings.setAllowPlainAuth(false); - } else { - settings.setDoDnsSrv(true); - settings.setDomain(domain); - settings.setPort(port); - settings.setServer(""); - settings.setRequireTls(true); - settings.setTlsCertVerify(true); - settings.setAllowPlainAuth(false); - } - } - - void confirmTermsOfUse(BrandingResources res, DialogInterface.OnClickListener accept) { - SpannableString message = new SpannableString( - res.getString(BrandingResourceIDs.STRING_TOU_MESSAGE)); - Linkify.addLinks(message, Linkify.ALL); - - new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(res.getString(BrandingResourceIDs.STRING_TOU_TITLE)).setMessage(message) - .setPositiveButton(res.getString(BrandingResourceIDs.STRING_TOU_DECLINE), null) - .setNegativeButton(res.getString(BrandingResourceIDs.STRING_TOU_ACCEPT), accept) - .show(); - } - - boolean shouldShowTermOfUse(BrandingResources res) { - return !TextUtils.isEmpty(res.getString(BrandingResourceIDs.STRING_TOU_MESSAGE)); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mAccountUri = savedInstanceState.getParcelable(ACCOUNT_URI_KEY); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(ACCOUNT_URI_KEY, mAccountUri); - } - - void signOutUsingActivity() { - - Intent intent = new Intent(CreateAccountActivity.this, SignoutActivity.class); - intent.setData(mAccountUri); - - startActivity(intent); - } - - private Handler mHandler = new Handler(); - private ImApp mApp = null; - - void signOut() { - //if you are signing out, then we will deactive "auto" sign in - ContentValues values = new ContentValues(); - values.put(AccountColumns.KEEP_SIGNED_IN, 0); - getContentResolver().update(mAccountUri, values, null, null); - - mApp = (ImApp)getApplication(); - - mApp.callWhenServiceConnected(mHandler, new Runnable() { - @Override - public void run() { - - signOut(mProviderId, mAccountId); - } - }); - - } - - void signOut(long providerId, long accountId) { - - try { - - IImConnection conn = mApp.getConnection(providerId); - if (conn != null) { - conn.logout(); - } else { - // Normally, we can always get the connection when user chose to - // sign out. However, if the application crash unexpectedly, the - // status will never be updated. Clear the status in this case - // to make it recoverable from the crash. - ContentValues values = new ContentValues(2); - values.put(AccountStatusColumns.PRESENCE_STATUS, CommonPresenceColumns.OFFLINE); - values.put(AccountStatusColumns.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); - String where = AccountStatusColumns.ACCOUNT + "=?"; - getContentResolver().update(Imps.AccountStatus.CONTENT_URI, values, where, - new String[] { Long.toString(accountId) }); - } - } catch (RemoteException ex) { - Log.e(ImApp.LOG_TAG, "signout: caught ", ex); - } finally { - - Toast.makeText(this, - getString(R.string.signed_out_prompt, this.mEditUserAccount.getText()), - Toast.LENGTH_SHORT).show(); - isSignedIn = false; - - mBtnSignIn.setText(getString(R.string.sign_in)); - mBtnSignIn.setBackgroundResource(R.drawable.btn_green); - } - } - - void createNewaccount (long accountId) - { - - ContentValues values = new ContentValues(2); - - values.put(AccountStatusColumns.PRESENCE_STATUS, CommonPresenceColumns.NEW_ACCOUNT); - values.put(AccountStatusColumns.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); - String where = AccountStatusColumns.ACCOUNT + "=?"; - getContentResolver().update(Imps.AccountStatus.CONTENT_URI, values, where, - new String[] { Long.toString(accountId) }); - - - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - - if (requestCode == REQUEST_SIGN_IN) { - if (resultCode == RESULT_OK) { - - finish(); - } else { - // sign in failed, let's show the screen! - } - } - } - - void updateWidgetState() { - boolean goodUsername = mEditUserAccount.getText().length() > 0; - boolean goodPassword = mEditPass.getText().length() > 0; - boolean hasNameAndPassword = goodUsername && goodPassword; - - mEditPass.setEnabled(goodUsername); - mEditPass.setFocusable(goodUsername); - mEditPass.setFocusableInTouchMode(goodUsername); - - // enable keep sign in only when remember password is checked. - boolean rememberPass = mRememberPass.isChecked(); - if (rememberPass && !hasNameAndPassword) { - mRememberPass.setChecked(false); - rememberPass = false; - - } - mRememberPass.setEnabled(hasNameAndPassword); - mRememberPass.setFocusable(hasNameAndPassword); - - mEditUserAccount.setEnabled(!isSignedIn); - mEditPass.setEnabled(!isSignedIn); - - if (!isSignedIn) { - mBtnSignIn.setEnabled(hasNameAndPassword); - mBtnSignIn.setFocusable(hasNameAndPassword); - } - } - - private final TextWatcher mTextWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int before, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int after) { - updateWidgetState(); - - } - - @Override - public void afterTextChanged(Editable s) { - - } - }; - - private void deleteAccount () - { - Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, mAccountId); - getContentResolver().delete(accountUri, null, null); - Uri providerUri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, mProviderId); - getContentResolver().delete(providerUri, null, null); - - } - - private void showAdvanced() { - - checkUserChanged(); - - Intent intent = new Intent(this, AccountSettingsActivity.class); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); - startActivity(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.account_settings_menu, menu); - - if (isEdit) { - //add delete menu option - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - finish(); - return true; - - case R.id.menu_gen_key: - otrGenKey(); - return true; - - - } - return super.onOptionsItemSelected(item); - } - - ProgressDialog pbarDialog; - - private void otrGenKey() { - - pbarDialog = new ProgressDialog(this); - - pbarDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - pbarDialog.setMessage(getString(R.string.otr_gen_key)); - pbarDialog.show(); - - KeyGenThread kgt = new KeyGenThread(); - kgt.start(); - - } - - private class KeyGenThread extends Thread { - - public KeyGenThread() { - - } - - @Override - public void run() { - - try { - if (otrKeyManager != null) { - otrKeyManager.generateLocalKeyPair(); - - } else { - Toast.makeText(CreateAccountActivity.this, "OTR is not initialized yet", - Toast.LENGTH_SHORT).show(); - } - } catch (Exception e) { - Log.e("OTR", "could not gen local key pair", e); - } finally { - handler.sendEmptyMessage(0); - } - - } - - private Handler handler = new Handler() { - - @Override - public void handleMessage(Message msg) { - - pbarDialog.dismiss(); - - try { - if (otrKeyManager != null) { - String lFingerprint = otrKeyManager.getLocalFingerprint(); - mTxtFingerprint.setText(processFingerprint(lFingerprint)); - } - - } catch (Exception e) { - Log.e("OTR", "could not gen local key pair", e); - } - - } - }; - } - - private String processFingerprint(String fingerprint) { - StringBuffer out = new StringBuffer(); - - for (int n = 0; n < fingerprint.length(); n++) { - for (int i = n; n < i + 4; n++) { - out.append(fingerprint.charAt(n)); - } - - out.append(' '); - } - - return out.toString(); - } - - - - -} diff --git a/src/info/guardianproject/otr/app/im/app/DatabaseUtils.java b/src/info/guardianproject/otr/app/im/app/DatabaseUtils.java index bf609a91a..4e1f7c635 100644 --- a/src/info/guardianproject/otr/app/im/app/DatabaseUtils.java +++ b/src/info/guardianproject/otr/app/im/app/DatabaseUtils.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -19,17 +19,20 @@ import info.guardianproject.otr.app.im.plugin.ImConfigNames; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; +import java.io.ByteArrayOutputStream; import java.util.Map; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; @@ -52,58 +55,53 @@ public static Cursor queryAccountsForProvider(ContentResolver cr, String[] proje return c; } - public static Drawable getAvatarFromCursor(Cursor cursor, int dataColumn, int width, int height) { - byte[] rawData = cursor.getBlob(dataColumn); - if (rawData == null) { + public static RoundedAvatarDrawable getAvatarFromCursor(Cursor cursor, int dataColumn, int width, int height) throws DecoderException { + String hexData = cursor.getString(dataColumn); + if (hexData.equals("NULL")) { return null; } - return decodeAvatar(rawData, width, height); - } - public static Uri getAvatarUri(Uri baseUri, long providerId, long accountId) { - Uri.Builder builder = baseUri.buildUpon(); - ContentUris.appendId(builder, providerId); - ContentUris.appendId(builder, accountId); - return builder.build(); + byte[] data = Hex.decodeHex(hexData.substring(2, hexData.length() - 1).toCharArray()); + return decodeAvatar(data, width, height); } - public static Drawable getAvatarFromCursor(Cursor cursor, int dataColumn, - int encodedDataColumn, String username, boolean updateBlobUseCursor, - ContentResolver resolver, Uri updateBlobUri, int width, int height) { - /** - * Optimization: the avatar table in IM content provider have two - * columns, one for the raw blob data, another for the base64 encoded - * data. The reason for this is when the avatars are initially - * downloaded, they are in the base64 encoded form, and instead of - * base64 decode the avatars for all the buddies up front, we can just - * simply store the encoded data in the table, and decode them on demand - * when displaying them. Once we decode the avatar, we store the decoded - * data as a blob, and null out the encoded column in the avatars table. - * query the raw blob data first, if present, great; if not, query the - * encoded data, decode it and store as the blob, and null out the - * encoded column. - */ - byte[] rawData = cursor.getBlob(dataColumn); - - if (rawData == null) { - String encodedData = cursor.getString(encodedDataColumn); - if (encodedData == null) { - // Log.e(LogTag.LOG_TAG, "getAvatarFromCursor for " + username + - // ", no raw or encoded data!"); + public static RoundedAvatarDrawable getAvatarFromAddress(ContentResolver cr, String address, int width, int height) throws DecoderException { + + String[] projection = {Imps.Contacts.AVATAR_DATA}; + String[] args = {address}; + String query = Imps.Contacts.USERNAME + " LIKE ?"; + Cursor cursor = cr.query(Imps.Contacts.CONTENT_URI,projection, + query, args, Imps.Contacts.DEFAULT_SORT_ORDER); + + if (cursor.moveToFirst()) + { + String hexData = cursor.getString(0); + cursor.close(); + if (hexData.equals("NULL")) { return null; } - if (updateBlobUseCursor) { - } - else { - updateAvatarBlob(resolver, updateBlobUri, rawData, username); - } + byte[] data = Hex.decodeHex(hexData.substring(2, hexData.length() - 1).toCharArray()); + + return decodeAvatar(data, width, height); } + else + { - return decodeAvatar(rawData, width, height); + cursor.close(); + return null; + } } - public static void updateAvatarBlob(ContentResolver resolver, Uri updateUri, byte[] data, + + public static Uri getAvatarUri(Uri baseUri, long providerId, long accountId) { + Uri.Builder builder = baseUri.buildUpon(); + ContentUris.appendId(builder, providerId); + ContentUris.appendId(builder, accountId); + return builder.build(); + } + + public static void updateAvatarBlob(ContentResolver resolver, Uri updateUri, byte[] data, String username) { ContentValues values = new ContentValues(3); values.put(Imps.Avatars.DATA, data); @@ -114,10 +112,10 @@ public static void updateAvatarBlob(ContentResolver resolver, Uri updateUri, byt String[] selectionArgs = new String[] { username }; resolver.update(updateUri, values, buf.toString(), selectionArgs); - + } - - public static boolean hasAvatarContact(ContentResolver resolver, Uri updateUri, + + public static boolean hasAvatarContact(ContentResolver resolver, Uri updateUri, String username) { ContentValues values = new ContentValues(3); values.put(Imps.Avatars.CONTACT, username); @@ -128,12 +126,33 @@ public static boolean hasAvatarContact(ContentResolver resolver, Uri updateUri, String[] selectionArgs = new String[] { username }; return resolver.update(updateUri, values, buf.toString(), selectionArgs) > 0; - + + } + + public static boolean doesAvatarHashExist(ContentResolver resolver, Uri queryUri, + String jid, String hash) { + + StringBuilder buf = new StringBuilder(Imps.Avatars.CONTACT); + buf.append("=?"); + buf.append(" AND "); + buf.append(Imps.Avatars.HASH); + buf.append("=?"); + + String[] selectionArgs = new String[] { jid, hash }; + + Cursor cursor = resolver.query(queryUri, null, buf.toString(), selectionArgs, null); + if (cursor == null) + return false; + try { + return cursor.getCount() > 0; + } finally { + cursor.close(); + } } - + public static void insertAvatarBlob(ContentResolver resolver, Uri updateUri, long providerId, long accountId, byte[] data, String hash, String contact) { - + ContentValues values = new ContentValues(3); values.put(Imps.Avatars.DATA, data); values.put(Imps.Avatars.CONTACT, contact); @@ -142,21 +161,28 @@ public static void insertAvatarBlob(ContentResolver resolver, Uri updateUri, lon values.put(Imps.Avatars.HASH, hash); resolver.insert(updateUri, values); + + } - - private static Drawable decodeAvatar(byte[] data, int width, int height) { - + + private static RoundedAvatarDrawable decodeAvatar(byte[] data, int width, int height) { + BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(data, 0, data.length,options); + BitmapFactory.decodeByteArray(data, 0, data.length,options); options.inSampleSize = calculateInSampleSize(options, width, height); options.inJustDecodeBounds = false; - Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length,options); - Drawable avatar = new BitmapDrawable(b); - return avatar; + Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length,options); + if (b != null) + { + RoundedAvatarDrawable avatar = new RoundedAvatarDrawable(b); + return avatar; + } + else + return null; } - + public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image @@ -181,7 +207,7 @@ public static int calculateInSampleSize( /** * Update IM provider database for a plugin using newly loaded information. - * + * * @param cr the resolver * @param providerName the plugin provider name * @param providerFullName the full name diff --git a/src/info/guardianproject/otr/app/im/app/DummyActivity.java b/src/info/guardianproject/otr/app/im/app/DummyActivity.java new file mode 100644 index 000000000..17f9d8a66 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/DummyActivity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source + * Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package info.guardianproject.otr.app.im.app; + +import info.guardianproject.otr.app.im.R; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +public class DummyActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.w(ImApp.LOG_TAG, "DummyActivity launched by swipe"); + super.onCreate(savedInstanceState); + ((ImApp)getApplication()).maybeInit(this); + finish(); + } + + // Unused for now + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + void showDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.im_label) + .setMessage(R.string.swipe_alert) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + if (Build.VERSION.SDK_INT >= 17) + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } + }); + builder.show(); + } +} diff --git a/src/info/guardianproject/otr/app/im/app/DynamicPagerAdapter.java b/src/info/guardianproject/otr/app/im/app/DynamicPagerAdapter.java new file mode 100644 index 000000000..800bf8d26 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/DynamicPagerAdapter.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package info.guardianproject.otr.app.im.app; + +import java.util.ArrayList; + +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +/** + * Implementation of {@link android.support.v4.view.PagerAdapter} that + * uses a {@link Fragment} to manage each page. This class also handles + * saving and restoring of fragment's state. + * + *

This version of the pager is more useful when there are a large number + * of pages, working more like a list view. When pages are not visible to + * the user, their entire fragment may be destroyed, only keeping the saved + * state of that fragment. This allows the pager to hold on to much less + * memory associated with each visited page as compared to + * {@link FragmentPagerAdapter} at the cost of potentially more overhead when + * switching between pages. + * + *

When using FragmentPagerAdapter the host ViewPager must have a + * valid ID set.

+ * + *

Subclasses only need to implement {@link #getItem(int)} + * and {@link #getCount()} to have a working adapter. + * + *

Here is an example implementation of a pager containing fragments of + * lists: + * + * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java + * complete} + * + *

The R.layout.fragment_pager resource of the top-level fragment is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml + * complete} + * + *

The R.layout.fragment_pager_list resource containing each + * individual fragment's layout is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml + * complete} + */ +public abstract class DynamicPagerAdapter extends PagerAdapter { + private static final String TAG = "FragmentStatePagerAdapter"; + private static final boolean DEBUG = false; + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + private Fragment mCurrentPrimaryItem = null; + + public DynamicPagerAdapter(FragmentManager fm) { + mFragmentManager = fm; + } + + /** + * Return the Fragment associated with a specified position. + */ + public abstract Fragment getItem(int position); + + public Fragment getItemAt(int position) { + if (position >= mFragments.size()) + return null; + return mFragments.get(position); + } + + @Override + public void startUpdate(ViewGroup container) { + } + + @Override + public void notifyDataSetChanged() { + // Fragments may have moved. Regenerate the fragment list. + ArrayList old = mFragments; + ArrayList oldState = mSavedState; + mFragments = new ArrayList(); + mSavedState = new ArrayList(); + for (int i = 0 ; i < old.size() ; i++) { + Fragment frag = old.get(i); + if (frag != null) { + int position = getItemPosition(frag); + if (position == POSITION_NONE) + continue; + if (position == POSITION_UNCHANGED) + position = i; + while (mFragments.size() <= position) { + mFragments.add(null); + mSavedState.add(null); + } + mFragments.set(position, frag); + if (oldState.size() > i) + mSavedState.set(position, oldState.get(i)); + } + } + + // The list must never shrink because other methods depend on it + while (mFragments.size() < old.size()) { + mFragments.add(null); + mSavedState.add(null); + } + + super.notifyDataSetChanged(); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + // If we already have this item instantiated, there is nothing + // to do. This can happen when we are restoring the entire pager + // from its saved state, where the fragment manager has already + // taken care of restoring the fragments we previously had instantiated. + if (mFragments.size() > position) { + Fragment f = mFragments.get(position); + if (f != null) { + return f; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + if (mSavedState.size() > position) { + Fragment.SavedState fss = mSavedState.get(position); + if (fss != null) { + fragment.setInitialSavedState(fss); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mFragments.set(position, fragment); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int _position, Object object) { + Fragment fragment = (Fragment)object; + // The supplied position is unreliable. When an item is deleted, the pre-reorg position is supplied, + // but when an item is scrolled off screen due to an insert, the post-reorg position is supplied. + // Find the item ourselves. + int position = mFragments.indexOf(fragment); + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + // Fragment might already have been reorged out of the list + if (position >= 0) + { + if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + + " v=" + ((Fragment)object).getView()); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + mFragments.set(position, null); + } + + // TODO do we need to unset the visible hint, etc.? + if (mCurrentPrimaryItem == fragment) + mCurrentPrimaryItem = null; + + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + mCurrentPrimaryItem = fragment; + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; +// FIXME don't save internal fragment state for now, until we figure out classloader issue +// if (mSavedState.size() > 0) { +// state = new Bundle(); +// Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; +// mSavedState.toArray(fss); +// state.putParcelableArray("states", fss); +// } + for (int i=0; i keys = bundle.keySet(); + for (String key: keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + } + } +} diff --git a/src/info/guardianproject/otr/app/im/app/ErrorResUtils.java b/src/info/guardianproject/otr/app/im/app/ErrorResUtils.java index 74e7e569c..d02d34ad9 100644 --- a/src/info/guardianproject/otr/app/im/app/ErrorResUtils.java +++ b/src/info/guardianproject/otr/app/im/app/ErrorResUtils.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/ExitActivity.java b/src/info/guardianproject/otr/app/im/app/ExitActivity.java new file mode 100644 index 000000000..86a01c1de --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/ExitActivity.java @@ -0,0 +1,36 @@ + +package info.guardianproject.otr.app.im.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +public class ExitActivity extends Activity { + + @SuppressLint("NewApi") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + + System.exit(0); + } + + public static void exitAndRemoveFromRecentApps(Activity activity) { + Intent intent = new Intent(activity, ExitActivity.class); + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NO_ANIMATION); + + activity.startActivity(intent); + } +} diff --git a/src/info/guardianproject/otr/app/im/app/ImApp.java b/src/info/guardianproject/otr/app/im/app/ImApp.java index 4c2d3a9a6..2bc40fd35 100644 --- a/src/info/guardianproject/otr/app/im/app/ImApp.java +++ b/src/info/guardianproject/otr/app/im/app/ImApp.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,8 +17,8 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.SQLCipherOpenHelper; +import info.guardianproject.cacheword.PRNGFixes; +import info.guardianproject.iocipher.VirtualFileSystem; import info.guardianproject.otr.app.Broadcaster; import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.IChatSessionManager; @@ -32,13 +32,14 @@ import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; import info.guardianproject.otr.app.im.plugin.ImPlugin; import info.guardianproject.otr.app.im.plugin.ImPluginInfo; +import info.guardianproject.otr.app.im.plugin.xmpp.XMPPCertPins; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.service.ImServiceConstants; import info.guardianproject.util.AssetUtil; -import info.guardianproject.util.LogCleaner; -import info.guardianproject.util.PRNGFixes; +import info.guardianproject.util.Debug; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -46,6 +47,14 @@ import java.util.Map; import java.util.Properties; +import net.hockeyapp.android.CrashManager; +import net.hockeyapp.android.CrashManagerListener; +import net.sqlcipher.database.SQLiteDatabase; + +import org.thoughtcrime.ssl.pinning.PinningTrustManager; +import org.thoughtcrime.ssl.pinning.SystemKeyStore; + +import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.content.ComponentName; @@ -62,18 +71,25 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.preference.PreferenceManager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.Toolbar; import android.util.Log; +import de.duenndns.ssl.MemorizingTrustManager; public class ImApp extends Application { - + public static final String LOG_TAG = "GB.ImApp"; public static final String EXTRA_INTENT_SEND_TO_USER = "Send2_U"; @@ -92,12 +108,20 @@ public class ImApp extends Application { public static final String HOCKEY_APP_ID = "2fa3b9252319e47367f1f125bb3adcd1"; public static final String DEFAULT_TIMEOUT_CACHEWORD = "-1"; //one day - + public static final String CACHEWORD_PASSWORD_KEY = "pkey"; - + public static final String CLEAR_PASSWORD_KEY = "clear_key"; + + public static final String NO_CREATE_KEY = "nocreate"; + + //ACCOUNT SETTINGS Imps defaults + public static final String DEFAULT_XMPP_RESOURCE = "ChatSecure"; + public static final int DEFAULT_XMPP_PRIORITY = 20; + public static final String DEFAULT_XMPP_OTR_MODE = "auto"; + private Locale locale = null; - private static ImApp sImApp; + public static ImApp sImApp; IRemoteImService mImService; @@ -107,6 +131,10 @@ public class ImApp extends Application { Broadcaster mBroadcaster; + public MemorizingTrustManager mTrustManager; + + public static boolean mUsingCacheword = false; + /** * A queue of messages that are waiting to be sent when service is * connected. @@ -180,7 +208,7 @@ public void initialize(Activity activity) { sImApp.onCreate(); } */ - + @Override public Resources getResources() { if (mApplicationContext == this) { @@ -199,12 +227,6 @@ public ContentResolver getContentResolver() { return mApplicationContext.getContentResolver(); } - public ImApp() { - super(); - mConnections = new HashMap(); - mApplicationContext = this; - sImApp = this; - } @Override public void onConfigurationChanged(Configuration newConfig) { @@ -215,7 +237,7 @@ public void onConfigurationChanged(Configuration newConfig) { // object causes an infinite relaunch loop in Android 4.2 (JB MR1) Configuration myConfig = new Configuration(newConfig); myConfig.locale = locale; - + Locale.setDefault(locale); getResources().updateConfiguration(myConfig, getResources().getDisplayMetrics()); } @@ -224,111 +246,169 @@ public void onConfigurationChanged(Configuration newConfig) { @Override public void onCreate() { super.onCreate(); - + + sImApp = this; + + Debug.onAppStart(); + PRNGFixes.apply(); //Google's fix for SecureRandom bug: http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html - + + // load these libs up front to shorten the delay after typing the passphrase + SQLiteDatabase.loadLibs(getApplicationContext()); + VirtualFileSystem.get().isMounted(); + + mConnections = new HashMap(); + mApplicationContext = this; + + initTrustManager(); + mBroadcaster = new Broadcaster(); - setAppTheme(null); - + setAppTheme(null,null); + checkLocale(); } + + private boolean mThemeDark = false; + + public boolean isThemeDark () + { + return mThemeDark; + } public void setAppTheme (Activity activity) { - /* - + setAppTheme(activity, null); + } + + private final static int COLOR_TOOLBAR_DARK = Color.parseColor("#263238"); + private final static int COLOR_TOOLBAR_LIGHT = Color.parseColor("#B0BEC5"); + + public void setAppTheme (Activity activity, Toolbar toolbar) + { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - //int themeId = settings.getInt("theme", R.style.Theme_Gibberbot_Light); - boolean themeDark = settings.getBoolean("themeDark", false); - - if (themeDark) - { - setTheme(R.style.Theme_Sherlock); - + + mThemeDark = settings.getBoolean("themeDark", false); + + if (mThemeDark) + { + setTheme(R.style.AppThemeDark); + + if (activity != null) - activity.setTheme(R.style.Theme_Sherlock); + { + activity.setTheme(R.style.AppThemeDark); + if (activity instanceof ActionBarActivity) + { + ActionBar ab = ((ActionBarActivity)activity).getSupportActionBar(); + + if (ab != null) + { + ab.setBackgroundDrawable(new ColorDrawable(COLOR_TOOLBAR_DARK)); + } + } + } + if (toolbar != null) + toolbar.setBackgroundColor(COLOR_TOOLBAR_DARK); + } else { - setTheme(R.style.Theme_Chatsecure); + setTheme(R.style.AppTheme); + + + if (activity != null) + { + activity.setTheme(R.style.AppTheme); + if (activity instanceof ActionBarActivity) + { + ActionBar ab = ((ActionBarActivity)activity).getSupportActionBar(); + + if (ab != null) + { + ab.setBackgroundDrawable(new ColorDrawable(COLOR_TOOLBAR_LIGHT)); + } + } + } + + if (toolbar != null) + toolbar.setBackgroundColor(COLOR_TOOLBAR_LIGHT); - if (activity != null) - activity.setTheme(R.style.Theme_Chatsecure); } - + Configuration config = getResources().getConfiguration(); getResources().updateConfiguration(config, getResources().getDisplayMetrics()); - */ - + + if (mImService != null) + { + boolean debugOn = settings.getBoolean("prefDebug", false); + try { + mImService.enableDebugLogging(debugOn); + } catch (RemoteException e) { + e.printStackTrace(); + } + } } - - public boolean checkLocale () + + public void checkLocale () { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); Configuration config = getResources().getConfiguration(); - String lang = settings.getString(getString(R.string.pref_default_locale), ""); - - + String lang = settings.getString(getString(R.string.key_default_locale_pref), ""); + + if ("".equals(lang)) { - Properties props = AssetUtil.getProperties("gibberbot.properties", this); + Properties props = AssetUtil.getProperties("chatsecure.properties", this); if (props != null) { String configuredLocale = props.getProperty("locale"); if (configuredLocale != null && !"CHOOSE".equals(configuredLocale)) { lang = configuredLocale; Editor editor = settings.edit(); - editor.putString(getString(R.string.pref_default_locale), lang); - editor.commit(); + editor.putString(getString(R.string.key_default_locale_pref), lang); + editor.apply(); } } } - - boolean updatedLocale = false; - + if (!"".equals(lang) && !config.locale.getLanguage().equals(lang)) { - locale = new Locale(lang); + locale = new Locale(lang); config.locale = locale; + Locale.setDefault(locale); getResources().updateConfiguration(config, getResources().getDisplayMetrics()); - updatedLocale = true; } loadDefaultBrandingRes(); - - return updatedLocale; } - public boolean setNewLocale(Context context, String localeString) { - - /* - Locale locale = new Locale(localeString); - - Configuration config = context.getResources().getConfiguration(); - config.locale = locale; - - context.getResources().updateConfiguration(config, - context.getResources().getDisplayMetrics()); + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public void setNewLocale(Context context, String language) { + /* handle locales with the country in it, i.e. zh_CN, zh_TW, etc */ + String localeSplit[] = language.split("_"); + if (localeSplit.length > 1) + locale = new Locale(localeSplit[0], localeSplit[1]); + else + locale = new Locale(language); + Configuration config = getResources().getConfiguration(); + if (Build.VERSION.SDK_INT >= 17) + config.setLocale(locale); + else + config.locale = locale; + Locale.setDefault(locale); + getResources().updateConfiguration(config, getResources().getDisplayMetrics()); - Log.d(LOG_TAG, "locale = " + locale.getDisplayName()); - */ - + /* Set the preference after setting the locale in case something goes + wrong. If setting the locale causes an Exception, it should be set in the + preferences, otherwise ChatSecure will be stuck in a crash loop. */ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); Editor prefEdit = prefs.edit(); - prefEdit.putString(context.getString(R.string.pref_default_locale), localeString); - prefEdit.commit(); - - Configuration config = getResources().getConfiguration(); - - locale = new Locale(localeString); - config.locale = locale; - getResources().updateConfiguration(config, getResources().getDisplayMetrics()); - - - return true; + prefEdit.putString(context.getString(R.string.key_default_locale_pref), language); + prefEdit.apply(); } - + + /** @Override public void onTerminate() { stopImServiceIfInactive(); @@ -340,47 +420,46 @@ public void onTerminate() { } } + Imps.clearPassphrase(this); super.onTerminate(); - } + }*/ public synchronized void startImServiceIfNeed() { startImServiceIfNeed(false); } - public synchronized void startImServiceIfNeed(boolean auto) { + public synchronized void startImServiceIfNeed(boolean isBoot) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) log("start ImService"); Intent serviceIntent = new Intent(); serviceIntent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT); - serviceIntent.putExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, auto); - + serviceIntent.putExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, true); + if (mImService == null) { mApplicationContext.startService(serviceIntent); - - mConnectionListener = new MyConnListener(new Handler()); + if (!isBoot) { + mConnectionListener = new MyConnListener(new Handler()); + } } - - mApplicationContext - .bindService(serviceIntent, mImServiceConn, Context.BIND_AUTO_CREATE); - + if (mImServiceConn != null && !isBoot) + mApplicationContext + .bindService(serviceIntent, mImServiceConn, Context.BIND_AUTO_CREATE); + + } public boolean hasActiveConnections () { return !mConnections.isEmpty(); - + } - + public synchronized void stopImServiceIfInactive() { - boolean hasActiveConnection = true; - synchronized (mConnections) { - hasActiveConnection = !mConnections.isEmpty(); - } - if (!hasActiveConnection) { + if (!hasActiveConnections()) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) log("stop ImService because there's no active connections"); @@ -391,14 +470,12 @@ public synchronized void stopImServiceIfInactive() { Intent intent = new Intent(); intent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT); mApplicationContext.stopService(intent); - + } } - - - //private boolean mKillServerOnStart = false; - - public synchronized void forceStopImService() + + + public synchronized void forceStopImService() { if (mImService != null) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) @@ -410,30 +487,7 @@ public synchronized void forceStopImService() Intent intent = new Intent(); intent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT); mApplicationContext.stopService(intent); - - } - } - - - private CacheWordActivityHandler mCacheWord; - public void setCacheWord ( CacheWordActivityHandler cacheWord) - { - mCacheWord = cacheWord; - } - - public void initOtrStoreKey () - { - if ( getRemoteImService() != null) - { - String pkey = SQLCipherOpenHelper.encodeRawKey(mCacheWord.getEncryptionKey()); - - try { - getRemoteImService().unlockOtrStore(pkey); - } catch (RemoteException e) { - - LogCleaner.error(ImApp.LOG_TAG, "eror initializing otr key", e); - } } } @@ -444,9 +498,6 @@ public void onServiceConnected(ComponentName className, IBinder service) { mImService = IRemoteImService.Stub.asInterface(service); fetchActiveConnections(); - - if (mCacheWord != null && mCacheWord.getEncryptionKey() != null) - initOtrStoreKey(); synchronized (mQueue) { for (Message msg : mQueue) { @@ -456,7 +507,7 @@ public void onServiceConnected(ComponentName className, IBinder service) { } Message msg = Message.obtain(null, EVENT_SERVICE_CONNECTED); mBroadcaster.broadcast(msg); - + /* if (mKillServerOnStart) { @@ -468,7 +519,7 @@ public void onServiceDisconnected(ComponentName className) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) log("service disconnected"); - //mConnections.clear(); + mConnections.clear(); mImService = null; } }; @@ -478,16 +529,16 @@ public boolean serviceConnected() { } // public boolean isBackgroundDataEnabled() { //"background data" is a deprectaed concept - public boolean isNetworkAvailableAndConnected () { - ConnectivityManager manager = (ConnectivityManager) mApplicationContext + public static boolean isNetworkAvailableAndConnected (Context context) { + ConnectivityManager manager = (ConnectivityManager) context .getSystemService(CONNECTIVITY_SERVICE); - + NetworkInfo nInfo = manager.getActiveNetworkInfo(); if (nInfo != null) { - Log.d(LOG_TAG,"network state: available=" + nInfo.isAvailable() + " connected/connecting=" + nInfo.isConnectedOrConnecting()); - return nInfo.isAvailable() && nInfo.isConnectedOrConnecting(); + Log.d(LOG_TAG,"isNetworkAvailableAndConnected? available=" + nInfo.isAvailable() + " connected=" + nInfo.isConnected()); + return nInfo.isAvailable() && nInfo.isConnected(); } else return false; //no network info is a bad idea @@ -529,9 +580,10 @@ public static long insertOrUpdateAccount(ContentResolver cr, long providerId, St } /** Used to reset the provider settings if a reload is required. */ + /* public void resetProviderSettings() { mProviders = null; - } + }*/ // For testing public void setImProviderSettings(HashMap providers) { @@ -539,9 +591,6 @@ public void setImProviderSettings(HashMap providers) { } private void loadImProviderSettings() { - if (mProviders != null) { - return; - } mProviders = new HashMap(); ContentResolver cr = getContentResolver(); @@ -563,6 +612,8 @@ private void loadImProviderSettings() { String fullName = c.getString(2); String signUpUrl = c.getString(3); + if (mProviders == null) // mProviders has been reset + break; mProviders.put(id, new ProviderDef(id, providerName, fullName, signUpUrl)); } } finally { @@ -573,7 +624,7 @@ private void loadImProviderSettings() { private void loadDefaultBrandingRes() { HashMap resMapping = new HashMap(); - resMapping.put(BrandingResourceIDs.DRAWABLE_LOGO, R.drawable.ic_launcher_gibberbot); + resMapping.put(BrandingResourceIDs.DRAWABLE_LOGO, R.drawable.ic_launcher); resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE, android.R.drawable.presence_online); resMapping @@ -646,7 +697,7 @@ private void loadThirdPartyResources() { Map resMap = plugin.getResourceMap(); //int[] smileyIcons = plugin.getSmileyIconIds(); - + BrandingResources res = new BrandingResources(packageRes, resMap, mDefaultBrandingResources); mBrandingResources.put(pluginInfo.mProviderName, res); @@ -692,10 +743,12 @@ public BrandingResources getBrandingResource(long providerId) { } public IImConnection createConnection(long providerId, long accountId) throws RemoteException { + if (mImService == null) { // Service hasn't been connected or has died. return null; } + IImConnection conn = getConnection(providerId); if (conn == null) { conn = mImService.createConnection(providerId, accountId); @@ -705,12 +758,25 @@ public IImConnection createConnection(long providerId, long accountId) throws Re public IImConnection getConnection(long providerId) { synchronized (mConnections) { - - if (mConnections.size() == 0) - fetchActiveConnections(); - - - return mConnections.get(providerId); + + IImConnection im = mConnections.get(providerId); + + if (im != null) + { + try + { + im.getState(); + } + catch (RemoteException doe) + { + mConnections.clear(); + //something is wrong + fetchActiveConnections(); + im = mConnections.get(providerId); + } + } + + return im; } } @@ -729,12 +795,9 @@ public IImConnection getConnectionByAccount(long accountId) { } } - public List getActiveConnections() { - synchronized (mConnections) { - ArrayList result = new ArrayList(); - result.addAll(mConnections.values()); - return result; - } + public Collection getActiveConnections() { + + return mConnections.values(); } public void callWhenServiceConnected(Handler target, Runnable callback) { @@ -752,22 +815,22 @@ public void callWhenServiceConnected(Handler target, Runnable callback) { public void deleteAccount (long accountId, long providerId) { ContentResolver resolver = getContentResolver(); - + Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); resolver.delete(accountUri, null, null); - + Uri providerUri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId); resolver.delete(providerUri, null, null); - + Uri.Builder builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); ContentUris.appendId(builder, providerId); - ContentUris.appendId(builder, accountId); + ContentUris.appendId(builder, accountId); resolver.delete(builder.build(), null, null); - - - + + + } - + public void removePendingCall(Handler target) { synchronized (mQueue) { Iterator iter = mQueue.iterator(); @@ -850,14 +913,14 @@ private void fetchActiveConnections() { // register the listener before fetch so that we won't miss any connection. mImService.addConnectionCreatedListener(mConnCreationListener); synchronized (mConnections) { - + for (IBinder binder : (List) mImService.getActiveConnections()) { IImConnection conn = IImConnection.Stub.asInterface(binder); long providerId = conn.getProviderId(); - if (!mConnections.containsKey(providerId)) { + // if (!mConnections.containsKey(providerId)) { mConnections.put(providerId, conn); conn.registerConnectionListener(mConnectionListener); - } + // } } } } catch (RemoteException e) { @@ -870,10 +933,10 @@ private void fetchActiveConnections() { public void onConnectionCreated(IImConnection conn) throws RemoteException { long providerId = conn.getProviderId(); synchronized (mConnections) { - if (!mConnections.containsKey(providerId)) { + // if (!mConnections.containsKey(providerId)) { mConnections.put(providerId, conn); conn.registerConnectionListener(mConnectionListener); - } + // } } broadcastConnEvent(EVENT_CONNECTION_CREATED, providerId, null); } @@ -891,6 +954,9 @@ public void onConnectionStateChange(IImConnection conn, int state, ImErrorInfo e } try { + + // fetchActiveConnections(); + int what = -1; long providerId = conn.getProviderId(); switch (state) { @@ -903,20 +969,18 @@ public void onConnectionStateChange(IImConnection conn, int state, ImErrorInfo e break; case ImConnection.LOGGING_OUT: + // NOTE: if this logic is changed, the logic in ImConnectionAdapter.ConnectionAdapterListener must be changed to match what = EVENT_CONNECTION_LOGGING_OUT; - // MIRON - remove only if disconnected! - // synchronized (mConnections) { - // mConnections.remove(providerId); - // } + break; case ImConnection.DISCONNECTED: + // NOTE: if this logic is changed, the logic in ImConnectionAdapter.ConnectionAdapterListener must be changed to match what = EVENT_CONNECTION_DISCONNECTED; - synchronized (mConnections) { - mConnections.remove(providerId); - } + // mConnections.remove(providerId); // stop the service if there isn't an active connection anymore. stopImServiceIfInactive(); + break; case ImConnection.SUSPENDED: @@ -962,7 +1026,7 @@ public IRemoteImService getRemoteImService() { return mImService; } - + public IChatSession getChatSession(long providerId, String remoteAddress) { IImConnection conn = getConnection(providerId); @@ -986,5 +1050,31 @@ public IChatSession getChatSession(long providerId, String remoteAddress) { return null; } - + public void maybeInit(Activity activity) { + startImServiceIfNeed(); + setAppTheme(activity,null); + ImPluginHelper.getInstance(this).loadAvailablePlugins(); + } + + public void checkForCrashes(final Activity activity) { + CrashManager.register(activity, ImApp.HOCKEY_APP_ID, new CrashManagerListener() { + @Override + public String getDescription() { + return Debug.getTrail(activity); + } + }); + } + + + private void initTrustManager () + { + PinningTrustManager trustPinning = new PinningTrustManager(SystemKeyStore.getInstance(this),XMPPCertPins.getPinList(), 0); + mTrustManager = new MemorizingTrustManager(this, trustPinning); + + } + + public MemorizingTrustManager getTrustManager () + { + return mTrustManager; + } } diff --git a/src/info/guardianproject/otr/app/im/app/ImPluginHelper.java b/src/info/guardianproject/otr/app/im/app/ImPluginHelper.java index eb13d08b7..09aaf6bd1 100644 --- a/src/info/guardianproject/otr/app/im/app/ImPluginHelper.java +++ b/src/info/guardianproject/otr/app/im/app/ImPluginHelper.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2009 Myriad Group AG. Copyright (C) 2009 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -104,13 +104,23 @@ public List getProviderNames() { return names; } + public List getProviderFullNames() { + List plugins = getPlugins(); + List names = new ArrayList(); + for (ResolveInfo plugin : plugins) { + names.add(plugin.serviceInfo.metaData + .getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME)); + } + return names; + } + public long createAdditionalProvider(String name) { List plugins = getPlugins(); ResolveInfo info = null; ServiceInfo serviceInfo = null; Bundle metaData = null; long providerId = -1; - + for (ResolveInfo _info : plugins) { serviceInfo = _info.serviceInfo; if (serviceInfo == null) { @@ -146,7 +156,7 @@ public long createAdditionalProvider(String name) { Log.e(TAG, "Ignore plugin in package: " + serviceInfo.packageName); return -1; } - + ImPluginInfo pluginInfo = new ImPluginInfo(providerName, serviceInfo.packageName, serviceInfo.name, serviceInfo.applicationInfo.sourceDir); @@ -281,7 +291,7 @@ private long updateProviderDb(ImPlugin plugin, ImPluginInfo info, String provide if (c == null) return 0; - + boolean pluginChanged; try { if (c.moveToFirst()) { diff --git a/src/info/guardianproject/otr/app/im/app/ImRingtonePreference.java b/src/info/guardianproject/otr/app/im/app/ImRingtonePreference.java index bc4643c7b..1e91868f6 100644 --- a/src/info/guardianproject/otr/app/im/app/ImRingtonePreference.java +++ b/src/info/guardianproject/otr/app/im/app/ImRingtonePreference.java @@ -1,13 +1,13 @@ /** * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -21,8 +21,10 @@ import info.guardianproject.otr.app.im.service.ImServiceConstants; import android.app.Activity; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.preference.RingtonePreference; import android.text.TextUtils; @@ -45,8 +47,13 @@ public ImRingtonePreference(Context context, AttributeSet attrs) { @Override protected Uri onRestoreRingtone() { + + ContentResolver cr = getContext().getContentResolver(); + + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContext().getContentResolver(), false /* keep updated */, null /* no handler */); + pCursor,cr, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, false /* keep updated */, null /* no handler */); String uri = settings.getRingtoneURI(); if (Log.isLoggable(ImApp.LOG_TAG, Log.VERBOSE)) { @@ -66,8 +73,13 @@ protected Uri onRestoreRingtone() { @Override protected void onSaveRingtone(Uri ringtoneUri) { - final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContext().getContentResolver(), false /* keep updated */, null /* no handler */); + + ContentResolver cr = getContext().getContentResolver(); + + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + pCursor, cr, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, false /* keep updated */, null /* no handler */); // When ringtoneUri is null, that means 'Silent' was chosen settings.setRingtoneURI(ringtoneUri == null ? "" : ringtoneUri.toString()); diff --git a/src/info/guardianproject/otr/app/im/app/ImUrlActivity.java b/src/info/guardianproject/otr/app/im/app/ImUrlActivity.java index 4b4ff3dd6..fdfbeadc9 100644 --- a/src/info/guardianproject/otr/app/im/app/ImUrlActivity.java +++ b/src/info/guardianproject/otr/app/im/app/ImUrlActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,9 +16,7 @@ */ package info.guardianproject.otr.app.im.app; -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.ICacheWordSubscriber; -import info.guardianproject.cacheword.SQLCipherOpenHelper; +import info.guardianproject.otr.OtrDataHandler; import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.IChatSessionManager; import info.guardianproject.otr.app.im.IImConnection; @@ -27,11 +25,19 @@ import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.service.ImServiceConstants; import info.guardianproject.util.LogCleaner; +import info.guardianproject.util.SystemServices; +import info.guardianproject.util.SystemServices.FileInfo; +import java.io.File; +import java.io.InputStream; +import java.util.Collection; import java.util.Iterator; -import java.util.List; +import java.util.Locale; import java.util.Set; +import java.util.UUID; +import net.java.otr4j.session.SessionStatus; +import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentUris; @@ -41,121 +47,241 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; -import android.widget.TextView; +import android.widget.Toast; -public class ImUrlActivity extends ThemeableActivity implements ICacheWordSubscriber { - private static final String[] ACCOUNT_PROJECTION = { Imps.Account._ID, Imps.Account.PASSWORD, }; - private static final int ACCOUNT_ID_COLUMN = 0; - private static final int ACCOUNT_PW_COLUMN = 1; +public class ImUrlActivity extends Activity { + private static final String TAG = "ImUrlActivity"; + + private static final int REQUEST_PICK_CONTACTS = RESULT_FIRST_USER + 1; + private static final int REQUEST_CREATE_ACCOUNT = RESULT_FIRST_USER + 2; + private static final int REQUEST_SIGNIN_ACCOUNT = RESULT_FIRST_USER + 3; + private static final int REQUEST_START_MUC = RESULT_FIRST_USER + 4; private String mProviderName; private String mToAddress; + private String mFromAddress; + private String mHost; - private ImApp mApp; private IImConnection mConn; + private IChatSessionManager mChatSessionManager; + + private Uri mSendUri; + private String mSendType; + private String mSendText; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - CacheWordActivityHandler cacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); - cacheWord.connectToService(); - - - Intent intent = getIntent(); - if (Intent.ACTION_SENDTO.equals(intent.getAction())) { - if (!resolveIntent(intent)) { - finish(); - return; - } + doOnCreate(); + } - if (TextUtils.isEmpty(mToAddress)) { - LogCleaner.warn(ImApp.LOG_TAG, "Invalid to address"); - // finish(); - return; - } - mApp = (ImApp)getApplication(); - - mApp.callWhenServiceConnected(new Handler(), new Runnable() { - public void run() { - handleIntent(); - } - }); - } else { - finish(); - } + + @Override + protected void onResume() { + super.onResume(); + } + + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + setIntent(intent); + } + + public void onDBLocked() { + + Intent intent = new Intent(getApplicationContext(), WelcomeActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + finish(); } void handleIntent() { + ContentResolver cr = getContentResolver(); - - - long providerId = -1;// = Imps.Provider.getProviderIdForName(cr, mProviderName); - long accountId; - - List listConns = ((ImApp)getApplication()).getActiveConnections(); - - if (!listConns.isEmpty()) + + long providerId = -1; + long accountId = -1; + + Collection listConns = ((ImApp)getApplication()).getActiveConnections(); + + //look for active connections that match the host we need + for (IImConnection conn : listConns) { - - mConn = listConns.get(0); + + try { - providerId = mConn.getProviderId(); - accountId = mConn.getAccountId(); + long connProviderId = conn.getProviderId(); + + Cursor cursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(connProviderId)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + cursor, cr, connProviderId, false /* don't keep updated */, null /* no handler */); + + try { + String domainToCheck = settings.getDomain(); + + if (domainToCheck != null && domainToCheck.length() > 0 && mHost.contains(domainToCheck)) + { + mConn = conn; + providerId = connProviderId; + accountId = conn.getAccountId(); + + break; + } + } finally { + settings.close(); + } + } catch (RemoteException e) { e.printStackTrace(); } - } -// mConn = mApp.getConnection(providerId); + } + + //nothing active, let's see if non-active connections match if (mConn == null) { - Cursor c = DatabaseUtils.queryAccountsForProvider(cr, ACCOUNT_PROJECTION, providerId); - if (c == null) { - addAccount(providerId); + + Cursor cursorProvider = initProviderCursor(); + + if (cursorProvider == null || cursorProvider.isClosed() || cursorProvider.getCount() == 0) { + + createNewAccount(); + return; } else { - accountId = c.getLong(ACCOUNT_ID_COLUMN); - if (c.isNull(ACCOUNT_PW_COLUMN)) { - editAccount(accountId); - } else { - signInAccount(accountId); + + + while (cursorProvider.moveToNext()) + { + //make sure there is a stored password + if (!cursorProvider.isNull(ACTIVE_ACCOUNT_PW_COLUMN)) { + + long cProviderId = cursorProvider.getLong(PROVIDER_ID_COLUMN); + Cursor cursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(cProviderId)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( + cursor, cr, cProviderId, false /* don't keep updated */, null /* no handler */); + + //does the conference host we need, match the settings domain for a logged in account + String domainToCheck = settings.getDomain(); + + if (domainToCheck != null && domainToCheck.length() > 0 && mHost.contains(domainToCheck)) + { + providerId = cProviderId; + accountId = cursorProvider.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + mConn = ((ImApp)getApplication()).getConnection(providerId); + + + //now sign in + signInAccount(accountId, providerId, cursorProvider.getString(ACTIVE_ACCOUNT_PW_COLUMN)); + + + settings.close(); + cursorProvider.close(); + + return; + + } + + settings.close(); + + } + } + + cursorProvider.close(); + + + + + } - } else { + + } + + if (mConn != null) + { try { int state = mConn.getState(); accountId = mConn.getAccountId(); + providerId = mConn.getProviderId(); if (state < ImConnection.LOGGED_IN) { - signInAccount(accountId); - } else if (state == ImConnection.LOGGED_IN || state == ImConnection.SUSPENDED) { - if (!isValidToAddress()) { + + Cursor cursorProvider = initProviderCursor(); + + while(cursorProvider.moveToNext()) + { + if (cursorProvider.getLong(ACTIVE_ACCOUNT_ID_COLUMN) == accountId) + { + signInAccount(accountId, providerId, cursorProvider.getString(ACTIVE_ACCOUNT_PW_COLUMN)); + + try { + Thread.sleep (500); + } catch (InterruptedException e1) { + e1.printStackTrace(); + }//wait here for three seconds + mConn = ((ImApp)getApplication()).getConnection(providerId); + + break; + } + } + + cursorProvider.close(); + } + + if (state == ImConnection.LOGGED_IN || state == ImConnection.SUSPENDED) { + + Uri data = getIntent().getData(); + + if (data.getScheme().equals("immu")) + { + this.openMultiUserChat(data); + + } + else if (!isValidToAddress()) { showContactList(accountId); } else { openChat(providerId, accountId); } + + + } } catch (RemoteException e) { // Ouch! Service died! We'll just disappear. Log.w("ImUrlActivity", "Connection disappeared!"); + finish(); } } - finish(); + else + { + createNewAccount(); + return; + } } + /* private void addAccount(long providerId) { Intent intent = new Intent(this, AccountActivity.class); intent.setAction(Intent.ACTION_INSERT); intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); - intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); +// intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); + + if (mFromAddress != null) + intent.putExtra("newuser", mFromAddress + '@' + mHost); startActivity(intent); - } + }*/ private void editAccount(long accountId) { Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); @@ -163,12 +289,33 @@ private void editAccount(long accountId) { intent.setAction(Intent.ACTION_EDIT); intent.setData(accountUri); intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); - startActivity(intent); + startActivityForResult(intent,REQUEST_SIGNIN_ACCOUNT); } - private void signInAccount(long accountId) { - editAccount(accountId); + private void signInAccount(long accountId, long providerId, String password) { + //editAccount(accountId); // TODO sign in? security implications? + SignInHelper signInHelper = new SignInHelper(this); + signInHelper.setSignInListener(new SignInHelper.SignInListener() { + public void connectedToService() { + } + public void stateChanged(int state, long accountId) { + if (state == ImConnection.LOGGED_IN) { + + mHandlerRouter.post(new Runnable() + { + public void run () + { + handleIntent(); + } + }); + + } + + } + }); + + signInHelper.signIn(password, providerId, accountId, true); } private void showContactList(long accountId) { @@ -185,7 +332,7 @@ private void openChat(long provider, long account) { IChatSessionManager manager = mConn.getChatSessionManager(); IChatSession session = manager.getChatSession(mToAddress); if (session == null) { - session = manager.createChatSession(mToAddress); + session = manager.createChatSession(mToAddress,false); } Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, session.getId()); @@ -201,22 +348,65 @@ private void openChat(long provider, long account) { } } - private boolean resolveIntent(Intent intent) { + private boolean resolveInsertIntent(Intent intent) { Uri data = intent.getData(); - String host = data.getHost(); - - if (data.getScheme().equals("immu")) + + if (data.getScheme().equals("ima")) { - this.openMultiUserChat(data); - + createNewAccount(); + + return true; + } + return false; + } + + // private static final String USERNAME_PATTERN = "^[a-z0-9_-]{3,15}$"; + + //private static final String USERNAME_NON_LETTERS_UNICODE = "[^\\p{L}\\p{Nd}]+"; + //private static final String USERNAME_NON_LETTERS_ALPHANUM = "[\\d[^\\w]]+"; + private static final String USERNAME_ONLY_ALPHANUM = "[^A-Za-z0-9]"; + + private boolean resolveIntent(Intent intent) { + Uri data = intent.getData(); + mHost = data.getHost(); + + if (data.getScheme().equals("immu")) { + mFromAddress = data.getUserInfo(); + + //remove username non-letters + mFromAddress = mFromAddress.replaceAll(USERNAME_ONLY_ALPHANUM, "").toLowerCase(Locale.ENGLISH); + + String chatRoom = null; + + if (data.getPathSegments().size() > 0) + { + chatRoom = data.getPathSegments().get(0); + + //replace chat room name non-letters with underscores + chatRoom = chatRoom.replaceAll("[\\d[^\\w]]+", "_"); + + mToAddress = chatRoom + '@' + mHost; + + mProviderName = findMatchingProvider(mHost); + + return true; + } + + return false; + + } + + if (data.getScheme().equals("otr-in-band")) { + this.openOtrInBand(data, intent.getType()); + return true; - } + } if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { - log("resolveIntent: host=" + host); + log("resolveIntent: host=" + mHost); } - if (TextUtils.isEmpty(host)) { + if (TextUtils.isEmpty(mHost)) { Set categories = intent.getCategories(); if (categories != null) { Iterator iter = categories.iterator(); @@ -231,13 +421,13 @@ private boolean resolveIntent(Intent intent) { } } } - + mToAddress = data.getSchemeSpecificPart(); } else { - mProviderName = findMatchingProvider(host); + mProviderName = findMatchingProvider(mHost); if (mProviderName == null) { - Log.w(ImApp.LOG_TAG, "resolveIntent: IM provider " + host + " not supported"); + Log.w(ImApp.LOG_TAG, "resolveIntent: IM provider " + mHost + " not supported"); return false; } @@ -270,10 +460,12 @@ private String findMatchingProvider(String provider) { return null; } - if (provider.equalsIgnoreCase("xmpp")) - return Imps.ProviderNames.XMPP; - - return null; +// if (provider.equalsIgnoreCase("xmpp")) + // return Imps.ProviderNames.XMPP; + + + return "Jabber (XMPP)"; + //return Imps.ProviderNames.XMPP; } private boolean isValidToAddress() { @@ -291,27 +483,22 @@ private boolean isValidToAddress() { private static void log(String msg) { Log.d(ImApp.LOG_TAG, " " + msg); } - - @Override - public void onCacheWordUninitialized() { - Log.d(ImApp.LOG_TAG,"cache word uninit"); - - showLockScreen(); - } - + void openMultiUserChat(final Uri data) { - - new AlertDialog.Builder(this) - .setTitle("Join Chat Room?") - .setMessage("An external app is attempting to connect you to a chatroom. Allow?") + + new AlertDialog.Builder(this) + .setTitle(getString(R.string.dialog_connect_chatroom_title)) + .setMessage(getString(R.string.dialog_connect_chatroom_message)) .setPositiveButton(R.string.connect, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Intent intent = new Intent(ImUrlActivity.this, NewChatActivity.class); + Intent intent = new Intent(ImUrlActivity.this, NewChatActivity.class); intent.setData(data); - startActivity(intent); - + ImUrlActivity.this.startActivityForResult(intent, REQUEST_START_MUC); + + dialog.dismiss(); + finish(); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @@ -322,27 +509,365 @@ public void onClick(DialogInterface dialog, int whichButton) { } }) .create().show(); + + + } + + void createNewAccount() { + + String username = getIntent().getData().getUserInfo(); + String appCreateAcct = String.format(getString(R.string.allow_s_to_create_a_new_chat_account_for_s_),username); + + new AlertDialog.Builder(this) + .setTitle(R.string.prompt_create_new_account_) + .setMessage(appCreateAcct) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + + mHandlerRouter.sendEmptyMessage(1); + dialog.dismiss(); + } + }) + .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + finish(); + } + }) + .create().show(); + } + + Handler mHandlerRouter = new Handler () + { + + @Override + public void handleMessage(Message msg) { + + if (msg.what == 1) + { + Uri uriAccountData = getIntent().getData(); + + if (uriAccountData.getScheme().equals("immu")) + { + //need to generate proper IMA url for account setup + String randomJid = ((int)(Math.random()*1000))+""; + String regUser = mFromAddress + randomJid; + String regPass = UUID.randomUUID().toString().substring(0,16); + String regDomain = mHost.replace("conference.", ""); + uriAccountData = Uri.parse("ima://" + regUser + ':' + regPass + '@' + regDomain); + } + + Intent intent = new Intent(ImUrlActivity.this, AccountActivity.class); + intent.setAction(Intent.ACTION_INSERT); + intent.setData(uriAccountData); + startActivityForResult(intent,REQUEST_CREATE_ACCOUNT); + + } + else if (msg.what == 2) + { + doOnCreate(); + } + } + + }; + + void openOtrInBand(final Uri data, final String type) { + + mSendType = getContentResolver().getType(data); - + if (mSendType != null ) { + + mSendUri = data; + startContactPicker(); + return; + } + else if (data.toString().startsWith(OtrDataHandler.URI_PREFIX_OTR_IN_BAND)) + { + String localUrl = data.toString().replaceFirst(OtrDataHandler.URI_PREFIX_OTR_IN_BAND, ""); + FileInfo info = null; + if (TextUtils.equals(data.getAuthority(), "com.android.contacts")) { + info = SystemServices.getContactAsVCardFile(this, data); + } else { + info = SystemServices.getFileInfoFromURI(ImUrlActivity.this, data); + } + if (info != null && !TextUtils.isEmpty(info.path)) { + mSendUri = Uri.fromFile(new File(info.path)); + mSendType = type != null ? type : info.type; + startContactPicker(); + return; + } + } + + Toast.makeText(this, R.string.unsupported_incoming_data, Toast.LENGTH_LONG).show(); + finish(); // make sure not to show this Activity's blank white screen + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_PICK_CONTACTS) { + String username = resultIntent.getExtras().getString(ContactsPickerActivity.EXTRA_RESULT_USERNAME); + long providerId = resultIntent.getExtras().getLong(ContactsPickerActivity.EXTRA_RESULT_PROVIDER); + long accountId = resultIntent.getExtras().getLong(ContactsPickerActivity.EXTRA_RESULT_ACCOUNT); + + sendOtrInBand(username, providerId, accountId); + finish(); + } + else if (requestCode == REQUEST_SIGNIN_ACCOUNT || requestCode == REQUEST_CREATE_ACCOUNT) + { + + mHandlerRouter.postDelayed(new Runnable() + { + @Override + public void run () + { + doOnCreate(); + } + }, 500); + + } + + } else { + finish(); + } + } + + private void sendOtrInBand(String username, long providerId, long accountId) { + + try + { + IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); + mChatSessionManager = conn.getChatSessionManager(); + + IChatSession session = getChatSession(username); + + if (mSendText != null) + session.sendMessage(mSendText); + else if (mSendUri != null && session.getOtrChatSession() != null) + { + + if (session.getOtrChatSession().getChatStatus() != SessionStatus.ENCRYPTED.ordinal()) + { + //can't do OTR transfer + Toast.makeText(this, R.string.err_otr_share_no_encryption, Toast.LENGTH_LONG).show(); + } + else + { + try { + + + String offerId = UUID.randomUUID().toString(); + // Log.i(TAG, "mSendUrl " +mSendUrl); + Uri vfsUri = null; + + if (ChatFileStore.isVfsUri(mSendUri)) + vfsUri = mSendUri; + else + { + InputStream is = getContentResolver().openInputStream(mSendUri); + String fileName = mSendUri.getLastPathSegment(); + FileInfo importInfo = SystemServices.getFileInfoFromURI(this, mSendUri); + + if (importInfo.type != null && importInfo.type.startsWith("image")) + vfsUri = ChatFileStore.resizeAndImportImage(this, session.getId() + "", mSendUri, importInfo.type); + else + vfsUri = ChatFileStore.importContent(session.getId() + "", fileName, is); + + } + + FileInfo info = SystemServices.getFileInfoFromURI(this, vfsUri); + session.offerData(offerId, info.path, mSendType ); + + Imps.insertMessageInDb( + getContentResolver(), false, session.getId(), true, null, vfsUri.toString(), + System.currentTimeMillis(), Imps.MessageType.OUTGOING_ENCRYPTED, // TODO show verified status + 0, offerId, mSendType); + + + } catch (Exception e) { + + Toast.makeText(this, R.string.unable_to_securely_share_this_file, Toast.LENGTH_LONG).show(); + + e.printStackTrace(); + } + } + } + } + catch (RemoteException e) + { + e.printStackTrace(); + } + + } + + private IChatSession getChatSession(String username) { + if (mChatSessionManager != null) { + try { + IChatSession session = mChatSessionManager.getChatSession(username); + + if (session == null) + session = mChatSessionManager.createChatSession(username,false); + + return session; + } catch (RemoteException e) { + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + } + } + return null; + } + + private void startContactPicker() { + + boolean noOnlineConnections = true; + Uri.Builder builder = Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY.buildUpon(); + Collection listConns = ((ImApp)getApplication()).getActiveConnections(); + + for (IImConnection conn : listConns) + { + try + { + if (conn.getState() == ImConnection.LOGGED_IN) + { + try { + mChatSessionManager = conn.getChatSessionManager(); + long mProviderId = conn.getProviderId(); + long mAccountId = conn.getAccountId(); + + ContentUris.appendId(builder, mProviderId); + ContentUris.appendId(builder, mAccountId); + Uri data = builder.build(); + + Intent i = new Intent(Intent.ACTION_PICK, data); + startActivityForResult(i, REQUEST_PICK_CONTACTS); + noOnlineConnections = false; + break; + + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + catch (RemoteException re){} + } + if (noOnlineConnections) { + Toast.makeText(this, R.string.no_connection_for_sending, Toast.LENGTH_LONG).show(); + finish(); // quit this Activity, nothing online + } } void showLockScreen() { Intent intent = new Intent(this, LockScreenActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + // intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra("originalIntent", getIntent()); startActivity(intent); - + finish(); + } - - @Override - public void onCacheWordLocked() { - - showLockScreen(); + private void doOnCreate () + { + Intent intent = getIntent(); + if (Intent.ACTION_INSERT.equals(intent.getAction())) { + if (!resolveInsertIntent(intent)) { + finish(); + return; + } + } else if (Intent.ACTION_SEND.equals(intent.getAction())) { + + Uri streamUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + String mimeType = intent.getType(); + String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); + + if (streamUri != null) + openOtrInBand(streamUri, mimeType); + else if (intent.getData() != null) + openOtrInBand(intent.getData(), mimeType); + else if (sharedText != null) + { + //do nothing for now :( + mSendText = sharedText; + + startContactPicker(); + + } + else + finish(); + + } else if (Intent.ACTION_SENDTO.equals(intent.getAction())) { + if (!resolveIntent(intent)) { + finish(); + return; + } + + if (TextUtils.isEmpty(mToAddress)) { + LogCleaner.warn(ImApp.LOG_TAG, "Invalid to address"); + // finish(); + return; + } + + ImApp mApp = (ImApp)getApplication(); + + if (mApp.serviceConnected()) + handleIntent(); + else + { + mApp.callWhenServiceConnected(new Handler(), new Runnable() { + public void run() { + + handleIntent(); + } + }); + Toast.makeText(ImUrlActivity.this, R.string.starting_the_chatsecure_service_, Toast.LENGTH_LONG).show(); + + } + } else { + finish(); + } + } + + private Cursor initProviderCursor () + { + Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + // uri = uri.buildUpon().appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pkey).build(); + + //just init the contentprovider db + return getContentResolver().query(uri, PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + } + + @Override - public void onCacheWordOpened() { - + protected void onDestroy() { + super.onDestroy(); } + + private static final String[] PROVIDER_PROJECTION = { + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.CATEGORY, + Imps.Provider.ACTIVE_ACCOUNT_ID, + Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + Imps.Provider.ACTIVE_ACCOUNT_PW, + Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + Imps.Provider.ACCOUNT_PRESENCE_STATUS, + Imps.Provider.ACCOUNT_CONNECTION_STATUS + }; + + + static final int PROVIDER_ID_COLUMN = 0; + static final int PROVIDER_NAME_COLUMN = 1; + static final int PROVIDER_FULLNAME_COLUMN = 2; + static final int PROVIDER_CATEGORY_COLUMN = 3; + static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; + static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; + static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; + static final int ACTIVE_ACCOUNT_LOCKED = 7; + static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; + static final int ACCOUNT_PRESENCE_STATUS = 9; + static final int ACCOUNT_CONNECTION_STATUS = 10; } diff --git a/src/info/guardianproject/otr/app/im/app/ImageListAdapter.java b/src/info/guardianproject/otr/app/im/app/ImageListAdapter.java index 8659b3711..9ab9591df 100644 --- a/src/info/guardianproject/otr/app/im/app/ImageListAdapter.java +++ b/src/info/guardianproject/otr/app/im/app/ImageListAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/IntTrie.java b/src/info/guardianproject/otr/app/im/app/IntTrie.java index c8ee83166..6292fe277 100644 --- a/src/info/guardianproject/otr/app/im/app/IntTrie.java +++ b/src/info/guardianproject/otr/app/im/app/IntTrie.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/LockScreenActivity.java b/src/info/guardianproject/otr/app/im/app/LockScreenActivity.java index 3553c878b..bd378afbe 100644 --- a/src/info/guardianproject/otr/app/im/app/LockScreenActivity.java +++ b/src/info/guardianproject/otr/app/im/app/LockScreenActivity.java @@ -1,12 +1,9 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.ICacheWordSubscriber; -import info.guardianproject.otr.app.im.R; - -import java.security.GeneralSecurityException; - +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; @@ -19,17 +16,26 @@ import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; import android.widget.ViewFlipper; -import com.actionbarsherlock.app.SherlockActivity; +import info.guardianproject.cacheword.CacheWordActivityHandler; +import info.guardianproject.cacheword.ICacheWordSubscriber; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.BackgroundBitmapLoaderTask; +import info.guardianproject.util.Languages; + +import java.security.GeneralSecurityException; -public class LockScreenActivity extends SherlockActivity implements ICacheWordSubscriber { +public class LockScreenActivity extends ThemeableActivity implements ICacheWordSubscriber { private static final String TAG = "LockScreenActivity"; private final static int MIN_PASS_LENGTH = 4; @@ -41,26 +47,35 @@ public class LockScreenActivity extends SherlockActivity implements ICacheWordSu private EditText mConfirmNewPassphrase; private View mViewCreatePassphrase; private View mViewEnterPassphrase; - private Button mBtnOpen; + private ImageButton mLanguageButton; + private CacheWordActivityHandler mCacheWord; private String mPasswordError; private TwoViewSlider mSlider; + private ImApp mApp; + private Button mBtnCreate; + private Button mBtnSkip; + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - getSherlock().getActionBar().hide(); - + + mApp = (ImApp)getApplication(); + + getSupportActionBar().hide(); + setContentView(R.layout.activity_lock_screen); - - mCacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); - + + mCacheWord = new CacheWordActivityHandler(mApp, (ICacheWordSubscriber)this); + mViewCreatePassphrase = findViewById(R.id.llCreatePassphrase); mViewEnterPassphrase = findViewById(R.id.llEnterPassphrase); mEnterPassphrase = (EditText) findViewById(R.id.editEnterPassphrase); + mNewPassphrase = (EditText) findViewById(R.id.editNewPassphrase); mConfirmNewPassphrase = (EditText) findViewById(R.id.editConfirmNewPassphrase); ViewFlipper vf = (ViewFlipper) findViewById(R.id.viewFlipper1); @@ -69,29 +84,56 @@ protected void onCreate(Bundle savedInstanceState) mSlider = new TwoViewSlider(vf, flipView1, flipView2, mNewPassphrase, mConfirmNewPassphrase); + // set up language chooser button + mLanguageButton = (ImageButton) findViewById(R.id.languageButton); + mLanguageButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Activity activity = LockScreenActivity.this; + final Languages languages = Languages.get(activity); + final ArrayAdapter languagesAdapter = new ArrayAdapter(activity, + android.R.layout.simple_list_item_single_choice, languages.getAllNames()); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setIcon(R.drawable.ic_settings_language); + builder.setTitle(R.string.KEY_PREF_LANGUAGE_TITLE); + builder.setAdapter(languagesAdapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int position) { + Log.i(TAG, "onItemClick: " + dialog + " " + position); + String[] languageCodes = languages.getSupportedLocales(); + resetLanguage(languageCodes[position]); + dialog.dismiss(); + } + }); + builder.show(); + } + }); + } @Override protected void onPause() { super.onPause(); - try { mCacheWord.onPause();} - catch (Exception e){} + mCacheWord.onPause(); + } @Override protected void onResume() { super.onResume(); - - try - { - mCacheWord.onResume(); - } - catch (Exception e){} + mCacheWord.onResume(); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + mCacheWord.disconnect(); } @Override public void onBackPressed() { - + //do nothing! } @@ -121,10 +163,47 @@ private boolean isPasswordValid() { return validatePassword(mNewPassphrase.getText().toString().toCharArray()); } + private boolean isPasswordFieldEmpty() { + return mNewPassphrase.getText().toString().length() == 0; + } + private boolean isConfirmationFieldEmpty() { return mConfirmNewPassphrase.getText().toString().length() == 0; } + private void initializeWithPassphrase() { + try { + String passphrase = mNewPassphrase.getText().toString(); + if (passphrase.isEmpty()) { + // Create DB with empty passphrase + if (Imps.setEmptyPassphrase(this, false)) { + + try + { + ChatFileStore.initWithoutPassword(this); + + // Simulate cacheword opening + afterCacheWordOpened(); + } + catch (Exception e) + { + Log.d(ImApp.LOG_TAG,"unable to mount VFS store"); //but let's not crash the whole app right now + } + + ChatFileStore.initWithoutPassword(this); + + } else { + // TODO failed + } + } else { + mCacheWord.setPassphrase(passphrase.toCharArray()); + } + } catch (Exception e) { + // TODO initialization failed + Log.e(TAG, "Cacheword pass initialization failed: " + e.getMessage()); + } + } + private void initializePassphrase() { // Passphrase is not set, so allow the user to create one @@ -141,7 +220,9 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (!isPasswordValid()) showValidationError(); - else + else if (isPasswordFieldEmpty()) { + initializeWithPassphrase(); + } else mSlider.showConfirmationField(); } return false; @@ -164,57 +245,53 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) } }); - Button btnCreate = (Button) findViewById(R.id.btnCreate); - btnCreate.setOnClickListener(new OnClickListener() + mBtnCreate = (Button) findViewById(R.id.btnCreate); + mBtnCreate.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { // validate if (!isPasswordValid()) { showValidationError(); mSlider.showNewPasswordField(); - } else if (isConfirmationFieldEmpty()) { + } else if (isConfirmationFieldEmpty() && !isPasswordFieldEmpty()) { + mBtnSkip.setVisibility(View.GONE); mSlider.showConfirmationField(); - } else if (!newEqualsConfirmation()) { + mBtnCreate.setText(R.string.lock_screen_confirm_passphrase); + } + else if (!newEqualsConfirmation()) { showInequalityError(); - mSlider.showNewPasswordField(); - } else { - try { - mCacheWord.setPassphrase(mNewPassphrase.getText().toString().toCharArray()); - } catch (GeneralSecurityException e) { - // TODO initialization failed - Log.e(TAG, "Cacheword pass initialization failed: " + e.getMessage()); - } + } + else if (!isConfirmationFieldEmpty() && !isPasswordFieldEmpty()) + { + initializeWithPassphrase(); } } }); - } - private void promptPassphrase() { - mViewCreatePassphrase.setVisibility(View.GONE); - mViewEnterPassphrase.setVisibility(View.VISIBLE); - mBtnOpen = (Button) findViewById(R.id.btnOpen); - mBtnOpen.setOnClickListener(new OnClickListener() - { + + mBtnSkip = (Button)findViewById(R.id.btnSkip); + mBtnSkip.setOnClickListener(new OnClickListener(){ + + @Override public void onClick(View v) { - if (mEnterPassphrase.getText().toString().length() == 0) - return; - // Check passphrase - try { - mCacheWord.setPassphrase(mEnterPassphrase.getText().toString().toCharArray()); - } catch (GeneralSecurityException e) { - mEnterPassphrase.setText(""); - // TODO implement try again and wipe if fail - Log.e(TAG, "Cacheword pass verification failed: " + e.getMessage()); - return; - } + if (isPasswordFieldEmpty()) + initializeWithPassphrase(); + } }); + } + + private void promptPassphrase() { + mViewCreatePassphrase.setVisibility(View.GONE); + mViewEnterPassphrase.setVisibility(View.VISIBLE); mEnterPassphrase.setOnEditorActionListener(new OnEditorActionListener() { + @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_GO) @@ -228,7 +305,22 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) protected void onReceiveResult(int resultCode, Bundle resultData) { super.onReceiveResult(resultCode, resultData); - mBtnOpen.performClick(); + + if (mEnterPassphrase.getText().toString().length() == 0) + return; + // Check passphrase + try { + char[] passphrase = mEnterPassphrase.getText().toString().toCharArray(); + + mCacheWord.setPassphrase(passphrase); + ImApp.mUsingCacheword = true; + } catch (Exception e) { + mEnterPassphrase.setText(""); + // TODO implement try again and wipe if fail + Log.e(TAG, "Cacheword pass verification failed: " + e.getMessage()); + return; + } + } }); return true; @@ -240,44 +332,14 @@ protected void onReceiveResult(int resultCode, Bundle resultData) private boolean validatePassword(char[] pass) { - boolean upper = false; - boolean lower = false; - boolean number = false; - for (char c : pass) { - if (Character.isUpperCase(c)) { - upper = true; - } else if (Character.isLowerCase(c)) { - lower = true; - } else if (Character.isDigit(c)) { - number = true; - } - } - if (pass.length < MIN_PASS_LENGTH) + if (pass.length < MIN_PASS_LENGTH && pass.length != 0) { // should we support some user string message here? mPasswordError = getString(R.string.pass_err_length); return false; } - //only enforce password length - /* - else if (!upper) - { - mPasswordError = getString(R.string.pass_err_upper); - return false; - } - else if (!lower) - { - mPasswordError = getString(R.string.pass_err_lower); - return false; - } - else if (!number) - { - mPasswordError = getString(R.string.pass_err_num); - return false; - }*/ - // if it got here, then must be okay - // hopefully the user can remember it + return true; } @@ -343,29 +405,64 @@ private void flip() { @Override public void onCacheWordUninitialized() { + + Intent intentOrig; + + if ((intentOrig = getIntent().getParcelableExtra("originalIntent"))!=null) + { + if (intentOrig.getData() != null) + { + if (intentOrig.getData().getScheme().equals("immu")|| + intentOrig.getData().getScheme().equals("ima")) + { + + initializeWithPassphrase(); + return; + } + } + } + + initializePassphrase(); - + } @Override public void onCacheWordLocked() { promptPassphrase(); - } @Override public void onCacheWordOpened() { + afterCacheWordOpened(); + ChatFileStore.init(this, mCacheWord.getEncryptionKey()); + } + + /** + * + */ + private void afterCacheWordOpened() { Intent intent = (Intent) getIntent().getParcelableExtra("originalIntent"); - + if (intent != null) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - startActivity(intent); + getIntent().removeExtra("originalIntent"); finish(); - LockScreenActivity.this.overridePendingTransition(0, 0); +// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); + // LockScreenActivity.this.overridePendingTransition(0, 0); } } - + private void resetLanguage(String language) { + ((ImApp) getApplication()).setNewLocale(this, language); + Intent intent = getIntent(); + intent.setClass(LockScreenActivity.this, LockScreenActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + finish(); + overridePendingTransition(0, 0); + startActivity(intent); + overridePendingTransition(0, 0); + } } \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/app/Markup.java b/src/info/guardianproject/otr/app/im/app/Markup.java index 7c3412ff1..2e66d7845 100644 --- a/src/info/guardianproject/otr/app/im/app/Markup.java +++ b/src/info/guardianproject/otr/app/im/app/Markup.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/MessageView.java b/src/info/guardianproject/otr/app/im/app/MessageView.java index 5dbdccd51..ca6f4fd8d 100644 --- a/src/info/guardianproject/otr/app/im/app/MessageView.java +++ b/src/info/guardianproject/otr/app/im/app/MessageView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -19,308 +19,799 @@ import info.guardianproject.emoji.EmojiManager; import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.engine.Presence; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.ui.ImageViewActivity; +import info.guardianproject.otr.app.im.ui.LetterAvatar; +import info.guardianproject.otr.app.im.ui.RoundedAvatarDrawable; +import info.guardianproject.util.AudioPlayer; +import info.guardianproject.util.LinkifyHelper; +import info.guardianproject.util.LogCleaner; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.provider.Browser; +import android.provider.MediaStore; import android.support.v4.util.LruCache; import android.text.Spannable; import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.text.style.ImageSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.AttributeSet; -import android.view.Gravity; +import android.util.Log; import android.view.View; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; + +public class MessageView extends FrameLayout { + + private static int sCacheSize = 10; // 1MiB + private static LruCache mBitmapCache = new LruCache(sCacheSize); -public class MessageView extends LinearLayout { public enum DeliveryState { NEUTRAL, DELIVERED, UNDELIVERED } public enum EncryptionState { NONE, ENCRYPTED, ENCRYPTED_AND_VERIFIED - + } - private View mMessageContainer; - private TextView mTextViewForMessages; - private TextView mTextViewForTimestamp; - - private ImageView mDeliveryIcon; - private Resources mResources; - private ImageView mAvatarLeft; - private ImageView mAvatarRight; - private CharSequence lastMessage = null; - - private static final int cacheSize = 10; // 4MiB - private static LruCache bitmapCache = new LruCache(cacheSize); - private static Drawable mAvatarUnknown; - + private Context context; + private boolean linkify = false; + public MessageView(Context context, AttributeSet attrs) { super(context, attrs); - - if (mAvatarUnknown == null) - mAvatarUnknown = context.getResources().getDrawable(R.drawable.avatar_unknown); + this.context = context; + } + + private ViewHolder mHolder = null; + + private final static DateFormat MESSAGE_DATETIME_FORMAT = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + private final static DateFormat MESSAGE_TIME_FORMAT = SimpleDateFormat.getTimeInstance(DateFormat.SHORT); + private static final SimpleDateFormat FMT_SAME_DAY = new SimpleDateFormat("yyyyMMdd"); + + private final static Date DATE_NOW = new Date(); + + private final static char DELIVERED_SUCCESS = '\u2714'; + private final static char DELIVERED_FAIL = '\u2718'; + private final static String LOCK_CHAR = "Secure"; + + + class ViewHolder + { + TextView mTextViewForMessages = (TextView) findViewById(R.id.message); + TextView mTextViewForTimestamp = (TextView) findViewById(R.id.messagets); + ImageView mAvatar = (ImageView) findViewById(R.id.avatar); + // View mStatusBlock = findViewById(R.id.status_block); + ImageView mMediaThumbnail = (ImageView) findViewById(R.id.media_thumbnail); + View mContainer = findViewById(R.id.message_container); + // save the media uri while the MediaScanner is creating the thumbnail + // if the holder was reused, the pair is broken + Uri mMediaUri = null; + + ViewHolder() { + // disable built-in autoLink so we can add custom ones + mTextViewForMessages.setAutoLinkMask(0); + } + + public void setOnClickListenerMediaThumbnail( final String mimeType, final Uri mediaUri ) { + mMediaThumbnail.setOnClickListener( new OnClickListener() { + @Override + public void onClick(View v) { + onClickMediaIcon( mimeType, mediaUri ); + } + }); + mMediaThumbnail.setOnLongClickListener( new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + onLongClickMediaIcon( mimeType, mediaUri ); + return false; + } + }); + } + + public void resetOnClickListenerMediaThumbnail() { + mMediaThumbnail.setOnClickListener( null ); + } + + long mTimeDiff = -1; + } + + /** + * This trickery is needed in order to have clickable links that open things + * in a new {@code Task} rather than in ChatSecure's {@code Task.} Thanks to @commonsware + * https://stackoverflow.com/a/11417498 + * + */ + class NewTaskUrlSpan extends ClickableSpan { + + private String urlString; + + NewTaskUrlSpan(String urlString) { + this.urlString = urlString; + } + + @Override + public void onClick(View widget) { + Uri uri = Uri.parse(urlString); + Context context = widget.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } + + class URLSpanConverter implements LinkifyHelper.SpanConverter { + @Override + public NewTaskUrlSpan convert(URLSpan span) { + return (new NewTaskUrlSpan(span.getURL())); + } } @Override protected void onFinishInflate() { super.onFinishInflate(); - mMessageContainer = findViewById (R.id.message_container); - mTextViewForMessages = (TextView) findViewById(R.id.message); - mTextViewForTimestamp = (TextView) findViewById(R.id.messagets); - mDeliveryIcon = (ImageView) findViewById(R.id.iconView); - mAvatarLeft = (ImageView) findViewById(R.id.avatar_left); - mAvatarRight = (ImageView) findViewById(R.id.avatar_right); - - mResources = getResources(); - + mHolder = (ViewHolder)getTag(); + + if (mHolder == null) + { + mHolder = new ViewHolder(); + setTag(mHolder); + + } + } + + public void setLinkify(boolean linkify) { + this.linkify = linkify; + } + + public void setMessageBackground (Drawable d) { + mHolder.mContainer.setBackgroundDrawable(d); } public URLSpan[] getMessageLinks() { - return mTextViewForMessages.getUrls(); + return mHolder.mTextViewForMessages.getUrls(); } - + public String getLastMessage () { return lastMessage.toString(); } - public void bindIncomingMessage(String address, String nickname, String body, Date date, Markup smileyRes, - boolean scrolling, EncryptionState encryption, boolean showContact) { - - ListView.LayoutParams lp = new ListView.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - setGravity(Gravity.LEFT); - setLayoutParams(lp); - setPadding(3,0,100,3); - - showAvatar(address,true); - - if (showContact) + + public void bindIncomingMessage(int id, int messageType, String address, String nickname, final String mimeType, final String body, Date date, Markup smileyRes, + boolean scrolling, EncryptionState encryption, boolean showContact, int presenceStatus) { + + mHolder = (ViewHolder)getTag(); + + mHolder.mTextViewForMessages.setVisibility(View.VISIBLE); + + if (nickname == null) + nickname = address; + + if (showContact && nickname != null) { - String[] nickParts = nickname.split("/"); - - lastMessage = nickParts[nickParts.length-1] + ": " + formatMessage(body); - + lastMessage = nickname + ": " + formatMessage(body); + showAvatar(address,nickname,true,presenceStatus); } else { lastMessage = formatMessage(body); - } - + showAvatar(address,nickname,true,presenceStatus); + + mHolder.resetOnClickListenerMediaThumbnail(); + if( mimeType != null ) { + + mHolder.mTextViewForMessages.setVisibility(View.GONE); + mHolder.mMediaThumbnail.setVisibility(View.VISIBLE); + + Uri mediaUri = Uri.parse( body ) ; + lastMessage = ""; + showMediaThumbnail(mimeType, mediaUri, id, mHolder); + + } else { + mHolder.mMediaThumbnail.setVisibility(View.GONE); + if (showContact) + { + String[] nickParts = nickname.split("/"); + + lastMessage = nickParts[nickParts.length-1] + ": " + formatMessage(body); + + } + else + { + lastMessage = formatMessage(body); + } + } + } + if (lastMessage.length() > 0) { try { SpannableString spannablecontent=new SpannableString(lastMessage); EmojiManager.getInstance(getContext()).addEmoji(getContext(), spannablecontent); - - mTextViewForMessages.setText(spannablecontent); + + mHolder.mTextViewForMessages.setText(spannablecontent); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + LogCleaner.error(ImApp.LOG_TAG, "error processing message", e); } } else { - mTextViewForMessages.setText(lastMessage); + mHolder.mTextViewForMessages.setText(lastMessage); } - - - mDeliveryIcon.setVisibility(INVISIBLE); - + + if (date != null) { - CharSequence tsText = formatTimeStamp(date); - - mTextViewForTimestamp.setText(tsText); - mTextViewForTimestamp.setGravity(Gravity.CENTER); - mTextViewForTimestamp.setVisibility(View.VISIBLE); - + CharSequence tsText = null; + + if (isSameDay(date,DATE_NOW)) + tsText = formatTimeStamp(date,messageType,MESSAGE_TIME_FORMAT, null, encryption); + else + tsText = formatTimeStamp(date,messageType,MESSAGE_DATETIME_FORMAT, null, encryption); + + mHolder.mTextViewForTimestamp.setText(tsText); + mHolder.mTextViewForTimestamp.setVisibility(View.VISIBLE); + } else { - - mTextViewForTimestamp.setText(""); - mTextViewForTimestamp.setVisibility(View.GONE); - + + mHolder.mTextViewForTimestamp.setText(""); + //mHolder.mTextViewForTimestamp.setVisibility(View.GONE); + } - - if (encryption == EncryptionState.NONE) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_plaintext); - else if (encryption == EncryptionState.ENCRYPTED) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_encrypted); - else if (encryption == EncryptionState.ENCRYPTED_AND_VERIFIED) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_verified); - - mTextViewForMessages.setTextColor(getResources().getColor(R.color.incoming_message_fg)); - + if (linkify) + LinkifyHelper.addLinks(mHolder.mTextViewForMessages, new URLSpanConverter()); + LinkifyHelper.addTorSafeLinks(mHolder.mTextViewForMessages); + } + + private void showMediaThumbnail (String mimeType, Uri mediaUri, int id, ViewHolder holder) + { + /* Guess the MIME type in case we received a file that we can display or play*/ + if (TextUtils.isEmpty(mimeType) || mimeType.startsWith("application")) { + String guessed = URLConnection.guessContentTypeFromName(mediaUri.toString()); + if (!TextUtils.isEmpty(guessed)) { + if (TextUtils.equals(guessed, "video/3gpp")) + mimeType = "audio/3gpp"; + else + mimeType = guessed; + } + } + holder.setOnClickListenerMediaThumbnail(mimeType, mediaUri); + + holder.mMediaThumbnail.setVisibility(View.VISIBLE); + holder.mTextViewForMessages.setText(lastMessage); + holder.mTextViewForMessages.setVisibility(View.GONE); + + if( mimeType.startsWith("image/") ) { + setImageThumbnail( getContext().getContentResolver(), id, holder, mediaUri ); + holder.mMediaThumbnail.setBackgroundColor(Color.TRANSPARENT); + // holder.mMediaThumbnail.setBackgroundColor(Color.WHITE); + + } + else if (mimeType.startsWith("audio")) + { + holder.mMediaThumbnail.setImageResource(R.drawable.media_audio_play); + holder.mMediaThumbnail.setBackgroundColor(Color.TRANSPARENT); + } + else + { + holder.mMediaThumbnail.setImageResource(R.drawable.ic_file); // generic file icon + + } + + holder.mContainer.setBackgroundColor(getResources().getColor(android.R.color.transparent)); + + } - private String formatMessage (String body) + + private boolean isSameDay (Date date1, Date date2) { - return android.text.Html.fromHtml(body).toString(); + return FMT_SAME_DAY.format(date1).equals(FMT_SAME_DAY.format(date2)); } - - public void bindOutgoingMessage(String address, String body, Date date, Markup smileyRes, boolean scrolling, - DeliveryState delivery, EncryptionState encryption) { - - - - ListView.LayoutParams lp = new ListView.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - // lp.setMargins(3,3,100,3); - setLayoutParams(lp); - setGravity(Gravity.RIGHT); - setPadding(100, 0, 3, 3); - - showAvatar(address,false); + protected String convertMediaUriToPath(Uri uri) { + String path = null; + + String [] proj={MediaStore.Images.Media.DATA}; + Cursor cursor = getContext().getContentResolver().query(uri, proj, null, null, null); + if (cursor != null && (!cursor.isClosed())) + { + if (cursor.isBeforeFirst()) + { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + path = cursor.getString(column_index); + } + + cursor.close(); + } + + return path; + } + + private MediaPlayer mMediaPlayer = null; + + /** + * @param mimeType + * @param body + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + protected void onClickMediaIcon(String mimeType, Uri mediaUri) { + + if (ChatFileStore.isVfsUri(mediaUri)) { + if (mimeType.startsWith("image")) { + Intent intent = new Intent(context, ImageViewActivity.class); + intent.putExtra( ImageViewActivity.FILENAME, mediaUri.getPath()); + context.startActivity(intent); + return; + } + if (mimeType.startsWith("audio")) { + new AudioPlayer(getContext(), mediaUri.getPath(), mimeType).play(); + return; + } + return; + } + else + { + + + String body = convertMediaUriToPath(mediaUri); + + if (body == null) + body = new File(mediaUri.getPath()).getAbsolutePath(); + + if (mimeType.startsWith("audio") || (body.endsWith("3gp")||body.endsWith("3gpp")||body.endsWith("amr"))) + { + + if (mMediaPlayer != null) + mMediaPlayer.release(); + + try + { + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setDataSource(body); + mMediaPlayer.prepare(); + mMediaPlayer.start(); + + return; + } catch (IOException e) { + Log.e(ImApp.LOG_TAG,"error playing audio: " + body,e); + } + + + } + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= 11) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + + //set a general mime type not specific + intent.setDataAndType(Uri.parse( body ), mimeType); + + Context context = getContext().getApplicationContext(); + + if (isIntentAvailable(context,intent)) + { + context.startActivity(intent); + } + else + { + Toast.makeText(getContext(), R.string.there_is_no_viewer_available_for_this_file_format, Toast.LENGTH_LONG).show(); + } + } + } + + protected void onLongClickMediaIcon(final String mimeType, final Uri mediaUri) { + + final java.io.File exportPath = ChatFileStore.exportPath(mimeType, mediaUri); + + new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.export_media)) + .setMessage(context.getString(R.string.export_media_file_to, exportPath.getAbsolutePath())) + .setPositiveButton(R.string.export, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + try { + ChatFileStore.exportContent(mimeType, mediaUri, exportPath); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(exportPath)); + shareIntent.setType(mimeType); + context.startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.export_media))); + } catch (IOException e) { + Toast.makeText(getContext(), "Export Failed " + e.getMessage(), Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + return; + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + return; + } + }) + .create().show(); + } + + public static boolean isIntentAvailable(Context context, Intent intent) { + final PackageManager packageManager = context.getPackageManager(); + List list = + packageManager.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + + + /** + * @param contentResolver + * @param id + * @param aHolder + * @param mediaUri + */ + private void setImageThumbnail(final ContentResolver contentResolver, final int id, final ViewHolder aHolder, final Uri mediaUri) { + // pair this holder to the uri. if the holder is recycled, the pairing is broken + aHolder.mMediaUri = mediaUri; + // if a content uri - already scanned + + setThumbnail(contentResolver, aHolder, mediaUri); + + + } + + /** + * @param contentResolver + * @param aHolder + * @param uri + */ + private void setThumbnail(final ContentResolver contentResolver, final ViewHolder aHolder, final Uri uri) { + new AsyncTask() { + + @Override + protected Bitmap doInBackground(String... params) { + + Bitmap result = mBitmapCache.get(uri.toString()); + + if (result == null) + return getThumbnail( contentResolver, uri ); + else + return result; + } + + @Override + protected void onPostExecute(Bitmap result) { + + if (uri != null && result != null) + { + mBitmapCache.put(uri.toString(), result); + + // confirm the holder is still paired to this uri + if( ! uri.equals( aHolder.mMediaUri ) ) { + return ; + } + // set the thumbnail + aHolder.mMediaThumbnail.setImageBitmap(result); + } + } + }.execute(); + } + + public final static int THUMBNAIL_SIZE_DEFAULT = 400; + + public static Bitmap getThumbnail(ContentResolver cr, Uri uri) { + // Log.e( MessageView.class.getSimpleName(), "getThumbnail uri:" + uri); + if (ChatFileStore.isVfsUri(uri)) { + return ChatFileStore.getThumbnailVfs(uri, THUMBNAIL_SIZE_DEFAULT); + } + return getThumbnailFile(cr, uri, THUMBNAIL_SIZE_DEFAULT); + } + + public static Bitmap getThumbnailFile(ContentResolver cr, Uri uri, int thumbnailSize) { + + + try + { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inInputShareable = true; + options.inPurgeable = true; - - lastMessage = body;//formatMessage(body); - - try { - SpannableString spannablecontent=new SpannableString(lastMessage); - - EmojiManager.getInstance(getContext()).addEmoji(getContext(), spannablecontent); - - mTextViewForMessages.setText(spannablecontent); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + InputStream is = cr.openInputStream(uri); + BitmapFactory.decodeStream(is, null, options); + if ((options.outWidth == -1) || (options.outHeight == -1)) + return null; + + int originalSize = (options.outHeight > options.outWidth) ? options.outHeight + : options.outWidth; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = originalSize / thumbnailSize; + + is = cr.openInputStream(uri); - if (delivery == DeliveryState.DELIVERED) { - mDeliveryIcon.setImageResource(R.drawable.ic_chat_msg_status_ok); - mDeliveryIcon.setVisibility(VISIBLE); - } else if (delivery == DeliveryState.UNDELIVERED) { - mDeliveryIcon.setImageResource(R.drawable.ic_chat_msg_status_failed); - mDeliveryIcon.setVisibility(VISIBLE); + Bitmap scaledBitmap = BitmapFactory.decodeStream(is, null, options); + + return scaledBitmap; + } + catch (Exception e) + { + Log.d(ImApp.LOG_TAG,"could not getThumbnailFile",e); + return null; + } + } + + private String formatMessage (String body) + { + if (body != null) + return android.text.Html.fromHtml(body).toString(); + else + return null; + } + + public void bindOutgoingMessage(int id, int messageType, String address, final String mimeType, final String body, Date date, Markup smileyRes, boolean scrolling, + DeliveryState delivery, EncryptionState encryption) { + + mHolder = (ViewHolder)getTag(); + + mHolder.mTextViewForMessages.setVisibility(View.VISIBLE); + mHolder.resetOnClickListenerMediaThumbnail(); + if( mimeType != null ) { + + lastMessage = ""; + Uri mediaUri = Uri.parse( body ) ; + + showMediaThumbnail(mimeType, mediaUri, id, mHolder); + + + mHolder.mTextViewForMessages.setVisibility(View.GONE); + mHolder.mMediaThumbnail.setVisibility(View.VISIBLE); + } else { - mDeliveryIcon.setVisibility(GONE); + mHolder.mMediaThumbnail.setVisibility(View.GONE); + lastMessage = body;//formatMessage(body); + + try { + + SpannableString spannablecontent=new SpannableString(lastMessage); + + EmojiManager.getInstance(getContext()).addEmoji(getContext(), spannablecontent); + + mHolder.mTextViewForMessages.setText(spannablecontent); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - if (date != null) { - mTextViewForTimestamp.setText(formatTimeStamp(date)); - mTextViewForTimestamp.setGravity(Gravity.CENTER); - mTextViewForTimestamp.setVisibility(View.VISIBLE); - mTextViewForTimestamp.setPadding(0,0,0,12); + + CharSequence tsText = null; + + if (isSameDay(date,DATE_NOW)) + tsText = formatTimeStamp(date,messageType, MESSAGE_TIME_FORMAT, delivery, encryption); + else + tsText = formatTimeStamp(date,messageType, MESSAGE_DATETIME_FORMAT, delivery, encryption); + + mHolder.mTextViewForTimestamp.setText(tsText); + + mHolder.mTextViewForTimestamp.setVisibility(View.VISIBLE); } else { - mTextViewForTimestamp.setText(""); - mTextViewForTimestamp.setVisibility(View.GONE); - mTextViewForTimestamp.setPadding(0,0,0,0); + mHolder.mTextViewForTimestamp.setText(""); } - - if (encryption == EncryptionState.NONE) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_plaintext); - else if (encryption == EncryptionState.ENCRYPTED) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_encrypted); - else if (encryption == EncryptionState.ENCRYPTED_AND_VERIFIED) - mMessageContainer.setBackgroundResource(R.color.incoming_message_bg_verified); - - mTextViewForMessages.setTextColor(getResources().getColor(R.color.outgoing_message_fg)); + if (linkify) + LinkifyHelper.addLinks(mHolder.mTextViewForMessages, new URLSpanConverter()); + LinkifyHelper.addTorSafeLinks(mHolder.mTextViewForMessages); } - private void showAvatar (String address, boolean isLeft) + private void showAvatar (String address, String nickname, boolean isLeft, int presenceStatus) { + if (mHolder.mAvatar == null) + return; - mAvatarLeft.setVisibility(View.GONE); - mAvatarRight.setVisibility(View.GONE); - - if (address != null) + mHolder.mAvatar.setVisibility(View.GONE); + + if (address != null && isLeft) { - Drawable avatar = ContactView.getAvatar(address); - - if (avatar == null) - { - avatar = mAvatarUnknown; - - } - - - if (isLeft) + + RoundedAvatarDrawable avatar = null; + + try { avatar = DatabaseUtils.getAvatarFromAddress(this.getContext().getContentResolver(),XmppAddress.stripResource(address), ImApp.DEFAULT_AVATAR_WIDTH,ImApp.DEFAULT_AVATAR_HEIGHT);} + catch (Exception e){} + + if (avatar != null) { - mAvatarLeft.setVisibility(View.VISIBLE); - mAvatarLeft.setImageDrawable(avatar); + mHolder.mAvatar.setVisibility(View.VISIBLE); + mHolder.mAvatar.setImageDrawable(avatar); + + setAvatarBorder(presenceStatus, avatar); + } else { - mAvatarRight.setVisibility(View.VISIBLE); - mAvatarRight.setImageDrawable(avatar); + int color = getAvatarBorder(presenceStatus); + int padding = 16; + LetterAvatar lavatar = new LetterAvatar(getContext(), color, nickname.substring(0,1).toUpperCase(), padding); + + mHolder.mAvatar.setVisibility(View.VISIBLE); + mHolder.mAvatar.setImageDrawable(lavatar); } - } + } + } + + public int getAvatarBorder(int status) { + switch (status) { + case Presence.AVAILABLE: + return (getResources().getColor(R.color.holo_green_light)); + + case Presence.IDLE: + return (getResources().getColor(R.color.holo_green_dark)); + case Presence.AWAY: + return (getResources().getColor(R.color.holo_orange_light)); + + case Presence.DO_NOT_DISTURB: + return(getResources().getColor(R.color.holo_red_dark)); + + case Presence.OFFLINE: + return(getResources().getColor(R.color.holo_grey_dark)); + + default: + } + + return Color.TRANSPARENT; } + public void bindPresenceMessage(String contact, int type, boolean isGroupChat, boolean scrolling) { + + mHolder = (ViewHolder)getTag(); + CharSequence message = formatPresenceUpdates(contact, type, isGroupChat, scrolling); - mTextViewForMessages.setText(message); - mTextViewForMessages.setTextColor(mResources.getColor(R.color.chat_msg_presence)); - mDeliveryIcon.setVisibility(INVISIBLE); + mHolder.mTextViewForMessages.setText(message); + // mHolder.mTextViewForMessages.setTextColor(getResources().getColor(R.color.chat_msg_presence)); + } public void bindErrorMessage(int errCode) { - mTextViewForMessages.setText(R.string.msg_sent_failed); - mTextViewForMessages.setTextColor(mResources.getColor(R.color.error)); - mDeliveryIcon.setVisibility(INVISIBLE); + + mHolder = (ViewHolder)getTag(); + + mHolder.mTextViewForMessages.setText(R.string.msg_sent_failed); + mHolder.mTextViewForMessages.setTextColor(getResources().getColor(R.color.error)); + } - + private SpannableString formatTimeStamp(Date date, int messageType, DateFormat format, MessageView.DeliveryState delivery, EncryptionState encryptionState) { + + + StringBuilder deliveryText = new StringBuilder(); + deliveryText.append(format.format(date)); + deliveryText.append(' '); + + if (delivery != null) + { + //this is for delivery + if (delivery == DeliveryState.DELIVERED) { + + deliveryText.append(DELIVERED_SUCCESS); + + } else if (delivery == DeliveryState.UNDELIVERED) { - private SpannableString formatTimeStamp(Date date) { - // DateFormat format = new SimpleDateFormat(mResources.getString(R.string.time_stamp)); + deliveryText.append(DELIVERED_FAIL); + } + } - DateFormat format = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - String dateStr = format.format(date); - SpannableString spanText = new SpannableString(dateStr); - int len = spanText.length(); - spanText.setSpan(new StyleSpan(Typeface.ITALIC), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - spanText.setSpan(new RelativeSizeSpan(0.8f), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - spanText.setSpan(new ForegroundColorSpan(mResources.getColor(android.R.color.darker_gray)), - 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - + if (messageType != Imps.MessageType.POSTPONED) + deliveryText.append(DELIVERED_SUCCESS);//this is for sent, so we know show 2 checks like WhatsApp! + + SpannableString spanText = null; + + if (encryptionState == EncryptionState.ENCRYPTED) + { + deliveryText.append('X'); + spanText = new SpannableString(deliveryText.toString()); + int len = spanText.length(); + + spanText.setSpan(new ImageSpan(getContext(), R.drawable.lock16), len-1,len,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + } + else if (encryptionState == EncryptionState.ENCRYPTED_AND_VERIFIED) + { + deliveryText.append('X'); + spanText = new SpannableString(deliveryText.toString()); + int len = spanText.length(); + + spanText.setSpan(new ImageSpan(getContext(), R.drawable.lock16), len-1,len,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + } + else + { + spanText = new SpannableString(deliveryText.toString()); + int len = spanText.length(); + + } + + // spanText.setSpan(new StyleSpan(Typeface.SANS_SERIF), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + // spanText.setSpan(new RelativeSizeSpan(0.8f), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + // spanText.setSpan(new ForegroundColorSpan(R.color.soft_grey), + // 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return spanText; } private CharSequence formatPresenceUpdates(String contact, int type, boolean isGroupChat, boolean scrolling) { String body; + + Resources resources =getResources(); + switch (type) { case Imps.MessageType.PRESENCE_AVAILABLE: - body = mResources.getString(isGroupChat ? R.string.contact_joined + body = resources.getString(isGroupChat ? R.string.contact_joined : R.string.contact_online, contact); break; case Imps.MessageType.PRESENCE_AWAY: - body = mResources.getString(R.string.contact_away, contact); + body = resources.getString(R.string.contact_away, contact); break; case Imps.MessageType.PRESENCE_DND: - body = mResources.getString(R.string.contact_busy, contact); + body = resources.getString(R.string.contact_busy, contact); break; case Imps.MessageType.PRESENCE_UNAVAILABLE: - body = mResources.getString(isGroupChat ? R.string.contact_left + body = resources.getString(isGroupChat ? R.string.contact_left : R.string.contact_offline, contact); break; @@ -340,4 +831,38 @@ private CharSequence formatPresenceUpdates(String contact, int type, boolean isG return spanText; } } + + public void setAvatarBorder(int status, RoundedAvatarDrawable avatar) { + switch (status) { + case Presence.AVAILABLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_light)); + avatar.setAlpha(255); + break; + + case Presence.IDLE: + avatar.setBorderColor(getResources().getColor(R.color.holo_green_dark)); + avatar.setAlpha(255); + + break; + + case Presence.AWAY: + avatar.setBorderColor(getResources().getColor(R.color.holo_orange_light)); + avatar.setAlpha(255); + break; + + case Presence.DO_NOT_DISTURB: + avatar.setBorderColor(getResources().getColor(R.color.holo_red_dark)); + avatar.setAlpha(255); + + break; + + case Presence.OFFLINE: + avatar.setBorderColor(getResources().getColor(R.color.holo_grey_light)); + avatar.setAlpha(150); + break; + + + default: + } + } } diff --git a/src/info/guardianproject/otr/app/im/app/MissingChatFileStoreActivity.java b/src/info/guardianproject/otr/app/im/app/MissingChatFileStoreActivity.java new file mode 100644 index 000000000..c778d8385 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/MissingChatFileStoreActivity.java @@ -0,0 +1,64 @@ +package info.guardianproject.otr.app.im.app; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; + +import info.guardianproject.otr.app.im.R; + +import java.io.File; + +public class MissingChatFileStoreActivity extends ThemeableActivity { + private static final String TAG = "MissingChatFileStoreActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getSupportActionBar().hide(); + setContentView(R.layout.missing_chat_file_store); + + TextView titleTextView = (TextView) findViewById(R.id.title); + TextView messageTextView = (TextView) findViewById(R.id.message); + Button deleteChatLogButton = (Button) findViewById(R.id.delete_chat_log); + Button shutdownAndLockButton = (Button) findViewById(R.id.shutdown_and_exit); + + if (getExternalFilesDir(null) == null) { + titleTextView.setText(R.string.external_storage_missing_title); + messageTextView.setText(R.string.external_storage_missing_message); + } else { + titleTextView.setText(R.string.media_store_file_missing_title); + messageTextView.setText(R.string.media_store_file_missing_message); + } + deleteChatLogButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Log.i(TAG, "init try again onClick"); + Context c = getApplicationContext(); + new File(ChatFileStore.getInternalDbFilePath(c)).delete(); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(c); + Editor editor = settings.edit(); + editor.putBoolean(getString(R.string.key_store_media_on_external_storage_pref), + false); + editor.apply(); + finish(); + } + }); + shutdownAndLockButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Log.i(TAG, "shutdownAndLock onClick"); + WelcomeActivity.shutdownAndLock(MissingChatFileStoreActivity.this); + } + }); + } + +} diff --git a/src/info/guardianproject/otr/app/im/app/NetworkConnectivityListener.java b/src/info/guardianproject/otr/app/im/app/NetworkConnectivityListener.java index c0cf90d3a..0708f46b1 100644 --- a/src/info/guardianproject/otr/app/im/app/NetworkConnectivityListener.java +++ b/src/info/guardianproject/otr/app/im/app/NetworkConnectivityListener.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2006 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -33,8 +33,8 @@ /** * A wrapper for a broadcast receiver that provides network connectivity state * information, independent of network type (mobile, Wi-Fi, etc.). {@hide - * - * + * + * * } */ public class NetworkConnectivityListener extends BroadcastReceiver { @@ -55,43 +55,54 @@ public class NetworkConnectivityListener extends BroadcastReceiver { * established, or may be attempting to establish, connectivity with another * network. If so, {@code mOtherNetworkInfo} will be non-null. */ - private NetworkInfo mOtherNetworkInfo; + // private NetworkInfo mOtherNetworkInfo; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - + /* if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || mListening == false) { Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent); return; }*/ - + boolean noConnectivity = intent.getBooleanExtra( ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - mNetworkInfo = (NetworkInfo) intent - .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); - mOtherNetworkInfo = (NetworkInfo) intent - .getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO); - - mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON); - mIsFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); - - //let's just check the state of our active network to set this value - if (isNetworkAvailableAndConnected(context.getApplicationContext())) { - mState = State.CONNECTED; - } else { + if (noConnectivity) + { mState = State.NOT_CONNECTED; } - + else + { + ConnectivityManager manager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + // Getting from intent is deprecated - get from manager + mNetworkInfo = manager.getActiveNetworkInfo(); + // mOtherNetworkInfo = (NetworkInfo) intent + // .getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO); + + mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON); + mIsFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); + + if (mNetworkInfo != null && mNetworkInfo.isConnected()) + { + mState = State.CONNECTED; + + } + else { + mState = State.NOT_CONNECTED; + } + } + /* Log.d(TAG, "onReceive(): mNetworkInfo=" utoConnect + mNetworkInfo + " mOtherNetworkInfo = " + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo + " noConn=" + noConnectivity) + " mState=" + mState);*/ - + if (mHandlers != null) { // Notifiy any handlers. @@ -103,9 +114,9 @@ public void onReceive(Context context, Intent intent) { } } } - - public enum State { + + public static enum State { UNKNOWN, /** This state is returned if there is connectivity to any network **/ @@ -128,7 +139,7 @@ public NetworkConnectivityListener() { /** * This method starts listening for network connectivity state changes. - * + * * @param context */ public synchronized void startListening(Context context) { @@ -148,7 +159,7 @@ public synchronized void stopListening() { mContext.unregisterReceiver(this); mContext = null; mNetworkInfo = null; - mOtherNetworkInfo = null; + // mOtherNetworkInfo = null; mIsFailover = false; mReason = null; mListening = false; @@ -158,7 +169,7 @@ public synchronized void stopListening() { /** * This methods registers a Handler to be called back onto with the * specified what code when the network connectivity state changes. - * + * * @param target The target handler. * @param what The what code to be used when posting a message to the * handler. @@ -169,7 +180,7 @@ public static void registerHandler(Handler target, int what) { /** * This methods unregisters the specified Handler. - * + * * @param target */ public static void unregisterHandler(Handler target) { @@ -183,7 +194,7 @@ public State getState() { /** * Return the NetworkInfo associated with the most recent connectivity * event. - * + * * @return {@code NetworkInfo} for the network that had the most recent * connectivity event. */ @@ -197,17 +208,18 @@ public NetworkInfo getNetworkInfo() { * might be available. If this returns a non-null value, then another * broadcast should follow shortly indicating whether connection to the * other network succeeded. - * + * * @return NetworkInfo */ + /** public NetworkInfo getOtherNetworkInfo() { return mOtherNetworkInfo; - } + }*/ /** * Returns true if the most recent event was for an attempt to switch over * to a new network following loss of connectivity on another network. - * + * * @return {@code true} if this was a failover attempt, {@code false} * otherwise. */ @@ -218,26 +230,12 @@ public boolean isFailover() { /** * An optional reason for the connectivity state change may have been * supplied. This returns it. - * + * * @return the reason for the state change, if available, or {@code null} * otherwise. */ public String getReason() { return mReason; } - - public boolean isNetworkAvailableAndConnected (Context context) { - ConnectivityManager manager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - - NetworkInfo nInfo = manager.getActiveNetworkInfo(); - if (nInfo != null) - { - Log.d(ImApp.LOG_TAG,"network state: available=" + nInfo.isAvailable() + " connected/connecting=" + nInfo.isConnectedOrConnecting()); - return nInfo.isAvailable() && nInfo.isConnectedOrConnecting(); - } - else - return false; //no network info is a bad idea - } } diff --git a/src/info/guardianproject/otr/app/im/app/NewChatActivity.java b/src/info/guardianproject/otr/app/im/app/NewChatActivity.java index 4d3530fc7..86fcb7724 100644 --- a/src/info/guardianproject/otr/app/im/app/NewChatActivity.java +++ b/src/info/guardianproject/otr/app/im/app/NewChatActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,39 +16,84 @@ */ package info.guardianproject.otr.app.im.app; +import info.guardianproject.iocipher.VirtualFileSystem; import info.guardianproject.otr.IOtrChatSession; +import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.OtrChatManager; import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.IChatSessionManager; +import info.guardianproject.otr.app.im.IContactList; +import info.guardianproject.otr.app.im.IContactListListener; import info.guardianproject.otr.app.im.IContactListManager; import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.ISubscriptionListener; import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.app.ContactListFilterView.ContactListListener; -import info.guardianproject.otr.app.im.app.adapter.ChatListenerAdapter; +import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.engine.ImErrorInfo; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.service.ImServiceConstants; +import info.guardianproject.otr.app.im.ui.SecureCameraActivity; import info.guardianproject.util.LogCleaner; - +import info.guardianproject.util.SystemServices; +import info.guardianproject.util.SystemServices.FileInfo; +import info.guardianproject.util.XmppUriHelper; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.session.SessionStatus; + +import org.ironrabbit.type.CustomTypefaceManager; +import org.jivesoftware.smackx.muc.MultiUserChat; import android.app.Activity; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.media.MediaScannerConnection; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.SimpleOnPageChangeListener; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.Toolbar; +import android.support.v7.widget.Toolbar.OnMenuItemClickListener; +import android.text.SpannableString; +import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; @@ -56,435 +101,991 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.Window; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.Button; -import android.widget.CursorAdapter; -import android.widget.ListView; +import android.widget.SimpleCursorAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; - +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; public class NewChatActivity extends FragmentActivity implements View.OnCreateContextMenuListener { + private static final String ICICLE_CHAT_PAGER_ADAPTER = "chatPagerAdapter"; + private static final String ICICLE_POSITION = "position"; private static final int MENU_RESEND = Menu.FIRST; + private static final int REQUEST_PICK_CONTACTS = RESULT_FIRST_USER + 1; + private static final int REQUEST_SEND_IMAGE = REQUEST_PICK_CONTACTS + 1; + private static final int REQUEST_SEND_FILE = REQUEST_SEND_IMAGE + 1; + private static final int REQUEST_SEND_AUDIO = REQUEST_SEND_FILE + 1; + private static final int REQUEST_TAKE_PICTURE = REQUEST_SEND_AUDIO + 1; + private static final int REQUEST_SETTINGS = REQUEST_TAKE_PICTURE + 1; + private static final int REQUEST_TAKE_PICTURE_SECURE = REQUEST_SETTINGS + 1; + private static final int REQUEST_ADD_CONTACT = REQUEST_TAKE_PICTURE_SECURE + 1; + + private static final int CONTACT_LIST_LOADER_ID = 4444; + private static final int CHAT_LIST_LOADER_ID = CONTACT_LIST_LOADER_ID+1; + private static final int CHAT_PAGE_LOADER_ID = CONTACT_LIST_LOADER_ID+2; private ImApp mApp; private ViewPager mChatPager; - private static ChatViewPagerAdapter mChatPagerAdapter; - - private Cursor mCursorChats; - + private ChatViewPagerAdapter mChatPagerAdapter; + + private Toolbar mToolbar; + private DrawerLayout mDrawer; + private ActionBarDrawerToggle mDrawerToggle; + + private int mLastPagePosition = -1; + private SimpleAlertHandler mHandler; - private MenuItem menuOtr, menuCall; - - private LayoutInflater mInflater; - private static long mAccountId = -1; - private static long mLastProviderId = -1; - - private String mSipAccount = null; - + private long mLastAccountId = -1; + private long mLastProviderId = -1; + private boolean mShowChatsOnly = true; + private MessageContextMenuHandler mMessageContextMenuHandler; - + private ContactListFragment mContactList = null; + private Imps.ProviderSettings.QueryMap mGlobalSettings = null; + + final static class MyHandler extends SimpleAlertHandler { + public MyHandler(NewChatActivity activity) { + super(activity); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == ImApp.EVENT_SERVICE_CONNECTED) { + ((NewChatActivity)mActivity).onServiceConnected(); + return; + } + super.handleMessage(msg); + } + } + + @Override protected void onCreate(Bundle icicle) { - + super.onCreate(icicle); - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.chat_pager); + checkCustomFont (); - mChatPager = (ViewPager) findViewById(R.id.chatpager); - mApp = (ImApp)getApplication(); - mInflater = LayoutInflater.from(this); - - mMessageContextMenuHandler = new MessageContextMenuHandler(); + mApp.maybeInit(this); - initSideBar (); - - mChatPagerAdapter = new ChatViewPagerAdapter(getSupportFragmentManager()); - mChatPager.setAdapter(mChatPagerAdapter); - - new java.util.Timer().schedule( - new java.util.TimerTask() { - @Override - public void run() { - handlerIntent.sendEmptyMessage(0); - } - }, - 1000 - ); - - - - } - - private Handler handlerIntent = new Handler () - { + setContentView(R.layout.chat_pager); - @Override - public void handleMessage(Message msg) { - super.handleMessage(msg); - - if (msg.what == 0) - resolveIntent(getIntent()); - - } - - }; - + mToolbar = (Toolbar) findViewById(R.id.toolbar); - private SlidingMenu menu = null; - - private void initSideBar () - { - menu = new SlidingMenu(this); - menu.setMode(SlidingMenu.LEFT); - menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN); - menu.setShadowWidthRes(R.dimen.shadow_width); - menu.setShadowDrawable(R.drawable.shadow); - menu.setBehindOffsetRes(R.dimen.slidingmenu_offset); - menu.setFadeDegree(0.35f); - menu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); - - menu.setMenu(R.layout.fragment_drawer); - - Button btnDrawerAccount = (Button) findViewById(R.id.btnDrawerAccount); - Button btnDrawerSettings = (Button) findViewById(R.id.btnDrawerSettings); - Button btnDrawerPanic = (Button) findViewById(R.id.btnDrawerPanic); - Button btnDrawerGroupChat = (Button) findViewById(R.id.btnDrawerGroupChat); - Button btnDrawerAddContact = (Button) findViewById(R.id.btnDrawerAddContact); - - - btnDrawerAccount.setOnClickListener(new OnClickListener () - { + mApp.setAppTheme(this,mToolbar); + ThemeableActivity.setBackgroundImage(this); - @Override - public void onClick(View v) { - - Intent intent = new Intent(NewChatActivity.this, AccountListActivity.class); - startActivity(intent); - - } - - - }); - - btnDrawerSettings.setOnClickListener(new OnClickListener () - { + mToolbar.inflateMenu(R.menu.new_chat_menu); + setupMenu (); - @Override - public void onClick(View v) { - Intent sintent = new Intent(NewChatActivity.this, SettingActivity.class); - startActivity(sintent); - - } - - }); + setTitle(R.string.app_name); + + mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); + mDrawerToggle = new ActionBarDrawerToggle( + this, mDrawer, mToolbar, + R.string.ok, R.string.cancel + ); - btnDrawerPanic.setOnClickListener(new OnClickListener () + // Set the drawer toggle as the DrawerListener + mDrawer.setDrawerListener(mDrawerToggle); + mDrawerToggle.setDrawerIndicatorEnabled(true); + mDrawerToggle.syncState(); + mDrawerToggle.setToolbarNavigationClickListener(new OnClickListener () { @Override public void onClick(View v) { - - /* - Intent intent = new Intent(NewChatActivity.this, AccountListActivity.class); - intent.putExtra("EXIT", true); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - */ - - - Uri packageURI = Uri.parse("package:info.guardianproject.otr.app.im"); - Intent intent = new Intent(Intent.ACTION_DELETE, packageURI); - startActivity(intent); - - + int currentPos = mChatPager.getCurrentItem(); + if (currentPos > 0) { + mChatPager.setCurrentItem(0); + + } + } - + + }); + + mHandler = new MyHandler(this); + mRequestedChatId = -1; + + ((Button)findViewById(R.id.btnAddAccount)).setOnClickListener (new OnClickListener () - btnDrawerGroupChat.setOnClickListener(new OnClickListener () { @Override public void onClick(View v) { - - showGroupChatDialog(); - - + // TODO Auto-generated method stub + startAccountSetup (); } }); - - btnDrawerAddContact.setOnClickListener(new OnClickListener () - { + mChatPager = (ViewPager) findViewById(R.id.chatpager); + //mChatPager.setSaveEnabled(false); + //mChatPager.setOffscreenPageLimit(3); + //mChatPager.setDrawingCacheEnabled(true); + //mChatPager.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO); + + mChatPager.setOnPageChangeListener(new SimpleOnPageChangeListener () { @Override - public void onClick(View v) { - - showInviteContactDialog(); - - - } - - }); - - - - - } - - private void showInviteContactDialog () - { - if (mLastProviderId != -1 && mAccountId != -1) - { - Intent i = new Intent(this, AddContactActivity.class); - i.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mLastProviderId); - i.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - // i.putExtra(ImServiceConstants.EXTRA_INTENT_LIST_NAME, - // mContactListView.getSelectedContactList()); - startActivity(i); - } - } + public void onPageSelected(int pos) { + if (pos > 0) { - @Override - protected void onNewIntent(Intent intent) { - - new java.util.Timer().schedule( - new java.util.TimerTask() { - @Override - public void run() { - handlerIntent.sendEmptyMessage(0); + if (mLastPagePosition != -1) + { + ChatViewFragment frag = (ChatViewFragment)mChatPagerAdapter.getItemAt(pos); + // Fragment isn't guaranteed to be initialized yet + if (frag != null) + frag.onDeselected(mApp); } - }, - 1000 - ); - - } - void resolveIntent(Intent intent) { - - if (requireOpenDashboardOnStart(intent)) { - long providerId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1L); - mAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, - -1L); - if (providerId == -1L || mAccountId == -1L) { - finish(); - } else { - // mChatSwitcher.open(); - } - return; - } + ChatViewFragment frag = (ChatViewFragment)mChatPagerAdapter.getItemAt(pos); + // Fragment isn't guaranteed to be initialized yet + if (frag != null) + frag.onSelected(mApp); - if (ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION.equals(intent.getAction())) { - long providerId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1); - mAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, - -1L); - String from = intent.getStringExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS); - if ((providerId == -1) || (from == null)) { - finish(); - } else { - //chatView.bindSubscription(providerId, from); - - showSubscriptionDialog (providerId, from); - - } - } else { - Uri data = intent.getData(); - - if (data != null) - { - if (data.getScheme().equals("immu")) - { - String user = data.getUserInfo(); - String host = data.getHost(); - String path = null; - - if (data.getPathSegments().size() > 0) - path = data.getPathSegments().get(0); - - if (host != null && path != null) + mLastPagePosition = pos; + + if (mMenu != null) { - List listConns = ((ImApp)getApplication()).getActiveConnections(); - - if (!listConns.isEmpty()) - { - - startGroupChat(path, host, listConns.get(0)); - - - } + + mMenu.setGroupVisible(R.id.menu_group_chats, true); + mMenu.setGroupVisible(R.id.menu_group_contacts, false); + } + + mDrawerToggle.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha); + mDrawerToggle.setDrawerIndicatorEnabled(false); + mDrawerToggle.syncState(); + + } else { - String type = getContentResolver().getType(data); - if (Imps.Chats.CONTENT_ITEM_TYPE.equals(type)) { - - long requestedChatId = ContentUris.parseId(data); - - if (mCursorChats != null) - { - mCursorChats.moveToPosition(0); - int posIdx = 0; - boolean foundChatView = false; - - while (mCursorChats.moveToNext()) - { - long chatId = mCursorChats.getLong(ChatView.CHAT_ID_COLUMN); - - if (chatId == requestedChatId) - { - mChatPager.setCurrentItem(posIdx+1); - foundChatView = true; - break; - } - - posIdx++; - } - - if (!foundChatView) - { - - Uri.Builder builder = Imps.Chats.CONTENT_URI.buildUpon(); - ContentUris.appendId(builder, requestedChatId); - Cursor cursor = getContentResolver().query(builder.build(), ChatView.CHAT_PROJECTION, null, null, null); - - mContactList.startChat(cursor); - - - } - } - - - } else if (Imps.Invitation.CONTENT_ITEM_TYPE.equals(type)) { - //chatView.bindInvitation(ContentUris.parseId(data)); - - + if (mMenu != null) + { + mMenu.setGroupVisible(R.id.menu_group_chats, false); + mMenu.setGroupVisible(R.id.menu_group_contacts, true); + + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); - } + + setTitle(R.string.app_name); + mDrawerToggle.setHomeAsUpIndicator(null); + mDrawerToggle.setDrawerIndicatorEnabled(true); + mDrawerToggle.syncState(); + } + } - else - { - mAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID,-1L); - - if (mContactList != null) - mContactList.initAccount(this, mAccountId); - + + + }); + + mMessageContextMenuHandler = new MessageContextMenuHandler(); + + // initSideBar (); + + mChatPagerAdapter = new ChatViewPagerAdapter(getSupportFragmentManager()); + mChatPager.setAdapter(mChatPagerAdapter); + + if (icicle != null) { + if (icicle.containsKey(ICICLE_CHAT_PAGER_ADAPTER)) { + mChatPagerAdapter.restoreState(icicle.getParcelable(ICICLE_CHAT_PAGER_ADAPTER), getClassLoader()); } - } - - if (mContactList != null) - { - mContactList.setSpinnerState(this); - } - - } - - public void showChat (long requestedChatId) - { - mCursorChats.moveToPosition(-1); - int posIdx = 1; - - while (mCursorChats.moveToNext()) - { - long chatId = mCursorChats.getLong(ChatView.CONTACT_ID_COLUMN); - - if (chatId == requestedChatId) - { - mChatPager.setCurrentItem(posIdx); - break; + if (icicle.containsKey(ICICLE_POSITION)) { + int position = icicle.getInt(ICICLE_POSITION); + if (position < mChatPagerAdapter.getCount()) + mChatPager.setCurrentItem(position); } - - posIdx++; } - } - public void refreshChatViews () - { - - mChatPagerAdapter = new ChatViewPagerAdapter(getSupportFragmentManager()); - mChatPager.setAdapter(mChatPagerAdapter); - - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.chat_screen_menu, menu); + mApp.registerForBroadcastEvent(ImApp.EVENT_SERVICE_CONNECTED, mHandler); - menuOtr = menu.findItem(R.id.menu_view_otr); - menuCall = menu.findItem(R.id.menu_secure_call); - - - return true; - } + initConnections(); - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - //updateOtrMenuState(); - return true; } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { + private void initChats () + { + LoaderManager lMgr =getSupportLoaderManager(); + lMgr.initLoader(CHAT_LIST_LOADER_ID, null, new LoaderManager.LoaderCallbacks () { - case R.id.menu_secure_call: - sendCallInvite (); - return true; + @Override + public Loader onCreateLoader(int id, Bundle args) { + CursorLoader loader = new CursorLoader(NewChatActivity.this, Imps.Contacts.CONTENT_URI_CHAT_CONTACTS, ChatView.CHAT_PROJECTION, null, null, null); + loader.setUpdateThrottle(100L); - case R.id.menu_view_profile: - //getChatView().viewProfile(); - return true; + return loader; + } - case R.id.menu_end_conversation: - //getChatView().closeChatSession(); - return true; - - + @Override + public void onLoadFinished(Loader loader, Cursor newCursor) { + + mChatPagerAdapter.swapCursor(newCursor); + updateChatList(); + + if (getIntent() != null) + resolveIntent(getIntent()); + + if (mRequestedChatId >= 0) { + if (showChat(mRequestedChatId)) { + mRequestedChatId = -1; + } + } - case android.R.id.home: - finish();// close this view and return to account list - return true; - - case R.id.menu_view_accounts: - startActivity(new Intent(getBaseContext(), ChooseAccountActivity.class)); - // finish(); - return true; - - - } - return super.onOptionsItemSelected(item); + } + + @Override + public void onLoaderReset(Loader loader) { + mChatPagerAdapter.swapCursor(null); + } + }); } - + + + + @Override + public void setTitle(CharSequence title) { + + mToolbar.setTitle(title); + // mToolbar.setLogo(null); + } + + public void setTitle(CharSequence title, Drawable icon) { + + mToolbar.setTitle(title); + // mToolbar.setLogo(icon); + + } + + + private void checkCustomFont () + { + if (CustomTypefaceManager.getCurrentTypeface(this)==null) + { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + List mInputMethodProperties = imm.getEnabledInputMethodList(); + + final int N = mInputMethodProperties.size(); + + for (int i = 0; i < N; i++) { + + InputMethodInfo imi = mInputMethodProperties.get(i); + + //imi contains the information about the keyboard you are using + if (imi.getPackageName().equals("org.ironrabbit.bhoboard")) + { + CustomTypefaceManager.loadFromKeyboard(this); + break; + } + + } + + + } + } + + /* + * We must have been thawed and the service was not previously connected, so our ChatViews are showing nothing. + * Refresh them. + */ + void onServiceConnected() { + if (mChatPagerAdapter != null) { + int size = mChatPagerAdapter.getCount(); + for (int i = 1; i < size ; i++) { + ChatViewFragment frag = (ChatViewFragment)mChatPagerAdapter.getItemAt(i); + if (frag != null) { + frag.onServiceConnected(); + } + } + } + } + + @Override + protected void onDestroy() { + unregisterSubListeners (); + + if (mGlobalSettings != null) + { + mGlobalSettings.close(); + mGlobalSettings = null; + } + + mApp.unregisterForBroadcastEvent(ImApp.EVENT_SERVICE_CONNECTED, mHandler); + mChatPagerAdapter.swapCursor(null); + //mAdapter.swapCursor(null); + super.onDestroy(); + mChatPagerAdapter = null; + // mAdapter = null; + + + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(ICICLE_POSITION, mChatPager.getCurrentItem()); + outState.putParcelable(ICICLE_CHAT_PAGER_ADAPTER, mChatPagerAdapter.saveState()); + } + + @Override + protected void onResume() { + super.onResume(); + + mApp.getTrustManager().bindDisplayActivity(this); + + mApp.checkForCrashes(this); + + //if VFS is not mounted, then send to WelcomeActivity + if (!VirtualFileSystem.get().isMounted()) + { + finish(); + startActivity(new Intent(this,WelcomeActivity.class)); + + } + } + + @Override + protected void onPause() { + super.onPause(); + + mApp.getTrustManager().unbindDisplayActivity(this); + + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + setIntent(intent); + resolveIntent(intent); + } + + @Override + public void onBackPressed() { + + int currentPos = mChatPager.getCurrentItem(); + if (currentPos > 0) { + mChatPager.setCurrentItem(0); + return; + } + + super.onBackPressed(); + } + + private void showInviteContactDialog () + { + + Intent i = new Intent(this, AddContactActivity.class); + startActivityForResult(i,REQUEST_ADD_CONTACT); + + } + + private IOtrChatSession getCurrentOtrChatSession() throws RemoteException { + IChatSession chatSession = getCurrentChatSession(); + if (chatSession == null) + return null; + else + return chatSession.getOtrChatSession(); + } + + private void displayQRCode () + { + try + { + IOtrChatSession iOtr = getCurrentOtrChatSession(); + if (iOtr != null) + { + String localUser = iOtr.getLocalUserId(); + String localFingerprint = iOtr.getLocalFingerprint(); + + if (localFingerprint != null) + { + String otrKeyURI = XmppUriHelper.getUri(localUser, localFingerprint); + + new IntentIntegrator(this).shareText(otrKeyURI); + return; + } + } + } + catch (RemoteException re) + { + } + + //did not work + Toast.makeText(this, R.string.please_start_a_secure_conversation_before_scanning_codes, Toast.LENGTH_LONG).show(); + } + + private void resolveIntent(Intent intent) { + + doResolveIntent(intent); + setIntent(null); + + } + + private IImConnection findConnectionForGroupChat (String user, String host) + { + Collection connActive = mApp.getActiveConnections(); + ContentResolver cr = this.getContentResolver(); + IImConnection result = null; + + for (IImConnection conn : connActive) + { + try + { + + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( conn.getProviderId())},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, cr, + conn.getProviderId(), false /* keep updated */, mHandler /* no handler */); + + if (host.contains(settings.getDomain())) + { + if (conn.getState() == ImConnection.LOGGED_IN) + { + + result = conn; + settings.close(); + pCursor.close(); + break; + } + } + + settings.close(); + pCursor.close(); + + } + catch (RemoteException e){//nothing to do here + } + + } + + return result; + } + + private void doResolveIntent(Intent intent) { + + if (requireOpenDashboardOnStart(intent)) { + long providerId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1L); + mLastAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, + -1L); + if (providerId == -1L || mLastAccountId == -1L) { + finish(); + } else { + // mChatSwitcher.open(); + } + return; + } + + if (ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION.equals(intent.getAction())) { + + long providerId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1); + mLastAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, + -1L); + String from = intent.getStringExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS); + + if ((providerId == -1) || (from == null)) { + finish(); + } else { + + showSubscriptionDialog (providerId, from); + + } + } else if (intent != null) { + Uri data = intent.getData(); + + if (intent.getBooleanExtra("showaccounts", false)) + mDrawer.openDrawer(GravityCompat.START); + + if (data != null) + { + if (data.getScheme() != null && data.getScheme().equals("immu")) + { + String user = data.getUserInfo(); + String host = data.getHost(); + String path = null; + + if (data.getPathSegments().size() > 0) + path = data.getPathSegments().get(0); + + if (host != null && path != null) + { + + IImConnection connMUC = findConnectionForGroupChat(user, host); + + if (connMUC != null) + { + + startGroupChat (path, host, user, connMUC); + setResult(RESULT_OK); + } + else + { + mHandler.showAlert("Connection Error", "Unable to find a connection to join a group chat from. Please sign in and try again."); + setResult(Activity.RESULT_CANCELED); + finish(); + } + + } + + + + } else { + + String type = getContentResolver().getType(data); + if (Imps.Chats.CONTENT_ITEM_TYPE.equals(type)) { + + long requestedContactId = ContentUris.parseId(data); + + Cursor cursorChats = mChatPagerAdapter.getCursor(); + + if (cursorChats != null) + { + cursorChats.moveToPosition(-1); + int posIdx = 1; + boolean foundChatView = false; + + while (cursorChats.moveToNext()) + { + long chatId = cursorChats.getLong(ChatView.CONTACT_ID_COLUMN); + + if (chatId == requestedContactId) + { + mChatPager.setCurrentItem(posIdx); + foundChatView = true; + break; + } + + posIdx++; + } + + if (!foundChatView) + { + + Uri.Builder builder = Imps.Contacts.CONTENT_URI.buildUpon(); + ContentUris.appendId(builder, requestedContactId); + Cursor cursor = getContentResolver().query(builder.build(), ChatView.CHAT_PROJECTION, null, null, null); + + try { + if (cursor.getCount() > 0) + { + cursor.moveToFirst(); + openExistingChat(cursor); + } + } finally { + cursor.close(); + } + } + } + + } else if (Imps.Invitation.CONTENT_ITEM_TYPE.equals(type)) { + //chatView.bindInvitation(ContentUris.parseId(data)); + + + + + } + } + } + else if (intent.hasExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID)) + { + //set the current account id + mLastAccountId = intent.getLongExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID,-1L); + + //move the pager back to the first page + if (mChatPager != null) + mChatPager.setCurrentItem(0); + + + + } + else + { + // refreshConnections(); + } + } + + + } + + public boolean showChat (long requestedChatId) + { + Cursor cursorChats = mChatPagerAdapter.getCursor(); + + if (cursorChats == null) + return false; + + cursorChats.moveToPosition(-1); + int posIdx = 1; + + while (cursorChats.moveToNext()) + { + long chatId = cursorChats.getLong(ChatView.CONTACT_ID_COLUMN); + + if (chatId == requestedChatId) + { + mChatPager.setCurrentItem(posIdx); + return true; + } + + posIdx++; + } + + // Was not found + return false; + } + + public void refreshChatViews () + { + mChatPagerAdapter.notifyDataSetChanged(); + + } + + private Menu mMenu; + // private AccountAdapter mAdapter; + protected Long[][] mAccountIds; + private long mRequestedChatId; + + public void updateEncryptionMenuState () + { + ChatView cView = getCurrentChatView(); + + if (cView == null) + return; + + if (mChatPager != null && mMenu != null) + { + if (mChatPager.getCurrentItem() > 0) + { + // phoenix-nz - a group chat should not be shown as 'unverified' as it (currently) + // cannot be verified. Thus, show as neutral. + if (cView.isGroupChat()) + { + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); + + } + else if (cView.getOtrSessionStatus() == SessionStatus.ENCRYPTED && cView.isOtrSessionVerified()) + { + mMenu.setGroupVisible(R.id.menu_group_otr_verified,true); + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); + + } + else if (cView.getOtrSessionStatus() == SessionStatus.ENCRYPTED) + { + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,true); + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); + + + + } + else if (cView.getOtrSessionStatus() == SessionStatus.FINISHED) + { + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,true); + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); + + } + else + { + mMenu.setGroupVisible(R.id.menu_group_otr_off,true); + + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,false); + + } + + } + + } + } + + private void setupMenu () + { + + mMenu = mToolbar.getMenu(); + + if (mMenu != null) + { + if (mChatPager != null && mChatPager.getCurrentItem() > 0) + { + mMenu.setGroupVisible(R.id.menu_group_chats, true); + mMenu.setGroupVisible(R.id.menu_group_contacts, false); + + } + else + { + mMenu.setGroupVisible(R.id.menu_group_chats, false); + mMenu.setGroupVisible(R.id.menu_group_contacts, true); + + mMenu.setGroupVisible(R.id.menu_group_otr_verified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_unverified,false); + mMenu.setGroupVisible(R.id.menu_group_otr_off,false); + + } + } + + mToolbar.setOnMenuItemClickListener(new OnMenuItemClickListener () + { + + @Override + public boolean onMenuItemClick(MenuItem item) { + + + switch (item.getItemId()) { +/* + case R.id.menu_send_image: + if (getCurrentChatView() != null && getCurrentChatView().getOtrSessionStatus() == SessionStatus.ENCRYPTED) + { + startImagePicker(); + } + else + { + mHandler.showServiceErrorAlert(getString(R.string.please_enable_chat_encryption_to_share_files)); + } + return true; + case R.id.menu_take_picture: + if (getCurrentChatView() != null && getCurrentChatView().getOtrSessionStatus() == SessionStatus.ENCRYPTED) + { + startPhotoTaker(); + } + else + { + mHandler.showServiceErrorAlert(getString(R.string.please_enable_chat_encryption_to_share_files)); + } + return true; + + case R.id.menu_send_file: + + if (getCurrentChatView() != null && getCurrentChatView().getOtrSessionStatus() == SessionStatus.ENCRYPTED) + { + startFilePicker(); + } + else + { + mHandler.showServiceErrorAlert(getString(R.string.please_enable_chat_encryption_to_share_files)); + } + + return true; + + case R.id.menu_send_audio: + + if (getCurrentChatView() != null && getCurrentChatView().getOtrSessionStatus() == SessionStatus.ENCRYPTED) + { + startAudioPicker(); + } + else + { + mHandler.showServiceErrorAlert(getString(R.string.please_enable_chat_encryption_to_share_files)); + } + + return true; +*/ + case R.id.menu_verify_or_view: + case R.id.menu_view_profile_verified: + + if (getCurrentChatView() != null) + getCurrentChatView().showVerifyDialog(); + return true; + + case R.id.menu_show_qr: + displayQRCode(); + return true; + case R.id.menu_end_conversation: + try { + endCurrentChatPrompt( getCurrentSessionId()); + } catch (Exception e) { + Toast.makeText(NewChatActivity.this, "Error:" + e.getMessage(), Toast.LENGTH_LONG).show(); // TODO i18n + e.printStackTrace(); + } + + return true; + /* + case R.id.menu_delete_conversation: + if (getCurrentChatView() != null) + getCurrentChatView().closeChatSession(true); + return true; + */ + case R.id.menu_settings: + Intent sintent = new Intent(NewChatActivity.this, SettingActivity.class); + startActivityForResult(sintent,REQUEST_SETTINGS); + return true; + + case R.id.menu_otr: + case R.id.menu_otr_stop: + case R.id.menu_otr_stop_verified: + + if (getCurrentChatView() != null) + { + + boolean isEnc = (getCurrentChatView().getOtrSessionStatus() == SessionStatus.ENCRYPTED || + getCurrentChatView().getOtrSessionStatus() == SessionStatus.FINISHED + ); + + getCurrentChatView().setOTRState(!isEnc); + + + + } + + return true; + + case android.R.id.home: + int currentPos = mChatPager.getCurrentItem(); + if (currentPos > 0) { + mChatPager.setCurrentItem(0); + + } + + return true; + + case R.id.menu_view_accounts: + startAccountSetup(); + + return true; + + case R.id.menu_new_chat: + startContactPicker(); + return true; + + case R.id.menu_exit: + WelcomeActivity.shutdownAndLock(NewChatActivity.this); + ExitActivity.exitAndRemoveFromRecentApps(NewChatActivity.this); + return true; + + case R.id.menu_add_contact: + showInviteContactDialog(); + return true; + + case R.id.menu_group_chat: + showGroupChatDialog(); + return true; + case R.id.menu_import_keys: + importKeyStore(); + return true; + } + + return false; + + } + + }); + + + } + + private void startAccountSetup () + { + startActivity(new Intent(getBaseContext(), ChooseAccountActivity.class)); + + } + + private void importKeyStore () + { + OtrAndroidKeyManagerImpl.checkForKeyImport(getIntent(), this); + } + + private void endCurrentChatPrompt( final String sessionId ) { + OtrChatManager otrChatManager = OtrChatManager.getInstance(); + if (otrChatManager != null) { + try { + IOtrChatSession otrSession = getCurrentOtrChatSession(); + otrSession.stopChatEncryption(); + } catch (RemoteException e) { + } + } + // if no files to delete - just end the session + if (!ChatFileStore.sessionExists(sessionId)) { + endCurrentChat(); + return; + } + new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(getString(R.string.end_chat_title)) + .setMessage(getString(R.string.end_chat_summary)) + .setPositiveButton(getString(R.string.end_chat_and_delete), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + endCurrentChat(); + } + }) + .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + return; + } + }) + .show(); + } + + private void endCurrentChat() + { + if (getCurrentChatView() != null) { + try { + // delete the chat session's files if any + deleteSessionVfs( getCurrentSessionId() ); + } catch (Exception e) { + // TODO error + e.printStackTrace(); + } + getCurrentChatView().closeChatSession(true); + } + + updateChatList(); + mChatPager.setCurrentItem(0); + + } + + private void deleteSessionVfs( final String sessionId ) throws Exception { + // if no files to delete - bail + if (!ChatFileStore.sessionExists(sessionId)) { + return; + } + // delete + ChatFileStore.deleteSession(sessionId); + } + + private void startContactPicker() { + + Uri.Builder builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); + Uri data = builder.build(); + + Intent i = new Intent(Intent.ACTION_PICK, data); + i.putExtra("invitations", false); + startActivityForResult(i, REQUEST_PICK_CONTACTS); + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { @@ -498,7 +1099,7 @@ public boolean dispatchKeyEvent(KeyEvent event) { && event.getAction() == KeyEvent.ACTION_DOWN) { mChatView.closeChatSessionIfInactive(); }*/ - + return super.dispatchKeyEvent(event); } @@ -507,171 +1108,328 @@ private boolean requireOpenDashboardOnStart(Intent intent) { return intent.getBooleanExtra(ImServiceConstants.EXTRA_INTENT_SHOW_MULTIPLE, false); } - private void sendCallInvite () - { - - // getChatView().sendMessage("☎ Click to start call sip:" + this.mSipAccount + ""); - + void startImagePicker() { + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setType("image/*"); + startActivityForResult(intent, REQUEST_SEND_IMAGE); } - - public void setOTRState(ChatView chatView, IOtrChatSession otrChatSession, boolean otrEnabled) { - - if (otrChatSession != null) - { - try { - // SessionStatus sessionStatus = SessionStatus.values()[otrChatSession.getChatStatus()]; - - if (otrEnabled) { - otrChatSession.startChatEncryption(); - } - else - { - otrChatSession.stopChatEncryption(); - } - - - } catch (RemoteException e) { - Log.d("Gibber", "error getting remote activity", e); - } - - chatView.updateWarningView(); - } + Uri mLastPhoto = null; + + void startPhotoTaker() { + + // create Intent to take a picture and return control to the calling application + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File photo = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "cs_" + new Date().getTime() + ".jpg"); + mLastPhoto = Uri.fromFile(photo); + intent.putExtra(MediaStore.EXTRA_OUTPUT, + mLastPhoto); + + // start the image capture Intent + startActivityForResult(intent, REQUEST_TAKE_PICTURE); } - /* - public void updateOtrMenuState() { - - ChatView chatView = getCurrentChatView (); + void startPhotoTakerSecure() { - if (menuOtr == null || chatView == null) - return; + // create Intent to take a picture and return control to the calling application + Intent intent = new Intent(this, SecureCameraActivity.class); + String time = ""+new Date().getTime(); + String filename = "/" + Environment.DIRECTORY_DCIM + "/" + "cs_" + time + ".jpg"; + String thumbnail = "/" + Environment.DIRECTORY_DCIM + "/" + "cs_" + time + "_thumb.jpg"; + intent.putExtra(SecureCameraActivity.FILENAME, filename ) ; + intent.putExtra(SecureCameraActivity.THUMBNAIL, thumbnail ) ; + + // start the secure image capture Intent + startActivityForResult(intent, REQUEST_TAKE_PICTURE_SECURE); + } + + void startFilePicker() { + Intent selectFile = new Intent(Intent.ACTION_GET_CONTENT); + selectFile.setType("file/*"); + Intent intentChooser = Intent.createChooser(selectFile, "Select File"); + + if (intentChooser != null) + startActivityForResult(Intent.createChooser(selectFile, "Select File"), REQUEST_SEND_FILE); + } + + void startAudioPicker() { + + + Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + if (!isCallable(intent)) + { + intent = new Intent("android.provider.MediaStore.RECORD_SOUND"); + intent.addCategory("android.intent.category.DEFAULT"); + + if (!isCallable(intent)) + { + intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("audio/*"); + + if (!isCallable(intent)) + return; + + } + } - IOtrChatSession otrChatSession = chatView.getOtrChatSession(); + startActivityForResult(intent, REQUEST_SEND_AUDIO); // intent and requestCode of 1 - if (otrChatSession != null) { - try { - SessionStatus sessionStatus = SessionStatus.values()[otrChatSession.getChatStatus()]; + } - if (sessionStatus != SessionStatus.PLAINTEXT) { - menuOtr.setTitle(R.string.menu_otr_stop); - menuOtr.setIcon(this.getResources().getDrawable(R.drawable.ic_menu_encrypt)); - - } else { - menuOtr.setTitle(R.string.menu_otr_start); - menuOtr.setIcon(this.getResources().getDrawable(R.drawable.ic_menu_unencrypt)); + private boolean isCallable(Intent intent) { + List list = getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + private void handleSendDelete( Uri contentUri, boolean delete, boolean resizeImage) { + try { + // import + FileInfo info = SystemServices.getFileInfoFromURI(this, contentUri); + String sessionId = getCurrentSessionId(); + Uri vfsUri; + if (resizeImage) + vfsUri = ChatFileStore.resizeAndImportImage(this, sessionId, contentUri, info.type); + else + vfsUri = ChatFileStore.importContent(sessionId, info.path); + // send + boolean sent = handleSend(vfsUri, info.type); + if (!sent) { + // not deleting if not sent + return; + } + // autu delete + if (delete) { + boolean deleted = delete(contentUri); + if (!deleted) { + throw new IOException("Error deleting " + contentUri); } - - } catch (RemoteException e) { - Log.d("NewChat", "Error accessing remote service", e); } - } else { - menuOtr.setTitle(R.string.menu_otr_start); + } catch (Exception e) { + // Toast.makeText(this, "Error sending file", Toast.LENGTH_LONG).show(); // TODO i18n + Log.e(ImApp.LOG_TAG,"error sending file",e); + } + } + private boolean delete(Uri uri) { + if (uri.getScheme().equals("content")) { + int deleted = getContentResolver().delete(uri,null,null); + return deleted == 1; + } + if (uri.getScheme().equals("file")) { + java.io.File file = new java.io.File(uri.toString().substring(5)); + return file.delete(); } - }*/ + return false; + } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_SEND_IMAGE || requestCode == REQUEST_SEND_FILE || requestCode == REQUEST_SEND_AUDIO) { + Uri uri = resultIntent.getData() ; + if( uri == null ) { + return ; + } + boolean deleteAudioFile = (requestCode == REQUEST_SEND_AUDIO); + boolean resizeImage = requestCode == REQUEST_SEND_IMAGE; //resize if is an image, not shared as "file" + handleSendDelete(uri, deleteAudioFile, resizeImage); + } + else if (requestCode == REQUEST_TAKE_PICTURE) + { + File file = new File(getRealPathFromURI(mLastPhoto)); + final Handler handler = new Handler(); + MediaScannerConnection.scanFile( + this, new String[] { file.toString() }, null, + new MediaScannerConnection.OnScanCompletedListener() { + @Override + public void onScanCompleted(String path, final Uri uri) { + + handler.post( new Runnable() { + @Override + public void run() { + handleSendDelete(mLastPhoto, true, true); + } + }); + } + }); + } + else if (requestCode == REQUEST_TAKE_PICTURE_SECURE) + { + String filename = resultIntent.getStringExtra(SecureCameraActivity.FILENAME); + String mimeType = resultIntent.getStringExtra(SecureCameraActivity.MIMETYPE); + Uri uri = Uri.parse("file:" + filename); + handleSend(uri,mimeType); + } + else if (requestCode == REQUEST_SETTINGS) + { - /* - private void switchChat(int delta) { - - ChatView chatView = getCurrentChatView (); - long providerId = chatView.getProviderId(); - long accountId = chatView.getAccountId(); - String contact = chatView.getUserName(); + try { + mApp.getRemoteImService().updateStateFromSettings(); + } catch (Exception e) { + Log.e(ImApp.LOG_TAG,"unable to update service settings",e); + } - mChatSwitcher.rotateChat(delta, contact, accountId, providerId); - }*/ + finish(); + Intent intent = new Intent(getApplicationContext(), NewChatActivity.class); + startActivity(intent); - /* - private void startContactPicker() { - Uri.Builder builder = Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY.buildUpon(); - ContentUris.appendId(builder, getChatView().getProviderId()); - ContentUris.appendId(builder, getChatView().getAccountId()); - Uri data = builder.build(); + } + else if (requestCode == REQUEST_PICK_CONTACTS || requestCode == REQUEST_ADD_CONTACT) { - try { - Intent i = new Intent(Intent.ACTION_PICK, data); - i.putExtra(ContactsPickerActivity.EXTRA_EXCLUDED_CONTACTS, getChatView() - .getCurrentChatSession().getParticipants()); - startActivityForResult(i, REQUEST_PICK_CONTACTS); - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(); - } - }*/ + String username = resultIntent.getStringExtra(ContactsPickerActivity.EXTRA_RESULT_USERNAME); + long providerId = resultIntent.getLongExtra(ContactsPickerActivity.EXTRA_RESULT_PROVIDER,-1); - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - - /* - if (resultCode == RESULT_OK) { - if (requestCode == REQUEST_PICK_CONTACTS) { - String username = data.getStringExtra(ContactsPickerActivity.EXTRA_RESULT_USERNAME); + //String message = resultIntent.getStringExtra(ContactsPickerActivity.EXTRA_RESULT_MESSAGE); try { - IChatSession chatSession = getChatView().getCurrentChatSession(); - if (chatSession.isGroupChatSession()) { + + IChatSession chatSession = this.getCurrentChatSession(); + if (chatSession != null && chatSession.isGroupChatSession()) { chatSession.inviteContact(username); showInvitationHasSent(username); } else { - chatSession.convertToGroupChat(); - new ContactInvitor(chatSession, username).start(); + startChat(providerId, username,Imps.ContactsColumns.TYPE_NORMAL,true, null); } } catch (RemoteException e) { - mHandler.showServiceErrorAlert(); + mHandler.showServiceErrorAlert("Error picking contacts"); + Log.d(ImApp.LOG_TAG,"error picking contact",e); + } + } + + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, + resultIntent); + + if (scanResult != null) { + String scannedString = scanResult.getContents(); + if (scannedString.startsWith("xmpp")) { + String result = XmppUriHelper.getOtrFingerprint(scannedString); + if (getCurrentChatView()!=null && result != null) + getCurrentChatView().verifyScannedFingerprint(result); + } else { + OtrAndroidKeyManagerImpl.handleKeyScanResult(scannedString, this); } } - }*/ + } } - void showInvitationHasSent(String contact) { - Toast.makeText(NewChatActivity.this, getString(R.string.invitation_sent_prompt, contact), - Toast.LENGTH_SHORT).show(); - } + IChatSession getCurrentChatSession() { + int currentPos = mChatPager.getCurrentItem(); + if (currentPos == 0) + return null; + Cursor cursorChats = mChatPagerAdapter.getCursor(); + cursorChats.moveToPosition(currentPos - 1); + long providerId = cursorChats.getLong(ChatView.PROVIDER_COLUMN); + String username = cursorChats.getString(ChatView.USERNAME_COLUMN); + IChatSessionManager sessionMgr = getChatSessionManager(providerId); + if (sessionMgr != null) { + try { + IChatSession session = sessionMgr.getChatSession(username); - private class ContactInvitor extends ChatListenerAdapter { - private final IChatSession mChatSession; - String mContact; + if (session == null) + session = sessionMgr.createChatSession(username, false); - public ContactInvitor(IChatSession session, String data) { - mChatSession = session; - mContact = data; + return session; + } catch (RemoteException e) { + + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); + } } - @Override - public void onConvertedToGroupChat(IChatSession ses) { - try { - final long chatId = mChatSession.getId(); - mChatSession.inviteContact(mContact); - mHandler.post(new Runnable() { - public void run() { - - ChatView chatView = getCurrentChatView (); + return null; + } - if (chatView != null) - { - chatView.bindChat(chatId); - showInvitationHasSent(mContact); - } - } - }); - mChatSession.unregisterChatListener(this); - } catch (RemoteException e) { + private String getCurrentSessionId() throws Exception { + return ""+getCurrentChatSession().getId(); + } + + private IChatSessionManager getChatSessionManager(long providerId) { + IImConnection conn = mApp.getConnection(providerId); + if (conn != null) { + try { + return conn.getChatSessionManager(); + } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "group chat error",e); + LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } + return null; + } + + + //---------------------------------------- + /** + * This method is used to get real path of file from from uri + * + * @param contentUri + * @return String + */ + //---------------------------------------- + public String getRealPathFromURI(Uri contentUri) + { + try + { + String[] proj = {MediaStore.Images.Media.DATA}; + Cursor cursor = managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } + catch (Exception e) + { + return contentUri.getPath(); + } + } + + private boolean handleSend(Uri uri, String mimeType) { + try { + FileInfo info = SystemServices.getFileInfoFromURI(this, uri); - public void start() throws RemoteException { - mChatSession.registerChatListener(this); + if (mimeType != null) + info.type = mimeType; + + if (info != null && info.path != null && ChatFileStore.exists(info.path)) + { + IChatSession session = getCurrentChatSession(); + + if (session != null) { + if (info.type == null) + if (mimeType != null) + info.type = mimeType; + else + info.type = "application/octet-stream"; + + String offerId = UUID.randomUUID().toString(); + session.offerData(offerId, info.path, info.type ); + ChatView cView = getCurrentChatView(); + int type = cView.isOtrSessionVerified() ? Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED : Imps.MessageType.OUTGOING_ENCRYPTED; + Imps.insertMessageInDb( + getContentResolver(), false, session.getId(), true, null, uri.toString(), + System.currentTimeMillis(), type, + 0, offerId, info.type); + return true; // sent + } + } + else + { + Toast.makeText(this, R.string.sorry_we_cannot_share_that_file_type, Toast.LENGTH_LONG).show(); + } + } catch (RemoteException e) { + Log.e(ImApp.LOG_TAG,"error sending file",e); } + return false; // was not sent + } + + void showInvitationHasSent(String contact) { + Toast.makeText(NewChatActivity.this, getString(R.string.invitation_sent_prompt, contact), + Toast.LENGTH_SHORT).show(); } /** Show the context menu on a history item. */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - + ChatView chatView = getCurrentChatView (); if (chatView != null) @@ -681,28 +1439,33 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn Cursor cursor = chatView.getMessageAtPosition(info.position); int type = cursor.getInt(cursor.getColumnIndexOrThrow(Imps.Messages.TYPE)); if (type == Imps.MessageType.OUTGOING) { - menu.add(0, MENU_RESEND, 0, R.string.menu_resend).setOnMenuItemClickListener( + android.view.MenuItem mi = menu.add(0, MENU_RESEND, 0, R.string.menu_resend); + + mi.setOnMenuItemClickListener( mMessageContextMenuHandler); + + + } - - + + } } - final class MessageContextMenuHandler implements OnMenuItemClickListener { + final class MessageContextMenuHandler implements android.view.MenuItem.OnMenuItemClickListener { int mPosition; - + @Override public boolean onMenuItemClick(android.view.MenuItem item) { - + ChatView chatView = getCurrentChatView (); - + if (chatView != null) { Cursor c; c = chatView.getMessageAtPosition(mPosition); - + switch (item.getItemId()) { case MENU_RESEND: String text = c.getString(c.getColumnIndexOrThrow(Imps.Messages.BODY)); @@ -716,163 +1479,338 @@ public boolean onMenuItemClick(android.view.MenuItem item) { return false; } } - - - public class ChatViewPagerAdapter extends FragmentStatePagerAdapter { - - + public class ChatViewPagerAdapter extends DynamicPagerAdapter { + Cursor mCursor; + boolean mDataValid; + public ChatViewPagerAdapter(FragmentManager fm) { super(fm); - - // if (mCursorChats != null && (!mCursorChats.isClosed())) - // mCursorChats.close(); - - if (mCursorChats == null) - mCursorChats = getContentResolver().query(Imps.Contacts.CONTENT_URI_CHAT_CONTACTS, ChatView.CHAT_PROJECTION, null, null, null); - else - mCursorChats.requery(); } - - - - @Override - public void notifyDataSetChanged() { - - mCursorChats.requery(); - - super.notifyDataSetChanged(); + public Cursor getCursor() { + return mCursor; + } - for (int i = 1; i < getCount(); i++) - { - ChatViewFragment frag = ((ChatViewFragment)getItem(i)); - View fragView = frag.getView(); - - if (frag != null && fragView != null && fragView instanceof ChatView) - { - ((ChatView)fragView).rebind(); - fragView.invalidate(); - } + public Cursor swapCursor(Cursor newCursor) { + if (newCursor == mCursor) { + return null; } + Cursor oldCursor = mCursor; + mCursor = newCursor; + if (newCursor != null) { + mDataValid = true; + + + } else { + mDataValid = false; + } + notifyDataSetChanged(); + // notify the observers about the new cursor + refreshChatViews(); - + return oldCursor; } - @Override public int getCount() { - if (mCursorChats != null && (!mCursorChats.isClosed())) - return mCursorChats.getCount() + 1; + if (mCursor != null) + return mCursor.getCount() + 1; else - return 1; + return 0; } @Override public Fragment getItem(int position) { - if (position == 0) { - return (mContactList = new ContactListFragment()); + if (mContactList == null) + mContactList = new ContactListFragment(); + + + return mContactList; } else { int positionMod = position - 1; + + mCursor.moveToPosition(positionMod); + long contactChatId = mCursor.getLong(ChatView.CONTACT_ID_COLUMN); + String contactName = mCursor.getString(ChatView.USERNAME_COLUMN); + long providerId = mCursor.getLong(ChatView.PROVIDER_COLUMN); - long contactChatId = -1; - - mCursorChats.moveToPosition(positionMod); - contactChatId = mCursorChats.getLong(ChatView.CONTACT_ID_COLUMN); + int chatType = mCursor.getInt(ChatView.TYPE_COLUMN); - return ChatViewFragment.newInstance(contactChatId); + + return ChatViewFragment.newInstance(contactChatId, contactName, providerId); } } @Override public int getItemPosition(Object object) { - + if (object instanceof ChatViewFragment) { - ChatViewFragment cvFrag = (ChatViewFragment)object; - int position = -1; - - mCursorChats.moveToFirst(); - - int posIdx = 1; - - do { - long chatId = mCursorChats.getLong(ChatView.CHAT_ID_COLUMN); - - View view = cvFrag.getView(); - - if (view instanceof ChatView && chatId == ((ChatView)view).mLastChatId) - { - position = posIdx; - - break; + ChatViewFragment cvFrag = (ChatViewFragment)object; + ChatView view = cvFrag.getChatView(); + long viewChatId = view.mLastChatId; + int position = PagerAdapter.POSITION_NONE; + + // TODO: cache positions so we don't scan the cursor every time + if (mCursor != null && mCursor.getCount() > 0) + { + mCursor.moveToFirst(); + + int posIdx = 1; + + do { + long chatId = mCursor.getLong(ChatView.CHAT_ID_COLUMN); + + if (chatId == viewChatId) + { + position = posIdx; + break; + } + + posIdx++; + } + while (mCursor.moveToNext()); + + } + + //` Log.d(TAG, "position of " + cvFrag.getArguments().getString("contactName") + " = " + position); + return position; + + } + else if (object instanceof ContactListFragment) + { + return 0; + + } + else { + throw new RuntimeException("got asked about an unknown fragment"); + } + } + + + @Override + public CharSequence getPageTitle(int position) { + + if (position == 0 || mCursor == null) + { + if (mShowChatsOnly) + return getString(R.string.title_chats); + else + return getString(R.string.contacts); + } + else + { + int positionMod = position - 1; + + mCursor.moveToPosition(positionMod); + if (!mCursor.isAfterLast()) + { + + + String nickname = mCursor.getString(ChatView.NICKNAME_COLUMN); + int presence = mCursor.getInt(ChatView.PRESENCE_STATUS_COLUMN); + int type = mCursor.getInt(ChatView.TYPE_COLUMN); + + BrandingResources brandingRes = mApp.getBrandingResource(mCursor.getInt(ChatView.PROVIDER_COLUMN)); + + + SpannableString s = null; + + Drawable statusIcon = null; + + if (Imps.Contacts.TYPE_GROUP == type) + { + s = new SpannableString(nickname); + } + else + { + s = new SpannableString("+ " + nickname); + statusIcon = brandingRes.getDrawable(PresenceUtils.getStatusIconId(presence)); + statusIcon.setBounds(0, 0, statusIcon.getIntrinsicWidth(), + statusIcon.getIntrinsicHeight()); + s.setSpan(new ImageSpan(statusIcon), 0, 1, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + + } + + + return s; + + } + else + return "";//unknown title + } + } + + @Override + public Object instantiateItem(ViewGroup container, int pos) { + Object item = super.instantiateItem(container, pos); + return item; + } + + @Override + public void destroyItem(ViewGroup container, int pos, Object object) { + + super.destroyItem(container, pos, object); + } + + public ChatView getChatViewAt(int pos) { + if (pos > 0) + { + ChatViewFragment frag = ((ChatViewFragment)getItemAt(pos)); + + if (frag != null) + return frag.getChatView(); + } + + return null; //this can happen if the user is quickly closing chats; just return null and swallow the event + //throw new RuntimeException("could not get chat view at " + pos); + } + } + + + private void initConnections () + { + getSupportLoaderManager().initLoader(CHAT_PAGE_LOADER_ID, null, new LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + CursorLoader loader = new CursorLoader(NewChatActivity.this, Imps.Provider.CONTENT_URI_WITH_ACCOUNT, ContactListFragment.PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL", + + new String[] { ImApp.IMPS_CATEGORY } , + Imps.Provider.DEFAULT_SORT_ORDER); + loader.setUpdateThrottle(50L); + return loader; + } + + @Override + public void onLoadFinished(Loader loader, Cursor newCursor) { + + if (newCursor != null && newCursor.getCount() > 0) + { + mAccountIds = new Long[newCursor.getCount()][2]; + newCursor.moveToFirst(); + int activeAccountIdColumn = 4; + int activeProviderIdColumn = 0; + + for (int i = 0; i < mAccountIds.length; i++) + { + mAccountIds[i][0] = newCursor.getLong(activeAccountIdColumn); + mAccountIds[i][1] = newCursor.getLong(activeProviderIdColumn); + + newCursor.moveToNext(); + + } + + for (int i = 0; i < mAccountIds.length; i++) + initConnection(mAccountIds[i][0],mAccountIds[i][1]); + + mLastAccountId = mAccountIds[0][0]; + mLastProviderId = mAccountIds[0][1]; + + newCursor.moveToFirst(); + + initChats(); + + + } + else + { + //no configured accounts, prompt to setup + Intent intent = new Intent(NewChatActivity.this, AccountWizardActivity.class); + startActivity(intent); + finish(); + } + + + } + + @Override + public void onLoaderReset(Loader loader) { + mAccountIds = null; + } + }); + + } + + + public void unregisterSubListeners () + { + if (mAccountIds != null) + for (int i = 0; i < mAccountIds.length; i++) + { + IImConnection conn = initConnection(mAccountIds[i][0],mAccountIds[i][1]); + if (conn != null) + { + try { + conn.getContactListManager().unregisterSubscriptionListener(mSubscriptionListener); + } catch (RemoteException e1) { + Log.e(ImApp.LOG_TAG,"error registering listener",e1); + } - - posIdx++; + } - while (mCursorChats.moveToNext()); - - - return position; - - } - else if (object instanceof ContactListFragment) - { - return 0; - } - - return POSITION_NONE; - - - } + } + public IImConnection initConnection (long accountId, long providerId) + { - @Override - public CharSequence getPageTitle(int position) { - - if (position == 0) - { - return getString(R.string.app_name); + IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); + + if (conn == null) + { + try { + conn = ((ImApp)getApplication()).createConnection(providerId, accountId); + } catch (RemoteException e) { + Log.e(ImApp.LOG_TAG,"error creating connection",e); } - else - { - int positionMod = position - 1; - try - { - mCursorChats.moveToPosition(positionMod); - return mCursorChats.getString(ChatView.NICKNAME_COLUMN); - } - catch (Exception e) - { - mChatPagerAdapter.notifyDataSetChanged(); - - if (mCursorChats == null) - { - Log.e(ImApp.LOG_TAG,"error getting chat",e); - return ""; - } - else - { - mCursorChats.moveToPosition(positionMod); - return mCursorChats.getString(ChatView.NICKNAME_COLUMN); - } + + if (conn != null) + { + + try { + conn.getContactListManager().registerSubscriptionListener(mSubscriptionListener); + // conn.getContactListManager().registerContactListListener(mContactListListener); + } catch (RemoteException e1) { + Log.e(ImApp.LOG_TAG,"error registering listener",e1); + } } + + } - + return conn; + } - - - public static class ContactListFragment extends Fragment implements ContactListListener, ProviderListItem.SignInManager + + public void updateChatList () { + + if (mContactList != null && mContactList.mFilterView != null) + { + mLastPagePosition = -1; + Uri baseUri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; + Uri.Builder builder = baseUri.buildUpon(); + + mContactList.mFilterView.doFilter(builder.build(), null); + } + mChatPager.invalidate(); + } + + + + public static class ContactListFragment extends Fragment implements ContactListListener + { + private static final String[] PROVIDER_PROJECTION = { Imps.Provider._ID, @@ -887,8 +1825,8 @@ public static class ContactListFragment extends Fragment implements ContactListL Imps.Provider.ACCOUNT_PRESENCE_STATUS, Imps.Provider.ACCOUNT_CONNECTION_STATUS, }; - - + + static final int PROVIDER_ID_COLUMN = 0; static final int PROVIDER_NAME_COLUMN = 1; static final int PROVIDER_FULLNAME_COLUMN = 2; @@ -900,433 +1838,243 @@ public static class ContactListFragment extends Fragment implements ContactListL static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; static final int ACCOUNT_PRESENCE_STATUS = 9; static final int ACCOUNT_CONNECTION_STATUS = 10; - - long[] mAccountIds = null; + ContactListFilterView mFilterView = null; - UserPresenceView mPresenceView = null; - - Cursor mProviderCursor = null; - SignInHelper mSignInHelper = null; - Spinner mSpinnerAccounts; + + ImApp mApp = null; private Handler mPresenceHandler = new Handler() { - + @Override public void handleMessage(Message msg) { - - - mPresenceView.refreshLogginInStatus(); + + + // mPresenceView.refreshLogginInStatus(); super.handleMessage(msg); - } + } }; - - /** - * When creating, retrieve this instance's number from its arguments. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mSignInHelper = new SignInHelper(getActivity()); - - } - /** + + /** * The Fragment's UI is just a simple text view showing its * instance number. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mFilterView = (ContactListFilterView) inflater.inflate( R.layout.contact_list_filter_view, null); - - mPresenceView = (UserPresenceView) mFilterView.findViewById(R.id.userPresence); + mFilterView.setListener(this); - + mFilterView.setLoaderManager(getLoaderManager(), CONTACT_LIST_LOADER_ID); + TextView txtEmpty = (TextView)mFilterView.findViewById(R.id.empty); - + txtEmpty.setOnClickListener(new OnClickListener () { @Override public void onClick(View v) { - - ((NewChatActivity)getActivity()).showInviteContactDialog(); + + ((NewChatActivity)getActivity()).startContactPicker(); } - + }); - - ((ListView)mFilterView.findViewById(R.id.filteredList)).setEmptyView(txtEmpty); - - - ((ImApp)getActivity().getApplication()).registerForConnEvents(mPresenceHandler); - - - // QueryMap mGlobalSettingMap = new Imps.ProviderSettings.QueryMap(getContext().getContentResolver(), true, mHandler); - - // Uri uri = mGlobalSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - // : Imps.Contacts.CONTENT_URI_CONTACTS_BY; - // uri = ContentUris.withAppendedId(uri, providerId); - // uri = ContentUris.withAppendedId(uri, accountId); - // mFilterView.doFilter( Imps.Contacts.CONTENT_URI_CONTACTS_BY, null); - - setupSpinners(mFilterView); - - setSpinnerState(getActivity()); - + + ((AbsListView)mFilterView.findViewById(R.id.filteredList)).setEmptyView(txtEmpty); + + Uri baseUri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; + Uri.Builder builder = baseUri.buildUpon(); + mFilterView.doFilter(builder.build(), null); + return mFilterView; - + } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (mProviderCursor != null && (!mProviderCursor.isClosed())) - mProviderCursor.close(); - } - - private void setupSpinners (ContactListFilterView filterView) - { - - mProviderCursor = getActivity().getContentResolver().query(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, PROVIDER_PROJECTION, - Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL", - - new String[] { ImApp.IMPS_CATEGORY } /* selection args */, - Imps.Provider.DEFAULT_SORT_ORDER); - - if (mProviderCursor == null) - { - getActivity().finish(); - return; + public void onAttach(Activity activity) { + super.onAttach(activity); - } - - // + " AND " + Imps.Provider.ACCOUNT_CONNECTION_STATUS + " != 0" - - /* selection */ - mAccountIds = new long[mProviderCursor.getCount()]; - - - mProviderCursor.moveToFirst(); - - ProviderAdapter pAdapter = new ProviderAdapter(getActivity(), mProviderCursor); - - mSpinnerAccounts = (Spinner)filterView.findViewById(R.id.spinnerAccounts); - - mSpinnerAccounts.setAdapter(pAdapter); - mSpinnerAccounts.setOnItemSelectedListener(new OnItemSelectedListener () - { + mApp = ((ImApp)activity.getApplication()); + mApp.registerForConnEvents(mPresenceHandler); - @Override - public void onItemSelected(AdapterView parent, View view, int itemPosition, long id) { - - // mAccountId = mAccountIds[itemPosition]; - //update account list - initAccount(getActivity(),mAccountIds[itemPosition]); - - - } - @Override - public void onNothingSelected(AdapterView arg0) { - // TODO Auto-generated method stub - - } - - }); - - mProviderCursor.moveToFirst(); - int activeAccountIdColumn = mProviderCursor.getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_ID); + } - for (int i = 0; i < mAccountIds.length; i++) - { - mAccountIds[i] = mProviderCursor.getLong(activeAccountIdColumn); - mProviderCursor.moveToNext(); - - } - } - - public void setSpinnerState (Activity activity) - { + @Override + public void onDetach() { + super.onDetach(); - if (mAccountIds.length == 1) //only one account, hide the spinner - { - mSpinnerAccounts.setVisibility(View.GONE); - initAccount(activity,mAccountIds[0]); - } - else if (mAccountId != -1) //multiple accounts, so select a spinner based on user input - { + mApp.unregisterForConnEvents(mPresenceHandler); + mApp = null; - mSpinnerAccounts.setVisibility(View.VISIBLE); - - int selIdx = 0; - - for (long accountId : mAccountIds) - { - if (accountId == mAccountId) - { - mSpinnerAccounts.setSelection(selIdx); - break; - } - - selIdx++; - } - - } - else if (getActivity() != null) //nothing from the user, show show an active account - { - List listConns = ((ImApp)getActivity().getApplication()).getActiveConnections(); - - for (IImConnection conn : listConns) - { - try - { - long activeAccountId = conn.getAccountId(); - int spinnerIdx = -1; - for (long accountId : mAccountIds ) - { - spinnerIdx++; - - if (accountId == activeAccountId) - { - mSpinnerAccounts.setSelection(spinnerIdx); - break; - } - } - - } - catch (Exception e){} - } - } - - - } - - public void initAccount (Activity activity, long accountId) - { - - if (accountId == -1) - return; - - ContentResolver cr = activity.getContentResolver(); - Cursor c = cr.query(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId), null, - null, null, null); - - if (c == null) { - // finish(); - return; - } - if (!c.moveToFirst()) { - c.close(); - // finish(); - return; - } - - mLastProviderId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); - - initConnection (activity, accountId, mLastProviderId); - - c.close(); - } - - private void initConnection (Activity activity, long accountId, long providerId) - { - IImConnection conn = ((ImApp)activity.getApplication()).getConnection(providerId); - - if (conn == null) - { - try { - conn = ((ImApp)getActivity().getApplication()).createConnection(providerId, accountId); - } catch (RemoteException e) { - Log.e(ImApp.LOG_TAG,"error creating connection",e); - } - } - - if (conn != null) - { - mPresenceView.setConnection(conn); + } - try { - mPresenceView.loggingIn(conn.getState() == ImConnection.LOGGING_IN); - } catch (RemoteException e) { - mPresenceView.loggingIn(false); - // mHandler.showServiceErrorAlert(); - } + @Override + public void onDestroyView() { + super.onDestroyView(); + } - //mGlobalSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - // : Imps.Contacts.CONTENT_URI_CONTACTS_BY; - Uri.Builder builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); - ContentUris.appendId(builder, providerId); - ContentUris.appendId(builder, accountId); - mFilterView.doFilter(builder.build(), null); - mChatPagerAdapter.notifyDataSetChanged(); - - } - - } @Override - public void startChat(Cursor c) { - + public void openChat(Cursor c) { + NewChatActivity activity = (NewChatActivity)getActivity(); - - if (c != null && activity != null) { - long chatContactId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); - - long providerId = mLastProviderId;//c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); - IImConnection conn = ((ImApp)activity.getApplication()).getConnection(providerId); - - if (conn != null) - { - try { - IChatSessionManager manager = conn.getChatSessionManager(); - IChatSession session = manager.getChatSession(username); - if (session == null) { - manager.createChatSession(username); - } - - activity.refreshChatViews(); - - activity.showChat(chatContactId); - - - - } catch (RemoteException e) { - // mHandler.showServiceErrorAlert(e.getMessage()); - LogCleaner.debug(ImApp.LOG_TAG, "remote exception starting chat"); + activity.openExistingChat(c); - } - - } - else - { - LogCleaner.debug(ImApp.LOG_TAG, "could not start chat as connection was null"); - } - } - } - + + @Override public void showProfile (Cursor c) { if (c != null) { long chatContactId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); - + long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); + long accountId = c.getLong(c.getColumnIndex(Imps.Contacts.ACCOUNT)); + Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatContactId); Intent intent = new Intent(Intent.ACTION_VIEW, data); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); + intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, accountId); startActivity(intent); - + } } - - public class ProviderAdapter extends CursorAdapter { - private LayoutInflater mInflater; - @SuppressWarnings("deprecation") - public ProviderAdapter(Context context, Cursor c) { - super(context, c); - mInflater = LayoutInflater.from(context).cloneInContext(context); - mInflater.setFactory(new ProviderListItemFactory()); - } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // create a custom view, so we can manage it ourselves. Mainly, we want to - // initialize the widget views (by calling getViewById()) in newView() instead of in - // bindView(), which can be called more often. - ProviderListItem view = (ProviderListItem) mInflater.inflate(R.layout.account_view_small, - parent, false); - view.init(cursor, true); - return view; - } - - + } - @Override - public void bindView(View view, Context context, Cursor cursor) { - ((ProviderListItem) view).bindView(cursor); - } - - - - } - - public class ProviderListItemFactory implements LayoutInflater.Factory { - public View onCreateView(String name, Context context, AttributeSet attrs) { - if (name != null && name.equals(ProviderListItem.class.getName())) { - return new ProviderListItem(context, getActivity(), ContactListFragment.this); - } - return null; - } - - - } - @Override - public void signIn(long accountId) { - - - long providerId = mProviderCursor.getLong(PROVIDER_ID_COLUMN); - String password = mProviderCursor.getString(ACTIVE_ACCOUNT_PW_COLUMN); - - boolean isActive = false; // TODO(miron) - mSignInHelper.signIn(password, mLastProviderId, accountId, isActive); - - + + + private void openExistingChat(Cursor c) { + + if (c != null && (! c.isAfterLast())) { + int type = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.TYPE)); + String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); + + startChat(providerId,username,type, false, null); } + else + updateChatList(); + } - @Override - public void signOut(long accountId) { - - IImConnection conn = ((ImApp)getActivity().getApplication()).getConnection(mLastProviderId); + private void startChat (long providerId, String address,int userType, boolean isNewChat, String message) + { + IImConnection conn = mApp.getConnection(providerId); + + if (conn != null) + { try { - conn.logout(); + IChatSessionManager manager = conn.getChatSessionManager(); + IChatSession session = manager.getChatSession(address); + + if (session == null && manager != null) { + + // Create session. Stash requested contact ID for when we get called back. + if (userType == Imps.ContactsColumns.TYPE_GROUP) + session = manager.createMultiUserChatSession(address, null, isNewChat); + else + session = manager.createChatSession(address, isNewChat); + + if (session != null) + { + mRequestedChatId = session.getId(); + + if (!showChat(session.getId())) { + // We have a session, but it's not in the cursor yet + mRequestedChatId = session.getId(); + session.reInit(); + } + else + { + mRequestedChatId = -1;//we showed the chat, so set this to -1; + } + + if (message != null) + session.sendMessage(message); + } + + } else { + // Already have session + if (!showChat(session.getId())) { + // We have a session, but it's not in the cursor yet + mRequestedChatId = session.getId(); + session.reInit(); + } + + } + + updateChatList(); } catch (RemoteException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // mHandler.showServiceErrorAlert(e.getMessage()); + LogCleaner.debug(ImApp.LOG_TAG, "remote exception starting chat"); + } + + } + else + { + LogCleaner.debug(ImApp.LOG_TAG, "could not start chat as connection was null"); } - - - } - - - - + public static class ChatViewFragment extends Fragment { - + ChatView mChatView; - + /** * Create a new instance of CountingFragment, providing "num" * as an argument. + * @param providerId + * @param contactName */ - static ChatViewFragment newInstance(long chatContactId) { - + static ChatViewFragment newInstance(long chatContactId, String contactName, long providerId) { + ChatViewFragment f = new ChatViewFragment(); // Supply num input as an argument. Bundle args = new Bundle(); args.putLong("contactChatId", chatContactId); + args.putString("contactName", contactName); + args.putLong("providerId", providerId); f.setArguments(args); - return f; +// Log.d(TAG, "CVF new " + contactName); + return f; + } + + public ChatViewFragment() { +// Log.d(TAG, "CVF construct " + super.toString()); + } + + @Override + public String toString() { + return super.toString() + " -> " + getArguments().getString("contactName"); + } + + public void onSelected(ImApp app) { + + //app.dismissChatNotification(getArguments().getLong("providerId"), getArguments().getString("contactName")); + + if (mChatView != null) + mChatView.setSelected(true); + + + } + + public void onDeselected(ImApp app) { + if (mChatView != null) + mChatView.setSelected(false); } /** @@ -1335,8 +2083,8 @@ static ChatViewFragment newInstance(long chatContactId) { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - + +// Log.d(TAG, "CVF create " + getArguments().getString("contactName")); } /** @@ -1346,140 +2094,279 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - + long chatContactId = getArguments().getLong("contactChatId"); mChatView = (ChatView)inflater.inflate(R.layout.chat_view, container, false); - mChatView.bindChat(chatContactId); - + mChatView.bindChat(chatContactId); + return mChatView; } + public void onServiceConnected() { + if (isResumed()) { + mChatView.onServiceConnected(); + } + } + + @Override + public void onResume() { + super.onResume(); + + mChatView.startListening(); + } + @Override public void onPause() { super.onPause(); - - if (mChatView != null) - mChatView.stopListening(); - + + mChatView.stopListening(); } @Override - public void onResume() { - super.onResume(); - - if (mChatView != null) - mChatView.startListening(); + public void onDestroy() { + mChatView.unbind(); + super.onDestroy(); } - + public ChatView getChatView() { + return mChatView; + } } - + public ChatView getCurrentChatView () { int cItemIdx; - - if ((cItemIdx = mChatPager.getCurrentItem()) > 0) + + // FIXME why is mChatPagerAdapter null here? Is this called after onDestroy? + if (mChatPagerAdapter != null && (cItemIdx = mChatPager.getCurrentItem()) > 0) { - return (ChatView)((ChatViewFragment)mChatPagerAdapter.getItem(cItemIdx)).getView(); + return mChatPagerAdapter.getChatViewAt(cItemIdx); } else return null; } - - + + + View mDialogGroup = null; private void showGroupChatDialog () { - ContentResolver cr = getContentResolver(); - - Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - cr, mLastProviderId, false /* don't keep updated */, null /* no handler */); - String chatDomain = "conference." + settings.getDomain(); - - settings.close(); - - - // This example shows how to add a custom layout to an AlertDialog LayoutInflater factory = LayoutInflater.from(this); - final View textEntryView = factory.inflate(R.layout.alert_dialog_group_chat, null); - final TextView tvServer = (TextView) textEntryView.findViewById(R.id.chat_server); + + mDialogGroup = factory.inflate(R.layout.alert_dialog_group_chat, null); - tvServer.setText(chatDomain); + final Spinner listAccounts = (Spinner) mDialogGroup.findViewById(R.id.choose_list); + setupAccountSpinner(listAccounts); - new AlertDialog.Builder(this) + new AlertDialog.Builder(this) .setTitle(R.string.create_or_join_group_chat) - .setView(textEntryView) + .setView(mDialogGroup) .setPositiveButton(R.string.connect, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { /* User clicked OK so do some stuff */ - + String chatRoom = null; String chatServer = null; - - TextView tv = (TextView)textEntryView.findViewById(R.id.chat_room); - + String nickname = null; + + TextView tv = (TextView)mDialogGroup.findViewById(R.id.chat_room); chatRoom = tv.getText().toString(); - - tv = (TextView) textEntryView.findViewById(R.id.chat_server); - + + tv = (TextView) mDialogGroup.findViewById(R.id.chat_server); chatServer = tv.getText().toString(); - - startGroupChat (chatRoom, chatServer, ((ImApp)getApplication()).getConnection(mLastProviderId)); - + + tv = (TextView) mDialogGroup.findViewById(R.id.nickname); + nickname = tv.getText().toString(); + + try + { + IImConnection conn = mApp.getConnection(mLastProviderId); + if (conn.getState() == ImConnection.LOGGED_IN) + startGroupChat (chatRoom, chatServer, nickname, conn); + else + { + //can't start group chat + mHandler.showAlert("Group Chat","Please enable your account to join a group chat"); + } + } catch (RemoteException re) { + + } + + dialog.dismiss(); + } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { /* User clicked cancel so do some stuff */ + dialog.dismiss(); } }) .create().show(); - - - + + + } - - public void startGroupChat (String room, String server, IImConnection conn) + + private void setupAccountSpinner (Spinner spinner) { - String roomAddress = room + '@' + server; - - try { - IChatSessionManager manager = conn.getChatSessionManager(); - IChatSession session = manager.getChatSession(roomAddress); - if (session == null) { - session = manager.createMultiUserChatSession(roomAddress); + final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + final Cursor cursorProviders = managedQuery(uri, PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + + SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, + android.R.layout.simple_spinner_dropdown_item, cursorProviders, new String[] { Imps.Provider.ACTIVE_ACCOUNT_USERNAME}, + new int[] { android.R.id.text1 }); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + if (cursorProviders.getCount() > 0) + { + cursorProviders.moveToFirst(); + mLastProviderId = cursorProviders.getLong(PROVIDER_ID_COLUMN); + mLastAccountId = cursorProviders.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView arg0, View arg1, + int arg2, long arg3) { + cursorProviders.moveToPosition(arg2); + + mLastProviderId = cursorProviders.getLong(PROVIDER_ID_COLUMN); + mLastAccountId = cursorProviders.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + + mHandler.post(new Runnable() + { + + public void run () + { + TextView tvServer = (TextView) mDialogGroup.findViewById(R.id.chat_server); + + IChatSessionManager manager; + try { + IImConnection conn = mApp.getConnection(mLastProviderId); + manager = conn.getChatSessionManager(); + String server = manager.getDefaultMultiUserChatServer(); + if (server != null) + tvServer.setText(server); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + }); + + } + + @Override + public void onNothingSelected(AdapterView arg0) { + // TODO Auto-generated method stub + + } + }); + } + else + { + spinner.setVisibility(View.GONE); + } + + } + + + + private IImConnection mLastConnGroup = null; + + public void startGroupChat (String room, String server, String nickname, IImConnection conn) + { + mLastConnGroup = conn; + + new AsyncTask() { + + private ProgressDialog dialog; + + + @Override + protected void onPreExecute() { + dialog = new ProgressDialog(NewChatActivity.this); + + dialog.setMessage(getString(R.string.connecting_to_group_chat_)); + dialog.setCancelable(true); + dialog.show(); } - if (session != null) - { - long id = session.getId(); - - Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); - Intent i = new Intent(Intent.ACTION_VIEW, data); - i.addCategory(ImApp.IMPS_CATEGORY); - - if (menu.isShown()) - menu.toggle(); - - startActivity(i); + @Override + protected String doInBackground(String... params) { + + String roomAddress = (params[0] + '@' + params[1]).toLowerCase(Locale.US).replace(' ', '_'); + String nickname = params[2]; + + try { + IChatSessionManager manager = mLastConnGroup.getChatSessionManager(); + IChatSession session = manager.getChatSession(roomAddress); + if (session == null) { + session = manager.createMultiUserChatSession(roomAddress, nickname, true); + + if (session != null) + { + mRequestedChatId = session.getId(); + publishProgress(mRequestedChatId); + + } else { + return getString(R.string.unable_to_create_or_join_group_chat); + + } + } else { + mRequestedChatId = session.getId(); + publishProgress(mRequestedChatId); + } + + return null; + + } catch (RemoteException e) { + return e.toString(); + } + + } + + @Override + protected void onProgressUpdate(Long... showChatId) { + showChat(showChatId[0]); } - else - { - mHandler.showServiceErrorAlert(getString(R.string.unable_to_create_or_join_group_chat)); - + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + + if (dialog.isShowing()) { + dialog.dismiss(); + } + + if (result != null) + { + mHandler.showServiceErrorAlert(result); + + } + + } - - } catch (RemoteException e) { - mHandler.showServiceErrorAlert(getString(R.string.unable_to_create_or_join_group_chat)); - } - + }.execute(room, server, nickname); + + + } - + void acceptInvitation(long providerId, long invitationId) { try { @@ -1503,40 +2390,59 @@ void declineInvitation(long providerId, long invitationId) { } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "decline invite error",e); + LogCleaner.error(ImApp.LOG_TAG, "decline invite error",e); } } - + void showSubscriptionDialog (final long subProviderId, final String subFrom) { - new AlertDialog.Builder(this) - .setTitle(getString(R.string.subscriptions)) - .setMessage(getString(R.string.subscription_prompt,subFrom)) - .setPositiveButton(R.string.approve_subscription, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { + if (! ((Activity) this).isFinishing()) { - approveSubscription(subProviderId, subFrom); - } - }) - .setNegativeButton(R.string.decline_subscription, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { + mHandler.postDelayed(new Runnable() + { - declineSubscription(subProviderId, subFrom); - } - }) - .create().show(); + @Override + public void run () + { + new AlertDialog.Builder(NewChatActivity.this) + .setTitle(getString(R.string.subscriptions)) + .setMessage(getString(R.string.subscription_prompt,subFrom)) + .setPositiveButton(R.string.approve_subscription, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + + approveSubscription(subProviderId, subFrom); + dialog.dismiss(); + } + }) + .setNegativeButton(R.string.decline_subscription, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + + declineSubscription(subProviderId, subFrom); + dialog.dismiss(); + } + }) + .create().show(); + } + },500); + } } void approveSubscription(long providerId, String userName) { IImConnection conn = mApp.getConnection(providerId); - try { - IContactListManager manager = conn.getContactListManager(); - manager.approveSubscription(userName); - } catch (RemoteException e) { + if (conn != null) + { + try { + IContactListManager manager = conn.getContactListManager(); - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "approve sub error",e); + manager.approveSubscription(new Contact(new XmppAddress(userName),userName)); + } catch (RemoteException e) { + + mHandler.showServiceErrorAlert(e.getLocalizedMessage()); + LogCleaner.error(ImApp.LOG_TAG, "approve sub error",e); + } } } @@ -1547,13 +2453,155 @@ void declineSubscription(long providerId, String userName) { { try { IContactListManager manager = conn.getContactListManager(); - manager.declineSubscription(userName); + manager.declineSubscription(new Contact(new XmppAddress(userName),userName)); } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "decline sub error",e); } } } + + + long getLastAccountId() { + return mLastAccountId; + } + + long getLastProviderId() { + return mLastProviderId; + } + + void setLastProviderId(long mLastProviderId) { + this.mLastProviderId = mLastProviderId; + } + + public class ProviderListItemFactory implements LayoutInflater.Factory { + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + if (name != null && name.equals(ProviderListItem.class.getName())) { + return new ProviderListItem(context, NewChatActivity.this, null); + } + return null; + } + + } + + private final ISubscriptionListener.Stub mSubscriptionListener = new ISubscriptionListener.Stub() { + + @Override + public void onSubScriptionRequest(Contact from, long providerId, long accountId) { + + showSubscriptionDialog (providerId, from.getAddress().getAddress()); + + } + + @Override + public void onSubscriptionApproved(Contact contact, long providerId, long accountId) { + + } + + @Override + public void onSubscriptionDeclined(Contact contact, long providerId, long accountId) { + + } + + }; + private final IContactListListener.Stub mContactListListener = new IContactListListener.Stub () + { + + @Override + public IBinder asBinder() { + + return null; + } + + @Override + public void onContactChange(int type, IContactList list, Contact contact) + throws RemoteException { + + + } + + @Override + public void onAllContactListsLoaded() throws RemoteException { + + Log.d(ImApp.LOG_TAG, "onAllContactListsLoaded"); + } + + @Override + public void onContactsPresenceUpdate(Contact[] contacts) throws RemoteException { + + + } + + @Override + public void onContactError(int errorType, ImErrorInfo error, String listName, + Contact contact) throws RemoteException { + + + } + + }; + + private synchronized Imps.ProviderSettings.QueryMap getGlobalSettings() { + if (mGlobalSettings == null) { + + ContentResolver contentResolver = getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + if (cursor == null) + return null; + + mGlobalSettings = new Imps.ProviderSettings.QueryMap(cursor, contentResolver, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, true, mHandler); + } + + return mGlobalSettings; + } + + public int getOtrPolicy() { + int otrPolicy = OtrPolicy.OPPORTUNISTIC; + + String otrModeSelect = getGlobalSettings().getOtrMode(); + + if (otrModeSelect.equals("auto")) { + otrPolicy = OtrPolicy.OPPORTUNISTIC; + } else if (otrModeSelect.equals("disabled")) { + otrPolicy = OtrPolicy.NEVER; + + } else if (otrModeSelect.equals("force")) { + otrPolicy = OtrPolicy.OTRL_POLICY_ALWAYS; + + } else if (otrModeSelect.equals("requested")) { + otrPolicy = OtrPolicy.OTRL_POLICY_MANUAL; + } + return otrPolicy; + } + + private static final String[] PROVIDER_PROJECTION = { + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.CATEGORY, + Imps.Provider.ACTIVE_ACCOUNT_ID, + Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + Imps.Provider.ACTIVE_ACCOUNT_PW, + Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + Imps.Provider.ACCOUNT_PRESENCE_STATUS, + Imps.Provider.ACCOUNT_CONNECTION_STATUS + }; + + static final int PROVIDER_ID_COLUMN = 0; + static final int PROVIDER_NAME_COLUMN = 1; + static final int PROVIDER_FULLNAME_COLUMN = 2; + static final int PROVIDER_CATEGORY_COLUMN = 3; + static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; + static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; + static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; + static final int ACTIVE_ACCOUNT_LOCKED = 7; + static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; + static final int ACCOUNT_PRESENCE_STATUS = 9; + static final int ACCOUNT_CONNECTION_STATUS = 10; } diff --git a/src/info/guardianproject/otr/app/im/app/PanicResponderActivity.java b/src/info/guardianproject/otr/app/im/app/PanicResponderActivity.java new file mode 100644 index 000000000..c4b85394d --- /dev/null +++ b/src/info/guardianproject/otr/app/im/app/PanicResponderActivity.java @@ -0,0 +1,31 @@ + +package info.guardianproject.otr.app.im.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +public class PanicResponderActivity extends Activity { + + public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER"; + + @SuppressLint("NewApi") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { + WelcomeActivity.shutdownAndLock(this); + ExitActivity.exitAndRemoveFromRecentApps(this); + } + + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + } +} diff --git a/src/info/guardianproject/otr/app/im/app/PresenceUtils.java b/src/info/guardianproject/otr/app/im/app/PresenceUtils.java index 27645f3d7..e08f4befc 100644 --- a/src/info/guardianproject/otr/app/im/app/PresenceUtils.java +++ b/src/info/guardianproject/otr/app/im/app/PresenceUtils.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/ProviderDef.java b/src/info/guardianproject/otr/app/im/app/ProviderDef.java index c92d7820d..ecbd5e215 100644 --- a/src/info/guardianproject/otr/app/im/app/ProviderDef.java +++ b/src/info/guardianproject/otr/app/im/app/ProviderDef.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/ProviderListItem.java b/src/info/guardianproject/otr/app/im/app/ProviderListItem.java index d37450a6a..708ab4b89 100644 --- a/src/info/guardianproject/otr/app/im/app/ProviderListItem.java +++ b/src/info/guardianproject/otr/app/im/app/ProviderListItem.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2009 Myriad Group AG Copyright (C) 2009 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,102 +17,83 @@ package info.guardianproject.otr.app.im.app; +import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.plugin.BrandingResourceIDs; +import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.service.ImServiceConstants; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.Drawable; +import android.graphics.Color; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; import android.util.Log; -import android.view.MotionEvent; import android.view.View; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class ProviderListItem extends LinearLayout { - private static final String TAG = "IM"; - private static final boolean LOCAL_DEBUG = false; - private Activity mActivity; - private SignInManager mSignInManager; - - private CompoundButton mSignInSwitch; - private OnCheckedChangeListener mCheckedChangeListner = new OnCheckedChangeListener(){ + //private SignInManager mSignInManager; + private ContentResolver mResolver; + // private CompoundButton mSignInSwitch; + + //private boolean mUserChanged = false; + private boolean mIsSignedIn; - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - - if (isChecked) - mSignInManager.signIn(mAccountId); - else - mSignInManager.signOut(mAccountId); - - mUserChanged = true; - } - - }; - private boolean mUserChanged = false; - private TextView mProviderName; private TextView mLoginName; - private TextView mChatView; - private View mUnderBubble; - private Drawable mBubbleDrawable; - private Drawable mDefaultBackground; - private ImageView mBtnSettings; - private int mProviderIdColumn; - private int mProviderFullnameColumn; private int mActiveAccountIdColumn; private int mActiveAccountUserNameColumn; private int mAccountPresenceStatusColumn; private int mAccountConnectionStatusColumn; - private ColorStateList mProviderNameColors; - private ColorStateList mLoginNameColors; - private ColorStateList mChatViewColors; - private long mAccountId; private boolean mShowLongName = false; - + private ImApp mApp = null; + + private static Handler mHandler = new Handler() + { + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + //update notifications from async task + } + + }; + public ProviderListItem(Context context, Activity activity, SignInManager signInManager) { super(context); mActivity = activity; - mSignInManager = signInManager; + //mSignInManager = signInManager; + + mApp = (ImApp)activity.getApplication(); + + mResolver = mApp.getContentResolver(); + } public void init(Cursor c, boolean showLongName) { - + mShowLongName = showLongName; - + mProviderIdColumn = c.getColumnIndexOrThrow(Imps.Provider._ID); - //mProviderIcon = (ImageView) findViewById(R.id.providerIcon); - // mStatusIcon = (ImageView) findViewById(R.id.statusIcon); - mSignInSwitch = (CompoundButton) findViewById(R.id.statusSwitch); + //mSignInSwitch = (CompoundButton) findViewById(R.id.statusSwitch); mProviderName = (TextView) findViewById(R.id.providerName); mLoginName = (TextView) findViewById(R.id.loginName); - mChatView = (TextView) findViewById(R.id.conversations); - mUnderBubble = findViewById(R.id.underBubble); - mBubbleDrawable = getResources().getDrawable(R.drawable.bubble); - mDefaultBackground = getResources().getDrawable(R.drawable.default_background); - - mBtnSettings = (ImageView)findViewById(R.id.btnSettings); - - mProviderFullnameColumn = c.getColumnIndexOrThrow(Imps.Provider.FULLNAME); + mActiveAccountIdColumn = c.getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_ID); mActiveAccountUserNameColumn = c .getColumnIndexOrThrow(Imps.Provider.ACTIVE_ACCOUNT_USERNAME); @@ -121,14 +102,25 @@ public void init(Cursor c, boolean showLongName) { mAccountConnectionStatusColumn = c .getColumnIndexOrThrow(Imps.Provider.ACCOUNT_CONNECTION_STATUS); - mProviderNameColors = mProviderName.getTextColors(); - - if (mLoginName != null) - mLoginNameColors = mLoginName.getTextColors(); - - if (mChatView != null) - mChatViewColors = mChatView.getTextColors(); - + setOnClickListener(new OnClickListener () + { + + @Override + public void onClick(View v) { + + + Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( + Imps.Account.CONTENT_URI, mAccountId)); + intent.addCategory(ImApp.IMPS_CATEGORY); + + intent.putExtra("isSignedIn", mIsSignedIn); + + mActivity.startActivity(intent); + } + + }); + + /* if (mSignInSwitch != null) { mProviderName.setOnClickListener(new OnClickListener () @@ -136,212 +128,271 @@ public void init(Cursor c, boolean showLongName) { @Override public void onClick(View v) { - - Intent intent = new Intent(getContext(), NewChatActivity.class); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - getContext().startActivity(intent); + + Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( + Imps.Account.CONTENT_URI, mAccountId)); + intent.addCategory(ImApp.IMPS_CATEGORY); + mActivity.startActivity(intent); } - + }); - + mLoginName.setOnClickListener(new OnClickListener () { @Override public void onClick(View v) { - - Intent intent = new Intent(getContext(), NewChatActivity.class); - intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - getContext().startActivity(intent); + + Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( + Imps.Account.CONTENT_URI, mAccountId)); + intent.addCategory(ImApp.IMPS_CATEGORY); + mActivity.startActivity(intent); } - + }); - - mSignInSwitch.setOnCheckedChangeListener(mCheckedChangeListner); - - - if (mBtnSettings != null) - { - mBtnSettings.setOnClickListener(new OnClickListener() - { - @Override - public void onClick(View v) { - - Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( - Imps.Account.CONTENT_URI, mAccountId)); - intent.addCategory(ImApp.IMPS_CATEGORY); - mActivity.startActivity(intent); - } - - }); - } + mSignInSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener(){ + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) + mSignInManager.signIn(mAccountId); + else + mSignInManager.signOut(mAccountId); + + mUserChanged = true; + } + + }); + + } - -/* + */ + +/* mStatusSwitch.setOnClickListener(new OnClickListener (){ @Override public void onClick(View v) { - + if (mStatusSwitch.isChecked()) mSignInManager.signIn(mAccountId); else mSignInManager.signOut(mAccountId); - + } - + });*/ } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + public void bindView(Cursor cursor) { - Resources r = getResources(); - // ImageView providerIcon = mProviderIcon; - - int providerId = cursor.getInt(mProviderIdColumn); - String providerDisplayName = cursor.getString(mProviderFullnameColumn); - - final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(getContext().getContentResolver(), - providerId, false , null); - - String userDomain = settings.getDomain(); - - + final Resources r = getResources(); + + final int providerId = cursor.getInt(mProviderIdColumn); + mAccountId = cursor.getLong(mActiveAccountIdColumn); setTag(mAccountId); - - ImApp app = (ImApp)mActivity.getApplication(); - if (mUnderBubble != null) - mUnderBubble.setBackgroundDrawable(mDefaultBackground); + if (!cursor.isNull(mActiveAccountIdColumn)) { - mProviderName.setTextColor(mProviderNameColors); - - if (mLoginNameColors != null) - mLoginName.setTextColor(mLoginNameColors); - - if (mChatViewColors != null) - mChatView.setTextColor(mChatViewColors); + final String activeUserName = cursor.getString(mActiveAccountUserNameColumn); - if (!cursor.isNull(mActiveAccountIdColumn)) { - - String activeUserName = cursor.getString(mActiveAccountUserNameColumn); - - if (mShowLongName) - mProviderName.setText(activeUserName + '@' + userDomain); - else - mProviderName.setText(activeUserName); + final int connectionStatus = cursor.getInt(mAccountConnectionStatusColumn); + final String presenceString = getPresenceString(cursor, getContext()); - - int connectionStatus = cursor.getInt(mAccountConnectionStatusColumn); + mHandler.postDelayed(new Runnable () { + public void run () + { + runBindTask(r, providerId, activeUserName, connectionStatus, presenceString); + } + } + , 200l); - StringBuffer secondRowText = new StringBuffer(); + } + } - mChatView.setVisibility(View.GONE); + @Override + protected void onDetachedFromWindow() { - switch (connectionStatus) { - - case Imps.ConnectionStatus.CONNECTING: - secondRowText.append(r.getString(R.string.signing_in_wait)); + super.onDetachedFromWindow(); + } - if (mSignInSwitch != null && (!mUserChanged)) - { - mSignInSwitch.setOnCheckedChangeListener(null); - mSignInSwitch.setChecked(true); - mSignInSwitch.setOnCheckedChangeListener(mCheckedChangeListner); - } - - break; + private void runBindTask(final Resources r, final int providerId, final String activeUserName, + final int dbConnectionStatus, final String presenceString) { - case Imps.ConnectionStatus.ONLINE: - - if (mSignInSwitch != null && (!mUserChanged)) - { - mSignInSwitch.setOnCheckedChangeListener(null); - mSignInSwitch.setChecked(true); - mSignInSwitch.setOnCheckedChangeListener(mCheckedChangeListner); - } - - - secondRowText.append(getPresenceString(cursor, getContext())); + String mProviderNameText; + String mSecondRowText; - secondRowText.append(" - "); - - if (settings.getServer() != null && settings.getServer().length() > 0) + try + { + Cursor pCursor = mResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( providerId)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, mResolver, + providerId, false /* keep updated */, mHandler /* no handler */); + + String userDomain = settings.getDomain(); + int connectionStatus = dbConnectionStatus; + + IImConnection conn = mApp.getConnection(providerId); + if (conn == null) { - secondRowText.append(settings.getServer()); - + connectionStatus = ImConnection.DISCONNECTED; } else { - secondRowText.append(settings.getDomain()); + try { + connectionStatus = conn.getState(); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - - - if (settings.getPort() != 5222 && settings.getPort() != 0) - secondRowText.append(':').append(settings.getPort()); - - - if (settings.getUseTor()) - { - secondRowText.append(" - "); - secondRowText.append(r.getString(R.string._via_orbot)); + + if (mShowLongName) + mProviderNameText = activeUserName + '@' + userDomain; + else + mProviderNameText = activeUserName; + + switch (connectionStatus) { + + case ImConnection.LOGGING_IN: + mSecondRowText = r.getString(R.string.signing_in_wait); + mIsSignedIn = true; + + break; + + case ImConnection.SUSPENDING: + case ImConnection.SUSPENDED: + mSecondRowText = r.getString(R.string.error_suspended_connection); + mIsSignedIn = true; + + break; + + + + case ImConnection.LOGGED_IN: + mIsSignedIn = true; + mSecondRowText = computeSecondRowText(presenceString, r, settings, true); + + break; + + case ImConnection.LOGGING_OUT: + mIsSignedIn = false; + mSecondRowText = r.getString(R.string.signing_out_wait); + + break; + + default: + + mIsSignedIn = false; + mSecondRowText = computeSecondRowText(presenceString, r, settings, true); + break; } - - - - break; - default: - + settings.close(); + pCursor.close(); - if (mSignInSwitch != null && (!mUserChanged)) - { - mSignInSwitch.setOnCheckedChangeListener(null); - mSignInSwitch.setChecked(false); - mSignInSwitch.setOnCheckedChangeListener(mCheckedChangeListner); + applyView(mProviderNameText, mIsSignedIn, mSecondRowText); } - - if (settings.getServer() != null && settings.getServer().length() > 0) + catch (NullPointerException npe) { - secondRowText.append(settings.getServer()); - + Log.d(ImApp.LOG_TAG,"null on QueryMap (this shouldn't happen anymore, but just in case)",npe); } + + + + + } + + private void applyView(String providerNameText, boolean isSignedIn, String secondRowText) { + + if (isSignedIn) + { + setBackgroundColor(getResources().getColor(R.color.holo_blue_dark)); + } + else + { + setBackgroundColor(getResources().getColor(android.R.color.transparent)); + } + + if (mProviderName != null) + { + mProviderName.setText(providerNameText); + + if (isSignedIn) + mProviderName.setTextColor(Color.WHITE); + else + mProviderName.setTextColor(Color.LTGRAY); + + + if (mLoginName != null) + { + mLoginName.setText(secondRowText); + + if (isSignedIn) + mLoginName.setTextColor(Color.WHITE); else - { - secondRowText.append(settings.getDomain()); - } - - - if (settings.getPort() != 5222 && settings.getPort() != 0) - secondRowText.append(':').append(settings.getPort()); - - - - if (settings.getUseTor()) - { - secondRowText.append(" - "); - secondRowText.append(r.getString(R.string._via_orbot)); - } - - break; + mLoginName.setTextColor(Color.LTGRAY); + + } + } + + } + + private String computeSecondRowText(String presenceString, Resources r, + final Imps.ProviderSettings.QueryMap settings, boolean showPresence) { + String secondRowText; + StringBuffer secondRowTextBuffer = new StringBuffer(); - mLoginName.setText(secondRowText); - } - - settings.close(); + if (showPresence && presenceString.length() > 0) + { + secondRowTextBuffer.append(presenceString); + secondRowTextBuffer.append(" - "); + } + + + if (settings.getServer() != null && settings.getServer().length() > 0) + { + + secondRowTextBuffer.append(settings.getServer()); + + } + else if (settings.getDomain() != null & settings.getDomain().length() > 0) + { + secondRowTextBuffer.append(settings.getDomain()); + } + + + if (settings.getPort() != 5222 && settings.getPort() != 0) + secondRowTextBuffer.append(':').append(settings.getPort()); + + + if (settings.getUseTor()) + { + secondRowTextBuffer.append(" - "); + secondRowTextBuffer.append(r.getString(R.string._via_orbot)); + } + + secondRowText = secondRowTextBuffer.toString(); + return secondRowText; } - + public Long getAccountID () { return mAccountId; } - + private String getPresenceString(Cursor cursor, Context context) { int presenceStatus = cursor.getInt(mAccountPresenceStatusColumn); @@ -353,7 +404,7 @@ private String getPresenceString(Cursor cursor, Context context) { case Imps.Presence.IDLE: return context.getString(R.string.presence_idle); - + case Imps.Presence.AWAY: return context.getString(R.string.presence_away); @@ -365,44 +416,18 @@ private String getPresenceString(Cursor cursor, Context context) { return context.getString(R.string.presence_invisible); default: - return context.getString(R.string.presence_offline); + return ""; } } - private int getPresenceIconId(Cursor cursor) { - int presenceStatus = cursor.getInt(mAccountPresenceStatusColumn); - - if (LOCAL_DEBUG) - log("getPresenceIconId: presenceStatus=" + presenceStatus); - - switch (presenceStatus) { - case Imps.Presence.AVAILABLE: - return BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE; - - case Imps.Presence.IDLE: - case Imps.Presence.AWAY: - return BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY; - - case Imps.Presence.DO_NOT_DISTURB: - return BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY; - - case Imps.Presence.INVISIBLE: - return BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE; - - default: - return BrandingResourceIDs.DRAWABLE_PRESENCE_OFFLINE; - } - } - - private void log(String msg) { - Log.d(TAG, msg); - } - public interface SignInManager { public void signIn (long accountId); public void signOut (long accountId); - }; + } + + + } diff --git a/src/info/guardianproject/otr/app/im/app/SettingActivity.java b/src/info/guardianproject/otr/app/im/app/SettingActivity.java index dba0d27a4..783a6f99f 100644 --- a/src/info/guardianproject/otr/app/im/app/SettingActivity.java +++ b/src/info/guardianproject/otr/app/im/app/SettingActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,16 +17,17 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.provider.Imps.ProviderSettings; +import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.preference.CheckBoxPreference; @@ -34,64 +35,138 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.text.TextUtils; -import com.actionbarsherlock.app.SherlockPreferenceActivity; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.Languages; -public class SettingActivity extends SherlockPreferenceActivity implements +public class SettingActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { + private static final String TAG = "SettingActivity"; private static final int DEFAULT_HEARTBEAT_INTERVAL = 1; + private String currentLanguage; ListPreference mOtrMode; + ListPreference mLanguage; + CheckBoxPreference mLinkifyOnTor; CheckBoxPreference mHideOfflineContacts; + CheckBoxPreference mDeleteUnsecuredMedia; + CheckBoxPreference mStoreMediaOnExternalStorage; CheckBoxPreference mEnableNotification; CheckBoxPreference mNotificationVibrate; CheckBoxPreference mNotificationSound; - // CheckBoxPreference mForegroundService; + CheckBoxPreference mForegroundService; EditTextPreference mHeartbeatInterval; - + EditTextPreference mThemeBackground; + Preference mNotificationRingtone; private void setInitialValues() { ContentResolver cr = getContentResolver(); - Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(cr, - false /* keep updated */, null /* no handler */); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, cr, + Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, false /* keep updated */, null /* no handler */); mOtrMode.setValue(settings.getOtrMode()); + mLinkifyOnTor.setChecked(settings.getLinkifyOnTor()); mHideOfflineContacts.setChecked(settings.getHideOfflineContacts()); + mDeleteUnsecuredMedia.setChecked(settings.getDeleteUnsecuredMedia()); mEnableNotification.setChecked(settings.getEnableNotification()); mNotificationVibrate.setChecked(settings.getVibrate()); mNotificationSound.setChecked(settings.getRingtoneURI() != null); - - //mForegroundService.setChecked(settings.getUseForegroundPriority()); - + + mForegroundService.setChecked(settings.getUseForegroundPriority()); + long heartbeatInterval = settings.getHeartbeatInterval(); if (heartbeatInterval == 0) heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL; mHeartbeatInterval.setText(String.valueOf(heartbeatInterval)); settings.close(); + + /* This uses SharedPreferences since it is used before Imps is setup */ + SharedPreferences sharedPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + mStoreMediaOnExternalStorage.setChecked(sharedPrefs.getBoolean( + getString(R.string.key_store_media_on_external_storage_pref), false)); } + /* + * Warning: must call settings.close() after usage! + */ + private static Imps.ProviderSettings.QueryMap getSettings(Context context) { + ContentResolver cr = context.getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI, + new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, + Imps.ProviderSettings.PROVIDER + "=?", + new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)}, + null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, + cr, + Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, + false /* keep updated */, + null /* no handler */); + return settings; + } + + public static boolean getDeleteUnsecuredMedia(Context context) { + Imps.ProviderSettings.QueryMap settings = getSettings(context); + boolean value = settings.getDeleteUnsecuredMedia(); + settings.close(); + return value; + } + + /* save the preferences in Imps so they are accessible everywhere */ @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { - final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( - getContentResolver(), false /* don't keep updated */, null /* no handler */); + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, cr, + Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, false /* keep updated */, null /* no handler */); if (key.equals("pref_security_otr_mode")) { settings.setOtrMode(prefs.getString(key, "auto")); + } else if (key.equals("pref_linkify_on_tor")) { + settings.setLinkifyOnTor(prefs.getBoolean(key, false)); } else if (key.equals("pref_hide_offline_contacts")) { settings.setHideOfflineContacts(prefs.getBoolean(key, false)); + } else if (key.equals("pref_delete_unsecured_media")) { + boolean test = prefs.getBoolean(key, false); + settings.setDeleteUnsecuredMedia(prefs.getBoolean(key, false)); + } else if (key.equals("pref_store_media_on_external_storage")) { + /* This uses SharedPreferences since it is used before Imps is setup */ + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + Editor editor = sharedPrefs.edit(); + editor.putBoolean(getString(R.string.key_store_media_on_external_storage_pref), + prefs.getBoolean(key, false)); + editor.apply(); } else if (key.equals("pref_enable_notification")) { settings.setEnableNotification(prefs.getBoolean(key, true)); } else if (key.equals("pref_notification_vibrate")) { settings.setVibrate(prefs.getBoolean(key, true)); } else if (key.equals("pref_notification_sound")) { + /** // TODO sort out notification sound pref - if (prefs.getBoolean(key, false)) { - settings.setRingtoneURI(ProviderSettings.RINGTONE_DEFAULT); + if (prefs.getBoolean(key, true)) { + settings.setRingtoneURI("android.resource://" + getPackageName() + "/" + R.raw.notify); } else { settings.setRingtoneURI(null); - } - } else if (key.equals("pref_foreground_service")) { - settings.setUseForegroundPriority(prefs.getBoolean(key, false)); + }*/ + } else if (key.equals("pref_enable_custom_notification")) { + /* + if (prefs.getBoolean(key, false)) { + settings.setRingtoneURI("android.resource://" + getPackageName() + "/" + R.raw.notify); + } else { + settings.setRingtoneURI(ProviderSettings.RINGTONE_DEFAULT); + }*/ + } + else if (key.equals("pref_foreground_enable")) { + settings.setUseForegroundPriority(prefs.getBoolean(key, true)); } else if (key.equals("pref_heartbeat_interval")) { try { @@ -102,18 +177,21 @@ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { settings.setHeartbeatInterval((DEFAULT_HEARTBEAT_INTERVAL)); } } - else if (key.equals("pref_default_locale")) + else if (key.equals("pref_language")) { - ((ImApp)getApplication()).setNewLocale(this, prefs.getString(key, "")); - setResult(2); - + String newLanguage = prefs.getString(key, Languages.USE_SYSTEM_DEFAULT); + if (!TextUtils.equals(currentLanguage, newLanguage)) { + ((ImApp)getApplication()).setNewLocale(this, newLanguage); + setResult(RESULT_OK); + finish(); // go to main screen to reset language + } } else if (key.equals("themeDark")) { - - setResult(2); + + setResult(RESULT_OK); } - + settings.close(); } @@ -122,69 +200,134 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - mHideOfflineContacts = (CheckBoxPreference) findPreference("pref_hide_offline_contacts"); mOtrMode = (ListPreference) findPreference("pref_security_otr_mode"); + mLanguage = (ListPreference) findPreference("pref_language"); + mLinkifyOnTor = (CheckBoxPreference) findPreference("pref_linkify_on_tor"); + mHideOfflineContacts = (CheckBoxPreference) findPreference("pref_hide_offline_contacts"); + mDeleteUnsecuredMedia = (CheckBoxPreference) findPreference("pref_delete_unsecured_media"); + mStoreMediaOnExternalStorage = (CheckBoxPreference) findPreference("pref_store_media_on_external_storage"); mEnableNotification = (CheckBoxPreference) findPreference("pref_enable_notification"); mNotificationVibrate = (CheckBoxPreference) findPreference("pref_notification_vibrate"); mNotificationSound = (CheckBoxPreference) findPreference("pref_notification_sound"); - // TODO re-enable Ringtone preference - //mNotificationRingtone = (CheckBoxPreference) findPreference("pref_notification_ringtone"); - // mForegroundService = (CheckBoxPreference) findPreference("pref_foreground_service"); + + mNotificationRingtone = findPreference("pref_notification_ringtone"); + + Languages languages = Languages.get(this); + currentLanguage = getResources().getConfiguration().locale.getLanguage(); + mLanguage.setDefaultValue(currentLanguage); + mLanguage.setEntries(languages.getAllNames()); + mLanguage.setEntryValues(languages.getSupportedLocales()); + + mNotificationRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() + { + + @Override + public boolean onPreferenceClick(Preference arg0) { + + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getString(R.string.notification_ringtone_title)); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (Uri) null); + startActivityForResult(intent, 5); + return true; + } + + }); + + mForegroundService = (CheckBoxPreference) findPreference("pref_foreground_enable"); mHeartbeatInterval = (EditTextPreference) findPreference("pref_heartbeat_interval"); - + mThemeBackground = (EditTextPreference) findPreference("pref_background"); - + + mThemeBackground.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference arg0) { - + showThemeChooserDialog (); return true; } - + }); } - + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == 888 && data != null && data.getData() != null){ Uri _uri = data.getData(); if (_uri != null) { - //User had pick an image. - Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null); - - if (cursor != null) - { - cursor.moveToFirst(); - + //Link to the image - final String imageFilePath = cursor.getString(0); - mThemeBackground.setText(imageFilePath); + String imageFilePath = getRealPathFromURI(_uri); + + if (imageFilePath != null) + mThemeBackground.setText(imageFilePath); + mThemeBackground.getDialog().cancel(); - } + } + + } + else if (resultCode == Activity.RESULT_OK && requestCode == 5) + { + Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + ContentResolver cr = getContentResolver(); + Cursor pCursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString( Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(pCursor, cr, + Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, false /* keep updated */, null /* no handler */); + + if (uri != null) + { + + settings.setRingtoneURI(uri.toString()); + + } + else + { + settings.setRingtoneURI(null); + } + + settings.close(); } super.onActivityResult(requestCode, resultCode, data); - + } + + + private String getRealPathFromURI(Uri contentURI) { + Cursor cursor = getContentResolver().query(contentURI, null, null, null, null); + if (cursor == null) { // Source is Dropbox or other similar local file path + return contentURI.getPath(); + } else { + cursor.moveToFirst(); + int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); + if (idx > -1) + return cursor.getString(idx); + else + return contentURI.toString(); + } + } + private void showThemeChooserDialog () { AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Choose Background"); - builder.setMessage("Do you want to select a background image from the Gallery?"); + builder.setTitle(getString(R.string.dialog_settings_choose_background_title)); + builder.setMessage(getString(R.string.dialog_settings_choose_background_body)); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); - startActivityForResult(Intent.createChooser(intent, "Select Picture"), 888); + startActivityForResult(Intent.createChooser(intent, getString(R.string.dialog_settings_choose_background_picker)), 888); dialog.dismiss(); } diff --git a/src/info/guardianproject/otr/app/im/app/SignInHelper.java b/src/info/guardianproject/otr/app/im/app/SignInHelper.java index 5de2fb8ea..5d55900f5 100644 --- a/src/info/guardianproject/otr/app/im/app/SignInHelper.java +++ b/src/info/guardianproject/otr/app/im/app/SignInHelper.java @@ -1,6 +1,5 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.otr.TorProxyInfo; import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.app.adapter.ConnectionListenerAdapter; @@ -21,19 +20,22 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; +import android.os.AsyncTask; +import android.os.DeadObjectException; import android.os.Handler; import android.os.RemoteException; import android.util.Log; +import android.widget.TextView; import android.widget.Toast; /** * Handle sign-in process for activities. - * + * * @author devrandom - * + * *

Users of this helper must call {@link SignInHelper#stop()} to clean up callbacks * in their onDestroy() or onPause() lifecycle methods. - * + * *

The helper listens to connection events. It automatically stops listening when the * connection state is logged-in or disconnected (failed). */ @@ -43,15 +45,15 @@ public class SignInHelper { private ImApp mApp; private MyConnectionListener mListener; private Collection connections; - private Listener mSignInListener; + private SignInListener mSignInListener; // This can be used to be informed of signin events - public interface Listener { + public interface SignInListener { void connectedToService(); void stateChanged(int state, long accountId); } - - public SignInHelper(Activity context, Listener listener) { + + public SignInHelper(Activity context, SignInListener listener) { this.mContext = context; mHandler = new SimpleAlertHandler(context); mListener = new MyConnectionListener(mHandler); @@ -60,18 +62,18 @@ public SignInHelper(Activity context, Listener listener) { mApp = (ImApp)mContext.getApplication(); } - + connections = new HashSet(); } - + public SignInHelper(Activity context) { this(context, null); } - - public void setSignInListener(Listener listener) { + + public void setSignInListener(SignInListener listener) { mSignInListener = listener; } - + public void stop() { for (IImConnection connection : connections) { try { @@ -120,27 +122,31 @@ private void handleConnectionEvent(IImConnection connection, int state, ImErrorI LogCleaner.error(ImApp.LOG_TAG, "handle connection error",e); } } - + if (state == ImConnection.DISCONNECTED) { // sign in failed final ProviderDef provider = mApp.getProvider(providerId); - String providerName = provider.mName; + if (provider != null) //a provider might have been deleted + { + String providerName = provider.mName; - Resources r = mContext.getResources(); - String errMsg = r.getString(R.string.login_service_failed, providerName, // FIXME - error == null ? "" : ErrorResUtils.getErrorRes(r, error.getCode())); - - Toast.makeText(mContext, errMsg, Toast.LENGTH_LONG).show(); - /* - new AlertDialog.Builder(mContext).setTitle(R.string.error) - .setMessage() - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - // FIXME - } - }).setCancelable(false).show(); - */ + + Resources r = mContext.getResources(); + String errMsg = r.getString(R.string.login_service_failed, providerName, // FIXME + error == null ? "" : ErrorResUtils.getErrorRes(r, error.getCode())); + + // Toast.makeText(mContext, errMsg, Toast.LENGTH_LONG).show(); + /* + new AlertDialog.Builder(mContext).setTitle(R.string.error) + .setMessage() + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // FIXME + } + }).setCancelable(false).show(); + */ + } } } @@ -159,34 +165,65 @@ public void signIn(final String password, final long providerId, final long acco final boolean isActive) { final ProviderDef provider = mApp.getProvider(providerId); - final String providerName = provider.mName; - - mApp.callWhenServiceConnected(mHandler, new Runnable() { - public void run() { - if (mApp.serviceConnected()) { - if (mSignInListener != null) - mSignInListener.connectedToService(); - if (!isActive) { - activateAccount(providerId, accountId); - } - signInAccount(password, providerId, providerName, accountId); + + if (provider != null) //provider may be null if deleted, or db not updated yet + { + final String providerName = provider.mName; + + if (mApp.serviceConnected()) { + if (mSignInListener != null) + mSignInListener.connectedToService(); + if (!isActive) { + activateAccount(providerId, accountId); } + signInAccount(password, providerId, providerName, accountId); } - }); + else + { + mApp.callWhenServiceConnected(mHandler, new Runnable() { + public void run() { + if (mApp.serviceConnected()) { + if (mSignInListener != null) + mSignInListener.connectedToService(); + if (!isActive) { + activateAccount(providerId, accountId); + } + signInAccount(password, providerId, providerName, accountId); + } + } + }); + } + } } - private void signInAccount(String password, long providerId, String providerName, long accountId) { + private void signInAccount(final String password, final long providerId, final String providerName, final long accountId) { + + + try { + signInAccountAsync(password, providerId, providerName, accountId); + } catch (RemoteException e) { + Log.d(ImApp.LOG_TAG,"error signing in",e); + } + + + + } + + private void signInAccountAsync(String password, long providerId, String providerName, long accountId) throws RemoteException { boolean autoLoadContacts = true; boolean autoRetryLogin = true; + IImConnection conn = null; + + + conn = mApp.getConnection(providerId); - try { - IImConnection conn = mApp.getConnection(providerId); if (conn != null) { connections.add(conn); conn.registerConnectionListener(mListener); int state = conn.getState(); if (mSignInListener != null) mSignInListener.stateChanged(state, accountId); + if (state != ImConnection.DISCONNECTED) { // already signed in or in the process if (state == ImConnection.LOGGED_IN) { @@ -196,6 +233,7 @@ private void signInAccount(String password, long providerId, String providerName handleConnectionEvent(conn, state, null); return; } + } else { conn = mApp.createConnection(providerId, accountId); if (conn == null) { @@ -207,46 +245,28 @@ private void signInAccount(String password, long providerId, String providerName conn.registerConnectionListener(mListener); } + conn.login(password, autoLoadContacts, autoRetryLogin); + + /* if (mApp.isNetworkAvailableAndConnected()) { - - conn.login(password, autoLoadContacts, autoRetryLogin); + } else { - promptForBackgroundDataSetting(providerName); + // promptForBackgroundDataSetting(providerName); return; - } - } catch (RemoteException e) { + }*/ - mHandler.showServiceErrorAlert(e.getLocalizedMessage()); - LogCleaner.error(ImApp.LOG_TAG, "sign in account",e); - } } - private static final String SYNC_SETTINGS_ACTION = "android.settings.SYNC_SETTINGS"; - private static final String SYNC_SETTINGS_CATEGORY = "android.intent.category.DEFAULT"; - /** * Popup a dialog to ask the user whether he/she wants to enable background * connection to continue. If yes, enable the setting and broadcast the * change. Otherwise, quit the signing in window immediately. */ private void promptForBackgroundDataSetting(String providerName) { - new AlertDialog.Builder(mContext) - .setTitle(R.string.bg_data_prompt_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(mContext.getString(R.string.bg_data_prompt_message, providerName)) - .setPositiveButton(R.string.bg_data_prompt_ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - Intent intent = new Intent(SYNC_SETTINGS_ACTION); - intent.addCategory(SYNC_SETTINGS_CATEGORY); - mContext.startActivity(intent); - } - }) - .setNegativeButton(R.string.bg_data_prompt_cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - } - }).show(); + + Toast.makeText(mContext, mContext.getString(R.string.bg_data_prompt_message, providerName), Toast.LENGTH_LONG).show(); + + } public void activateAccount(long providerId, long accountId) { diff --git a/src/info/guardianproject/otr/app/im/app/SignoutActivity.java b/src/info/guardianproject/otr/app/im/app/SignoutActivity.java index 1df221c44..c8255e02b 100644 --- a/src/info/guardianproject/otr/app/im/app/SignoutActivity.java +++ b/src/info/guardianproject/otr/app/im/app/SignoutActivity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -51,7 +51,7 @@ protected void onCreate(Bundle icicle) { } ContentResolver cr = getContentResolver(); - + Cursor c = cr.query(data, ACCOUNT_SELECTION, null /* selection */, null /* selection args */, null /* sort order */); final long providerId; @@ -102,7 +102,7 @@ private void signOut(long providerId, long accountId) { } finally { //finish(); - Toast.makeText(this, getString(R.string.signed_out_prompt), Toast.LENGTH_LONG).show(); + // Toast.makeText(this, getString(R.string.signed_out_prompt), Toast.LENGTH_LONG).show(); } } diff --git a/src/info/guardianproject/otr/app/im/app/SimpleAlertHandler.java b/src/info/guardianproject/otr/app/im/app/SimpleAlertHandler.java index c21b3f40e..29b8acea6 100644 --- a/src/info/guardianproject/otr/app/im/app/SimpleAlertHandler.java +++ b/src/info/guardianproject/otr/app/im/app/SimpleAlertHandler.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,18 +17,15 @@ package info.guardianproject.otr.app.im.app; +import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ContactListListener; import info.guardianproject.otr.app.im.engine.ImErrorInfo; - -import info.guardianproject.otr.app.im.R; - import android.app.Activity; -import android.app.AlertDialog; import android.content.res.Resources; import android.os.Handler; -import android.os.Looper; import android.os.Message; +import android.util.Log; import android.widget.Toast; public class SimpleAlertHandler extends Handler { @@ -46,14 +43,18 @@ protected void promptDisconnectedEvent(Message msg) { ImApp app = (ImApp)mActivity.getApplication(); ProviderDef provider = app.getProvider(providerId); ImErrorInfo error = (ImErrorInfo) msg.obj; - String promptMsg; - if (error != null) { + String promptMsg = null; + if (error != null && provider != null) { promptMsg = mActivity.getString(R.string.signed_out_prompt_with_error, provider.mName, ErrorResUtils.getErrorRes(mRes, error.getCode())); - } else { - promptMsg = mActivity.getString(R.string.signed_out_prompt, provider.mName); } - // showAlert(R.string.error, promptMsg); //TODO debug issue + else + { + promptMsg = mActivity.getString(R.string.error); + } + + if (promptMsg != null) + showAlert(R.string.error, promptMsg); } public void registerForBroadcastEvents() { @@ -83,23 +84,21 @@ public void showAlert(CharSequence title, int messageId) { } public void showAlert(final CharSequence title, final CharSequence message) { - if (Looper.myLooper() == getLooper()) { - new AlertDialog.Builder(mActivity).setTitle(title).setMessage(message) - .setPositiveButton(R.string.ok, null).show(); - } else { - post(new Runnable() { - public void run() { - new AlertDialog.Builder(mActivity).setTitle(title).setMessage(message) - .setPositiveButton(R.string.ok, null).show(); - } - }); + + if (title == null || message == null) + return; + + if (!title.equals(message)) //sometimes this reads Attention: Attention! + { + Toast.makeText(mActivity, title + ": " + message, Toast.LENGTH_SHORT).show(); } + } public void showServiceErrorAlert(String msg) { showAlert(R.string.error, msg); } - + public void showContactError(int errorType, ImErrorInfo error, String listName, Contact contact) { int id = 0; switch (errorType) { diff --git a/src/info/guardianproject/otr/app/im/app/SmpResponseActivity.java b/src/info/guardianproject/otr/app/im/app/SmpResponseActivity.java index c96cacbb9..95c527bed 100644 --- a/src/info/guardianproject/otr/app/im/app/SmpResponseActivity.java +++ b/src/info/guardianproject/otr/app/im/app/SmpResponseActivity.java @@ -3,8 +3,9 @@ import info.guardianproject.otr.IOtrChatSession; import info.guardianproject.otr.OtrDebugLogger; import info.guardianproject.otr.app.im.IChatSession; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.engine.Address; import info.guardianproject.otr.app.im.service.ImServiceConstants; - import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -33,9 +34,12 @@ protected void onCreate(Bundle savedInstanceState) { private void showQuestionDialog() { - new AlertDialog.Builder(this).setTitle("OTR Verification").setMessage(mQuestion) + String title = getString(R.string.smp_question_title); + String strQuestion = mSessionId + ": " + mQuestion; + + new AlertDialog.Builder(this).setTitle(title).setMessage(strQuestion) .setView(mInputSMP) - .setPositiveButton("Answer", new DialogInterface.OnClickListener() { + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String secret = mInputSMP.getText().toString(); @@ -43,7 +47,7 @@ public void onClick(DialogInterface dialog, int whichButton) { SmpResponseActivity.this.finish(); } - }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } @@ -52,14 +56,14 @@ public void onClick(DialogInterface dialog, int whichButton) { } private void respondSmp(String sid, String answer) { - + ImApp app = (ImApp)getApplication(); - + IOtrChatSession iOtrSession; try { - IChatSession chatSession = app.getChatSession(mProviderId, sid); + IChatSession chatSession = app.getChatSession(mProviderId, Address.stripResource(sid)); iOtrSession = chatSession.getOtrChatSession(); if (iOtrSession == null) { OtrDebugLogger.log("no session in progress for provider " + mProviderId); diff --git a/src/info/guardianproject/otr/app/im/app/ThemeableActivity.java b/src/info/guardianproject/otr/app/im/app/ThemeableActivity.java index b4149c46c..34a3697f1 100644 --- a/src/info/guardianproject/otr/app/im/app/ThemeableActivity.java +++ b/src/info/guardianproject/otr/app/im/app/ThemeableActivity.java @@ -1,85 +1,155 @@ package info.guardianproject.otr.app.im.app; +import info.guardianproject.otr.app.im.R; + import java.io.File; import android.app.Activity; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.v7.app.ActionBarActivity; import android.view.Display; +import android.view.View; -import com.actionbarsherlock.app.SherlockActivity; - -public class ThemeableActivity extends SherlockActivity { +public class ThemeableActivity extends ActionBarActivity { private static String mThemeBg = null; private static Drawable mThemeDrawable = null; + protected static boolean mHasBackground = false; + @Override protected void onCreate(Bundle savedInstanceState) { - + ((ImApp)this.getApplication()).setAppTheme(this); - - setBackgroundImage (this); - + + mHasBackground = setBackgroundImage (this); + super.onCreate(savedInstanceState); } - public static void setBackgroundImage (Activity activity) + public static boolean setBackgroundImage (Activity activity) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity); - //int themeId = settings.getInt("theme", R.style.Theme_Gibberbot_Light); boolean themeDark = settings.getBoolean("themeDark", false); String themebg = settings.getString("pref_background", ""); + + if (themeDark) + { + + if (activity != null) + activity.setTheme(R.style.AppThemeDark); + } + else + { + + if (activity != null) + activity.setTheme(R.style.AppTheme); + } + + + if (themebg != null && themebg.length() > 0) + { + + + File fileThemeBg = new File(themebg); + if (!fileThemeBg.exists()) + return false; + + if (mThemeBg == null || (!mThemeBg.equals(themebg))) + { + mThemeBg = themebg; + + Display display = activity.getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); // deprecated + int height = display.getHeight(); // deprecated + + final BitmapFactory.Options options = new BitmapFactory.Options(); + // Calculate inSampleSize + options.inSampleSize = 4; + + Bitmap b = BitmapFactory.decodeFile(themebg, options); + if (b == null) + return false; + + float ratio = ((float)width)/((float)height); + int bgHeight = b.getHeight(); + int bgWidth = (int)(((float)b.getHeight()) * ratio); + + b = Bitmap.createBitmap(b, 0, 0,Math.min(b.getWidth(),bgWidth),bgHeight); + + mThemeDrawable = new BitmapDrawable(b); + + + } + + activity.getWindow().setBackgroundDrawable(mThemeDrawable); + return true; + } - + return false; + + } + + public static void setBackgroundImage (View view, Activity activity) + { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity); + boolean themeDark = settings.getBoolean("themeDark", false); + String themebg = settings.getString("pref_background", ""); + + if(themeDark) + view.setBackgroundColor(activity.getResources().getColor(R.color.background_dark)); + else + view.setBackgroundColor(activity.getResources().getColor(R.color.background_light)); + if (themebg != null && themebg.length() > 0) { File fileThemeBg = new File(themebg); if (!fileThemeBg.exists()) return; - + if (mThemeBg == null || (!mThemeBg.equals(themebg))) { mThemeBg = themebg; - - Display display = activity.getWindowManager().getDefaultDisplay(); + + Display display = activity.getWindowManager().getDefaultDisplay(); int width = display.getWidth(); // deprecated int height = display.getHeight(); // deprecated - + final BitmapFactory.Options options = new BitmapFactory.Options(); // Calculate inSampleSize options.inSampleSize = 4; Bitmap b = BitmapFactory.decodeFile(themebg, options); - + if (b == null) + return; + float ratio = ((float)width)/((float)height); int bgHeight = b.getHeight(); int bgWidth = (int)(((float)b.getHeight()) * ratio); - + b = Bitmap.createBitmap(b, 0, 0,Math.min(b.getWidth(),bgWidth),bgHeight); - + mThemeDrawable = new BitmapDrawable(b); mThemeDrawable.setAlpha(200); } - - - activity.getWindow().setBackgroundDrawable(mThemeDrawable); + + view.setBackgroundDrawable(mThemeDrawable); } - + } - + @Override protected void onResume() { ((ImApp)this.getApplication()).setAppTheme(this); super.onResume(); } - + } diff --git a/src/info/guardianproject/otr/app/im/app/UserPresenceView.java b/src/info/guardianproject/otr/app/im/app/UserPresenceView.java index effa1a1b9..6b18bc583 100644 --- a/src/info/guardianproject/otr/app/im/app/UserPresenceView.java +++ b/src/info/guardianproject/otr/app/im/app/UserPresenceView.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -76,13 +76,14 @@ protected void onFinishInflate() { if (isInEditMode()) return; - mStatusDialogButton = (ImageButton) findViewById(R.id.statusDropDownButton); + /** + mStatusDialogButton = (ImageButton) findViewById(R.id.statusDropDownButton); mStatusDialogButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { showStatusListDialog(); } }); - + */ mProgressBar = (ProgressBar) findViewById(R.id.progressBar1); } @@ -115,7 +116,7 @@ private StatusIconAdapter getStatusAdapter() { } ImApp app = (ImApp)((Activity)mContext).getApplication(); - + BrandingResources brandingRes = app.getBrandingResource(mProviderId); Drawable icon = brandingRes.getDrawable(PresenceUtils.getStatusIconId(s)); String text = brandingRes.getString(PresenceUtils.getStatusStringRes(s)); @@ -160,7 +161,7 @@ public void setConnection(IImConnection conn) { private void updateView() { ImApp app = (ImApp)((Activity)mContext).getApplication(); - + BrandingResources brandingRes = app.getBrandingResource(mProviderId); int status = PresenceUtils.convertStatus(mPresence.getStatus()); mStatusDialogButton.setImageDrawable(brandingRes.getDrawable(PresenceUtils @@ -189,6 +190,7 @@ private void updateView() { private TextView initStatusBar(long providerId, boolean showEdit) { + /** EditText statusEdit = (EditText) findViewById(R.id.statusEdit); statusEdit.setVisibility(View.GONE); TextView statusView = (TextView) findViewById(R.id.statusView); @@ -240,6 +242,10 @@ public void onClick(View v) { return statusView; } + */ + + return null; + } void updatePresence(int status, String statusText) { @@ -253,7 +259,7 @@ void updatePresence(int status, String statusText) { if (status != -1) { newPresence.setStatus(status); } - + if (statusText != null) newPresence.setStatusText(statusText); @@ -264,11 +270,11 @@ void updatePresence(int status, String statusText) { } else { mPresence = newPresence; updateView(); - + ContentResolver cr = mContext.getContentResolver(); Imps.ProviderSettings.setPresence(cr, mProviderId, status, statusText); - - + + } } catch (RemoteException e) { // mHandler.showServiceErrorAlert(); @@ -315,9 +321,9 @@ public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); return view; } - + } - + public void refreshLogginInStatus () { if (mConn != null) @@ -325,7 +331,7 @@ public void refreshLogginInStatus () try { loggingIn(mConn.getState() == ImConnection.LOGGING_IN); } catch (RemoteException e) { - + loggingIn(false); // mHandler.showServiceErrorAlert(); } @@ -343,6 +349,6 @@ public void loggingIn(boolean loggingIn) { } updateView(); } - + } diff --git a/src/info/guardianproject/otr/app/im/app/WelcomeActivity.java b/src/info/guardianproject/otr/app/im/app/WelcomeActivity.java index 64346be27..e79214b50 100644 --- a/src/info/guardianproject/otr/app/im/app/WelcomeActivity.java +++ b/src/info/guardianproject/otr/app/im/app/WelcomeActivity.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2008 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,52 +16,53 @@ package info.guardianproject.otr.app.im.app; -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.ICacheWordSubscriber; -import info.guardianproject.cacheword.SQLCipherOpenHelper; -import info.guardianproject.otr.OtrAndroidKeyManagerImpl; -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.engine.ImConnection; -import info.guardianproject.otr.app.im.provider.Imps; -import info.guardianproject.otr.app.im.ui.AboutActivity; -import net.hockeyapp.android.CrashManager; -import net.hockeyapp.android.UpdateManager; -import net.sqlcipher.database.SQLiteDatabase; +import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.ContentUris; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Configuration; +import android.content.SharedPreferences.Editor; import android.database.Cursor; import android.net.Uri; +import android.net.Uri.Builder; +import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; +import android.os.StatFs; import android.preference.PreferenceManager; +import android.text.TextUtils; import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; import android.widget.Toast; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; +import info.guardianproject.cacheword.CacheWordActivityHandler; +import info.guardianproject.cacheword.CacheWordService; +import info.guardianproject.cacheword.ICacheWordSubscriber; +import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.app.im.IImConnection; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.provider.SQLCipherOpenHelper; + +import java.io.File; + +import net.hockeyapp.android.UpdateManager; public class WelcomeActivity extends ThemeableActivity implements ICacheWordSubscriber { - + private static final String TAG = "WelcomeActivity"; - private boolean mDidAutoLaunch = false; private Cursor mProviderCursor; private ImApp mApp; private SimpleAlertHandler mHandler; - private String mDefaultLocale; private SignInHelper mSignInHelper; private boolean mDoSignIn = true; - + + private ProgressDialog dialog; + static final String[] PROVIDER_PROJECTION = { Imps.Provider._ID, Imps.Provider.NAME, Imps.Provider.FULLNAME, Imps.Provider.CATEGORY, Imps.Provider.ACTIVE_ACCOUNT_ID, @@ -83,82 +84,112 @@ public class WelcomeActivity extends ThemeableActivity implements ICacheWordSubs static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; static final int ACCOUNT_PRESENCE_STATUS = 9; static final int ACCOUNT_CONNECTION_STATUS = 10; - - private SharedPreferences mPrefs = null; - + private CacheWordActivityHandler mCacheWord = null; + private boolean mDoLock; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - SQLiteDatabase.loadLibs(this); - + + mApp = (ImApp)getApplication(); + mHandler = new MyHandler(this); + mSignInHelper = new SignInHelper(this); - - mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - mDefaultLocale = mPrefs.getString(getString(R.string.pref_default_locale), null); - - this.getSupportActionBar().hide(); - - - mDoSignIn = getIntent().getBooleanExtra("doSignIn", true); - - checkForCrashes(); - - checkForUpdates(); - - + + Intent intent = getIntent(); + mDoSignIn = intent.getBooleanExtra("doSignIn", true); + mDoLock = intent.getBooleanExtra("doLock", false); + + if (!mDoLock) + { + if (checkMediaStoreFile()) { + return; + } + + mApp.maybeInit(this); + } + + if (ImApp.mUsingCacheword) + connectToCacheWord(); + else + { + if (openEncryptedStores(null, false)) { + try + { + ChatFileStore.initWithoutPassword(this); + } + catch (Exception e) + { + Log.d(ImApp.LOG_TAG,"unable to mount VFS store"); //but let's not crash the whole app right now + } + } else { + connectToCacheWord(); //first time setup + } + } + + // if we have an incoming contact, send it to the right place + String scheme = intent.getScheme(); + if(TextUtils.equals(scheme, "xmpp")) + { + intent.setClass(this, AddContactActivity.class); + startActivity(intent); + finish(); + return; + } } - + private void connectToCacheWord () { - - mCacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); mCacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); - - ((ImApp)getApplication()).setCacheWord(mCacheWord); - + mCacheWord.connectToService(); - + + } - - + + @SuppressWarnings("deprecation") - private boolean cursorUnlocked(String pKey) { + private boolean cursorUnlocked(String pKey, boolean allowCreate) { try { - mApp = (ImApp)getApplication(); - mHandler = new MyHandler(this); - ImPluginHelper.getInstance(this).loadAvailablePlugins(); - Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; - - uri = uri.buildUpon().appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pKey).build(); - + + Builder builder = uri.buildUpon(); + if (pKey != null) + builder.appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pKey); + if (!allowCreate) + builder = builder.appendQueryParameter(ImApp.NO_CREATE_KEY, "1"); + uri = builder.build(); + mProviderCursor = managedQuery(uri, PROVIDER_PROJECTION, Imps.Provider.CATEGORY + "=?" /* selection */, new String[] { ImApp.IMPS_CATEGORY } /* selection args */, Imps.Provider.DEFAULT_SORT_ORDER); - + if (mProviderCursor != null) { + ImPluginHelper.getInstance(this).loadAvailablePlugins(); + mProviderCursor.moveToFirst(); - + return true; } else { return false; } - + } catch (Exception e) { - Log.e(ImApp.LOG_TAG, e.getMessage(), e); - - Toast.makeText(this, "MAJOR ERROR: Unable to unlock or load app database. Please re-install the app or clear data.",Toast.LENGTH_LONG).show(); - finish(); - + // Only complain if we thought this password should succeed + if (allowCreate) { + Log.e(ImApp.LOG_TAG, e.getMessage(), e); + + Toast.makeText(this, getString(R.string.error_welcome_database), Toast.LENGTH_LONG).show(); + finish(); + } + // needs to be unlocked return false; } @@ -178,73 +209,43 @@ protected void onPause() { mHandler.unregisterForBroadcastEvents(); super.onPause(); - mCacheWord.onPause(); + if (mCacheWord != null) + mCacheWord.onPause(); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mCacheWord != null) + mCacheWord.disconnect(); + if (dialog != null) + dialog.dismiss(); + } @Override protected void onResume() { super.onResume(); - if (mCacheWord == null) - connectToCacheWord (); - - try - { + if (mCacheWord != null) mCacheWord.onResume(); - } - catch (Exception e) - { - Log.e("CacheWord","unable to bind to cacheword"); - } - - if (!mCacheWord.isLocked()) - { - String pkey = SQLCipherOpenHelper.encodeRawKey(mCacheWord.getEncryptionKey()); - - if (pkey != null) - { - cursorUnlocked(pkey); - - doOnResume(); - } - } - else - { - showLockScreen(); - } - - } private void doOnResume() { - - - if (mApp == null) { - - mApp = (ImApp)getApplication(); - mHandler = new MyHandler(this); - ImPluginHelper.getInstance(this).loadAvailablePlugins(); - } - - - mApp.setAppTheme(this); mHandler.registerForBroadcastEvents(); - int countSignedIn = accountsSignedIn(); int countAvailable = accountsAvailable(); - int countConfigured = accountsConfigured(); - if (countAvailable == 1) { // If just one account is available for auto-signin, go there immediately after service starts trying // to connect. - mSignInHelper.setSignInListener(new SignInHelper.Listener() { + mSignInHelper.setSignInListener(new SignInHelper.SignInListener() { + @Override public void connectedToService() { } + @Override public void stateChanged(int state, long accountId) { if (state == ImConnection.LOGGING_IN) { mSignInHelper.goToAccount(accountId); @@ -254,116 +255,29 @@ public void stateChanged(int state, long accountId) { } else { mSignInHelper.setSignInListener(null); } - - - if (countSignedIn == 0 && countAvailable > 0 && !mDidAutoLaunch && mDoSignIn) { - mDidAutoLaunch = true; - signInAll(); - showAccounts(); - } else if (countSignedIn >= 1) { - showActiveAccount(); - } else { - showAccounts(); - }/* - else - { - setContentView(R.layout.welcome_activity); - - Button getStarted = ((Button) findViewById(R.id.btnSplashAbout)); - - getStarted.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - finish(); - Intent intent = new Intent(getBaseContext(), AboutActivity.class); - startActivity(intent); - } - }); - - - }*/ - - } - - - // Show signed in account - - protected boolean showActiveAccount() { - if (!mProviderCursor.moveToFirst()) - return false; - do { - if (!mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN) && isSignedIn(mProviderCursor)) { - showAccounts(); - return true; - } - } while (mProviderCursor.moveToNext()); - return false; - } - - - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.main_list_menu, menu); - - return true; - } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_account_settings: - finish(); - showAccounts(); - return true; - - case R.id.menu_about: - showAbout(); - return true; - - case R.id.menu_locale: - showLocaleDialog(); - return true; + Intent intent = getIntent(); + if (intent != null && intent.getAction() != null && (!intent.getAction().equals(Intent.ACTION_MAIN))) + { + handleIntentAPILaunch(intent); } - return super.onOptionsItemSelected(item); - } - - private void signInAll() { - - Log.i(TAG, "signInAll"); - if (!mProviderCursor.moveToFirst()) - return; - - do { - int position = mProviderCursor.getPosition(); - signInAccountAtPosition(position); - - } while (mProviderCursor.moveToNext()); - - } - - private boolean signInAccountAtPosition(int position) { - mProviderCursor.moveToPosition(position); - - if (!mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { - int state = mProviderCursor.getInt(ACCOUNT_CONNECTION_STATUS); - long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); - - if (state == Imps.ConnectionStatus.OFFLINE) { - boolean isKeepSignedIn = mProviderCursor.getInt(ACTIVE_ACCOUNT_KEEP_SIGNED_IN) != 0; - if (isKeepSignedIn) { - signIn(accountId); - return true; - } - + else + { + if (countAvailable > 0 && mDoSignIn && mProviderCursor.moveToFirst()) { + do { + if (!mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { + int state = mProviderCursor.getInt(ACCOUNT_CONNECTION_STATUS); + long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + if (mProviderCursor.getInt(ACTIVE_ACCOUNT_KEEP_SIGNED_IN) != 0) { + signIn(accountId); + } + } + } while (mProviderCursor.moveToNext()); } + startActivity(new Intent(getBaseContext(), NewChatActivity.class)); + finish(); } - - return false; } private void signIn(long accountId) { @@ -388,11 +302,6 @@ private void signIn(long accountId) { boolean isActive = false; // TODO(miron) mSignInHelper.signIn(password, providerId, accountId, isActive); } - - boolean isSigningIn(Cursor cursor) { - int connectionStatus = cursor.getInt(ACCOUNT_CONNECTION_STATUS); - return connectionStatus == Imps.ConnectionStatus.CONNECTING; - } private boolean isSignedIn(Cursor cursor) { int connectionStatus = cursor.getInt(ACCOUNT_CONNECTION_STATUS); @@ -400,20 +309,6 @@ private boolean isSignedIn(Cursor cursor) { return connectionStatus == Imps.ConnectionStatus.ONLINE; } - private int accountsSignedIn() { - if (!mProviderCursor.moveToFirst()) { - return 0; - } - int count = 0; - do { - if (isSignedIn(mProviderCursor)) { - count++; - } - } while (mProviderCursor.moveToNext()); - - return count; - } - private int accountsAvailable() { if (!mProviderCursor.moveToFirst()) { return 0; @@ -430,44 +325,23 @@ private int accountsAvailable() { return count; } - private int accountsConfigured() { - if (!mProviderCursor.moveToFirst()) { - return 0; - } - int count = 0; - do { - if (!mProviderCursor.isNull(ACTIVE_ACCOUNT_USERNAME_COLUMN) && - !mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { - count++; - } - } while (mProviderCursor.moveToNext()); - - return count; - } + void handleIntentAPILaunch (Intent srcIntent) + { + Intent intent = new Intent(this, ImUrlActivity.class); + intent.setAction(srcIntent.getAction()); - // private void showAccountSetup() { - // if (!mProviderCursor.moveToFirst() || mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { - // // add account - // startActivity(getCreateAccountIntent()); - // } else { - // // edit existing account - // startActivity(getEditAccountIntent()); - // } - // } - - private void showAbout() { - //TODO implement this about form - Toast.makeText(this, "About Gibberbot\nhttps://guardianproject.info/apps/gibber", - Toast.LENGTH_LONG).show(); - } + if (srcIntent.getData() != null) + intent.setData(srcIntent.getData()); - + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (srcIntent.getExtras()!= null) + intent.putExtras(srcIntent.getExtras()); + startActivity(intent); - void showAccounts() { - startActivity(new Intent(getBaseContext(), AccountListActivity.class)); + setIntent(null); finish(); } - + Intent getEditAccountIntent() { Intent intent = new Intent(Intent.ACTION_EDIT, ContentUris.withAppendedId( Imps.Account.CONTENT_URI, mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN))); @@ -475,8 +349,8 @@ Intent getEditAccountIntent() { intent.addCategory(getProviderCategory(mProviderCursor)); return intent; } - - + + private String getProviderCategory(Cursor cursor) { return cursor.getString(PROVIDER_CATEGORY_COLUMN); } @@ -496,88 +370,221 @@ public void handleMessage(Message msg) { } } - private void showLocaleDialog() { - AlertDialog.Builder ad = new AlertDialog.Builder(this); - ad.setTitle(getResources().getString(R.string.KEY_PREF_LANGUAGE_TITLE)); - - Configuration config = getResources().getConfiguration(); - String defaultLangName = config.locale.getDefault().getDisplayName(); - String defaultLangCode = config.locale.getDefault().getCountry(); - - String[] langs = getResources().getStringArray(R.array.languages); - langs[0] = langs[0] + " (" + defaultLangName + ")"; - - ad.setItems(langs, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - - String[] locs = getResources().getStringArray(R.array.languages_values); - - if (which < locs.length) { - ((ImApp)getApplication()).setNewLocale(WelcomeActivity.this.getBaseContext(), locs[which]); - - Intent intent = getIntent(); - finish(); - startActivity(intent); - } - } - }); - - ad.show(); - } - @Override public void onCacheWordUninitialized() { Log.d(ImApp.LOG_TAG,"cache word uninit"); - - showLockScreen(); + + if (mDoLock) { + completeShutdown(); + } else { + showLockScreen(); + } + finish(); } void showLockScreen() { Intent intent = new Intent(this, LockScreenActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra("originalIntent", getIntent()); + Intent returnIntent = getIntent(); + returnIntent.putExtra("doSignIn", mDoSignIn); + intent.putExtra("originalIntent", returnIntent); startActivity(intent); - + } - + @Override public void onCacheWordLocked() { - - + if (mDoLock) { + Log.d(ImApp.LOG_TAG, "cacheword lock requested but already locked"); + + } else { + showLockScreen(); + } + finish(); } @Override public void onCacheWordOpened() { - Log.d(ImApp.LOG_TAG,"cache word opened"); - - - String pkey = SQLCipherOpenHelper.encodeRawKey(mCacheWord.getEncryptionKey()); - - if (pkey != null) - { - - cursorUnlocked(pkey); - - ((ImApp)getApplication()).initOtrStoreKey(); - - - int defaultTimeout = Integer.parseInt(mPrefs.getString("pref_cacheword_timeout",ImApp.DEFAULT_TIMEOUT_CACHEWORD)); - mCacheWord.setTimeoutMinutes(defaultTimeout); - } - - + if (mDoLock) { + completeShutdown(); + return; + } + + byte[] encryptionKey = mCacheWord.getEncryptionKey(); + openEncryptedStores(encryptionKey, true); + + ChatFileStore.init(this, mCacheWord.getEncryptionKey()); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void shutdownAndLock(Activity activity) { + ImApp app = (ImApp) activity.getApplication(); + if (app != null) { + for (IImConnection conn : app.getActiveConnections()) { + try { + conn.logout(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + Intent intent = new Intent(activity, WelcomeActivity.class); + // Request lock + intent.putExtra("doLock", true); + // Clear the backstack + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= 11) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + activity.startActivity(intent); + activity.finish(); + } + + private void completeShutdown () + { + /* ignore unmount errors and quit ASAP. Threads actively using the VFS will + * cause IOCipher's VirtualFileSystem.unmount() to throw an IllegalStateException */ + try { + ChatFileStore.unmount(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + new AsyncTask() { + + @Override + protected void onPreExecute() { + if (mApp.getActiveConnections().size() > 0) + { + dialog = new ProgressDialog(WelcomeActivity.this); + dialog.setCancelable(true); + dialog.setMessage(getString(R.string.signing_out_wait)); + dialog.show(); + } + } + + @Override + protected String doInBackground(String... params) { + + boolean stillConnected = true; + + while (stillConnected) + { + + try{ + IImConnection conn = mApp.getActiveConnections().iterator().next(); + + if (conn.getState() == ImConnection.DISCONNECTED || conn.getState() == ImConnection.LOGGING_OUT) + { + stillConnected = false; + } + else + { + conn.logout(); + stillConnected = true; + } + + + Thread.sleep(500); + }catch(Exception e){} + + + } + + return ""; + } + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + + if (dialog != null) + dialog.dismiss(); + + mApp.forceStopImService(); + + Imps.clearPassphrase(mApp); + + if (mCacheWord != null) + { + mCacheWord.manuallyLock(); + } + + Intent cacheWordIntent = CacheWordService + .getBlankServiceIntent(getApplicationContext()); + stopService(cacheWordIntent); + finish(); + } + }.execute(); + } + + private boolean checkMediaStoreFile() { + /* First set location based on pref, then override based on where the file is. + * This crazy logic is necessary to support old installs that used logic that + * is not really predictable, since it was based on whether the SD card was + * present or not. */ + File internalDbFile = new File(ChatFileStore.getInternalDbFilePath(this)); + boolean internalDbFileUsabe = internalDbFile.isFile() && internalDbFile.canWrite(); + + boolean externalDbFileUsable = false; + java.io.File externalFilesDir = getExternalFilesDir(null); + if (externalFilesDir != null) { + File externalDbFile = new File(ChatFileStore.getExternalDbFilePath(this)); + externalDbFileUsable = externalDbFile.isFile() && externalDbFile.canWrite(); + } + final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + boolean isPrefSet = settings.contains( + getString(R.string.key_store_media_on_external_storage_pref)); + boolean storeMediaOnExternalStorage; + if (isPrefSet) { + storeMediaOnExternalStorage = settings.getBoolean( + getString(R.string.key_store_media_on_external_storage_pref), false); + if (storeMediaOnExternalStorage && !externalDbFileUsable) { + Intent i = new Intent(this, MissingChatFileStoreActivity.class); + startActivity(i); + finish(); + return true; + } + } else { + /* only use external if file already exists only there or internal is almost full */ + boolean forceExternalStorage = !enoughSpaceInInternalStorage(internalDbFile); + if (!internalDbFileUsabe && (externalDbFileUsable || forceExternalStorage)) { + storeMediaOnExternalStorage = true; + } else { + storeMediaOnExternalStorage = false; + } + Editor editor = settings.edit(); + editor.putBoolean(getString(R.string.key_store_media_on_external_storage_pref), + storeMediaOnExternalStorage); + editor.apply(); + } + return false; + } + + private static boolean enoughSpaceInInternalStorage(File f) { + StatFs stat = new StatFs(f.getParent()); + long freeSizeInBytes = stat.getAvailableBlocks() * (long) stat.getBlockSize(); + return freeSizeInBytes > 536870912; // 512 MB } - - private void checkForCrashes() { - CrashManager.register(this, ImApp.HOCKEY_APP_ID); - } + private boolean openEncryptedStores(byte[] key, boolean allowCreate) { + String pkey = (key != null) ? new String(SQLCipherOpenHelper.encodeRawKey(key)) : ""; + OtrAndroidKeyManagerImpl.setKeyStorePassword(pkey); + + if (cursorUnlocked(pkey, allowCreate)) { - private void checkForUpdates() { + if (mDoLock) + completeShutdown(); + else + doOnResume(); + + return true; + } else { + return false; + } + } + + + private void checkForUpdates() { // Remove this for store builds! UpdateManager.register(this, ImApp.HOCKEY_APP_ID); - } + } } diff --git a/src/info/guardianproject/otr/app/im/app/adapter/ChatListenerAdapter.java b/src/info/guardianproject/otr/app/im/app/adapter/ChatListenerAdapter.java index 044be6fa3..e1dcfb4d6 100644 --- a/src/info/guardianproject/otr/app/im/app/adapter/ChatListenerAdapter.java +++ b/src/info/guardianproject/otr/app/im/app/adapter/ChatListenerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,15 +17,13 @@ package info.guardianproject.otr.app.im.app.adapter; +import info.guardianproject.otr.app.im.IChatListener; +import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.Message; - -import info.guardianproject.otr.app.im.IChatSession; -import info.guardianproject.otr.app.im.IChatListener; import info.guardianproject.util.LogCleaner; - import android.os.RemoteException; import android.util.Log; @@ -45,10 +43,18 @@ public void onContactLeft(IChatSession ses, Contact contact) { } } - public void onIncomingMessage(IChatSession ses, Message msg) { + public boolean onIncomingMessage(IChatSession ses, Message msg) { if (Log.isLoggable(TAG, Log.DEBUG)) { LogCleaner.debug(TAG, "onIncomingMessage(" + ses + ", " + msg + ")"); } + + return true; + } + + public void onIncomingData(IChatSession ses, byte[] data) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + LogCleaner.debug(TAG, "onIncomingMessage(" + ses + ", len=" + data.length + ")"); + } } public void onSendMessageError(IChatSession ses, Message msg, ImErrorInfo error) { @@ -82,4 +88,27 @@ public void onStatusChanged(IChatSession ses) throws RemoteException { LogCleaner.debug(TAG, "onStatusChanged(" + ses + ")"); } } + + @Override + public void onIncomingFileTransfer(String from, String file) throws RemoteException { + if (Log.isLoggable(TAG, Log.DEBUG)) { + LogCleaner.debug(TAG, "onIncomingFileTransfer(" + from + "," + file + ")"); + } + } + + @Override + public void onIncomingFileTransferProgress(String file, int percent) throws RemoteException { + if (Log.isLoggable(TAG, Log.DEBUG)) { + LogCleaner.debug(TAG, "onIncomingFileTransferProgress(" + file + "," + percent + ")"); + } + } + + @Override + public void onIncomingFileTransferError(String file, String message) throws RemoteException { + if (Log.isLoggable(TAG, Log.DEBUG)) { + LogCleaner.debug(TAG, "onIncomingFileTransferError(" + file + "," + message + ")"); + } + + } + } diff --git a/src/info/guardianproject/otr/app/im/app/adapter/ChatSessionListenerAdapter.java b/src/info/guardianproject/otr/app/im/app/adapter/ChatSessionListenerAdapter.java index 4ed7ca618..6f88bcfbd 100644 --- a/src/info/guardianproject/otr/app/im/app/adapter/ChatSessionListenerAdapter.java +++ b/src/info/guardianproject/otr/app/im/app/adapter/ChatSessionListenerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/adapter/ConnectionListenerAdapter.java b/src/info/guardianproject/otr/app/im/app/adapter/ConnectionListenerAdapter.java index 07b818df5..41fde00c0 100644 --- a/src/info/guardianproject/otr/app/im/app/adapter/ConnectionListenerAdapter.java +++ b/src/info/guardianproject/otr/app/im/app/adapter/ConnectionListenerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/app/adapter/ContactListListenerAdapter.java b/src/info/guardianproject/otr/app/im/app/adapter/ContactListListenerAdapter.java index 1421163cb..80470acd5 100644 --- a/src/info/guardianproject/otr/app/im/app/adapter/ContactListListenerAdapter.java +++ b/src/info/guardianproject/otr/app/im/app/adapter/ContactListListenerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/engine/Address.java b/src/info/guardianproject/otr/app/im/engine/Address.java index 3faf1ffcd..9f0fc100c 100644 --- a/src/info/guardianproject/otr/app/im/engine/Address.java +++ b/src/info/guardianproject/otr/app/im/engine/Address.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,45 +18,54 @@ package info.guardianproject.otr.app.im.engine; import android.os.Parcel; +import android.text.TextUtils; /** * An abstract representation of the address to any addressable entities such as * User, Contact list, User Group, etc. */ public abstract class Address { - + /** * Gets a string representation of this address. - * + * * @return a string representation of this address. */ public abstract String getAddress(); - + /** * Gets a user friendly screen name of this address object. - * + * * @return the screen name. */ - public abstract String getScreenName(); + public abstract String getUser(); /** * Gets a resource value - * + * * @return the resource name. */ public abstract String getResource(); - + /** + * Gets the bare address without any resource + * @return the bare address + */ + public abstract String getBareAddress(); + + public abstract void writeToParcel(Parcel dest); public abstract void readFromParcel(Parcel source); - + static public boolean hasResource(String address) { return address.contains("/"); } - + static public String stripResource(String address) { - if (address.contains("/")) + if (TextUtils.isEmpty(address)) + return "null"; + else if (address.contains("/")) return address.split("/")[0]; else return address; diff --git a/src/info/guardianproject/otr/app/im/engine/AddressParcelHelper.java b/src/info/guardianproject/otr/app/im/engine/AddressParcelHelper.java index 0f7aa50ed..a5b4a0672 100644 --- a/src/info/guardianproject/otr/app/im/engine/AddressParcelHelper.java +++ b/src/info/guardianproject/otr/app/im/engine/AddressParcelHelper.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/engine/ChatGroup.java b/src/info/guardianproject/otr/app/im/engine/ChatGroup.java index 935f1e7e3..230122741 100644 --- a/src/info/guardianproject/otr/app/im/engine/ChatGroup.java +++ b/src/info/guardianproject/otr/app/im/engine/ChatGroup.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -36,7 +36,7 @@ public ChatGroup(Address address, String name, ChatGroupManager manager) { public ChatGroup(Address address, String name, Collection members, ChatGroupManager manager) { - + mAddress = address; mName = name; mManager = manager; @@ -55,7 +55,7 @@ public Address getAddress() { /** * Gets the name of the group. - * + * * @return the name of the group. */ public String getName() { @@ -72,7 +72,7 @@ public void removeMemberListener(GroupMemberListener listener) { /** * Gets an unmodifiable collection of the members of the group. - * + * * @return an unmodifiable collection of the members of the group. */ public List getMembers() { @@ -81,7 +81,7 @@ public List getMembers() { /** * Adds a member to this group. TODO: more docs on async callbacks. - * + * * @param contact the member to add. */ public synchronized void addMemberAsync(Contact contact) { @@ -90,7 +90,7 @@ public synchronized void addMemberAsync(Contact contact) { /** * Removes a member from this group. TODO: more docs on async callbacks. - * + * * @param contact the member to remove. */ public synchronized void removeMemberAsync(Contact contact) { @@ -99,7 +99,7 @@ public synchronized void removeMemberAsync(Contact contact) { /** * Notifies that a contact has joined into this group. - * + * * @param contact the contact who has joined into the group. */ void notifyMemberJoined(Contact contact) { @@ -111,7 +111,7 @@ void notifyMemberJoined(Contact contact) { /** * Notifies that a contact has left this group. - * + * * @param contact the contact who has left this group. */ void notifyMemberLeft(Contact contact) { @@ -124,7 +124,7 @@ void notifyMemberLeft(Contact contact) { /** * Notifies that previous operation on this group has failed. - * + * * @param error the error information. */ void notifyGroupMemberError(ImErrorInfo error) { @@ -132,4 +132,9 @@ void notifyGroupMemberError(ImErrorInfo error) { listener.onError(this, error); } } + + @Override + public boolean isGroup() { + return true; + } } diff --git a/src/info/guardianproject/otr/app/im/engine/ChatGroupManager.java b/src/info/guardianproject/otr/app/im/engine/ChatGroupManager.java index b061b1a5b..fff871baf 100644 --- a/src/info/guardianproject/otr/app/im/engine/ChatGroupManager.java +++ b/src/info/guardianproject/otr/app/im/engine/ChatGroupManager.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -27,7 +27,7 @@ * ChatGroupManager manages the creating, removing and the member of ChatGroups. */ public abstract class ChatGroupManager { - + protected HashMap mGroups; protected HashMap mInvitations; @@ -45,7 +45,7 @@ protected ChatGroupManager() { /** * Adds a GroupListener to this manager so that it will be notified when a * certain group changes. - * + * * @param listener the listener to be notified. */ public void addGroupListener(GroupListener listener) { @@ -55,7 +55,7 @@ public void addGroupListener(GroupListener listener) { /** * Removes a GroupListener from this manager so that it won't be notified * any more. - * + * * @param listener the listener to remove. */ public void removeGroupListener(GroupListener listener) { @@ -65,7 +65,7 @@ public void removeGroupListener(GroupListener listener) { /** * Sets the InvitationListener to the manager so that it will be notified * when an invitation from another users received. - * + * * @param listener the InvitationListener. */ public synchronized void setInvitationListener(InvitationListener listener) { @@ -78,17 +78,17 @@ public synchronized void setInvitationListener(InvitationListener listener) { * group is created or any error occurs. The newly created group is a * temporary group and will be automatically deleted when all joined users * have left. - * + * * @param name the name of the ChatGroup to be created. - * @throws Exception + * @throws Exception */ - public abstract boolean createChatGroupAsync(String name) throws Exception; + public abstract boolean createChatGroupAsync(String address, String nickname) throws Exception; /** * Deletes a certain ChatGroup. This method returns immediately and the * registered GroupListeners will be notified when the group is deleted or * any error occurs. Only the administrator of the ChatGroup can delete it. - * + * * @param group the ChatGroup to be deleted. */ public abstract void deleteChatGroupAsync(ChatGroup group); @@ -98,7 +98,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { * the GroupGroupListeners registered on the group will be notified when the * member is added or any error occurs. Only the administrator of the * ChatGroup can add member to it. - * + * * @param group the ChatGroup to which the member will add. * @param contact the member to add. */ @@ -109,7 +109,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { * and the GroupGroupListeners registered on the group will be notified when * the member is added or any error occurs. Only the administrator of the * ChatGroup can remove its members. - * + * * @param group the ChatGroup whose member will be removed. * @param contact the member to be removed. */ @@ -119,7 +119,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { * Joins into a certain ChatGroup. This method returns immediately and the * registered GroupListeners will be notified when the user joined into the * group or any error occurs. - * + * * @param address the address of the ChatGroup. */ public abstract void joinChatGroupAsync(Address address); @@ -128,7 +128,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { * Leaves a certain ChatGroup.This method returns immediately and the * registered GroupListeners will be notified when the the user left the * group or any error occurs. - * + * * @param group the ChatGroup. */ public abstract void leaveChatGroupAsync(ChatGroup group); @@ -137,7 +137,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { * Invites a user to join a certain ChatGroup. If success, the invitee will * receive an invitation with information of the group. Otherwise, the * registered GroupListeners will be notified if any error occurs. - * + * * @param group the ChatGroup. * @param invitee the invitee. */ @@ -146,7 +146,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { /** * Accepts an invitation. The user will join the group automatically after * accept the invitation. - * + * * @param invitation the invitation to accept. */ public abstract void acceptInvitationAsync(Invitation invitation); @@ -154,7 +154,7 @@ public synchronized void setInvitationListener(InvitationListener listener) { /** * Accepts an invitation. The user can only accept or reject the same * invitation only once. - * + * * @param inviteId the id of the invitation to accept. * @see #acceptInvitationAsync(Invitation) */ @@ -167,7 +167,7 @@ public void acceptInvitationAsync(String inviteId) { /** * Rejects an invitation. - * + * * @param inviteId the id of the invitation to reject. * @see #rejectInvitationAsync(Invitation) */ @@ -180,24 +180,24 @@ public void rejectInvitationAsync(String inviteId) { /** * Rejects an invitation. - * + * * @param invitation the invitation to reject. */ public abstract void rejectInvitationAsync(Invitation invitation); /** * Gets a ChatGroup by address. - * + * * @param address the address of the ChatGroup. * @return a ChatGroup. */ public ChatGroup getChatGroup(Address address) { - return mGroups.get(address.getAddress()); + return mGroups.get(address.getBareAddress()); } /** * Notifies the GroupListeners that a ChatGroup has changed. - * + * * @param groupAddress the address of group which has changed. * @param joined a list of users that have joined the group. * @param left a list of users that have left the group. @@ -206,7 +206,7 @@ protected void notifyGroupChanged(Address groupAddress, ArrayList joine ArrayList left) { ChatGroup group = mGroups.get(groupAddress.getAddress()); if (group == null) { - group = new ChatGroup(groupAddress, groupAddress.getScreenName(), this); + group = new ChatGroup(groupAddress, groupAddress.getUser(), this); mGroups.put(groupAddress.getAddress(), group); } if (joined != null) { @@ -244,7 +244,7 @@ protected synchronized void notifyJoinedGroup(ChatGroup group) { /** * Notifies the GroupListeners that the user has left a certain group. - * + * * @param groupAddress the address of the group. */ protected synchronized void notifyLeftGroup(ChatGroup group) { @@ -273,7 +273,7 @@ protected synchronized void notifyGroupInvitation(Invitation invitation) { /** * Notifies that a contact has joined into this group. - * + * * @param group the group into which the contact has joined. * @param contact the contact who has joined into the group. */ @@ -283,7 +283,7 @@ protected void notifyMemberJoined(ChatGroup group, Contact contact) { /** * Notifies that a contact has left this group. - * + * * @param group the group which the contact has left. * @param contact the contact who has left this group. */ @@ -293,10 +293,17 @@ protected void notifyMemberLeft(ChatGroup group, Contact contact) { /** * Notifies that previous operation on this group has failed. - * + * * @param error the error information. */ protected void notifyGroupMemberError(ChatGroup group, ImErrorInfo error) { group.notifyGroupMemberError(error); } + + /** + * ask the server what the default host is for MUC + * @return + */ + public abstract String getDefaultMultiUserChatServer (); + } diff --git a/src/info/guardianproject/otr/app/im/engine/ChatSession.java b/src/info/guardianproject/otr/app/im/engine/ChatSession.java index 3ab432af4..59db2933b 100644 --- a/src/info/guardianproject/otr/app/im/engine/ChatSession.java +++ b/src/info/guardianproject/otr/app/im/engine/ChatSession.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,12 +18,12 @@ package info.guardianproject.otr.app.im.engine; import info.guardianproject.otr.OtrChatManager; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import info.guardianproject.otr.app.im.provider.Imps; import java.util.Collections; import java.util.List; import java.util.Vector; -import java.util.concurrent.CopyOnWriteArrayList; import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionStatus; @@ -38,23 +38,22 @@ public class ChatSession { private ImEntity mParticipant; private ChatSessionManager mManager; - private OtrChatManager mOtrChatManager; + // private OtrChatManager mOtrChatManager; - private CopyOnWriteArrayList mListeners; + private MessageListener mListener = null; private Vector mHistoryMessages; + /** * Creates a new ChatSession with a particular participant. - * + * * @param participant the participant with who the user communicates. * @param connection the underlying network connection. */ ChatSession(ImEntity participant, ChatSessionManager manager) { mParticipant = participant; mManager = manager; - mListeners = new CopyOnWriteArrayList(); mHistoryMessages = new Vector(); - } public ImEntity getParticipant() { @@ -65,40 +64,35 @@ public void setParticipant(ImEntity participant) { mParticipant = participant; } + /* public void setOtrChatManager(OtrChatManager otrChatManager) { mOtrChatManager = otrChatManager; } public OtrChatManager getOtrChatManager() { return mOtrChatManager; - } + }*/ /** * Adds a MessageListener so that it can be notified of any new message in * this session. - * + * * @param listener */ - public synchronized void addMessageListener(MessageListener listener) { - if ((listener != null) && !mListeners.contains(listener)) { - mListeners.add(listener); - } + public void setMessageListener(MessageListener listener) { + mListener = listener; } - - /** - * Removes a listener from this session. - * - * @param listener - */ - public synchronized void removeMessageListener(MessageListener listener) { - mListeners.remove(listener); + + public MessageListener getMessageListener () + { + return mListener; } /** * Sends a text message to other participant(s) in this session * asynchronously and adds the message to the history. TODO: more docs on * async callbacks. - * + * * @param text the text to send. */ // TODO these sendMessageAsync() should probably be renamed to sendMessageAsyncAndLog()/ @@ -111,23 +105,21 @@ public void sendMessageAsync(String text) { /** * Sends a message to other participant(s) in this session asynchronously * and adds the message to the history. TODO: more docs on async callbacks. - * + * * @param message the message to send. */ public int sendMessageAsync(Message message) { - if (message.getTo() == null) - message.setTo(mParticipant.getAddress()); - - // TODO OTRCHAT setFrom here, therefore add the mConnection in ChatSession - ??? NF 8/2013 still needs to be done? - SessionStatus otrStatus = mOtrChatManager.getSessionStatus(message.getFrom().getAddress(),message.getTo().getAddress()); + OtrChatManager cm = OtrChatManager.getInstance(); + SessionID sId = cm.getSessionId(message.getFrom().getAddress(),mParticipant.getAddress().getAddress()); + SessionStatus otrStatus = cm.getSessionStatus(sId); + message.setTo(new XmppAddress(sId.getRemoteUserId())); + if (otrStatus == SessionStatus.ENCRYPTED) { - SessionID sId = mOtrChatManager.getSessionId(message.getFrom().getAddress(),message.getTo().getAddress()); - boolean verified = mOtrChatManager.getKeyManager().isVerified(sId); - - + boolean verified = cm.getKeyManager().isVerified(sId); + if (verified) { message.setType(Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED); @@ -136,102 +128,129 @@ public int sendMessageAsync(Message message) { { message.setType(Imps.MessageType.OUTGOING_ENCRYPTED); } - - mHistoryMessages.add(message); - - mOtrChatManager.transformSending(message); - - mManager.sendMessageAsync(this, message); + } else if (otrStatus == SessionStatus.FINISHED) { - - onSendMessageError(message, new ImErrorInfo(ImErrorInfo.INVALID_SESSION_CONTEXT,"Please turn off encryption")); + message.setType(Imps.MessageType.POSTPONED); + // onSendMessageError(message, new ImErrorInfo(ImErrorInfo.INVALID_SESSION_CONTEXT,"error - session finished")); + return message.getType(); } else { - mHistoryMessages.add(message); - - mOtrChatManager.transformSending(message); + //not encrypted, send to all + //message.setTo(new XmppAddress(XmppAddress.stripResource(sId.getRemoteUserId()))); + message.setType(Imps.MessageType.OUTGOING); + } + mHistoryMessages.add(message); + boolean canSend = cm.transformSending(message); + + if (canSend) + { mManager.sendMessageAsync(this, message); - - } + } + else + { + //can't be sent due to OTR state + message.setType(Imps.MessageType.POSTPONED); + + } + return message.getType(); + + + + } + + /** + * Sends message + data to other participant(s) in this session asynchronously. + * + * @param message the message to send. + * @param data the data to send. + */ + public void sendDataAsync(Message message, boolean isResponse, byte[] data) { + if (message.getTo() == null) + message.setTo(mParticipant.getAddress()); + + OtrChatManager cm = OtrChatManager.getInstance(); + + cm.transformSending(message, isResponse, data); + + mManager.sendMessageAsync(this, message); } /** * Called by ChatSessionManager when received a message of the ChatSession. * All the listeners registered in this session will be notified. - * + * * @param message the received message. - * + * * @return true if the message was processed correctly, or false * otherwise (e.g. decryption error) */ public boolean onReceiveMessage(Message message) { mHistoryMessages.add(message); - SessionStatus otrStatus = mOtrChatManager.getSessionStatus(message.getTo().getAddress(), message.getFrom().getAddress()); + OtrChatManager cm = OtrChatManager.getInstance(); - SessionID sId = mOtrChatManager.getSessionId(message.getTo().getAddress(),message.getFrom().getAddress()); - - if (otrStatus == SessionStatus.ENCRYPTED) + if (cm != null) { - boolean verified = mOtrChatManager.getKeyManager().isVerified(sId); - - if (verified) - { - message.setType(Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED); - } - else + SessionStatus otrStatus = cm.getSessionStatus(message.getTo().getAddress(), message.getFrom().getAddress()); + + SessionID sId = cm.getSessionId(message.getTo().getAddress(),message.getFrom().getAddress()); + + if (otrStatus == SessionStatus.ENCRYPTED) { - message.setType(Imps.MessageType.INCOMING_ENCRYPTED); + boolean verified = cm.getKeyManager().isVerified(sId); + + if (verified) + { + message.setType(Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED); + } + else + { + message.setType(Imps.MessageType.INCOMING_ENCRYPTED); + } + } - - } - - - // BUG it only seems to find the most recently added listener. - boolean good = true; - - for (MessageListener listener : mListeners) { - good = good && listener.onIncomingMessage(this, message); + } - - return good; + + if (mListener != null) + mListener.onIncomingMessage(this, message); + + return true; } public void onMessageReceipt(String id) { - for (MessageListener listener : mListeners) { - listener.onIncomingReceipt(this, id); - } + if (mListener != null) + mListener.onIncomingReceipt(this, id); + } public void onMessagePostponed(String id) { - for (MessageListener listener : mListeners) { - listener.onMessagePostponed(this, id); - } + if (mListener != null) + mListener.onMessagePostponed(this, id); } public void onReceiptsExpected() { - for (MessageListener listener : mListeners) { - listener.onReceiptsExpected(this); - } + if (mListener != null) + mListener.onReceiptsExpected(this); } /** * Called by ChatSessionManager when an error occurs to send a message. - * + * * @param message - * + * * @param error the error information. */ public void onSendMessageError(Message message, ImErrorInfo error) { - for (MessageListener listener : mListeners) { - listener.onSendMessageError(this, message, error); - } + if (mListener != null) + mListener.onSendMessageError(this, message, error); + } public void onSendMessageError(String messageId, ImErrorInfo error) { @@ -246,11 +265,12 @@ public void onSendMessageError(String messageId, ImErrorInfo error) { /** * Returns a unmodifiable list of the history messages in this session. - * + * * @return a unmodifiable list of the history messages in this session. */ public List getHistoryMessages() { return Collections.unmodifiableList(mHistoryMessages); } + } diff --git a/src/info/guardianproject/otr/app/im/engine/ChatSessionListener.java b/src/info/guardianproject/otr/app/im/engine/ChatSessionListener.java index 252a5bf98..166dabe17 100644 --- a/src/info/guardianproject/otr/app/im/engine/ChatSessionListener.java +++ b/src/info/guardianproject/otr/app/im/engine/ChatSessionListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -27,7 +27,7 @@ public interface ChatSessionListener { /** * Called when a new ChatSession is created. - * + * * @param session the created ChatSession. */ public void onChatSessionCreated(ChatSession session); diff --git a/src/info/guardianproject/otr/app/im/engine/ChatSessionManager.java b/src/info/guardianproject/otr/app/im/engine/ChatSessionManager.java index 945e91d82..16c1e552d 100644 --- a/src/info/guardianproject/otr/app/im/engine/ChatSessionManager.java +++ b/src/info/guardianproject/otr/app/im/engine/ChatSessionManager.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,7 +17,10 @@ package info.guardianproject.otr.app.im.engine; -import java.util.Vector; +import info.guardianproject.otr.app.im.service.ChatSessionAdapter; +import info.guardianproject.otr.app.im.service.ChatSessionManagerAdapter; + +import java.util.Hashtable; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -27,22 +30,33 @@ public abstract class ChatSessionManager { private CopyOnWriteArrayList mListeners; + private ChatSessionManagerAdapter mAdapter; /** Map session to the participant communicate with. */ - protected Vector mSessions; + protected Hashtable mSessions; protected ChatSessionManager() { mListeners = new CopyOnWriteArrayList(); - mSessions = new Vector(); + mSessions = new Hashtable(); + } + + public void setAdapter (ChatSessionManagerAdapter adapter) + { + mAdapter = adapter; + } + + public ChatSessionManagerAdapter getAdapter () + { + return mAdapter; } /** * Registers a ChatSessionListener with the ChatSessionManager to receive * events related to ChatSession. - * + * * @param listener the listener */ - public synchronized void addChatSessionListener(ChatSessionListener listener) { + public void addChatSessionListener(ChatSessionListener listener) { if ((listener != null) && !mListeners.contains(listener)) { mListeners.add(listener); } @@ -50,35 +64,43 @@ public synchronized void addChatSessionListener(ChatSessionListener listener) { /** * Removes a ChatSessionListener so that it will no longer be notified. - * + * * @param listener the listener to remove. */ - public synchronized void removeChatSessionListener(ChatSessionListener listener) { + public void removeChatSessionListener(ChatSessionListener listener) { mListeners.remove(listener); } /** * Creates a new ChatSession with specified participant. - * + * * @param participant the participant. * @return the created ChatSession. */ - public synchronized ChatSession createChatSession(ImEntity participant) { - for (ChatSession session : mSessions) { + public ChatSession createChatSession(ImEntity participant, boolean isNewSession) { + + String sessionKey = Address.stripResource(participant.getAddress().getAddress()); + ChatSession session = mSessions.get(sessionKey); + + if (session == null) + { + session = new ChatSession(participant, this); + ChatSessionAdapter csa = mAdapter.getChatSessionAdapter(session, isNewSession); - if (session.getParticipant().equals(participant)) { - return session; + + mSessions.put(sessionKey,session); + + for (ChatSessionListener listener : mListeners) { + listener.onChatSessionCreated(session); } - } - - - ChatSession session = new ChatSession(participant, this); - for (ChatSessionListener listener : mListeners) { - listener.onChatSessionCreated(session); } - - mSessions.add(session); + else + { + ChatSessionAdapter csa = mAdapter.getChatSessionAdapter(session, isNewSession); + session.setMessageListener(csa.getAdaptee().getMessageListener()); + + } return session; } @@ -87,17 +109,17 @@ public synchronized ChatSession createChatSession(ImEntity participant) { * Closes a ChatSession. This only removes the session from the list; the * protocol implementation should override this if it has special work to * do. - * + * * @param session the ChatSession to close. */ public void closeChatSession(ChatSession session) { - mSessions.remove(session); + mSessions.remove(session.getParticipant().getAddress().getAddress()); } /** * Sends a message to specified participant(s) asynchronously. TODO: more * docs on async callbacks. - * + * * @param message the message to send. */ public abstract void sendMessageAsync(ChatSession session, Message message); diff --git a/src/info/guardianproject/otr/app/im/engine/ConnectionFactory.java b/src/info/guardianproject/otr/app/im/engine/ConnectionFactory.java index 2733e40d6..8e2764f8f 100644 --- a/src/info/guardianproject/otr/app/im/engine/ConnectionFactory.java +++ b/src/info/guardianproject/otr/app/im/engine/ConnectionFactory.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,6 +18,7 @@ package info.guardianproject.otr.app.im.engine; // import info.guardianproject.otr.app.im.plugin.loopback.LoopbackConnection; +import info.guardianproject.otr.app.im.plugin.ImConfigNames; import info.guardianproject.otr.app.im.plugin.xmpp.LLXmppConnection; import info.guardianproject.otr.app.im.plugin.xmpp.XmppConnection; @@ -34,7 +35,7 @@ private ConnectionFactory() { /** * Gets the singleton instance of the factory. - * + * * @return the singleton instance. */ public synchronized static ConnectionFactory getInstance() { @@ -46,14 +47,15 @@ public synchronized static ConnectionFactory getInstance() { /** * Creates a new ImConnection. - * + * * @return the new ImConnection. * @throws IMException if an error occurs during creating a connection. */ - public ImConnection createConnection(Map settings, Context context) + public synchronized ImConnection createConnection(Map settings, Context context) throws ImException { - if ("XMPP".equals(settings.get("im.protocol"))) { - + String protocolName = settings.get(ImConfigNames.PROTOCOL_NAME); + if ("XMPP".equals(protocolName)) { + try { return new XmppConnection(context); @@ -63,14 +65,14 @@ public ImConnection createConnection(Map settings, Context conte throw new ImException(e.getMessage()); } } - if ("LLXMPP".equals(settings.get("im.protocol"))) { + if ("LLXMPP".equals(protocolName)) { return new LLXmppConnection(context); } - /*else if ("LOOPBACK".equals(settings.get("im.protocol"))) { + /*else if ("LOOPBACK".equals(protocolName)) { return new SMSConnection(); } */ else { - throw new ImException("Unsupported protocol"); + throw new ImException("Unsupported protocol: " + protocolName); } } } diff --git a/src/info/guardianproject/otr/app/im/engine/ConnectionListener.java b/src/info/guardianproject/otr/app/im/engine/ConnectionListener.java index 6256a49eb..0805a8581 100644 --- a/src/info/guardianproject/otr/app/im/engine/ConnectionListener.java +++ b/src/info/guardianproject/otr/app/im/engine/ConnectionListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -24,7 +24,7 @@ public interface ConnectionListener { /** * Called when the connection's state has changed. - * + * * @param state the new state of the connection. * @param error the error which caused the state change or null * it's a normal state change. diff --git a/src/info/guardianproject/otr/app/im/engine/Contact.java b/src/info/guardianproject/otr/app/im/engine/Contact.java index be69b444c..498404130 100644 --- a/src/info/guardianproject/otr/app/im/engine/Contact.java +++ b/src/info/guardianproject/otr/app/im/engine/Contact.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,10 +17,12 @@ package info.guardianproject.otr.app.im.engine; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import android.os.Parcel; import android.os.Parcelable; public class Contact extends ImEntity implements Parcelable { + private Address mAddress; private String mName; private Presence mPresence; @@ -46,13 +48,17 @@ public String getName() { return mName; } + public void setName( String aName ) { + mName = aName; + } + public Presence getPresence() { return mPresence; } public boolean equals(Object other) { - - return other instanceof Contact && mAddress.getAddress().equals(((Contact) other).mAddress.getAddress()); + + return other instanceof Contact && mAddress.getBareAddress().equals(((Contact) other).getAddress().getBareAddress()); } public int hashCode() { @@ -61,11 +67,13 @@ public int hashCode() { /* Set the presence of the Contact. Note that this method is public but not * provide to the user. - * + * * @param presence the new presence */ public void setPresence(Presence presence) { mPresence = presence; + if (mPresence != null && mPresence.getResource() != null) + mAddress = new XmppAddress(mAddress.getBareAddress() + '/' + mPresence.getResource()); } public void writeToParcel(Parcel dest, int flags) { diff --git a/src/info/guardianproject/otr/app/im/engine/ContactList.java b/src/info/guardianproject/otr/app/im/engine/ContactList.java index af90c3d73..eb6baaa7d 100644 --- a/src/info/guardianproject/otr/app/im/engine/ContactList.java +++ b/src/info/guardianproject/otr/app/im/engine/ContactList.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,11 +17,14 @@ package info.guardianproject.otr.app.im.engine; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; public class ContactList extends ImEntity { + protected Address mAddress; protected String mName; protected boolean mDefault; @@ -39,7 +42,9 @@ public ContactList(Address address, String name, boolean isDefault, mContactsCache = new HashMap(); if (contacts != null) { for (Contact c : contacts) { - mContactsCache.put(manager.normalizeAddress(c.getAddress().getAddress()), c); + String aKey = mManager.normalizeAddress(address.getAddress()); + if (!mContactsCache.containsKey(aKey)) + mContactsCache.put(aKey, c); } } } @@ -72,14 +77,13 @@ public boolean isDefault() { /** * Add a contact to the list. The contact is specified by its address * string. - * + * * @param address the address string specifies the contact. * @throws IllegalArgumentException if the address is invalid. * @throws NullPointerException if the address string is null * @throws ImException if the contact is not allowed to be added */ - public void addContact(String address) throws ImException { - address = mManager.normalizeAddress(address); + public void addContact(final String address) throws ImException { if (null == address) { throw new NullPointerException(); @@ -92,19 +96,61 @@ public void addContact(String address) throws ImException { } } - if (containsContact(address)) { + new Thread () + { + + public void run () + { + Contact contact = getContact(address); + + if (contact == null) + { + contact = new Contact (new XmppAddress(address),address); + } + + try { + mManager.addContactToListAsync(contact, ContactList.this); + } catch (ImException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }.start(); + } + + /** + * Add a contact to the list. The contact is specified by its address + * string. + * + * @param address the address string specifies the contact. + * @throws IllegalArgumentException if the address is invalid. + * @throws NullPointerException if the address string is null + * @throws ImException if the contact is not allowed to be added + */ + public void addExistingContact(Contact contact) throws ImException { + + String aKey = mManager.normalizeAddress(contact.getAddress().getAddress()); + + if (mManager.getState() == ContactListManager.BLOCKED_LIST_LOADED) { + if (mManager.isBlocked(aKey)) { + throw new ImException(ImErrorInfo.CANT_ADD_BLOCKED_CONTACT, + "Contact has been blocked"); + } + } + + if (containsContact(aKey)) { throw new ImException(ImErrorInfo.CONTACT_EXISTS_IN_LIST, "Contact already exists in the list"); } - mManager.addContactToListAsync(address, this); + mContactsCache.put(aKey, contact); } /** * Remove a contact from the list. If the contact is not in the list, * nothing will happen. Otherwise, the contact will be removed from the list * on the server asynchronously. - * + * * @param address the address of the contact to be removed from the list * @throws NullPointerException If the address is null */ @@ -122,7 +168,7 @@ public void removeContact(Address address) throws ImException { * Remove a contact from the list. If the contact is not in the list, * nothing will happen. Otherwise, the contact will be removed from the list * on the server asynchronously. - * + * * @param contact the contact to be removed from the list * @throws NullPointerException If the contact is null */ @@ -133,30 +179,31 @@ public void removeContact(Contact contact) throws ImException { if (containsContact(contact)) { mManager.removeContactFromListAsync(contact, this); + mContactsCache.remove(mManager.normalizeAddress(contact.getAddress().getAddress())); } } - public synchronized Contact getContact(Address address) { - return mContactsCache.get(mManager.normalizeAddress(address.getAddress())); + public Contact getContact(Address address) { + return getContact(mManager.normalizeAddress(address.getAddress())); } - public synchronized Contact getContact(String address) { - return mContactsCache.get(mManager.normalizeAddress(address)); + public Contact getContact(String address) { + return mContactsCache.get(address); } - public synchronized int getContactsCount() { + public int getContactsCount() { return mContactsCache.size(); } - public synchronized Collection getContacts() { + public Collection getContacts() { return new ArrayList(mContactsCache.values()); } - public synchronized boolean containsContact(String address) { + public boolean containsContact(String address) { return mContactsCache.containsKey(mManager.normalizeAddress(address)); } - public synchronized boolean containsContact(Address address) { + public boolean containsContact(Address address) { return address == null ? false : mContactsCache.containsKey(mManager .normalizeAddress(address.getAddress())); } diff --git a/src/info/guardianproject/otr/app/im/engine/ContactListListener.java b/src/info/guardianproject/otr/app/im/engine/ContactListListener.java index 9effa3763..65238ba71 100644 --- a/src/info/guardianproject/otr/app/im/engine/ContactListListener.java +++ b/src/info/guardianproject/otr/app/im/engine/ContactListListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -49,7 +49,7 @@ public interface ContactListListener { * Called when:

  • a contact list has been created, deleted, renamed * or loaded, or
  • a contact has been added to or removed from a list, or *
  • a contact has been blocked or unblocked
- * + * * @param type one of the following values:
  • {@link #LIST_CREATED} * list: the newly created list; contact: null
  • * {@link #LIST_DELETED} list: the delete list; contact: null @@ -76,7 +76,7 @@ public interface ContactListListener { /** * Called when received one or more contacts' updated presence information * from the server. - * + * * @param contacts one or more contacts that have updated presence * information. */ diff --git a/src/info/guardianproject/otr/app/im/engine/ContactListManager.java b/src/info/guardianproject/otr/app/im/engine/ContactListManager.java index 0e1a4bb1c..a2a7cb179 100644 --- a/src/info/guardianproject/otr/app/im/engine/ContactListManager.java +++ b/src/info/guardianproject/otr/app/im/engine/ContactListManager.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,6 +17,8 @@ package info.guardianproject.otr.app.im.engine; +import info.guardianproject.otr.app.im.ISubscriptionListener; + import java.util.Collection; import java.util.Collections; import java.util.List; @@ -52,7 +54,7 @@ public abstract class ContactListManager { protected Vector mContactLists; protected CopyOnWriteArrayList mContactListListeners; - protected SubscriptionRequestListener mSubscriptionRequestListener; + protected ISubscriptionListener mSubscriptionRequestListener; protected Vector mBlockedList; @@ -71,7 +73,7 @@ public abstract class ContactListManager { /** * Creates a new ContactListManager. - * + * * @param conn The underlying protocol connection. */ protected ContactListManager() { @@ -87,7 +89,7 @@ protected ContactListManager() { /** * Set the state of the ContactListManager - * + * * @param state the new state */ protected synchronized void setState(int state) { @@ -100,7 +102,7 @@ protected synchronized void setState(int state) { /** * Get the state of the ContactListManager - * + * * @return the current state of the manager */ public synchronized int getState() { @@ -110,7 +112,7 @@ public synchronized int getState() { /** * Adds a listener to the manager so that it will be notified for contact * list changed. - * + * * @param listener the listener to add. */ public synchronized void addContactListListener(ContactListListener listener) { @@ -121,7 +123,7 @@ public synchronized void addContactListListener(ContactListListener listener) { /** * Removes a listener from this manager. - * + * * @param listener the listener to remove. */ public synchronized void removeContactListListener(ContactListListener listener) { @@ -131,20 +133,20 @@ public synchronized void removeContactListListener(ContactListListener listener) /** * Sets the SubscriptionRequestListener to the manager so that it will be * notified when a subscription request from another user is received. - * + * * @param listener the ContactInvitationListener. */ - public synchronized void setSubscriptionRequestListener(SubscriptionRequestListener listener) { + public synchronized void setSubscriptionRequestListener(ISubscriptionListener listener) { mSubscriptionRequestListener = listener; } - public synchronized SubscriptionRequestListener getSubscriptionRequestListener() { + public synchronized ISubscriptionListener getSubscriptionRequestListener() { return mSubscriptionRequestListener; } /** * Gets a collection of the contact lists. - * + * * @return a collection of the contact lists. */ public Collection getContactLists() { @@ -153,7 +155,7 @@ public Collection getContactLists() { /** * Gets a contact by address. - * + * * @param address the address of the Contact. * @return the Contact or null if the Contact doesn't exist in any list. */ @@ -162,8 +164,9 @@ public Contact getContact(Address address) { } public Contact getContact(String address) { - for (ContactList list : mContactLists) { - Contact c = list.getContact(address); + for (int i = 0; i < mContactLists.size(); i++) + { + Contact c = mContactLists.get(i).getContact(normalizeAddress(address)); if (c != null) { return c; } @@ -176,15 +179,15 @@ public Contact getContact(String address) { /** * Creates a temporary contact. It's usually used when we want to create a * chat with someone not in the list. - * + * * @param address the address of the temporary contact. * @return the created temporary contact */ - public abstract Contact createTemporaryContact(String address); + public abstract Contact[] createTemporaryContacts(String[] addresses); /** * Tell whether the manager contains the specified contact - * + * * @param contact the specified contact * @return true if the contact is contained in the lists of the manager, * otherwise, return false @@ -201,7 +204,7 @@ public boolean containsContact(Contact contact) { /** * Gets a contact list by name. - * + * * @param name the name of the contact list. * @return the ContactList or null if the contact list doesn't exist. */ @@ -216,7 +219,7 @@ public ContactList getContactList(String name) { /** * Get the contact list by the address - * + * * @param address the address of the contact list * @return the ContactList or null if the list doesn't exist */ @@ -232,7 +235,7 @@ public ContactList getContactList(Address address) { /** * Gets the default contact list. - * + * * @return the default contact list. * @throws ImException */ @@ -243,7 +246,7 @@ public ContactList getDefaultContactList() throws ImException { /** * Create a contact list with the specified name asynchronously. - * + * * @param name the specific name of the contact list * @throws ImException */ @@ -254,7 +257,7 @@ public void createContactListAsync(String name) throws ImException { /** * Create a contact list with specified name and whether it is to be created * as the default list. - * + * * @param name the specific name of the contact list * @param isDefault whether the contact list is to be created as the default * list @@ -266,7 +269,7 @@ public void createContactListAsync(String name, boolean isDefault) throws ImExce /** * Create a contact list with specified name and contacts asynchronously. - * + * * @param name the specific name of the contact list * @param contacts the initial contacts of the contact list * @throws ImException @@ -279,7 +282,7 @@ public void createContactListAsync(String name, Collection contacts) /** * Create a contact list with specified name and contacts asynchronously, * and whether it is to be created as the default contact list. - * + * * @param name the name of the contact list * @param contacts the initial contacts of the list * @param isDefault whether the contact list is the default list @@ -302,7 +305,7 @@ public synchronized void createContactListAsync(String name, Collection /** * Delete a contact list of the specified name asynchronously - * + * * @param name the specific name of the contact list * @throws ImException */ @@ -312,7 +315,7 @@ public void deleteContactListAsync(String name) throws ImException { /** * Delete a specified contact list asynchronously - * + * * @param list the contact list to be deleted * @throws ImException if any error raised */ @@ -334,7 +337,7 @@ public void blockContactAsync(Contact contact) throws ImException { * Blocks a certain Contact. The contact will be removed from any * ContactList after be blocked. If the contact has already been blocked, * the method does nothing. - * + * * @param address the address of the contact to block. * @throws ImException if an error occurs */ @@ -362,7 +365,7 @@ public void unblockContactAsync(Contact contact) throws ImException { * nothing. Whether the unblocked contact will be added to the ContactList * it belongs before blocked or not depends on the underlying protocol * implementation. - * + * * @param address the address of the contact to unblock. * @throws ImException if the current state is illegal */ @@ -376,7 +379,7 @@ public void unblockContactAsync(String address) throws ImException { doBlockContactAsync(address, false); } - protected void addContactToListAsync(String address, ContactList list) throws ImException { + protected void addContactToListAsync(Contact address, ContactList list) throws ImException { checkState(); doAddContactToListAsync(address, list); @@ -392,9 +395,36 @@ protected void removeContactFromListAsync(Contact contact, ContactList list) thr doRemoveContactFromListAsync(contact, list); } + /** + * @param address + * @param name + * @return + * @throws ImException + */ + public void setContactName(String address, String name) throws ImException { + checkState(); + + doSetContactName(address,name); + updateCache(address,name); // used to refresh the display + } + + protected abstract void doSetContactName(String address, String name) throws ImException; + + protected void updateCache(String address, String name) { + // each contact list holds a cache + for (ContactList list : mContactLists) { + Contact contact = list.getContact(normalizeAddress(address)); + if (contact != null) { + // refresh the cache + contact.setName(name); + list.insertToCache(contact); + } + } + } + /** * Gets a unmodifiable list of blocked contacts. - * + * * @return a unmodifiable list of blocked contacts. * @throws ImException */ @@ -406,7 +436,7 @@ public List getBlockedList() throws ImException { /** * Checks if a contact is blocked. - * + * * @param contact the contact. * @return true if it's blocked, false otherwise. * @throws ImException if contacts has not been loaded. @@ -417,7 +447,7 @@ public boolean isBlocked(Contact contact) throws ImException { /** * Checks if a contact is blocked. - * + * * @param address the address of the contact. * @return true if it's blocked, false otherwise. * @throws ImException if contacts has not been loaded. @@ -439,7 +469,7 @@ public synchronized boolean isBlocked(String address) throws ImException { /** * Check the state of the ContactListManager. Only the LIST_LOADED state is * permitted. - * + * * @throws ImException if the current state is not LIST_LOADED */ protected void checkState() throws ImException { @@ -460,15 +490,15 @@ protected void checkState() throws ImException { */ public abstract void loadContactListsAsync(); - public abstract void approveSubscriptionRequest(String contact); + public abstract void approveSubscriptionRequest(Contact contact); - public abstract void declineSubscriptionRequest(String contact); + public abstract void declineSubscriptionRequest(Contact contact); protected abstract ImConnection getConnection(); /** * Block or unblock a contact. - * + * * @param address the address of the contact to block or unblock. * @param block true to block the contact; false * to unblock the contact. @@ -482,7 +512,7 @@ protected abstract void doCreateContactListAsync(String name, Collection headers) throws IOException; + + void setDataListener(IDataListener dataListener); + + void onOtrStatusChanged(SessionStatus status); +} diff --git a/src/info/guardianproject/otr/app/im/engine/GroupListener.java b/src/info/guardianproject/otr/app/im/engine/GroupListener.java index 24038a76d..32dcbe916 100644 --- a/src/info/guardianproject/otr/app/im/engine/GroupListener.java +++ b/src/info/guardianproject/otr/app/im/engine/GroupListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -26,21 +26,21 @@ public interface GroupListener { /** * Called when a chat group was successfully created. - * + * * @param group the group was created. */ public void onGroupCreated(ChatGroup group); /** * Called when a chat group was successfully deleted. - * + * * @param group the group was deleted. */ public void onGroupDeleted(ChatGroup group); /** * Called on joining in a chat group successfully. - * + * * @param group the group which was joined into. */ public void onJoinedGroup(ChatGroup group); @@ -49,14 +49,14 @@ public interface GroupListener { * Called on leaving a chat group. It may be triggered by the user leaving a * group or a server initiated group leaving, e.g. the user got kicked out * of the group, the group is deleted, etc. - * + * * @param group the group has left. */ public void onLeftGroup(ChatGroup group); /** * Called when an error occurs with a certain group operation. - * + * * @param errorType the type of the error * @param error the error information. */ diff --git a/src/info/guardianproject/otr/app/im/engine/GroupMemberListener.java b/src/info/guardianproject/otr/app/im/engine/GroupMemberListener.java index c7e8e9691..19efbd5da 100644 --- a/src/info/guardianproject/otr/app/im/engine/GroupMemberListener.java +++ b/src/info/guardianproject/otr/app/im/engine/GroupMemberListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -20,14 +20,14 @@ public interface GroupMemberListener { /** * Notifies that a contact has joined into this group. - * + * * @param contact the contact who has joined into this group. */ public void onMemberJoined(ChatGroup group, Contact contact); /** * Notifies that a contact has left this group. - * + * * @param contact the contact who has left the group. */ public void onMemberLeft(ChatGroup group, Contact contact); @@ -35,7 +35,7 @@ public interface GroupMemberListener { /** * Called when a previous request to add or remove a member to/from a group * failed. - * + * * @param error the error information */ public void onError(ChatGroup group, ImErrorInfo error); diff --git a/src/info/guardianproject/otr/app/im/engine/HeartbeatService.java b/src/info/guardianproject/otr/app/im/engine/HeartbeatService.java index 5799e6222..e57ef3724 100644 --- a/src/info/guardianproject/otr/app/im/engine/HeartbeatService.java +++ b/src/info/guardianproject/otr/app/im/engine/HeartbeatService.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -20,7 +20,7 @@ public interface HeartbeatService { public interface Callback { /** * Called on heartbeat schedule. - * + * * @return the offset in milliseconds that the method wants to be called * the next time. Return 0 or negative value indicates to stop * the schedule of this callback. @@ -30,7 +30,7 @@ public interface Callback { /** * Start to schedule a heartbeat operation. - * + * * @param callback The operation wants to be called repeat. * @param triggerTime The time(in milliseconds) until the operation will be * executed the first time. @@ -39,7 +39,7 @@ public interface Callback { /** * Stop scheduling a heartbeat operation. - * + * * @param callback The operation will be stopped. */ public void stopHeartbeat(Callback callback); diff --git a/src/info/guardianproject/otr/app/im/engine/ImConnection.java b/src/info/guardianproject/otr/app/im/engine/ImConnection.java index f36276fce..fb1468bfc 100644 --- a/src/info/guardianproject/otr/app/im/engine/ImConnection.java +++ b/src/info/guardianproject/otr/app/im/engine/ImConnection.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -101,6 +101,8 @@ public Presence getUserPresence() { return new Presence(mUserPresence); } + public abstract void initUser (long providerId, long accountId) throws ImException; + public void updateUserPresenceAsync(Presence newPresence) throws ImException { if (mState != LOGGED_IN) { throw new ImException(ImErrorInfo.NOT_LOGGED_IN, "NOT logged in"); @@ -113,7 +115,7 @@ public void updateUserPresenceAsync(Presence newPresence) throws ImException { * Tells the engine that the network type has changed, e.g. switch from gprs * to wifi. The engine should drop all the network connections created * before because they are not available anymore. - * + * * The engine might also need to redo authentication on the new network * depending on the underlying protocol. */ @@ -127,7 +129,7 @@ public int getState() { /** * Sets the state of the connection. - * + * * @param state the new state of the connection. * @param error the error information which caused the state change or null. */ @@ -159,14 +161,14 @@ protected void notifyUpdateUserPresenceError(ImErrorInfo error) { * Gets bit-or of capabilities supported by the underlying protocol. Valid * capability bits are: {@value #CAPABILITY_GROUP_CHAT}, * {@value #CAPABILITY_SESSION_REESTABLISHMENT} - * + * * @return bit-or of capabilities supported by the underlying protocol */ public abstract int getCapability(); /** * Log in to the IM server, using the settings stored in Imps. - * + * * @param accountId the ID to get the Account record * @param passwordTemp a one time use password, not to be saved * @param providerId the ID to get the ProviderSettings record @@ -184,7 +186,7 @@ public abstract void loginAsync(long accountId, String passwordTemp, long provid * stored context should be removed by the client.

    The client can query * if session re-establishment is supported through {@link #getCapability()} * . - * + * * @param sessionContext the session context which was fetched from previous * session by {@link #getSessionContext()} and persisted by the * client. @@ -208,7 +210,7 @@ public abstract void loginAsync(long accountId, String passwordTemp, long provid * context and use it to re-establish the session by * {@link #reestablishSessionAsync(Map)} . The stored context MUST be * removed upon the connection logout/disconnect. - * + * * @return the context of the current session or null if the * user has not logged in yet. * @throws UnsupportedOperationException if session re-establishment is not @@ -218,32 +220,38 @@ public abstract void loginAsync(long accountId, String passwordTemp, long provid /** * Gets the instance of ChatSessionManager for the connection. - * + * * @return the instance of ChatSessionManager for the connection. */ public abstract ChatSessionManager getChatSessionManager(); /** * Gets the instance of ContactListManager for the connection. - * + * * @return the instance of ContactListManager for the connection. */ public abstract ContactListManager getContactListManager(); /** * Gets the instance of ChatGroupManager for the connection. - * + * * @return the instance of ChatGroupManager for the connection. * @throws UnsupportedOperationException if group chat is not supported by * the underlying protocol. */ public abstract ChatGroupManager getChatGroupManager(); + /** + * Whether this connection is going over Tor or not. + * @return boolean + */ + public abstract boolean isUsingTor(); + protected abstract void doUpdateUserPresenceAsync(Presence presence); /** * Handle a heartbeat. - * + * * @param heartbeatInterval the number of heartbeats before a ping should be sent. */ public abstract void sendHeartbeat(long heartbeatInterval); diff --git a/src/info/guardianproject/otr/app/im/engine/ImEntity.java b/src/info/guardianproject/otr/app/im/engine/ImEntity.java index 752394f8e..8bfcf9950 100644 --- a/src/info/guardianproject/otr/app/im/engine/ImEntity.java +++ b/src/info/guardianproject/otr/app/im/engine/ImEntity.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -20,4 +20,7 @@ /** The abstract representation of any addressable entities. */ public abstract class ImEntity { public abstract Address getAddress(); + public boolean isGroup() { + return false; + } } diff --git a/src/info/guardianproject/otr/app/im/engine/ImErrorInfo.java b/src/info/guardianproject/otr/app/im/engine/ImErrorInfo.java index 2792217d6..60b5036cb 100644 --- a/src/info/guardianproject/otr/app/im/engine/ImErrorInfo.java +++ b/src/info/guardianproject/otr/app/im/engine/ImErrorInfo.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -64,7 +64,7 @@ public class ImErrorInfo implements Parcelable, Serializable { /** * Creates a new error with specified code and description. - * + * * @param code the error code. * @param description the description of the error. */ @@ -80,7 +80,7 @@ public ImErrorInfo(Parcel source) { /** * Gets the error code. - * + * * @return the error code. */ public int getCode() { @@ -89,7 +89,7 @@ public int getCode() { /** * Gets the description of the error. - * + * * @return the description of the error. */ public String getDescription() { diff --git a/src/info/guardianproject/otr/app/im/engine/ImException.java b/src/info/guardianproject/otr/app/im/engine/ImException.java index 95d394b35..aa4f357d9 100644 --- a/src/info/guardianproject/otr/app/im/engine/ImException.java +++ b/src/info/guardianproject/otr/app/im/engine/ImException.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -26,7 +26,7 @@ public class ImException extends Exception { /** * Creates a new ImException with the specified detail message. - * + * * @param message the detail message. */ public ImException(String message) { @@ -36,7 +36,7 @@ public ImException(String message) { /** * Creates a new ImException with the IMError which was the cause of the * exception. - * + * * @param error the cause of the exception. */ public ImException(ImErrorInfo error) { @@ -46,7 +46,7 @@ public ImException(ImErrorInfo error) { /** * Creates a new ImException with the specified cause. - * + * * @param cause the cause. */ public ImException(Throwable cause) { @@ -55,7 +55,7 @@ public ImException(Throwable cause) { /** * Creates a new ImException with the specified detail message and cause. - * + * * @param message the detail message. * @param cause the cause. */ @@ -65,7 +65,7 @@ public ImException(String message, Throwable cause) { /** * Creates a new ImException with specified IM error code and description - * + * * @param imErrorCode * @param string */ @@ -76,7 +76,7 @@ public ImException(int imErrorCode, String description) { /** * Gets the IMError which caused the exception or null if there * isn't one. - * + * * @return the IMError which caused the exception. */ public ImErrorInfo getImError() { diff --git a/src/info/guardianproject/otr/app/im/engine/Invitation.java b/src/info/guardianproject/otr/app/im/engine/Invitation.java index 9ee26805e..bc6dd500e 100644 --- a/src/info/guardianproject/otr/app/im/engine/Invitation.java +++ b/src/info/guardianproject/otr/app/im/engine/Invitation.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/engine/InvitationListener.java b/src/info/guardianproject/otr/app/im/engine/InvitationListener.java index 38e74d5d1..6c46b284c 100644 --- a/src/info/guardianproject/otr/app/im/engine/InvitationListener.java +++ b/src/info/guardianproject/otr/app/im/engine/InvitationListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -29,7 +29,7 @@ public interface InvitationListener { * acceptInvitation} or * {@link ChatGroupManager#rejectInvitationAsync(Invitation) * rejectInvitation} - * + * * @param invitation the invitation received. */ public void onGroupInvitation(Invitation invitation); diff --git a/src/info/guardianproject/otr/app/im/engine/Message.java b/src/info/guardianproject/otr/app/im/engine/Message.java index b05209b33..ade150425 100644 --- a/src/info/guardianproject/otr/app/im/engine/Message.java +++ b/src/info/guardianproject/otr/app/im/engine/Message.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -24,14 +24,14 @@ /** Represents an instant message send between users. */ public class Message implements Parcelable { - + private String mId; private Address mFrom; private Address mTo; private String mBody; private Date mDate; - private int mType; - + private int mType; + /** * @param msg * @throws NullPointerException if msg is null. @@ -58,7 +58,7 @@ public Message(Parcel source) { /** * Gets an identifier of this message. May be null if the * underlying protocol doesn't support it. - * + * * @return the identifier of this message. */ public String getID() { @@ -67,7 +67,7 @@ public String getID() { /** * Gets the body of this message. - * + * * @return the body of this message. */ public String getBody() { @@ -76,7 +76,7 @@ public String getBody() { /** * Gets the address where the message is sent from. - * + * * @return the address where the message is sent from. */ public Address getFrom() { @@ -85,7 +85,7 @@ public Address getFrom() { /** * Gets the address where the message is sent to. - * + * * @return the address where the message is sent to. */ public Address getTo() { @@ -97,7 +97,7 @@ public Address getTo() { * from this client, the date time is when the message is sent. If it's a * message received from other users, the date time is either when the * message was received or sent, depending on the underlying protocol. - * + * * @return the date time. */ public Date getDateTime() { @@ -133,7 +133,7 @@ public void setDateTime(Date dateTime) { } public String toString() { - return "From: " + mFrom.getScreenName() + " To: " + mTo.getScreenName() + " " + mBody; + return "From: " + mFrom.getAddress() + " To: " + mTo.getAddress() + " " + mBody; } public void writeToParcel(Parcel dest, int flags) { @@ -158,7 +158,7 @@ public Message[] newArray(int size) { return new Message[size]; } }; - + public int getType() { diff --git a/src/info/guardianproject/otr/app/im/engine/MessageListener.java b/src/info/guardianproject/otr/app/im/engine/MessageListener.java index 2c771fc70..6af2bb280 100644 --- a/src/info/guardianproject/otr/app/im/engine/MessageListener.java +++ b/src/info/guardianproject/otr/app/im/engine/MessageListener.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,6 +17,9 @@ package info.guardianproject.otr.app.im.engine; +import info.guardianproject.otr.OtrDataHandler.Transfer; +import net.java.otr4j.session.SessionStatus; + /** * Interface that allows for implementing classes to listen for new message. * Listeners are registered with ChatSession objects. @@ -24,10 +27,10 @@ public interface MessageListener { /** * Calls when a new message has arrived. - * + * * @param ses the ChatSession. * @param msg the incoming message. - * + * * @return true if the message was processed correctly, or false * otherwise (e.g. decryption error) */ @@ -35,7 +38,7 @@ public interface MessageListener { /** * Calls when an error occurs to send a message. - * + * * @param ses the ChatSession. * @param msg the message which was sent. * @param error the error information. @@ -44,7 +47,7 @@ public interface MessageListener { /** * Called when a message was not transmitted. - * + * * @param ses the ChatSession. * @param msg the message which should be sent later. */ @@ -52,7 +55,7 @@ public interface MessageListener { /** * Called when a message receipt was received. - * + * * @param ses the ChatSession. * @param id the message ID. */ @@ -61,13 +64,19 @@ public interface MessageListener { /** * Called when we determine that the remote supports message delivery * receipts. - * + * *
    XEP-0184 - * + * * @param ses the ChatSession. */ public void onReceiptsExpected(ChatSession ses); /** Called when OTR status changes */ - public void onStatusChanged(ChatSession session); + public void onStatusChanged(ChatSession session, SessionStatus status); + + public void onIncomingDataRequest(ChatSession session, Message msg, byte[] value); + + public void onIncomingDataResponse(ChatSession session, Message msg, byte[] value); + + public void onIncomingTransferRequest (Transfer transfer); } diff --git a/src/info/guardianproject/otr/app/im/engine/Presence.java b/src/info/guardianproject/otr/app/im/engine/Presence.java index 6785dea21..b2ea217e8 100644 --- a/src/info/guardianproject/otr/app/im/engine/Presence.java +++ b/src/info/guardianproject/otr/app/im/engine/Presence.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007 Esmertec AG. Copyright (C) 2007 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,16 +17,16 @@ package info.guardianproject.otr.app.im.engine; -import java.util.Collections; -import java.util.Map; - import android.os.Parcel; import android.os.Parcelable; +import java.util.Collections; +import java.util.Map; + /** * A Presence is an abstract presentation of the user's presence * information. - * + * * Note that changes made to the Presence data won't be reflected to the server * until ImConnection.updateUserPresence is called. Only the logged * in user can update its own presence data via @@ -34,11 +34,27 @@ * presence data won't be saved or sent to the server. */ public final class Presence implements Parcelable { + + public static final int MIN_PRESENCE = 0; public static final int OFFLINE = 0; - public static final int DO_NOT_DISTURB = 1; + public static final int INVISIBLE = 1; public static final int AWAY = 2; public static final int IDLE = 3; - public static final int AVAILABLE = 4; + public static final int DO_NOT_DISTURB = 4; + public static final int AVAILABLE = 5; + //public static final int NOT_SUBSCRIBED = 5; + public static final int MAX_PRESENCE = 5; + + + /** + * + int OFFLINE = 0; + int INVISIBLE = 1; + int AWAY = 2; + int IDLE = 3; + int DO_NOT_DISTURB = 4; + int AVAILABLE = 5; + */ public static final int CLIENT_TYPE_DEFAULT = 0; public static final int CLIENT_TYPE_MOBILE = 1; @@ -49,7 +65,7 @@ public final class Presence implements Parcelable { private String mAvatarType; private int mClientType; private String mResource; - + private int mPriority = 0; private Map mExtendedInfo; public Presence() { @@ -88,15 +104,15 @@ public Presence(Parcel source) { // TODO - what ClassLoader should be passed to readMap? // TODO - switch to Bundle mExtendedInfo = source.readHashMap(null); - + //this may not exist for older persisted presence data if (source.dataAvail() > 0) - mResource = source.readString(); + mResource = source.readString(); } /** * Get avatar bitmap. - * + * * @return Avatar bitmap. Note any changes made to the bitmap itself won't * be saved or sent back to the server. To change avatar call * setAvatar with a new bitmap instance. FIXME: @@ -116,7 +132,7 @@ public byte[] getAvatarData() { /** * Get the MIME type of avatar. - * + * * @return the MIME type of avatar. */ public String getAvatarType() { @@ -140,7 +156,7 @@ public int getStatus() { } public void setStatus(int status) { - if (status < OFFLINE || status > AVAILABLE) { + if (status < MIN_PRESENCE || status > MAX_PRESENCE) { throw new IllegalArgumentException("invalid presence status value"); } mStatus = status; @@ -168,6 +184,7 @@ public void setClientType(int clientType) { mClientType = clientType; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mStatus); dest.writeString(mStatusText); @@ -178,27 +195,40 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(mResource); } + @Override public int describeContents() { return 0; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public Presence createFromParcel(Parcel source) { return new Presence(source); } + @Override public Presence[] newArray(int size) { return new Presence[size]; } }; - + public String getResource () { return mResource; } - + public void setResource (String resource) { mResource = resource; } + + public int getPriority() { + return mPriority; + } + + public void setPriority(int mPriority) { + this.mPriority = mPriority; + } + + } diff --git a/src/info/guardianproject/otr/app/im/engine/SmsService.java b/src/info/guardianproject/otr/app/im/engine/SmsService.java index 9826a59b3..d7ee0dfe1 100644 --- a/src/info/guardianproject/otr/app/im/engine/SmsService.java +++ b/src/info/guardianproject/otr/app/im/engine/SmsService.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -22,7 +22,7 @@ public interface SmsService { public interface SmsListener { /** * Called on new SMS received. - * + * * @param data */ public void onIncomingSms(byte[] data); @@ -37,7 +37,7 @@ public interface SmsSendFailureCallback { /** * Called when send an SMS failed. - * + * * @param errorCode the error code; will be one of * {@link #ERROR_GENERIC_FAILURE}, {@link #ERROR_RADIO_OFF} */ @@ -46,14 +46,14 @@ public interface SmsSendFailureCallback { /** * The max number of bytes an SMS can take. - * + * * @return the max number of bytes an SMS can take. */ public int getMaxSmsLength(); /** * Sends a data SMS to the destination. - * + * * @param dest The address to send the message to. * @param port The port to deliver the message to. * @param data The body of the message to send. @@ -62,7 +62,7 @@ public interface SmsSendFailureCallback { /** * Sends a data SMS to the destination. - * + * * @param dest The address to send the message to. * @param port The port to deliver the message to. * @param data The body of the message to send. @@ -74,7 +74,7 @@ public interface SmsSendFailureCallback { /** * Add a SmsListener so that it can be notified when new SMS from specific * address and application port has been received. - * + * * @param from The address of the sender. * @param port The application port. * @param listener The listener which will be notified when SMS received. @@ -84,7 +84,7 @@ public interface SmsSendFailureCallback { /** * Remove a SmsListener from the service so that it won't be notified * anymore. - * + * * @param listener The listener to be removed. */ public void removeSmsListener(SmsListener listener); diff --git a/src/info/guardianproject/otr/app/im/engine/SubscriptionRequestListener.java b/src/info/guardianproject/otr/app/im/engine/SubscriptionRequestListener.java deleted file mode 100644 index 814c0aa61..000000000 --- a/src/info/guardianproject/otr/app/im/engine/SubscriptionRequestListener.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package info.guardianproject.otr.app.im.engine; - -/** - * Interface that allows the implementing class to listen to subscription - * request from other users. - */ -public interface SubscriptionRequestListener { - /** - * Called when a subscription request from another user is received. - * - * @param from - */ - void onSubScriptionRequest(Contact from); - - /** - * Called when a subscription request is approved. - * - * @param contact - */ - void onSubscriptionApproved(String contact); - - /** - * Called when a subscription request is declined. - * - * @param contact - */ - void onSubscriptionDeclined(String contact); - - /** - * Called when an error occurs during approving a subscription request. - * - * @param contact - * @param error - */ - void onApproveSubScriptionError(String contact, ImErrorInfo error); - - /** - * Called when an error occurs during declining a subscription request. - * - * @param contact - * @param error - */ - void onDeclineSubScriptionError(String contact, ImErrorInfo error); -} diff --git a/src/info/guardianproject/otr/app/im/engine/SystemService.java b/src/info/guardianproject/otr/app/im/engine/SystemService.java index 7312a0664..bc84cfacb 100644 --- a/src/info/guardianproject/otr/app/im/engine/SystemService.java +++ b/src/info/guardianproject/otr/app/im/engine/SystemService.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -20,7 +20,7 @@ public abstract class SystemService { /** * Gets the default instance of the system service. - * + * * @return the default instance of the system service. */ public static SystemService getDefault() { @@ -30,14 +30,14 @@ public static SystemService getDefault() { /** * Gets the system HeartbeatService. - * + * * @return the instance of the HeartbeatService. */ public abstract HeartbeatService getHeartbeatService(); /** * Gets the system SmsService. - * + * * @return the instance of the SmsService. */ // TODO public abstract SmsService getSmsService(); diff --git a/src/info/guardianproject/otr/app/im/plugin/BrandingResourceIDs.java b/src/info/guardianproject/otr/app/im/plugin/BrandingResourceIDs.java index 9fd022b92..3d37b6820 100644 --- a/src/info/guardianproject/otr/app/im/plugin/BrandingResourceIDs.java +++ b/src/info/guardianproject/otr/app/im/plugin/BrandingResourceIDs.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/ImConfigNames.java b/src/info/guardianproject/otr/app/im/plugin/ImConfigNames.java index 0d1c5aa34..0a14bfbd1 100644 --- a/src/info/guardianproject/otr/app/im/plugin/ImConfigNames.java +++ b/src/info/guardianproject/otr/app/im/plugin/ImConfigNames.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/ImPlugin.java b/src/info/guardianproject/otr/app/im/plugin/ImPlugin.java index 0001e4daa..fa5259dac 100644 --- a/src/info/guardianproject/otr/app/im/plugin/ImPlugin.java +++ b/src/info/guardianproject/otr/app/im/plugin/ImPlugin.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -23,7 +23,7 @@ public interface ImPlugin { * Gets a map of branding resources for the provider. The keys are defined * in {@link info.guardianproject.otr.app.im.plugin.BrandingResourceIDs}. * The values are the resource identifiers generated by the aapt tool. - * + * * @return The map of branding resources. */ Map getResourceMap(); @@ -31,7 +31,7 @@ public interface ImPlugin { /** * Gets the configuration for the provider. The keys MUST match the values * defined in {@link ImConfigNames} and {@link ImpsConfigNames} - * + * * @return the configuration for the provider. */ Map getProviderConfig(); diff --git a/src/info/guardianproject/otr/app/im/plugin/ImPluginConstants.java b/src/info/guardianproject/otr/app/im/plugin/ImPluginConstants.java index b666391f1..2ae09ccfc 100644 --- a/src/info/guardianproject/otr/app/im/plugin/ImPluginConstants.java +++ b/src/info/guardianproject/otr/app/im/plugin/ImPluginConstants.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/ImPluginInfo.java b/src/info/guardianproject/otr/app/im/plugin/ImPluginInfo.java index b0eb70d8f..ab21dfd09 100644 --- a/src/info/guardianproject/otr/app/im/plugin/ImPluginInfo.java +++ b/src/info/guardianproject/otr/app/im/plugin/ImPluginInfo.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/ImpsConfigNames.java b/src/info/guardianproject/otr/app/im/plugin/ImpsConfigNames.java index fbb68b34f..fc01702d4 100644 --- a/src/info/guardianproject/otr/app/im/plugin/ImpsConfigNames.java +++ b/src/info/guardianproject/otr/app/im/plugin/ImpsConfigNames.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/PasswordDigest.java b/src/info/guardianproject/otr/app/im/plugin/PasswordDigest.java index 737aa9c63..55c9b8d9c 100644 --- a/src/info/guardianproject/otr/app/im/plugin/PasswordDigest.java +++ b/src/info/guardianproject/otr/app/im/plugin/PasswordDigest.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -23,14 +23,14 @@ public interface PasswordDigest { /** * Gets an array of supported digest schema. - * + * * @return an array of digest schema */ String[] getSupportedDigestSchema(); /** * Generates the digest bytes of the password. - * + * * @param schema The digest schema to use. * @param nonce The nonce string returned by the server. * @param password The user password. diff --git a/src/info/guardianproject/otr/app/im/plugin/PresenceMapping.java b/src/info/guardianproject/otr/app/im/plugin/PresenceMapping.java index 18ccefd20..25c3285e6 100644 --- a/src/info/guardianproject/otr/app/im/plugin/PresenceMapping.java +++ b/src/info/guardianproject/otr/app/im/plugin/PresenceMapping.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -27,7 +27,7 @@ public interface PresenceMapping { * Tells if the mapping needs all presence values sent in protocol. If this * method returns true, the framework will pass all the presence values * received from the server when map to the predefined status. - * + * * @return true if needs; false otherwise. */ boolean requireAllPresenceValues(); @@ -35,7 +35,7 @@ public interface PresenceMapping { /** * Map the presence values sent in protocol to the predefined presence * status. - * + * * @param onlineStatus The value of presence <OnlineStatus> received * from the server. * @param userAvailability The value of presence <UserAvailibility> @@ -50,7 +50,7 @@ int getPresenceStatus(boolean onlineStatus, String userAvailability, /** * Gets the value of <OnlineStatus> will be sent to the server when * update presence to the predefined status. - * + * * @param status the predefined status. * @return The value of <OnlineStatus> will be sent to the server */ @@ -59,7 +59,7 @@ int getPresenceStatus(boolean onlineStatus, String userAvailability, /** * Gets the value of <UserAvaibility> will be sent to the server when * update presence to the predefined status. - * + * * @param status the predefined status. * @return The value of <UserAvaibility> will be sent to the server */ @@ -69,7 +69,7 @@ int getPresenceStatus(boolean onlineStatus, String userAvailability, * Gets the extra presence values other than <OnlineStatus> and * <UserAvaibility> will be sent to the server when update presence to * the predefined status. - * + * * @param status the predefined status. * @return The extra values that will be sent to the server. */ @@ -78,7 +78,7 @@ int getPresenceStatus(boolean onlineStatus, String userAvailability, /** * Gets an array of the supported presence status. The client can only * update presence to the values in the array. - * + * * @return an array of the supported presence status. */ int[] getSupportedPresenceStatus(); diff --git a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackConnection.java b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackConnection.java index 6388fa714..99d692865 100644 --- a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackConnection.java +++ b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackConnection.java @@ -12,6 +12,7 @@ import info.guardianproject.otr.app.im.engine.ImException; import info.guardianproject.otr.app.im.engine.Message; import info.guardianproject.otr.app.im.engine.Presence; +import info.guardianproject.otr.app.im.plugin.xmpp.XmppAddress; import info.guardianproject.otr.app.im.provider.Imps; import java.util.ArrayList; @@ -55,6 +56,18 @@ protected void doUpdateUserPresenceAsync(Presence presence) { mContactListManager.doPresence(contacts_array); } + @Override + public void initUser(long providerId, long accountId) + { + + mUser = makeUser(); + } + + private Contact makeUser() { + + return new Contact(new XmppAddress("test@foo"), "test"); + } + @Override public int getCapability() { // TODO Auto-generated method stub @@ -106,6 +119,11 @@ public int[] getSupportedPresenceStatus() { Presence.DO_NOT_DISTURB, }; } + @Override + public boolean isUsingTor() { + return false; // loopback will never use Tor + } + @Override public void loginAsync(long accountId, String passwordTemp, long providerId, boolean retry) { ContentResolver contentResolver = mContext.getContentResolver(); @@ -202,8 +220,7 @@ protected void doBlockContactAsync(String address, boolean block) { } @Override - protected void doAddContactToListAsync(String address, ContactList list) throws ImException { - Contact contact = new Contact(new LoopbackAddress(address, address, null), address); + protected void doAddContactToListAsync(Contact contact, ContactList list) throws ImException { contact.setPresence(new Presence(Presence.AVAILABLE, "available", null, null, Presence.CLIENT_TYPE_DEFAULT)); notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_ADDED, contact); @@ -212,22 +229,27 @@ protected void doAddContactToListAsync(String address, ContactList list) throws } @Override - public void declineSubscriptionRequest(String contact) { + public void declineSubscriptionRequest(Contact contact) { // TODO Auto-generated method stub } @Override - public Contact createTemporaryContact(String address) { + public Contact[] createTemporaryContacts(String[] address) { // TODO Auto-generated method stub return null; } @Override - public void approveSubscriptionRequest(String contact) { + public void approveSubscriptionRequest(Contact contact) { // TODO Auto-generated method stub return; } + + @Override + protected void doSetContactName(String address, String name) throws ImException { + // stub - no server + } } class LoopbackAddress extends Address { @@ -235,7 +257,7 @@ class LoopbackAddress extends Address { private String address; private String name; private String resource; - + public LoopbackAddress() { } @@ -246,15 +268,20 @@ public LoopbackAddress(String name, String address, String resource) { } @Override - public String getAddress() { - return name; + public String getBareAddress() { + return address; } @Override - public String getScreenName() { + public String getAddress() { return address; } - + + @Override + public String getUser() { + return name; + } + @Override public String getResource() { return null; @@ -274,11 +301,14 @@ public void writeToParcel(Parcel dest) { dest.writeString(resource); } + } + @Override public void sendHeartbeat(long heartbeatInterval) { } + @Override public void setProxy(String type, String host, int port) { } diff --git a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackImPlugin.java b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackImPlugin.java index fb3284b50..6d6990900 100644 --- a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackImPlugin.java +++ b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackImPlugin.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackPresenceMapping.java b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackPresenceMapping.java index 6764db05b..43d9f450c 100644 --- a/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackPresenceMapping.java +++ b/src/info/guardianproject/otr/app/im/plugin/loopback/LoopbackPresenceMapping.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/DummySSLSocketFactory.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/DummySSLSocketFactory.java index 13fa8897d..319e149e9 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/DummySSLSocketFactory.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/DummySSLSocketFactory.java @@ -26,11 +26,11 @@ public class DummySSLSocketFactory extends SSLSocketFactory { private SSLSocketFactory factory; private static X509TrustManager sTrustManager; - + public DummySSLSocketFactory(X509TrustManager trustManager) { DummySSLSocketFactory.sTrustManager = trustManager; - + try { SSLContext sslcontent = SSLContext.getInstance("TLS"); sslcontent.init(null, // KeyManager not required diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppConnection.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppConnection.java index b08771f8b..96467f7d5 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppConnection.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppConnection.java @@ -1,5 +1,6 @@ package info.guardianproject.otr.app.im.plugin.xmpp; +import info.guardianproject.otr.app.im.engine.Address; import info.guardianproject.otr.app.im.engine.ChatGroupManager; import info.guardianproject.otr.app.im.engine.ChatSession; import info.guardianproject.otr.app.im.engine.ChatSessionManager; @@ -13,18 +14,15 @@ import info.guardianproject.otr.app.im.engine.Message; import info.guardianproject.otr.app.im.engine.Presence; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.LogCleaner; import java.io.IOException; import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.Enumeration; -import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -49,6 +47,7 @@ import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.MulticastLock; @@ -57,9 +56,9 @@ public class LLXmppConnection extends ImConnection implements CallbackHandler { - final static String TAG = "Gibberbot.LLXmppConnection"; + final static String TAG = "ChatSecure.LLXmppConnection"; - private XmppContactList mContactListManager; + private XmppContactListManager mContactListManager; private Contact mUser; private XmppChatSessionManager mSessionManager; @@ -79,7 +78,8 @@ public class LLXmppConnection extends ImConnection implements CallbackHandler { private InetAddress ipAddress; - private String serviceName; + private String mServiceName; + private String mResource; static { LLServiceDiscoveryManager.addServiceListener(); @@ -96,8 +96,11 @@ public LLXmppConnection(Context context) { DeliveryReceipts.addExtensionProviders(); - LLServiceDiscoveryManager.setIdentityName("Gibberbot"); - LLServiceDiscoveryManager.setIdentityType("phone"); + String identityResource = "ChatSecure"; + String identityType = "phone"; + + LLServiceDiscoveryManager.setIdentityName(identityResource); + LLServiceDiscoveryManager.setIdentityType(identityType); } private void createExecutor() { @@ -135,7 +138,7 @@ public void sendPacket(final org.jivesoftware.smack.packet.Message message) { public void run() { LLChat chat; try { - chat = mService.getChat(message.getTo()); + chat = mService.getChat(Address.stripResource(message.getTo())); chat.sendMessage(message); } catch (XMPPException e) { Log.e(TAG, "Could not send message", e); @@ -160,6 +163,7 @@ protected void doUpdateUserPresenceAsync(Presence presence) { } mService.getLocalPresence().setStatus(mode); mService.getLocalPresence().setMsg(statusText); + try { mService.updatePresence(mService.getLocalPresence()); } catch (XMPPException e) { @@ -190,10 +194,10 @@ public synchronized ChatSessionManager getChatSessionManager() { } @Override - public synchronized XmppContactList getContactListManager() { + public synchronized XmppContactListManager getContactListManager() { if (mContactListManager == null) - mContactListManager = new XmppContactList(); + mContactListManager = new XmppContactListManager(); return mContactListManager; } @@ -214,6 +218,11 @@ public int[] getSupportedPresenceStatus() { return new int[] { Presence.AVAILABLE, Presence.AWAY, Presence.DO_NOT_DISTURB, }; } + @Override + public boolean isUsingTor() { + return false; // link-local will never use Tor + } + @Override public void loginAsync(long accountId, String passwordTemp, long providerId, boolean retry) { mAccountId = accountId; @@ -228,15 +237,22 @@ public void run() { public void do_login() { ContentResolver contentResolver = mContext.getContentResolver(); - Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( - contentResolver, mProviderId, false, null); - // providerSettings is closed in initConnection() - String userName = Imps.Account.getUserName(contentResolver, mAccountId); - String domain = providerSettings.getDomain(); - providerSettings.close(); // close this, which was opened in do_login() try { - initConnection(userName, domain); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + + if (cursor == null) + throw new ImException ("unable to query the provider settings"); + + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( + cursor, contentResolver, mProviderId, false, null); + // providerSettings is closed in initConnection() + String userName = Imps.Account.getUserName(contentResolver, mAccountId); + String domain = providerSettings.getDomain(); + mResource = providerSettings.getXmppResource(); + + initConnection(userName, domain, providerSettings); } catch (Exception e) { Log.w(TAG, "login failed", e); ImErrorInfo info = new ImErrorInfo(ImErrorInfo.UNKNOWN_ERROR, e.getMessage()); @@ -245,22 +261,19 @@ public void do_login() { } } + @Override public void setProxy(String type, String host, int port) { // Ignore proxies for mDNS } // Runs in executor thread - private void initConnection(String userName, String domain) throws Exception { - setState(LOGGING_IN, null); - mUserPresence = new Presence(Presence.AVAILABLE, "", null, null, - Presence.CLIENT_TYPE_DEFAULT); + private void initConnection(String userName, String domain, Imps.ProviderSettings.QueryMap providerSettings) throws Exception { - domain = domain.replace('.', '_'); - serviceName = userName + "@" + domain; - LLPresence presence = new LLPresence(serviceName); + setState(LOGGING_IN, null); - ipAddress = getMyAddress(serviceName, true); + mServiceName = userName + '@' + domain;// + '/' + mResource; + ipAddress = getMyAddress(mServiceName, true); if (ipAddress == null) { ImErrorInfo info = new ImErrorInfo(ImErrorInfo.WIFI_NOT_CONNECTED_ERROR, "network connection is required"); @@ -268,12 +281,22 @@ private void initConnection(String userName, String domain) throws Exception { return; } + mUserPresence = new Presence(Presence.AVAILABLE, "", null, null, + Presence.CLIENT_TYPE_MOBILE); + + LLPresence presence = new LLPresence(mServiceName); + presence.setNick(userName); + presence.setJID(mServiceName); + presence.setServiceName(mServiceName); + mService = JmDNSService.create(presence, ipAddress); mService.addServiceStateListener(new LLServiceStateListener() { + @Override public void serviceNameChanged(String newName, String oldName) { debug(TAG, "Service named changed from " + oldName + " to " + newName + "."); } + @Override public void serviceClosed() { debug(TAG, "Service closed"); if (getState() != SUSPENDED) { @@ -282,6 +305,7 @@ public void serviceClosed() { releaseLocks(); } + @Override public void serviceClosedOnError(Exception e) { debug(TAG, "Service closed on error"); ImErrorInfo info = new ImErrorInfo(ImErrorInfo.UNKNOWN_ERROR, e.getMessage()); @@ -289,6 +313,7 @@ public void serviceClosedOnError(Exception e) { releaseLocks(); } + @Override public void unknownOriginMessage(org.jivesoftware.smack.packet.Message m) { debug(TAG, "This message has unknown origin:"); debug(TAG, m.toXML()); @@ -297,16 +322,20 @@ public void unknownOriginMessage(org.jivesoftware.smack.packet.Message m) { // Adding presence listener. mService.addPresenceListener(new LLPresenceListener() { + @Override public void presenceRemove(final LLPresence presence) { execute(new Runnable() { + @Override public void run() { mContactListManager.handlePresenceChanged(presence, true); } }); } + @Override public void presenceNew(final LLPresence presence) { execute(new Runnable() { + @Override public void run() { mContactListManager.handlePresenceChanged(presence, false); } @@ -321,8 +350,10 @@ public void run() { // Start listen for Link-local chats mService.addLLChatListener(new LLChatListener() { + @Override public void newChat(LLChat chat) { chat.addMessageListener(new LLMessageListener() { + @Override public void processMessage(LLChat chat, org.jivesoftware.smack.packet.Message message) { String address = message.getFrom(); @@ -339,6 +370,8 @@ public void processMessage(LLChat chat, rec.setTo(mUser.getAddress()); rec.setFrom(session.getParticipant().getAddress()); rec.setDateTime(new Date()); + + rec.setType(Imps.MessageType.INCOMING); session.onReceiveMessage(rec); if (message.getExtension("request", DeliveryReceipts.NAMESPACE) != null) { @@ -351,13 +384,13 @@ public void processMessage(LLChat chat, }); } + @Override public void chatInvalidated(LLChat chat) { // TODO } }); - String xmppName = userName + '@' + domain; - mUser = new Contact(new XmppAddress(userName, xmppName), xmppName); + makeUser(providerSettings);//mUser = new Contact(new XmppAddress(mServiceName), userName); // Initiate Link-local message session mService.init(); @@ -366,11 +399,48 @@ public void chatInvalidated(LLChat chat) { setState(LOGGED_IN, null); } + @Override + public void initUser(long providerId, long accountId) throws ImException + { + ContentResolver contentResolver = mContext.getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + + if (cursor == null) + throw new ImException("unable to query settings"); + + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( + cursor, contentResolver, mProviderId, false, null); + + mProviderId = providerId; + mAccountId = accountId; + mUser = makeUser(providerSettings); + + providerSettings.close(); + } + + private Contact makeUser(Imps.ProviderSettings.QueryMap providerSettings) { + ContentResolver contentResolver = mContext.getContentResolver(); + + String userName = Imps.Account.getUserName(contentResolver, mAccountId); + String domain = providerSettings.getDomain(); + String xmppName = userName + '@' + domain + '/' + providerSettings.getXmppResource(); + + return new Contact(new XmppAddress(xmppName), userName); + } + private InetAddress getMyAddress(final String serviceName, boolean doLock) { + + if (mServiceName == null) + return null; + WifiManager wifi = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); WifiInfo connectionInfo = wifi.getConnectionInfo(); if (connectionInfo == null || connectionInfo.getBSSID() == null) { + //not on wifi, nothing to do + return null; + /* Log.w(TAG, "Not connected to wifi. This may not work."); // Get the IP the usual Java way try { @@ -388,7 +458,7 @@ private InetAddress getMyAddress(final String serviceName, boolean doLock) { } catch (SocketException e) { Log.e(TAG, "while enumerating interfaces", e); return null; - } + }*/ } int ip = connectionInfo.getIpAddress(); @@ -480,10 +550,19 @@ public void run() { } // Force immediate logout - public synchronized void logout() { - if (mService != null) { - mService.close(); - mService = null; + @Override + public void logout() { + try + { + if (mService != null) { + mService.close(); + mService = null; + } + } + catch (Exception e) + { + debug(TAG, "error logging out"); + } } @@ -508,7 +587,7 @@ private ChatSession findOrCreateSession(String address) { if (session == null) { Contact contact = findOrCreateContact(parseAddressName(address), address); - session = mSessionManager.createChatSession(contact); + session = mSessionManager.createChatSession(contact,true); } return session; } @@ -523,7 +602,7 @@ Contact findOrCreateContact(String name, String address) { } private static Contact makeContact(String name, String address) { - Contact contact = new Contact(new XmppAddress(name, address), address); + Contact contact = new Contact(new XmppAddress(address), name); return contact; } @@ -531,22 +610,25 @@ private static Contact makeContact(String name, String address) { private final class XmppChatSessionManager extends ChatSessionManager { @Override public void sendMessageAsync(ChatSession session, Message message) { - org.jivesoftware.smack.packet.Message msg = new org.jivesoftware.smack.packet.Message( + org.jivesoftware.smack.packet.Message msgXmpp = new org.jivesoftware.smack.packet.Message( message.getTo().getAddress(), org.jivesoftware.smack.packet.Message.Type.chat); - msg.addExtension(new DeliveryReceipts.DeliveryReceiptRequest()); - msg.setBody(message.getBody()); - debug(TAG, "sending packet ID " + msg.getPacketID()); - message.setID(msg.getPacketID()); - sendPacket(msg); + + msgXmpp.addExtension(new DeliveryReceipts.DeliveryReceiptRequest()); + msgXmpp.setBody(message.getBody()); + + if (message.getID() != null) + msgXmpp.setPacketID(message.getID()); + else + message.setID(msgXmpp.getPacketID()); + + sendPacket(msgXmpp); } ChatSession findSession(String address) { - for (Iterator iter = mSessions.iterator(); iter.hasNext();) { - ChatSession session = iter.next(); - if (session.getParticipant().getAddress().getAddress().equals(address)) - return session; - } - return null; + + return mSessions.get(Address.stripResource(address)); + + } } @@ -555,16 +637,25 @@ public ChatSession findSession(String address) { } public ChatSession createChatSession(Contact contact) { - return mSessionManager.createChatSession(contact); + return mSessionManager.createChatSession(contact,true); } - public class XmppContactList extends ContactListManager { + public class XmppContactListManager extends ContactListManager { + + public XmppContactListManager () + { + super(); + + } + + private void do_loadContactLists() { String generalGroupName = "Buddies"; Collection contacts = new ArrayList(); ContactList cl = new ContactList(mUser.getAddress(), generalGroupName, true, contacts, this); + notifyContactListCreated(cl); notifyContactListsLoaded(); } @@ -581,7 +672,7 @@ public void run() { @Override public String normalizeAddress(String address) { - return address; + return new XmppAddress(address).getBareAddress(); } @Override @@ -597,39 +688,53 @@ public void run() { } private void handlePresenceChanged(LLPresence presence, boolean offline) { + + + if (presence.getServiceName().equals(mServiceName)) + return; //this is from us! + + // Create default lists on first presence received - if (mContactListManager.getState() != ContactListManager.LISTS_LOADED) { - do_loadContactLists(); + if (getState() != ContactListManager.LISTS_LOADED) { + loadContactListsAsync(); } - String name = presence.getFirstName(); - String address = presence.getServiceName(); - mContactListManager.doAddContact(name, address); + String name = presence.getNick(); + String address = presence.getJID(); - XmppAddress xaddress = new XmppAddress(name, address); + if (address == null) //sometimes with zeroconf/bonjour there may not be a JID + address = presence.getServiceName(); - Contact contact = getContact(xaddress.getAddress()); + XmppAddress xaddress = new XmppAddress(address); - int type = parsePresence(presence, offline); + if (name == null) + name = xaddress.getUser(); - if (contact == null) { - contact = new Contact(xaddress, name); + Contact contact = findOrCreateContact(name,xaddress.getAddress()); - debug(TAG, "got presence updated for NEW user: " - + contact.getAddress().getAddress() + " presence:" + type); - } else { - debug(TAG, "Got present update for EXISTING user: " - + contact.getAddress().getAddress() + " presence:" + type); + try { - Presence p = new Presence(type, presence.getMsg(), null, null, - Presence.CLIENT_TYPE_DEFAULT); - contact.setPresence(p); - Contact[] contacts = new Contact[] { contact }; + if (!mContactListManager.getDefaultContactList().containsContact(contact)) + { + mContactListManager.getDefaultContactList().addExistingContact(contact); + notifyContactListUpdated(mContactListManager.getDefaultContactList(), ContactListListener.LIST_CONTACT_ADDED, contact); + } + + } catch (ImException e) { + LogCleaner.error(TAG, "unable to add contact to list", e); + } + + Presence p = new Presence(parsePresence(presence, offline), presence.getMsg(), null, null, + Presence.CLIENT_TYPE_DEFAULT); + + contact.setPresence(p); + + Contact[] contacts = new Contact[] { contact }; + + notifyContactsPresenceUpdated(contacts); - notifyContactsPresenceUpdated(contacts); - } } @Override @@ -676,27 +781,42 @@ private void doAddContact(String name, String address) { } @Override - protected void doAddContactToListAsync(String address, ContactList list) throws ImException { + protected void doAddContactToListAsync(Contact address, ContactList list) throws ImException { debug(TAG, "add contact to " + list.getName()); // TODO } @Override - public void declineSubscriptionRequest(String contact) { + public void declineSubscriptionRequest(Contact contact) { debug(TAG, "decline subscription"); // TODO } @Override - public void approveSubscriptionRequest(String contact) { + public void approveSubscriptionRequest(Contact contact) { debug(TAG, "approve subscription"); // TODO } @Override - public Contact createTemporaryContact(String address) { - debug(TAG, "create temporary " + address); - return makeContact(parseAddressName(address), address); + public Contact[] createTemporaryContacts(String[] addresses) { + + Contact[] contacts = new Contact[addresses.length]; + + int i = 0; + + for (String address : addresses) + { + debug(TAG, "create temporary " + address); + contacts[i++] = makeContact(parseAddressName(address), address); + } + + return contacts; + } + + @Override + protected void doSetContactName(String address, String name) throws ImException { + // stub - no server } } @@ -712,7 +832,7 @@ protected void setState(int state, ImErrorInfo error) { } public static void debug(String tag, String msg) { - Log.d(tag, msg); + LogCleaner.debug(tag, msg); } @Override @@ -727,6 +847,7 @@ public void handle(Callback[] arg0) throws IOException { @Override public void reestablishSessionAsync(Map sessionContext) { execute(new Runnable() { + @Override public void run() { do_login(); } @@ -735,10 +856,11 @@ public void run() { @Override public void sendHeartbeat(long heartbeatInterval) { - InetAddress newAddress = getMyAddress(serviceName, false); - if (!ipAddress.equals(newAddress)) { + InetAddress newAddress = getMyAddress(mServiceName, false); + if (newAddress != null && !ipAddress.equals(newAddress)) { debug(TAG, "new address, reconnect"); execute(new Runnable() { + @Override public void run() { do_suspend(); do_login(); diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppImPlugin.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppImPlugin.java index 6a00dfdfc..dcde596af 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppImPlugin.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/LLXmppImPlugin.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPins.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPins.java index d4c3dca86..fbdc45eb0 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPins.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPins.java @@ -1,99 +1,176 @@ package info.guardianproject.otr.app.im.plugin.xmpp; -public class XMPPCertPins +import java.util.ArrayList; +import java.util.HashSet; + + +public class XMPPCertPins { - -/* -## Certificate 0 ## -Subject: CN=xmpp.binaryparadox.net -Issuer: CN=xmpp.binaryparadox.net -SHA1 FP: 0B93EB84CCBB7AA2CB92CF61A0348F63CCED14C1 -SPKI Pin: B3A7C02FC620C25F3C395AB043BF3C7729CE3C41 - -Connecting to jabber.ccc.de [2 of 4 hosts] -There were 3 certs in chain. -*/ - public final static String BINARYPARADOX = "B3A7C02FC620C25F3C395AB043BF3C7729CE3C41"; - - /* -## Certificate 0 ## -Subject: CN=jabber.ccc.de, O=Chaos Computer Club e.V., L=Hamburg, ST=Hamburg, -C=DE -Issuer: CN=CAcert Class 3 Root, OU=http://www.CAcert.org, O=CAcert Inc. -SHA1 FP: 8155CF376967A47417A7BEAA9B712AC63D161D50 -SPKI Pin: ADE7618FE3BB26C20FC089F3EF9963D548D21457 -*/ - - public final static String JABBERCCCDE = "ADE7618FE3BB26C20FC089F3EF9963D548D21457"; - - /* -## Certificate 1 ## -Subject: CN=CAcert Class 3 Root, OU=http://www.CAcert.org, O=CAcert Inc. -Issuer: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, -OU=http://www.cacert.org, O=Root CA -SHA1 FP: DB4C4269073FE9C2A37D890A5C1B18C4184E2A2D -SPKI Pin: F061D83F958F4D78B147B31339978EA9C251BA9B -*/ - - /* -## Certificate 2 ## -Subject: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, -OU=http://www.cacert.org, O=Root CA -Issuer: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, -OU=http://www.cacert.org, O=Root CA -SHA1 FP: 135CEC36F49CB8E93B1AB270CD80884676CE8F33 -SPKI Pin: 10DA624DEF41A3046DCDBA3D018F19DF3DC9A07C -*/ - - /* -Connecting to chat.facebook.com [3 of 4 hosts] -There were 2 certs in chain. - -## Certificate 0 ## -Subject: CN=chat.facebook.com, O="Facebook, Inc.", L=Palo Alto, ST=California, -C=US -Issuer: CN=DigiCert High Assurance CA-3, OU=www.digicert.com, O=DigiCert Inc, -C=US -SHA1 FP: 22E50EEEAF2DAF8E440377196C4D95734DEE94D9 -SPKI Pin: 1C5CC68C8ABE4AA0DBC7729BEA05A4EC756464B6 - -## Certificate 1 ## -Subject: CN=DigiCert High Assurance CA-3, OU=www.digicert.com, O=DigiCert Inc, -C=US -Issuer: CN=DigiCert High Assurance EV Root CA, OU=www.digicert.com, O=DigiCert -Inc, C=US -SHA1 FP: A2E32A1A2E9FAB6EAD6B05F64EA0641339E10011 -SPKI Pin: 95F9D7434B1CE71DEF4211EE6BE3C0E0256FAD95 -*/ - - public final static String CHATFACEBOOK = "1C5CC68C8ABE4AA0DBC7729BEA05A4EC756464B6"; - - /* -Connecting to dukgo.com [4 of 4 hosts] -There were 2 certs in chain. - -## Certificate 0 ## -Subject: CN=*.dukgo.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated -Issuer: CN=EssentialSSL CA, O=COMODO CA Limited, L=Salford, ST=Greater -Manchester, C=GB -SHA1 FP: 7727F3D42E00BDBFBEF697470F013B9E1C41A8CB -SPKI Pin: F44CF8786F4346082E18AB760CC49B6167B1B9D8 - -## Certificate 1 ## -Subject: CN=EssentialSSL CA, O=COMODO CA Limited, L=Salford, ST=Greater -Manchester, C=GB -Issuer: CN=COMODO Certification Authority, O=COMODO CA Limited, L=Salford, -ST=Greater Manchester, C=GB -SHA1 FP: 73820A20F8F47A457CD0B54CC4E4E31CEFA5C1E7 -SPKI Pin: CA91EDBE3EEF0F1736BDA1BA53E48E79B8ED7389 -*/ - public final static String DUKGO = "F44CF8786F4346082E18AB760CC49B6167B1B9D8"; - - /* Gmail/ Gtalk - * Calculating PIN for certificate: C=US, ST=California, L=Mountain View, O=Google Inc, CN=gmail.com -Pin Value: 4b09f2c32d093a31a175168346a459e2f0179d89 - - */ - - public final static String TALKGOOGLE = "4b09f2c32d093a31a175168346a459e2f0179d89"; + + // Use the following rules + // https://wiki.mozilla.org/Security/Server_Side_TLS + // AEADs over everything else + // PFS over non-PFS + // AES-128 over AES-256 ( https://www.schneier.com/blog/archives/2009/07/another_new_aes.html ) + // Avoid SHA-1 + // Remove RC4, MD5, DES + public final static String[] SSL_IDEAL_CIPHER_SUITES_API_20 = { + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES256_GCM_SHA384", + + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA384", + + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", +// "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", //not support in Android 6 + // "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + +// "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + // "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", +// "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", +// "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA" + }; + + // Follow above rules but as closely as possible but if we have to use RC4, use it last + public final static String[] SSL_IDEAL_CIPHER_SUITES = { + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + // "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + // "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + + // "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + // "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + //"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + // "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + + // UNCOMMENT THIS BLOCK ONLY IF ABSOLUTELY NECESSARY + /* + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + */ + }; + + public static ArrayList PINLIST = null; + + /** + * These are currently all pins of the CA's signing keys for the CAs used by + * servers that we trust. AndroidPinning always validates using the normal + * CA method, so there is no use to include cacert.org, similar CAs, or + * self-signed certificates here. AndroidPinning will fail anyway when it + * runs its built-in check against the system's trust manager. + * + * @return + */ + public static String[] getPinList() { + if (PINLIST == null) { + PINLIST = new ArrayList(); + // generated using http://gitlab.doeg.gy/cpu/jabberpinfetch + + /* chat.facebook.com + SubjectDN: CN=DigiCert High Assurance CA-3, OU=www.digicert.com, O=DigiCert Inc, C=US + IssuerDN: CN=DigiCert High Assurance EV Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US + Fingerprint: 42857855FB0EA43F54C9911E30E7791D8CE82705 + SPKI Pin: 95F9D7434B1CE71DEF4211EE6BE3C0E0256FAD95 + */ + PINLIST.add("95F9D7434B1CE71DEF4211EE6BE3C0E0256FAD95"); + + /* gmail.com + SubjectDN: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US + IssuerDN: OU=Equifax Secure Certificate Authority, O=Equifax, C=US + Fingerprint: 7359755C6DF9A0ABC3060BCE369564C8EC4542A3 + SPKI Pin: C07A98688D89FBAB05640C117DAA7D65B8CACC4E + */ + PINLIST.add("C07A98688D89FBAB05640C117DAA7D65B8CACC4E"); + + /* duck.co/dukgo.com im.mayfirst.org jabberpl.org neko.im riseup.net + SubjectDN: CN=COMODO RSA Certification Authority, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB + IssuerDN: CN=AddTrust External CA Root, OU=AddTrust External TTP Network, O=AddTrust AB, C=SE + Fingerprint: F5AD0BCC1AD56CD150725B1C866C30AD92EF21B0 + SPKI Pin: 6E584E3375BD57F6D5421B1601C2D8C0F53A9F6E + */ + PINLIST.add("6E584E3375BD57F6D5421B1601C2D8C0F53A9F6E"); + + /* jabber.calyxinstitute.org + SubjectDN: CN=RapidSSL CA, O="GeoTrust, Inc.", C=US + IssuerDN: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US + Fingerprint: C039A3269EE4B8E82D00C53FA797B5A19E836F47 + SPKI Pin: A39399C404C3B209B081C21F21622778C2748E4C + */ + PINLIST.add("A39399C404C3B209B081C21F21622778C2748E4C"); + + /* xmpp.jp + SubjectDN: CN=StartCom Certification Authority, OU=Secure Digital Certificate Signing, O=StartCom Ltd., C=IL + IssuerDN: CN=StartCom Certification Authority, OU=Secure Digital Certificate Signing, O=StartCom Ltd., C=IL + Fingerprint: 3E2BF7F2031B96F38CE6C4D8A85D3E2D58476A0F + SPKI Pin: 234B71255613E130DDE34269C9CC30D46F0841E0 + */ + PINLIST.add("234B71255613E130DDE34269C9CC30D46F0841E0"); + + /* The following pins are for self-signed certificates and the + * cacert.org Certificate Authority certificate. AndroidPinning + * will always fail on these unless they have been manually + * installed into the system's keystore. AndroidPinning always + * does a check using the system's default trust manager. + */ + + /* + SubjectDN: CN=jabber.ccc.de, O=Chaos Computer Club e.V., L=Hamburg, ST=Hamburg, C=DE + IssuerDN: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, OU=http://www.cacert.org, O=Root CA + Fingerprint: 4E09F9D9F224174684768D467A84B139B86A021F + SPKI Pin: 686B3569ABE87202E9018532719CB67DD7EA3356 + */ + PINLIST.add("686B3569ABE87202E9018532719CB67DD7EA3356"); + + /* + SubjectDN: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, OU=http://www.cacert.org, O=Root CA + IssuerDN: EMAILADDRESS=support@cacert.org, CN=CA Cert Signing Authority, OU=http://www.cacert.org, O=Root CA + Fingerprint: 135CEC36F49CB8E93B1AB270CD80884676CE8F33 + SPKI Pin: 10DA624DEF41A3046DCDBA3D018F19DF3DC9A07C + */ + PINLIST.add("10DA624DEF41A3046DCDBA3D018F19DF3DC9A07C"); + //added pin from cacert.org downloadable class3 crt + PINLIST.add("f061d83f958f4d78b147b31339978ea9c251ba9b"); + + /* guardianproject.info/hyper.to self-signed + SubjectDN: CN=hyper.to, O=Chaos Inc., L=San Francisco, ST=California, C=US + IssuerDN: CN=hyper.to, O=Chaos Inc., L=San Francisco, ST=California, C=US + Fingerprint: 1064712E64D1AE7F4FDC2DEFDE7F19B1CEEB82B8 + SPKI Pin: 2B1292D6CD084EC90B5DBD398AEA15B853337971 + */ + PINLIST.add("2B1292D6CD084EC90B5DBD398AEA15B853337971"); + + // double check there are no duplicates by mistake + if (PINLIST.size() != new HashSet(PINLIST).size()) + throw new SecurityException("PINLIST has duplicate entries!"); + } + + return PINLIST.toArray(new String[PINLIST.size()]); + + } } diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppAddress.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppAddress.java index 06a5343bd..b88691297 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppAddress.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppAddress.java @@ -2,42 +2,34 @@ import info.guardianproject.otr.app.im.engine.Address; import android.os.Parcel; +import android.os.Parcelable; public class XmppAddress extends Address { private String mAddress; - private String mScreenName; + private String mUser; private String mResource; - - public XmppAddress() { - - } - public XmppAddress(String name, String address, String resource) { - mScreenName = name; - mAddress = address; - mResource = resource; - } - - public XmppAddress(String name, String address) { - mScreenName = name; - mAddress = address; - + + public XmppAddress() {} + + @Override + public String getBareAddress() { int resIdx; - if ((resIdx = mAddress.indexOf("/"))!=-1) - mResource = mAddress.substring(resIdx+1); - + return mAddress.substring(0, resIdx); + else + return mAddress; } public XmppAddress(String fullJid) { - - mScreenName = fullJid.replaceFirst("@.*", ""); + + mUser = fullJid.replaceFirst("@.*", ""); mAddress = fullJid; - - int resIdx; - if ((resIdx = fullJid.indexOf("/"))!=-1) - mResource = fullJid.substring(resIdx+1); - + + String[] presenceParts = fullJid.split("/"); + if (presenceParts.length > 1) + mResource = presenceParts[1]; + } @Override @@ -46,27 +38,27 @@ public String getAddress() { } @Override - public String getScreenName() { - return mScreenName; + public String getUser() { + return mUser; } - + @Override public void readFromParcel(Parcel source) { - mScreenName = source.readString(); + mUser = source.readString(); mAddress = source.readString(); mResource = source.readString(); } @Override public void writeToParcel(Parcel dest) { - dest.writeString(mScreenName); + dest.writeString(mUser); dest.writeString(mAddress); dest.writeString(mResource); } - + @Override public String getResource() { return mResource; } - + } \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppConnection.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppConnection.java index 55c9f864c..72df9fd8a 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppConnection.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppConnection.java @@ -14,6 +14,7 @@ import info.guardianproject.otr.app.im.engine.ContactListListener; import info.guardianproject.otr.app.im.engine.ContactListManager; import info.guardianproject.otr.app.im.engine.ImConnection; +import info.guardianproject.otr.app.im.engine.ImEntity; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.ImException; import info.guardianproject.otr.app.im.engine.Invitation; @@ -22,39 +23,40 @@ import info.guardianproject.otr.app.im.plugin.xmpp.auth.GTalkOAuth2; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.provider.ImpsErrorInfo; +import info.guardianproject.otr.app.im.service.ChatSessionAdapter; import info.guardianproject.util.DNSUtil; import info.guardianproject.util.Debug; -import info.guardianproject.util.LogCleaner; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.KeyManagementException; -import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedList; import java.util.Map; import java.util.Random; -import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.net.ssl.KeyManager; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; -import net.java.otr4j.session.SessionStatus; - import org.apache.harmony.javax.security.auth.callback.Callback; import org.apache.harmony.javax.security.auth.callback.CallbackHandler; import org.jivesoftware.smack.ConnectionConfiguration; @@ -74,10 +76,9 @@ import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; -import org.jivesoftware.smack.packet.DefaultPacketExtension; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message.Body; import org.jivesoftware.smack.packet.Packet; -import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.Presence.Mode; import org.jivesoftware.smack.packet.Presence.Type; import org.jivesoftware.smack.provider.PrivacyProvider; @@ -113,12 +114,16 @@ import org.jivesoftware.smackx.provider.VCardProvider; import org.jivesoftware.smackx.provider.XHTMLExtensionProvider; import org.jivesoftware.smackx.search.UserSearch; -import org.thoughtcrime.ssl.pinning.PinningTrustManager; -import org.thoughtcrime.ssl.pinning.SystemKeyStore; import android.accounts.AccountManager; import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import de.duenndns.ssl.MemorizingTrustManager; @@ -128,14 +133,17 @@ public class XmppConnection extends ImConnection implements CallbackHandler { final static String TAG = "GB.XmppConnection"; private final static boolean PING_ENABLED = true; - private XmppContactList mContactListManager; + private XmppContactListManager mContactListManager; private Contact mUser; + private boolean mUseTor; // watch out, this is a different XMPPConnection class than XmppConnection! ;) // Synchronized by executor thread private MyXMPPConnection mConnection; private XmppStreamHandler mStreamHandler; + private Roster mRoster; + private XmppChatSessionManager mSessionManager; private ConnectionConfiguration mConfig; @@ -145,35 +153,20 @@ public class XmppConnection extends ImConnection implements CallbackHandler { private boolean mRetryLogin; private ThreadPoolExecutor mExecutor; + private Timer mTimerPresence; private ProxyInfo mProxyInfo = null; private long mAccountId = -1; private long mProviderId = -1; - private String mPasswordTemp; private boolean mIsGoogleAuth = false; - - /* - private final static String TRUSTSTORE_TYPE = "BKS"; - private final static String TRUSTSTORE_PATH = "debiancacerts.bks"; - private final static String TRUSTSTORE_PASS = "changeit"; - private final static String KEYMANAGER_TYPE = "X509"; - */ - private final static String SSLCONTEXT_TYPE = "TLS"; - private X509TrustManager mTrustManager; - //private StrongTrustManager mStrongTrustManager; - - private SSLContext sslContext; - - private KeyStore ks = null; - private KeyManager[] kms = null; - private Context aContext; + private final static String SSLCONTEXT_TYPE = "TLS"; - private final static String IS_GOOGLE = "google"; + private static SSLContext sslContext; - private final static int SOTIMEOUT = 15000; + private final static int SOTIMEOUT = 60000; private PacketCollector mPingCollector; private String mUsername; @@ -183,15 +176,16 @@ public class XmppConnection extends ImConnection implements CallbackHandler { private int mGlobalId; private static int mGlobalCount; - + private final Random rndForTorCircuits = new Random(); - + // Maintains a sequence counting up to the user configured heartbeat interval private int heartbeatSequence = 0; - - LinkedBlockingQueue qAvatar = new LinkedBlockingQueue (); + private LinkedList qAvatar = new LinkedList (); + private LinkedList qPresence = new LinkedList(); + private LinkedList qPacket = new LinkedList(); public XmppConnection(Context context) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { super(context); @@ -200,45 +194,62 @@ public XmppConnection(Context context) throws IOException, KeyStoreException, No mGlobalId = mGlobalCount++; } - aContext = context; - Debug.onConnectionStart(); - - //setup SSL managers + SmackConfiguration.setPacketReplyTimeout(SOTIMEOUT); // Create a single threaded executor. This will serialize actions on the underlying connection. createExecutor(); addProviderManagerExtensions(); - + XmppStreamHandler.addExtensionProviders(); DeliveryReceipts.addExtensionProviders(); - ServiceDiscoveryManager.setIdentityName("Gibberbot"); + ServiceDiscoveryManager.setIdentityName("ChatSecure"); ServiceDiscoveryManager.setIdentityType("phone"); - - mUser = makeUser(); } - Contact makeUser() { + public void initUser(long providerId, long accountId) throws ImException + { ContentResolver contentResolver = mContext.getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(providerId)},null); + + if (cursor == null) + throw new ImException("unable to query settings"); + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( - contentResolver, mProviderId, false, null); + cursor, contentResolver, providerId, false, null); + + mProviderId = providerId; + mAccountId = accountId; + mUser = makeUser(providerSettings, contentResolver); + mUseTor = providerSettings.getUseTor(); + + providerSettings.close(); + } + + private Contact makeUser(Imps.ProviderSettings.QueryMap providerSettings, ContentResolver contentResolver) { + String userName = Imps.Account.getUserName(contentResolver, mAccountId); String domain = providerSettings.getDomain(); - String xmppName = userName + '@' + domain; - providerSettings.close(); - - return new Contact(new XmppAddress(userName, xmppName), xmppName); + String xmppName = userName + '@' + domain + '/' + providerSettings.getXmppResource(); + + return new Contact(new XmppAddress(xmppName), userName); } private void createExecutor() { - mExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue()); + mExecutor = new ThreadPoolExecutor(1, 1, 1L, TimeUnit.SECONDS, + new LinkedBlockingQueue()); + } private boolean execute(Runnable runnable) { + + if (mExecutor == null) + createExecutor (); //if we disconnected, will need to recreate executor here, because join() made it null + try { mExecutor.execute(runnable); } catch (RejectedExecutionException ex) { @@ -251,8 +262,9 @@ private boolean execute(Runnable runnable) { private boolean executeIfIdle(Runnable runnable) { if (mExecutor.getActiveCount() + mExecutor.getQueue().size() == 0) { return execute(runnable); - } - return false; + } + + return false; } // This runs in executor thread, and since there is only one such thread, we will definitely @@ -276,134 +288,138 @@ boolean joinGracefully() throws InterruptedException { executor.shutdown(); return executor.awaitTermination(1, TimeUnit.SECONDS); } - + return false; } public void sendPacket(final org.jivesoftware.smack.packet.Packet packet) { - execute(new Runnable() { - @Override - public void run() { - if (mConnection == null) { - Log.w(TAG, "postponed packet to " + packet.getTo() - + " because we are not connected"); - postpone(packet); - return; - } - try { - mConnection.sendPacket(packet); - } catch (IllegalStateException ex) { - postpone(packet); - Log.w(TAG, "postponed packet to " + packet.getTo() - + " because socket is disconnected"); - } - } - }); + qPacket.add(packet); + } void postpone(final org.jivesoftware.smack.packet.Packet packet) { if (packet instanceof org.jivesoftware.smack.packet.Message) { - ChatSession session = findOrCreateSession(packet.getTo()); + boolean groupChat = ((org.jivesoftware.smack.packet.Message) packet).getType().equals( org.jivesoftware.smack.packet.Message.Type.groupchat); + ChatSession session = findOrCreateSession(packet.getTo(), groupChat); session.onMessagePostponed(packet.getPacketID()); } } - - /* + + + private boolean mLoadingAvatars = false; + private void loadVCardsAsync () { - // Using an AsyncTask to load the slow images in a background thread - new AsyncTask() { - - @Override - protected String doInBackground(String... params) { - loadVCards(); - return ""; - } - - @Override - protected void onPostExecute(String result) { - super.onPostExecute(result); - - } - }.execute(""); + if (!mLoadingAvatars) + { + execute(new AvatarLoader()); + } } - - private void loadVCards () + + private class AvatarLoader implements Runnable { - String jid = null; - ContentResolver resolver = mContext.getContentResolver(); - - - try - { - while ((jid = qAvatar.poll(1000, TimeUnit.MILLISECONDS)) != null) + @Override + public void run () { + + mLoadingAvatars = true; + + ContentResolver resolver = mContext.getContentResolver(); + + try { - - loadVCard (resolver, jid); - - + while (qAvatar.size()>0) + { + + loadVCard (resolver, qAvatar.pop(), null); + + } } + catch (Exception e) {} + + mLoadingAvatars = false; } - catch (Exception e) {} - - - - };*/ - - private boolean loadVCard (ContentResolver resolver, String jid, boolean forceLoad) + } + + private boolean loadVCard (ContentResolver resolver, String jid, String hash) { try { - - - if (forceLoad || (!DatabaseUtils.hasAvatarContact(resolver, Imps.Avatars.CONTENT_URI, jid))) + + boolean loadAvatar = false; + + if (hash != null) + loadAvatar = (!DatabaseUtils.doesAvatarHashExist(resolver, Imps.Avatars.CONTENT_URI, jid, hash)); + else + { + loadAvatar = DatabaseUtils.hasAvatarContact(resolver, Imps.Avatars.CONTENT_URI, jid); + } + + if (!loadAvatar) { debug(ImApp.LOG_TAG, "loading vcard for: " + jid); - + VCard vCard = new VCard(); - + // FIXME synchronize this to executor thread - - vCard.load(mConnection, jid); - + + vCard.load(mConnection, jid); // If VCard is loaded, then save the avatar to the personal folder. String avatarHash = vCard.getAvatarHash(); - + if (avatarHash != null) { byte[] avatarBytes = vCard.getAvatar(); - + if (avatarBytes != null) { - + debug(ImApp.LOG_TAG, "found avatar image in vcard for: " + jid); - - DatabaseUtils.insertAvatarBlob(resolver, Imps.Avatars.CONTENT_URI, mProviderId, mAccountId, avatarBytes, avatarHash, jid); - + debug(ImApp.LOG_TAG, "start avatar length: " + avatarBytes.length); + + int width = ImApp.DEFAULT_AVATAR_WIDTH; + int height = ImApp.DEFAULT_AVATAR_HEIGHT; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length,options); + options.inSampleSize = DatabaseUtils.calculateInSampleSize(options, width, height); + options.inJustDecodeBounds = false; + + Bitmap b = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length,options); + b = Bitmap.createScaledBitmap(b, ImApp.DEFAULT_AVATAR_WIDTH, ImApp.DEFAULT_AVATAR_HEIGHT, false); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.compress(Bitmap.CompressFormat.JPEG, 80, stream); + byte[] avatarBytesCompressed = stream.toByteArray(); + + debug(ImApp.LOG_TAG, "compressed avatar length: " + avatarBytesCompressed.length); + + DatabaseUtils.insertAvatarBlob(resolver, Imps.Avatars.CONTENT_URI, mProviderId, mAccountId, avatarBytesCompressed, avatarHash, XmppAddress.stripResource(jid)); + // int providerId, int accountId, byte[] data, String hash,String contact return true; } } - + } - + } catch (XMPPException e) { - - Log.d(ImApp.LOG_TAG,"err loading vcard"); - + + // Log.d(ImApp.LOG_TAG,"err loading vcard"); + if (e.getStreamError() != null) { String streamErr = e.getStreamError().getCode(); - + if (streamErr != null && (streamErr.contains("404") || streamErr.contains("503"))) - { - return false; - } + { + return false; + } } - + } - + return false; } @@ -411,7 +427,6 @@ private boolean loadVCard (ContentResolver resolver, String jid, boolean forceLo protected void doUpdateUserPresenceAsync(Presence presence) { org.jivesoftware.smack.packet.Presence packet = makePresencePacket(presence); - sendPacket(packet); mUserPresence = presence; notifyUserPresenceUpdated(); @@ -449,18 +464,18 @@ private org.jivesoftware.smack.packet.Presence makePresencePacket(Presence prese @Override public int getCapability() { - - return ImConnection.CAPABILITY_SESSION_REESTABLISHMENT & ImConnection.CAPABILITY_GROUP_CHAT; + + return ImConnection.CAPABILITY_SESSION_REESTABLISHMENT | ImConnection.CAPABILITY_GROUP_CHAT; } private XmppChatGroupManager mChatGroupManager = null; - + @Override public synchronized ChatGroupManager getChatGroupManager() { - + if (mChatGroupManager == null) mChatGroupManager = new XmppChatGroupManager(); - + return mChatGroupManager; } @@ -468,19 +483,47 @@ public class XmppChatGroupManager extends ChatGroupManager { private Hashtable mMUCs = new Hashtable(); - + public MultiUserChat getMultiUserChat (String chatRoomJid) { return mMUCs.get(chatRoomJid); } + public void reconnectAll () + { + for (MultiUserChat muc : mMUCs.values()) + { + if (!muc.isJoined()) + { + try { + muc.join(muc.getNickname()); + } catch (XMPPException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + @Override - public boolean createChatGroupAsync(String chatRoomJid) throws Exception { - + public boolean createChatGroupAsync(String chatRoomJid, String nickname) throws Exception { + + if (mConnection == null || getState() != ImConnection.LOGGED_IN) + return false; + RoomInfo roomInfo = null; - Address address = new XmppAddress (chatRoomJid); + if (chatRoomJid.indexOf("@")==-1) + { + //let's add a host to that! + + Collection servers = MultiUserChat.getServiceNames(mConnection); + chatRoomJid += '@' + servers.iterator().next(); + + } + Address address = new XmppAddress (chatRoomJid); + try { //first check if the room already exists @@ -490,26 +533,31 @@ public boolean createChatGroupAsync(String chatRoomJid) throws Exception { { //who knows? } - + if (roomInfo == null) { - //if the room does not exist, then create one - + //if the room does not exist, then create one + //should be room@server String[] parts = chatRoomJid.split("@"); String room = parts[0]; String server = parts[1]; - String nickname = mUser.getName().split("@")[0]; + + if (nickname == null || nickname.length() == 0) + { + nickname = mUsername; + } try { - + // Create a MultiUserChat using a Connection for a room MultiUserChat muc = new MultiUserChat(mConnection, chatRoomJid); - + try { // Create the room muc.create(nickname); + } catch (XMPPException iae) { @@ -522,17 +570,17 @@ public boolean createChatGroupAsync(String chatRoomJid) throws Exception { throw iae; } } - + try { Form form = muc.getConfigurationForm(); Form submitForm = form.createAnswerForm(); for (Iterator fields = form.getFields();fields.hasNext();){ - FormField field = (FormField) fields.next(); + FormField field = (FormField) fields.next(); if(!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable()!= null){ submitForm.setDefaultAnswer(field.getVariable()); } - } + } submitForm.setAnswer("muc#roomconfig_publicroom", true); muc.sendConfigurationForm(submitForm); } @@ -541,17 +589,18 @@ public boolean createChatGroupAsync(String chatRoomJid) throws Exception { if (Debug.DEBUG_ENABLED) Log.w(ImApp.LOG_TAG,"(ignoring) got an error configuring MUC room: " + xe.getLocalizedMessage()); } - + muc.join(nickname); - + ChatGroup chatGroup = new ChatGroup(address,room,this); - mGroups.put(address.getAddress(), chatGroup); - mMUCs.put(chatRoomJid, muc); + mGroups.put(address.getAddress(), chatGroup); + mMUCs.put(chatRoomJid, muc); + return true; - + } catch (XMPPException e) { - + Log.e(ImApp.LOG_TAG,"error creating MUC",e); return false; } @@ -559,132 +608,173 @@ public boolean createChatGroupAsync(String chatRoomJid) throws Exception { else { //otherwise, join the room! - + joinChatGroupAsync(address); return true; } - + } @Override public void deleteChatGroupAsync(ChatGroup group) { - + String chatRoomJid = group.getAddress().getAddress(); - + if (mMUCs.containsKey(chatRoomJid)) { MultiUserChat muc = mMUCs.get(chatRoomJid); - + try { muc.destroy("", null); - + mMUCs.remove(chatRoomJid); - + } catch (XMPPException e) { Log.e(ImApp.LOG_TAG,"error destroying MUC",e); } - + } - + } @Override protected void addGroupMemberAsync(ChatGroup group, Contact contact) { - // TODO Auto-generated method stub - - - + + String chatRoomJid = group.getAddress().getAddress(); + + if (mMUCs.containsKey(chatRoomJid)) + { + MultiUserChat muc = mMUCs.get(chatRoomJid); + muc.invite(contact.getAddress().getBareAddress(),""); + } + + } @Override protected void removeGroupMemberAsync(ChatGroup group, Contact contact) { - // TODO Auto-generated method stub - - - } - @Override - public void joinChatGroupAsync(Address address) { - - String chatRoomJid = address.getAddress(); - String[] parts = chatRoomJid.split("@"); - String room = parts[0]; - String server = parts[1]; - String nickname = mUser.getName().split("@")[0]; - - try { - - // Create a MultiUserChat using a Connection for a room - MultiUserChat muc = new MultiUserChat(mConnection, chatRoomJid); - // Create the room - muc.join(nickname); - - ChatGroup chatGroup = new ChatGroup(address,room,this); - mGroups.put(address.getAddress(), chatGroup); - mMUCs.put(chatRoomJid, muc); - - - - } catch (XMPPException e) { - Log.e(ImApp.LOG_TAG,"error joining MUC",e); + String chatRoomJid = group.getAddress().getAddress(); + + if (mMUCs.containsKey(chatRoomJid)) + { + MultiUserChat muc = mMUCs.get(chatRoomJid); + try { + muc.kickParticipant(chatRoomJid, contact.getAddress().getBareAddress()); + } catch (XMPPException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } - @Override - public void leaveChatGroupAsync(ChatGroup group) { - String chatRoomJid = group.getAddress().getAddress(); - - if (mMUCs.containsKey(chatRoomJid)) + public String getDefaultMultiUserChatServer () + { + try { - MultiUserChat muc = mMUCs.get(chatRoomJid); - muc.leave(); - mMUCs.remove(chatRoomJid); + if (mConnection == null) + return null; + + Collection servers = MultiUserChat.getServiceNames(mConnection); + + if (servers == null || servers.isEmpty()) + return null; + else + return servers.iterator().next(); + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"error finding MUC",e); } + return null; } + @Override - public void inviteUserAsync(ChatGroup group, Contact invitee) { - - String chatRoomJid = group.getAddress().getAddress(); - + public void joinChatGroupAsync(Address address) { + + String chatRoomJid = address.getAddress(); + String[] parts = chatRoomJid.split("@"); + String room = parts[0]; + String server = parts[1]; + String nickname = mUser.getName().split("@")[0]; + + try { + + // Create a MultiUserChat using a Connection for a room + MultiUserChat muc = new MultiUserChat(mConnection, chatRoomJid); + + // Create the room + muc.join(nickname); + + ChatGroup chatGroup = new ChatGroup(address,room,this); + mGroups.put(address.getAddress(), chatGroup); + mMUCs.put(chatRoomJid, muc); + + + + } catch (XMPPException e) { + Log.e(ImApp.LOG_TAG,"error joining MUC",e); + } + + } + + @Override + public void leaveChatGroupAsync(ChatGroup group) { + String chatRoomJid = group.getAddress().getAddress(); + if (mMUCs.containsKey(chatRoomJid)) { - MultiUserChat muc = mMUCs.get(chatRoomJid); - + MultiUserChat muc = mMUCs.get(chatRoomJid); + muc.leave(); + mMUCs.remove(chatRoomJid); + + } + + } + + @Override + public void inviteUserAsync(ChatGroup group, Contact invitee) { + + String chatRoomJid = group.getAddress().getAddress(); + + if (mMUCs.containsKey(chatRoomJid)) + { + MultiUserChat muc = mMUCs.get(chatRoomJid); + String reason = ""; //no reason for now muc.invite(invitee.getAddress().getAddress(),reason); - + } - + } @Override public void acceptInvitationAsync(Invitation invitation) { - + Address addressGroup = invitation.getGroupAddress(); - + joinChatGroupAsync (addressGroup); - + } @Override public void rejectInvitationAsync(Invitation invitation) { - + Address addressGroup = invitation.getGroupAddress(); - + String reason = ""; // no reason for now - - MultiUserChat.decline(mConnection, addressGroup.getAddress(),invitation.getSender().getAddress(),reason); - - + + MultiUserChat.decline(mConnection, addressGroup.getAddress(),invitation.getSender().getAddress(),reason); + + } - + }; - + @Override public synchronized ChatSessionManager getChatSessionManager() { @@ -695,10 +785,10 @@ public synchronized ChatSessionManager getChatSessionManager() { } @Override - public synchronized XmppContactList getContactListManager() { + public synchronized XmppContactListManager getContactListManager() { if (mContactListManager == null) - mContactListManager = new XmppContactList(); + mContactListManager = new XmppContactListManager(); return mContactListManager; } @@ -717,18 +807,46 @@ public Map getSessionContext() { @Override public int[] getSupportedPresenceStatus() { return new int[] { Presence.AVAILABLE, Presence.AWAY, Presence.IDLE, Presence.OFFLINE, - Presence.DO_NOT_DISTURB, }; + Presence.DO_NOT_DISTURB, }; + } + + @Override + public boolean isUsingTor() { + return mUseTor; } @Override public void loginAsync(long accountId, String passwordTemp, long providerId, boolean retry) { + mAccountId = accountId; - mPasswordTemp = passwordTemp; + mPassword = passwordTemp; mProviderId = providerId; mRetryLogin = retry; - mUser = makeUser(); - + ContentResolver contentResolver = mContext.getContentResolver(); + + if (mPassword == null) + mPassword = Imps.Account.getPassword(contentResolver, mAccountId); + + mIsGoogleAuth = mPassword.startsWith(GTalkOAuth2.NAME); + + if (mIsGoogleAuth) + { + mPassword = mPassword.split(":")[1]; + } + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + + if (cursor == null) + return; + + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( + cursor, contentResolver, mProviderId, false, null); + + mUser = makeUser(providerSettings, contentResolver); + + providerSettings.close(); + execute(new Runnable() { @Override public void run() { @@ -739,100 +857,128 @@ public void run() { // Runs in executor thread private void do_login() { - + + /* if (mConnection != null) { setState(getState(), new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, "still trying...")); return; - } - + }*/ + ContentResolver contentResolver = mContext.getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(mProviderId)},null); + + if (cursor == null) + return; //not going to work + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( - contentResolver, mProviderId, false, null); - + cursor, contentResolver, mProviderId, false, null); + + // providerSettings is closed in initConnection(); String userName = Imps.Account.getUserName(contentResolver, mAccountId); - String password = Imps.Account.getPassword(contentResolver, mAccountId); - + String defaultStatus = null; - + mNeedReconnect = true; setState(LOGGING_IN, null); - + mUserPresence = new Presence(Presence.AVAILABLE, defaultStatus, Presence.CLIENT_TYPE_MOBILE); try { - if (userName.length() == 0) + if (userName == null || userName.length() == 0) throw new XMPPException("empty username not allowed"); - initConnectionAndLogin(providerSettings, userName, password); - } catch (Exception e) { - debug(TAG, "login failed: " + e.getLocalizedMessage()); - mConnection = null; + + initConnectionAndLogin(providerSettings, userName); + + setState(LOGGED_IN, null); + debug(TAG, "logged in"); + mNeedReconnect = false; + + + + } catch (XMPPException e) { + debug(TAG, "exception thrown on connection",e); + + ImErrorInfo info = new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, e.getMessage()); - - if (e == null || e.getMessage() == null) { - debug(TAG, "NPE: " + e.getMessage()); - Log.e(TAG,"login error",e); - info = new ImErrorInfo(ImErrorInfo.INVALID_USERNAME, "unknown error"); - disconnected(info); - mRetryLogin = false; - } else if (e.getMessage().contains("not-authorized") - || e.getMessage().contains("authentication failed")) { - - if (mIsGoogleAuth && password.contains(GTalkOAuth2.NAME)) + mRetryLogin = true; // our default behavior is to retry + + if (mConnection != null && mConnection.isConnected() && (!mConnection.isAuthenticated())) { + + if (mIsGoogleAuth) { debug (TAG, "google failed; may need to refresh"); - password = refreshGoogleToken (userName, password,providerSettings.getDomain()); - + String newPassword = refreshGoogleToken (userName, mPassword,providerSettings.getDomain()); + + if (newPassword != null) + mPassword = newPassword; + mRetryLogin = true; - setState(LOGGING_IN, info); + } else { debug(TAG, "not authorized - will not retry"); info = new ImErrorInfo(ImErrorInfo.INVALID_USERNAME, "invalid user/password"); - disconnected(info); mRetryLogin = false; + mNeedReconnect = false; } - } else if (mRetryLogin) { + } + + + if (mRetryLogin && getState() != SUSPENDED) { debug(TAG, "will retry"); setState(LOGGING_IN, info); + maybe_reconnect(); + } else { - debug(TAG, "will not retry"); - mConnection = null; - disconnected(info); + //debug(TAG, "will not retry"); //WE MUST ALWAYS RETRY! + // disconnect(); + // disconnected(info); } - return; - } finally { - mNeedReconnect = false; - } + } catch (Exception e) { - // TODO should we really be using the same name for both address and name? - setState(LOGGED_IN, null); - debug(TAG, "logged in"); - - + debug(TAG, "login failed",e); + mRetryLogin = true; + mNeedReconnect = true; + + debug(TAG, "will retry"); + ImErrorInfo info = new ImErrorInfo(ImErrorInfo.UNKNOWN_ERROR, "keymanagement exception"); + setState(LOGGING_IN, info); + + } + finally { + providerSettings.close(); + + if (!cursor.isClosed()) + cursor.close(); + } } - - private String refreshGoogleToken (String userName, String oldPassword, String domain) + + private String refreshGoogleToken (String userName, String expiredToken, String domain) { - //invalidate our old one, that is locally cached - AccountManager.get(mContext.getApplicationContext()).invalidateAuthToken("com.google", oldPassword.split(":")[1]); - + + //invalidate our old one, that is locally cached + AccountManager.get(mContext.getApplicationContext()).invalidateAuthToken("com.google", expiredToken); + //request a new one - String password = GTalkOAuth2.getGoogleAuthToken(userName + '@' + domain, mContext.getApplicationContext()); + String newToken = GTalkOAuth2.getGoogleAuthToken(userName + '@' + domain, mContext.getApplicationContext()); - password = GTalkOAuth2.NAME + ':' + password; - //now store the new one, for future use until it expires - final long accountId = ImApp.insertOrUpdateAccount(mContext.getContentResolver(), mProviderId, userName, - password ); - - return password; + if (newToken != null) + { + //now store the new one, for future use until it expires + ImApp.insertOrUpdateAccount(mContext.getContentResolver(), mProviderId, userName, + GTalkOAuth2.NAME + ':' + newToken ); + } + + return newToken; } @@ -841,11 +987,11 @@ public void setProxy(String type, String host, int port) { if (type == null) { mProxyInfo = ProxyInfo.forNoProxy(); } else { - + ProxyInfo.ProxyType pType = ProxyType.valueOf(type); String username = null; String password = null; - + if (type.equals(TorProxyInfo.PROXY_TYPE) //socks5 && host.equals(TorProxyInfo.PROXY_HOST) //127.0.0.1 && port == TorProxyInfo.PROXY_PORT) //9050 @@ -853,67 +999,61 @@ public void setProxy(String type, String host, int port) { //if the proxy is for Orbot/Tor then generate random usr/pwd to isolate Tor streams username = rndForTorCircuits.nextInt(100000)+""; password = rndForTorCircuits.nextInt(100000)+""; - + } - + mProxyInfo = new ProxyInfo(pType, host, port, username, password); - + } } public void initConnection(MyXMPPConnection connection, Contact user, int state) { mConnection = connection; + mRoster = mConnection.getRoster(); mUser = user; setState(state, null); } - - private void initConnectionAndLogin (Imps.ProviderSettings.QueryMap providerSettings,String userName, String password) throws Exception - { + + private void initConnectionAndLogin (Imps.ProviderSettings.QueryMap providerSettings,String userName) throws XMPPException, KeyManagementException, NoSuchAlgorithmException, IllegalStateException, RuntimeException + { Debug.onConnectionStart(); //only activates if Debug TRUE is set, so you can leave this in! - - - if (mPasswordTemp != null) - password = mPasswordTemp; - - mIsGoogleAuth = password.startsWith(GTalkOAuth2.NAME); - if (mIsGoogleAuth) - { - String domain = providerSettings.getDomain(); - password = refreshGoogleToken(userName, password, domain); - password = password.split(":")[1]; - mUsername = userName + '@' + domain; - - } - else - { - mUsername = userName; - } - - initConnection(providerSettings); + initConnection(providerSettings, userName); - mPassword = password; mResource = providerSettings.getXmppResource(); //disable compression based on statement by Ge0rg mConfig.setCompressionEnabled(false); - mConnection.login(mUsername, mPassword, mResource); - - mStreamHandler.notifyInitialLogin(); - initServiceDiscovery(); + if (mConnection.isConnected()) + { + + mConnection.login(mUsername, mPassword, mResource); + + String fullJid = mConnection.getUser(); + XmppAddress xa = new XmppAddress(fullJid); + mUser = new Contact(xa, xa.getUser()); + + mStreamHandler.notifyInitialLogin(); + initServiceDiscovery(); + + sendPresencePacket(); + + mRoster = mConnection.getRoster(); + mRoster.setSubscriptionMode(Roster.SubscriptionMode.manual); - sendPresencePacket(); + getContactListManager().listenToRoster(mRoster); + + } + - Roster roster = mConnection.getRoster(); - roster.setSubscriptionMode(Roster.SubscriptionMode.manual); - getContactListManager().listenToRoster(roster); } + // Runs in executor thread - private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) throws Exception { - - + private void initConnection(Imps.ProviderSettings.QueryMap providerSettings, String userName) throws NoSuchAlgorithmException, KeyManagementException, XMPPException { + + boolean allowPlainAuth = providerSettings.getAllowPlainAuth(); boolean requireTls = providerSettings.getRequireTls(); boolean doDnsSrv = providerSettings.getDoDnsSrv(); @@ -921,17 +1061,14 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr boolean useSASL = true;//!allowPlainAuth; - String domain = providerSettings.getDomain(); - String requestedServer = providerSettings.getServer(); - if ("".equals(requestedServer)) - requestedServer = null; + mPriority = providerSettings.getXmppResourcePrio(); int serverPort = providerSettings.getPort(); - String server = requestedServer; - - providerSettings.close(); // close this, which was opened in do_login() + String server = providerSettings.getServer(); + if ("".equals(server)) + server = null; debug(TAG, "TLS required? " + requireTls); debug(TAG, "cert verification? " + tlsCertVerify); @@ -944,12 +1081,12 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr { setProxy(null, null, -1); } - + if (mProxyInfo == null) mProxyInfo = ProxyInfo.forNoProxy(); // If user did not specify a server, and SRV requested then lookup SRV - if (doDnsSrv && requestedServer == null) { + if (doDnsSrv) { //java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); //java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); @@ -963,26 +1100,50 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr } debug(TAG, "(DNS SRV) resolved: " + domain + "=" + server + ":" + serverPort); + } - - if (serverPort == 0) + + if (server != null && server.contains("google.com")) + { + mUsername = userName + '@' + domain; + } + else if (domain.contains("gmail.com")) + { + mUsername = userName + '@' + domain; + } + else if (mIsGoogleAuth) + { + mUsername = userName + '@' + domain; + } + else + { + mUsername = userName; + } + + + if (serverPort == 0) //if serverPort is set to 0 then use 5222 as default serverPort = 5222; // No server requested and SRV lookup wasn't requested or returned nothing - use domain if (server == null) { debug(TAG, "(use domain) ConnectionConfiguration(" + domain + ", " + serverPort + ", " - + domain + ", mProxyInfo);"); + + domain + ", mProxyInfo);"); if (mProxyInfo == null) mConfig = new ConnectionConfiguration(domain, serverPort); else mConfig = new ConnectionConfiguration(domain, serverPort, mProxyInfo); - - server = domain; + + //server = domain; } else { debug(TAG, "(use server) ConnectionConfiguration(" + server + ", " + serverPort + ", " - + domain + ", mProxyInfo);"); + + domain + ", mProxyInfo);"); + + //String serviceName = domain; + + //if (server != null && (!server.endsWith(".onion"))) //if a connect server was manually entered, and is not an .onion address + // serviceName = server; if (mProxyInfo == null) mConfig = new ConnectionConfiguration(server, serverPort, domain); @@ -990,40 +1151,111 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr mConfig = new ConnectionConfiguration(server, serverPort, domain, mProxyInfo); } - + mConfig.setDebuggerEnabled(Debug.DEBUG_ENABLED); + mConfig.setSASLAuthenticationEnabled(useSASL); // Android has no support for Kerberos or GSSAPI, so disable completely SASLAuthentication.unregisterSASLMechanism("KERBEROS_V4"); SASLAuthentication.unregisterSASLMechanism("GSSAPI"); - //add gtalk auth in - - + SASLAuthentication.registerSASLMechanism( GTalkOAuth2.NAME, GTalkOAuth2.class ); + + if (mIsGoogleAuth) //if using google auth enable sasl + SASLAuthentication.supportSASLMechanism( GTalkOAuth2.NAME, 0); + else if (domain.contains("google.com")||domain.contains("gmail.com")) //if not google auth, disable if doing direct google auth + SASLAuthentication.unsupportSASLMechanism( GTalkOAuth2.NAME); + SASLAuthentication.supportSASLMechanism("PLAIN", 1); SASLAuthentication.supportSASLMechanism("DIGEST-MD5", 2); - if (requireTls) { - + + if (requireTls) { + + MemorizingTrustManager trustManager = ImApp.sImApp.getTrustManager(); + + if (sslContext == null) + { + + sslContext = SSLContext.getInstance(SSLCONTEXT_TYPE); + SecureRandom secureRandom = new java.security.SecureRandom(); + sslContext.init(null, new javax.net.ssl.TrustManager[] { trustManager }, + secureRandom); + + try + { + sslContext.getDefaultSSLParameters().getCipherSuites(); + + if (Build.VERSION.SDK_INT >= 20) { + + sslContext.getDefaultSSLParameters().setCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES_API_20); + } + else + { + sslContext.getDefaultSSLParameters().setCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); + } + } + catch (Exception e) + { + //this can happen if the cipher suites aren't available on the devices + debug(TAG, "Error setting ideal cipher suites: " + e); + + } + + + } + + + int currentapiVersion = android.os.Build.VERSION.SDK_INT; + if (currentapiVersion >= 16){ + // Enable TLS1.2 and TLS1.1 on supported versions of android + // http://stackoverflow.com/questions/16531807/android-client-server-on-tls-v1-2 + + //mConfig.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" }); + sslContext.getDefaultSSLParameters().setProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" }); + + } + + if (currentapiVersion >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH){ + mConfig.setEnabledCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); + } + + + HostnameVerifier hv = trustManager.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()); + + mConfig.setHostnameVerifier(hv); + mConfig.setCustomSSLContext(sslContext); + mConfig.setSecurityMode(SecurityMode.required); mConfig.setVerifyChainEnabled(true); mConfig.setVerifyRootCAEnabled(true); mConfig.setExpiredCertificatesCheckEnabled(true); + mConfig.setNotMatchingDomainCheckEnabled(true); + mConfig.setSelfSignedCertificateEnabled(false); - - // Per XMPP specs, cert must match domain, not SRV lookup result. Otherwise, DNS spoofing - // can enable MITM. - initSSLContext(domain, requestedServer, mConfig); + mConfig.setCallbackHandler(this); } else { - // if it finds a cert, still use it, but don't check anything since + // if it finds a cert, still use it, but don't check anything since // TLS errors are not expected by the user mConfig.setSecurityMode(SecurityMode.enabled); - mConfig.setSocketFactory(new DummySSLSocketFactory(getTrustManager())); + + if (sslContext == null) + { + sslContext = SSLContext.getInstance(SSLCONTEXT_TYPE); + + SecureRandom mSecureRandom = new java.security.SecureRandom(); + + sslContext.init(null, new javax.net.ssl.TrustManager[] { getDummyTrustManager () }, + mSecureRandom); + + sslContext.getDefaultSSLParameters().setCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); + } + mConfig.setCustomSSLContext(sslContext); if (!allowPlainAuth) SASLAuthentication.unsupportSASLMechanism("PLAIN"); @@ -1035,25 +1267,12 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr mConfig.setSelfSignedCertificateEnabled(true); } - if (mIsGoogleAuth) - { - mConfig.setSASLAuthenticationEnabled(true); - - SASLAuthentication.registerSASLMechanism( GTalkOAuth2.NAME, GTalkOAuth2.class ); - SASLAuthentication.supportSASLMechanism( GTalkOAuth2.NAME, 0); - } - else - { - SASLAuthentication.unregisterSASLMechanism( GTalkOAuth2.NAME); - SASLAuthentication.unsupportSASLMechanism( GTalkOAuth2.NAME); - } - // Don't use smack reconnection - not reliable mConfig.setReconnectionAllowed(false); mConfig.setSendPresence(true); - + mConfig.setRosterLoadedAtLogin(true); - + mConnection = new MyXMPPConnection(mConfig); //debug(TAG,"is secure connection? " + mConnection.isSecureConnection()); @@ -1064,50 +1283,92 @@ private void initConnection(Imps.ProviderSettings.QueryMap providerSettings) thr @Override public void processPacket(Packet packet) { debug(TAG, "receive message: " + packet.getFrom() + " to " + packet.getTo()); + org.jivesoftware.smack.packet.Message smackMessage = (org.jivesoftware.smack.packet.Message) packet; + String address = smackMessage.getFrom(); String body = smackMessage.getBody(); - - DeliveryReceipts.DeliveryReceipt dr = (DeliveryReceipts.DeliveryReceipt) smackMessage - .getExtension("received", DeliveryReceipts.NAMESPACE); - - if (dr != null) { + + if (smackMessage.getError() != null) + { + // smackMessage.getError().getCode(); - debug(TAG, "got delivery receipt for " + dr.getId()); - ChatSession session = findOrCreateSession(address); - session.onMessageReceipt(dr.getId()); - } + String error = "Error " + smackMessage.getError().getCode() + " (" + smackMessage.getError().getCondition() + "): " + smackMessage.getError().getMessage(); + + debug (TAG, error); + + return; + + } + + if (body == null) + { + + Collection mColl = smackMessage.getBodies(); + for (Body bodyPart : mColl) + { + String msg = bodyPart.getMessage(); + if (msg != null) + { + body = msg; + break; + } + } + + } + + DeliveryReceipts.DeliveryReceipt drIncoming = (DeliveryReceipts.DeliveryReceipt) smackMessage + .getExtension("received", DeliveryReceipts.NAMESPACE); + + if (drIncoming != null) { + + debug(TAG, "got delivery receipt for " + drIncoming.getId()); + boolean groupMessage = smackMessage.getType() == org.jivesoftware.smack.packet.Message.Type.groupchat; + ChatSession session = findOrCreateSession(address, groupMessage); + session.onMessageReceipt(drIncoming.getId()); + + } + if (body != null) { + XmppAddress aFrom = new XmppAddress(smackMessage.getFrom()); + XmppAddress aTo = new XmppAddress(smackMessage.getTo()); + + boolean isGroupMessage = smackMessage.getType() == org.jivesoftware.smack.packet.Message.Type.groupchat; + + ChatSession session = findOrCreateSession(address, isGroupMessage); - ChatSession session = findOrCreateSession(address); - Message rec = new Message(body); - rec.setTo(mUser.getAddress()); - rec.setFrom(new XmppAddress(smackMessage.getFrom())); + rec.setTo(aTo); + rec.setFrom(aFrom); rec.setDateTime(new Date()); - rec.setType(Imps.MessageType.INCOMING); - + rec.setType(Imps.MessageType.INCOMING); + + /* + // Detect if this was said by us, and mark message as outgoing + if (isGroupMessage && rec.getFrom().getResource().equals(rec.getTo().getUser())) { + rec.setType(Imps.MessageType.OUTGOING); + }*/ + boolean good = session.onReceiveMessage(rec); - + if (smackMessage.getExtension("request", DeliveryReceipts.NAMESPACE) != null) { if (good) { debug(TAG, "sending delivery receipt"); - // got XEP-0184 request, send receipt + // got XEP-0184 request, send receipt sendReceipt(smackMessage); session.onReceiptsExpected(); - } else { - debug(TAG, "not sending delivery receipt due to processing error"); + } else { + debug(TAG, "not sending delivery receipt due to processing error"); } - - } else if (!good) { - debug(TAG, "packet processing error"); - } - + + } else if (!good) { + debug(TAG, "packet processing error"); + } + } - } }, new PacketTypeFilter(org.jivesoftware.smack.packet.Message.class)); @@ -1116,55 +1377,22 @@ public void processPacket(Packet packet) { @Override public void processPacket(Packet packet) { - org.jivesoftware.smack.packet.Presence presence = (org.jivesoftware.smack.packet.Presence) packet; - Contact contact = findOrCreateContact(presence.getFrom()); - - if (presence.getType() == Type.subscribe) { - mContactListManager.getSubscriptionRequestListener().onSubScriptionRequest( - contact); - } else { - int type = parsePresence(presence); - - - Presence p = new Presence(type, presence.getStatus(), null, null, - Presence.CLIENT_TYPE_DEFAULT); - - - String from = presence.getFrom(); - String resource = null; - if (from != null && from.lastIndexOf("/") > 0) { - resource = from.substring(from.lastIndexOf("/") + 1); - - if (resource.indexOf('.')!=-1) - resource = resource.substring(0,resource.indexOf('.')); - - p.setResource(resource); - } - - contact.setPresence(p); - - /* - Message rec = new Message(presence.getStatus()); - rec.setTo(mUser.getAddress()); - rec.setFrom(new XmppAddress(address, name)); - rec.setDateTime(new Date()); - - rec.setType(Imps.MessageType.STATUS); - - ChatSession session = findOrCreateSession(address); - - boolean good = session.onReceiveMessage(rec); - */ - + org.jivesoftware.smack.packet.Presence presence = (org.jivesoftware.smack.packet.Presence) packet; + qPresence.push(presence); - } } }, new PacketTypeFilter(org.jivesoftware.smack.packet.Presence.class)); + if (mTimerPackets == null) + initPacketProcessor(); + + if (mTimerPresence == null) + initPresenceProcessor (); + ConnectionListener connectionListener = new ConnectionListener() { /** * Called from smack when connect() is fully successful - * + * * This is called on the executor thread while we are in reconnect() */ @Override @@ -1172,6 +1400,7 @@ public void reconnectionSuccessful() { if (mStreamHandler == null || !mStreamHandler.isResumePending()) { debug(TAG, "Reconnection success"); onReconnectionSuccessful(); + mRoster = mConnection.getRoster(); } else { debug(TAG, "Ignoring reconnection callback due to pending resume"); } @@ -1185,8 +1414,8 @@ public void reconnectionFailed(Exception e) { @Override public void reconnectingIn(int seconds) { - // We are not using the reconnection manager - throw new UnsupportedOperationException(); + // // We are not using the reconnection manager + // throw new UnsupportedOperationException(); } @Override @@ -1210,15 +1439,23 @@ public void run() { } }); } else if (!mNeedReconnect) { + execute(new Runnable() { - @Override + public void run() { if (getState() == LOGGED_IN) + { + //Thread.sleep(1000); + mNeedReconnect = true; setState(LOGGING_IN, - new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage())); - maybe_reconnect(); + new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network error")); + reconnect(); + } } + }); + + } } @@ -1232,7 +1469,7 @@ public void connectionClosed() { * - Connection is shutting down * - because we are calling disconnect * - in do_logout - * + * * - NOT * - because server disconnected "normally" * - we were trying to log in (initConnection), but are failing @@ -1241,20 +1478,35 @@ public void connectionClosed() { */ } }; - + mConnection.addConnectionListener(connectionListener); mStreamHandler = new XmppStreamHandler(mConnection, connectionListener); - - mConnection.connect(); - + for (int i = 0; i < 3; i++) + { + try + { + mConnection.connect(); + break; + } + catch (Exception uhe) + { + //sometimes DNS fails.. let's wait and try again a few times + try { Thread.sleep(500);} catch (Exception e){} + + } + + } + + if (!mConnection.isConnected()) + throw new XMPPException("Unable to connect to host"); + } - private void sendPresencePacket() { - org.jivesoftware.smack.packet.Presence presence = makePresencePacket(mUserPresence); - mConnection.sendPacket(presence); + private void sendPresencePacket() { + qPacket.add(makePresencePacket(mUserPresence)); } public void sendReceipt(org.jivesoftware.smack.packet.Message msg) { @@ -1262,53 +1514,70 @@ public void sendReceipt(org.jivesoftware.smack.packet.Message msg) { org.jivesoftware.smack.packet.Message ack = new org.jivesoftware.smack.packet.Message( msg.getFrom(), msg.getType()); ack.addExtension(new DeliveryReceipts.DeliveryReceipt(msg.getPacketID())); - mConnection.sendPacket(ack); + sendPacket(ack); } - private void initSSLContext(String domain, String requestedServer, - ConnectionConfiguration config) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { - - - sslContext = SSLContext.getInstance(SSLCONTEXT_TYPE); - - mTrustManager = getTrustManager (); - SecureRandom mSecureRandom = new java.security.SecureRandom(); - - sslContext.init(null, new javax.net.ssl.TrustManager[] { mTrustManager }, - mSecureRandom); - - config.setCustomSSLContext(sslContext); - config.setCallbackHandler(this); - } - - public synchronized X509TrustManager getTrustManager () + public X509TrustManager getDummyTrustManager () { - if (mTrustManager == null) - { - String[] PINLIST = {XMPPCertPins.TALKGOOGLE, XMPPCertPins.DUKGO, XMPPCertPins.CHATFACEBOOK, XMPPCertPins.JABBERCCCDE, XMPPCertPins.BINARYPARADOX}; - PinningTrustManager trustPinning = new PinningTrustManager(SystemKeyStore.getInstance(aContext),PINLIST, 0); - - mTrustManager = new MemorizingTrustManager(aContext, trustPinning, null); - } - - return mTrustManager; + + return new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + } - protected static int parsePresence(org.jivesoftware.smack.packet.Presence presence) { - int type = Presence.AVAILABLE; + protected int parsePresence(org.jivesoftware.smack.packet.Presence presence) { + int type = Imps.Presence.AVAILABLE; Mode rmode = presence.getMode(); Type rtype = presence.getType(); - - if (rmode == Mode.away || rmode == Mode.xa) - type = Presence.AWAY; + + //if a device sends something other than available, check if there is a higher priority one available on the server + /* + if (rmode != Mode.available) + { + if (mRoster != null) + { + org.jivesoftware.smack.packet.Presence npresence = mRoster.getPresence(XmppAddress.stripResource(presence.getFrom())); + rmode = npresence.getMode(); + rtype = npresence.getType(); + + if (rmode == Mode.away || rmode == Mode.xa) + type = Presence.AWAY; + else if (rmode == Mode.dnd) + type = Presence.DO_NOT_DISTURB; + else if (rtype == Type.unavailable || rtype == Type.error) + type = Presence.OFFLINE; + } + }*/ + + if (rmode == Mode.chat) + type = Imps.Presence.AVAILABLE; + else if (rmode == Mode.away || rmode == Mode.xa) + type = Imps.Presence.AWAY; else if (rmode == Mode.dnd) - type = Presence.DO_NOT_DISTURB; + type = Imps.Presence.DO_NOT_DISTURB; else if (rtype == Type.unavailable || rtype == Type.error) - type = Presence.OFFLINE; - + type = Imps.Presence.OFFLINE; + else if (rtype == Type.unsubscribed) + type = Imps.Presence.OFFLINE; + return type; } @@ -1322,22 +1591,23 @@ void disconnected(ImErrorInfo info) { @Override public void logoutAsync() { - execute(new Runnable() { + + new Thread(new Runnable() { @Override public void run() { do_logout(); } - }); + }).start(); + } // Force immediate logout public void logout() { - do_logout(); + logoutAsync(); } // Usually runs in executor thread, unless called from logout() private void do_logout() { - Log.w(TAG, "logout"); setState(LOGGING_OUT, null); disconnect(); disconnected(null); @@ -1365,6 +1635,7 @@ public void reestablishSessionAsync(Map sessionContext) { public void run() { if (getState() == SUSPENDED) { debug(TAG, "reestablish"); + mNeedReconnect = false; setState(LOGGING_IN, null); maybe_reconnect(); } @@ -1382,36 +1653,79 @@ public void run() { mNeedReconnect = false; clearPing(); // Do not try to reconnect anymore if we were asked to suspend - mStreamHandler.quickShutdown(); + + if (mStreamHandler != null) + mStreamHandler.quickShutdown(); + } }); } - private ChatSession findOrCreateSession(String address) { + private ChatSession findOrCreateSession(String address, boolean groupChat) { ChatSession session = mSessionManager.findSession(address); if (session == null) { - Contact contact = findOrCreateContact(address); - session = mSessionManager.createChatSession(contact); + ImEntity participant = findOrCreateParticipant(address, groupChat); + session = mSessionManager.createChatSession(participant,false); + } + return session; } - Contact findOrCreateContact(String address) { - Contact contact = mContactListManager.getContact(address); - if (contact == null) { - contact = makeContact(address); + ImEntity findOrCreateParticipant(String address, boolean groupChat) { + ImEntity participant = mContactListManager.getContact(address); + if (participant == null) { + if (!groupChat) { + participant = makeContact(address); + } + else { + try { + mChatGroupManager.createChatGroupAsync(address, mUser.getName()); + + Address xmppAddress = new XmppAddress(address); + + participant = mChatGroupManager.getChatGroup(xmppAddress); + } + catch (Exception e) { + Log.e(ImApp.LOG_TAG,"unable to join group chat",e); + } + } } - return contact; + return participant; } - - private static Contact makeContact(String address) { - - XmppAddress xAddress = new XmppAddress(address); - - Contact contact = new Contact(xAddress, xAddress.getScreenName()); + Contact findOrCreateContact(String address) { + return (Contact) findOrCreateParticipant(address, false); + } + + private Contact makeContact(String address) { + + Contact contact = null; + + //load from roster if we don't have the contact + RosterEntry rEntry = null; + + if (mConnection != null) + rEntry = mConnection.getRoster().getEntry(address); + + if (rEntry != null) + { + XmppAddress xAddress = new XmppAddress(address); + + String name = rEntry.getName(); + if (name == null) + name = xAddress.getUser(); + + contact = new Contact(xAddress, name); + } + else + { + XmppAddress xAddress = new XmppAddress(address); + + contact = new Contact(xAddress, xAddress.getUser()); + } return contact; } @@ -1419,57 +1733,76 @@ private static Contact makeContact(String address) { private final class XmppChatSessionManager extends ChatSessionManager { @Override public void sendMessageAsync(ChatSession session, Message message) { - - + String chatRoomJid = message.getTo().getAddress(); MultiUserChat muc = ((XmppChatGroupManager)getChatGroupManager()).getMultiUserChat(chatRoomJid); + + org.jivesoftware.smack.packet.Message msgXmpp = null; if (muc != null) { - org.jivesoftware.smack.packet.Message msg = muc.createMessage(); - - msg.setBody(message.getBody()); - - message.setID(msg.getPacketID()); - sendPacket(msg); + msgXmpp = muc.createMessage(); + } else { - org.jivesoftware.smack.packet.Message msg = new org.jivesoftware.smack.packet.Message( + msgXmpp = new org.jivesoftware.smack.packet.Message( message.getTo().getAddress(), org.jivesoftware.smack.packet.Message.Type.chat); - msg.addExtension(new DeliveryReceipts.DeliveryReceiptRequest()); + msgXmpp.addExtension(new DeliveryReceipts.DeliveryReceiptRequest()); - msg.setBody(message.getBody()); + Contact contact = mContactListManager.getContact(message.getTo().getBareAddress()); + + if (contact != null && contact.getPresence() !=null && (!contact.getPresence().isOnline())) + requestPresenceRefresh(message.getTo().getBareAddress()); - // debug(TAG, "sending packet ID " + msg.getPacketID()); - message.setID(msg.getPacketID()); - sendPacket(msg); } + + if (message.getFrom() == null) + msgXmpp.setFrom(mUser.getAddress().getAddress()); + else + msgXmpp.setFrom(message.getFrom().getAddress()); + + msgXmpp.setBody(message.getBody()); + + if (message.getID() != null) + msgXmpp.setPacketID(message.getID()); + else + message.setID(msgXmpp.getPacketID()); + + sendPacket(msgXmpp); + } ChatSession findSession(String address) { - for (Iterator iter = mSessions.iterator(); iter.hasNext();) { - ChatSession session = iter.next(); - if (session.getParticipant().getAddress().getAddress().equals(address)) - return session; - } - return null; + + return mSessions.get(Address.stripResource(address)); } - - } - - public ChatSession findSession(String address) { - return mSessionManager.findSession(address); - } + @Override + public ChatSession createChatSession(ImEntity participant, boolean isNewSession) { + + requestPresenceRefresh(participant.getAddress().getAddress()); + + ChatSession session = super.createChatSession(participant,isNewSession); + + //do avatar check if we have a no dominant presence + qAvatar.push(participant.getAddress().getAddress()); + + // mSessions.put(Address.stripResource(participant.getAddress().getAddress()),session); + return session; + } - public ChatSession createChatSession(Contact contact) { - return mSessionManager.createChatSession(contact); } - public class XmppContactList extends ContactListManager { + + private void requestPresenceRefresh (String address) + { + org.jivesoftware.smack.packet.Presence p = new org.jivesoftware.smack.packet.Presence(Type.error); + p.setFrom(address); + qPresence.push(p); + } - //private Hashtable unprocdPresence = new Hashtable(); + public class XmppContactListManager extends ContactListManager { @Override protected void setListNameAsync(final String name, final ContactList list) { @@ -1490,7 +1823,7 @@ private void do_setListName(String name, ContactList list) { @Override public String normalizeAddress(String address) { - return address.split("/")[0]; + return Address.stripResource(address); } @Override @@ -1500,7 +1833,7 @@ public void loadContactListsAsync() { @Override public void run() { do_loadContactLists(); - + } }); @@ -1514,14 +1847,15 @@ public void loadContactLists() { /** * Create new list of contacts from roster entries. - * + * * Runs in executor thread - * + * * @param entryIter iterator of roster entries to add to contact list * @param skipList list of contacts which should be omitted; new * contacts are added to this list automatically * @return contacts from roster which were not present in skiplist. */ + /* private Collection fillContacts(Collection entryIter, Set skipList) { @@ -1529,107 +1863,233 @@ private Collection fillContacts(Collection entryIter, Collection contacts = new ArrayList(); for (RosterEntry entry : entryIter) { - - String address = entry.getUser(); - /* Skip entries present in the skip list */ + String address = entry.getUser(); if (skipList != null && !skipList.add(address)) continue; String name = entry.getName(); if (name == null) name = address; - - XmppAddress xaddress = new XmppAddress(name, address); + + XmppAddress xaddress = new XmppAddress(address); org.jivesoftware.smack.packet.Presence presence = roster.getPresence(address); - + String status = presence.getStatus(); String resource = null; - + Presence p = new Presence(parsePresence(presence), status, null, null, Presence.CLIENT_TYPE_DEFAULT); - + String from = presence.getFrom(); if (from != null && from.lastIndexOf("/") > 0) { resource = from.substring(from.lastIndexOf("/") + 1); - + if (resource.indexOf('.')!=-1) resource = resource.substring(0,resource.indexOf('.')); - p.setResource(resource); - } - - Contact contact = mContactListManager.getContact(xaddress.getAddress()); + p.setResource(resource); + } + + Contact contact = mContactListManager.getContact(xaddress.getBareAddress()); + + if (contact == null) + contact = new Contact(xaddress, name); + + contact.setPresence(p); + + contacts.add(contact); + + + } + return contacts; + } + */ + + // Runs in executor thread + private void do_loadContactLists() { + + debug(TAG, "load contact lists"); + + if (mConnection == null) + return; + + Roster roster = mConnection.getRoster(); + + //Set seen = new HashSet(); + + // This group will also contain all the unfiled contacts. We will create it locally if it + // does not exist. + /* + String generalGroupName = mContext.getString(R.string.buddies); + + for (Iterator giter = roster.getGroups().iterator(); giter.hasNext();) { + + RosterGroup group = giter.next(); + + debug(TAG, "loading group: " + group.getName() + " size:" + group.getEntryCount()); + + Collection contacts = fillContacts(group.getEntries(), null); + + if (group.getName().equals(generalGroupName) && roster.getUnfiledEntryCount() > 0) { + Collection unfiled = fillContacts(roster.getUnfiledEntries(), null); + contacts.addAll(unfiled); + } + + XmppAddress groupAddress = new XmppAddress(group.getName()); + ContactList cl = new ContactList(groupAddress, group.getName(), group + .getName().equals(generalGroupName), contacts, this); + + notifyContactListCreated(cl); + + notifyContactsPresenceUpdated(contacts.toArray(new Contact[contacts.size()])); + } + + Collection contacts; + if (roster.getUnfiledEntryCount() > 0) { + contacts = fillContacts(roster.getUnfiledEntries(), null); + } else { + contacts = new ArrayList(); + } + + ContactList cl = getContactList(generalGroupName); + cl = new ContactList(groupAddress, group.getName(), group + .getName().equals(generalGroupName), contacts, this); + + // We might have already created the Buddies contact list above + if (cl == null) { + cl = new ContactList(mUser.getAddress(), generalGroupName, true, contacts, this); + notifyContactListCreated(cl); + + notifyContactsPresenceUpdated(contacts.toArray(new Contact[contacts.size()])); + } + */ + + //since we don't show lists anymore, let's just load all entries together + + + ContactList cl; + + try { + cl = mContactListManager.getDefaultContactList(); + } catch (ImException e1) { + debug(TAG,"couldn't read default list"); + cl = null; + } + + if (cl == null) + { + String generalGroupName = mContext.getString(R.string.buddies); + + Collection contacts = new ArrayList(); + XmppAddress groupAddress = new XmppAddress(generalGroupName); + + cl = new ContactList(groupAddress,generalGroupName, true, contacts, this); + + notifyContactListCreated(cl); + } + + for (RosterEntry rEntry : roster.getEntries()) + { + String address = rEntry.getUser(); + String name = rEntry.getName(); + + if (mUser.getAddress().getBareAddress().equals(address)) //don't load a roster for yourself + continue; + + Contact contact = getContact(address); if (contact == null) - contact = new Contact(xaddress, xaddress.getScreenName()); + { + XmppAddress xAddr = new XmppAddress(address); - contact.setPresence(p); + if (name == null || name.length() == 0) + name = xAddr.getUser(); - contacts.add(contact); + contact = new Contact(xAddr,name); + } + + requestPresenceRefresh(address); + + if (!cl.containsContact(contact)) + { + try { + cl.addExistingContact(contact); + } catch (ImException e) { + debug(TAG,"could not add contact to list: " + e.getLocalizedMessage()); + } + } + } - return contacts; + + notifyContactListLoaded(cl); + notifyContactListsLoaded(); + } - // Runs in executor thread - private void do_loadContactLists() { - - debug(TAG, "load contact lists"); + // Runs in executor thread + public void addContactsToList(Collection addresses) { + + debug(TAG, "add contacts to lists"); if (mConnection == null) return; - Roster roster = mConnection.getRoster(); + ContactList cl; - //Set seen = new HashSet(); + try { + cl = mContactListManager.getDefaultContactList(); + } catch (ImException e1) { + debug(TAG,"couldn't read default list"); + cl = null; + } - // This group will also contain all the unfiled contacts. We will create it locally if it - // does not exist. - String generalGroupName = mContext.getString(R.string.buddies); + if (cl == null) + { + String generalGroupName = mContext.getString(R.string.buddies); - for (Iterator giter = roster.getGroups().iterator(); giter.hasNext();) { + Collection contacts = new ArrayList(); + XmppAddress groupAddress = new XmppAddress(generalGroupName); - RosterGroup group = giter.next(); + cl = new ContactList(groupAddress,generalGroupName, true, contacts, this); - debug(TAG, "loading group: " + group.getName() + " size:" + group.getEntryCount()); + notifyContactListCreated(cl); + } - Collection contacts = fillContacts(group.getEntries(), null); + for (String address : addresses) + { - if (group.getName().equals(generalGroupName) && roster.getUnfiledEntryCount() > 0) { - Collection unfiled = fillContacts(roster.getUnfiledEntries(), null); - contacts.addAll(unfiled); - } + if (mUser.getAddress().getBareAddress().equals(address)) //don't load a roster for yourself + continue; - XmppAddress groupAddress = new XmppAddress(group.getName()); - ContactList cl = new ContactList(groupAddress, group.getName(), group - .getName().equals(generalGroupName), contacts, this); + Contact contact = getContact(address); - notifyContactListCreated(cl); + if (contact == null) + { + XmppAddress xAddr = new XmppAddress(address); - notifyContactsPresenceUpdated(contacts.toArray(new Contact[contacts.size()])); - } + contact = new Contact(xAddr,xAddr.getUser()); - Collection contacts; - if (roster.getUnfiledEntryCount() > 0) { - contacts = fillContacts(roster.getUnfiledEntries(), null); - } else { - contacts = new ArrayList(); - } + } - ContactList cl = getContactList(generalGroupName); + //org.jivesoftware.smack.packet.Presence p = roster.getPresence(contact.getAddress().getBareAddress()); + //qPresence.push(p); - // We might have already created the Buddies contact list above - if (cl == null) { - cl = new ContactList(mUser.getAddress(), generalGroupName, true, contacts, this); - notifyContactListCreated(cl); + if (!cl.containsContact(contact)) + { + try { + cl.addExistingContact(contact); + } catch (ImException e) { + debug(TAG,"could not add contact to list: " + e.getLocalizedMessage()); + } + } - notifyContactsPresenceUpdated(contacts.toArray(new Contact[contacts.size()])); } - - + + notifyContactListLoaded(cl); notifyContactListsLoaded(); } @@ -1641,15 +2101,15 @@ private void do_loadContactLists() { /* private void processQueuedPresenceNotifications (Collection contacts) { - + Roster roster = mConnection.getRoster(); - + //now iterate through the list of queued up unprocessed presence changes for (Contact contact : contacts) { - + String address = parseAddressBase(contact.getAddress().getFullName()); - + org.jivesoftware.smack.packet.Presence presence = roster.getPresence(address); if (presence != null) @@ -1659,12 +2119,12 @@ private void processQueuedPresenceNotifications (Collection contacts) unprocdPresence.remove(address); contact.setPresence(new Presence(parsePresence(presence), presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT)); - + Contact[] updatedContact = {contact}; notifyContactsPresenceUpdated(updatedContact); } - - + + } }*/ @@ -1674,136 +2134,101 @@ public void listenToRoster(final Roster roster) { roster.addRosterListener(rListener); } - + RosterListener rListener = new RosterListener() { - - - @Override - public void presenceChanged(org.jivesoftware.smack.packet.Presence presence) { - LogCleaner.debug(ImApp.LOG_TAG, "presence changed: " + presence.getFrom()); - handlePresenceChanged(presence); + @Override + public void presenceChanged(org.jivesoftware.smack.packet.Presence presence) { + + qPresence.push(presence); + } @Override public void entriesUpdated(Collection addresses) { - execute(new UpdateContactsRunnable(XmppContactList.this,addresses)); + for (String address :addresses) + { + + requestPresenceRefresh(address); + + } } @Override public void entriesDeleted(Collection addresses) { - //LogCleaner.debug(ImApp.LOG_TAG, "entries deleted notification: " + addresses.size()); - } - @Override - public void entriesAdded(Collection addresses) { - - - // LogCleaner.debug(ImApp.LOG_TAG, "entries added notification: " + addresses.size()); - - } - }; - - class UpdateContactsRunnable implements Runnable { - - private ContactListManager mConMgr; - private Collection mAddresses; - - public UpdateContactsRunnable (ContactListManager conMgr, Collection addresses) - { - mConMgr = conMgr; - mAddresses = addresses; - } - - public void run () - { - Collection contacts = new ArrayList(); - - for (String address :mAddresses) - contacts.add(findOrCreateContact(address)); - - mConMgr.notifyContactsPresenceUpdated(contacts.toArray(new Contact[contacts.size()])); + ContactList cl; + try { + cl = mContactListManager.getDefaultContactList(); - LogCleaner.debug(ImApp.LOG_TAG, "entries updated notification: " +contacts.size()); - - } + for (String address : addresses) + { + requestPresenceRefresh(address); + Contact contact = mContactListManager.getContact(XmppAddress.stripResource(address)); + mContactListManager.notifyContactListUpdated(cl, ContactListListener.LIST_CONTACT_REMOVED, contact); + } + - } + } catch (ImException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } - private void handlePresenceChanged(org.jivesoftware.smack.packet.Presence presence) { - - - String status = presence.getStatus(); - String resource = null; - - String from = presence.getFrom(); - if (from != null && from.lastIndexOf("/") > 0) { - resource = from.substring(from.lastIndexOf("/") + 1); - - if (resource.indexOf('.')!=-1) - resource = resource.substring(0,resource.indexOf('.')); - } - - XmppAddress xaddress = new XmppAddress(presence.getFrom()); - - if (mConnection == null) - return; - - // Get presence from the Roster to handle priorities and such - /* - final Roster roster = mConnection.getRoster(); - if (roster != null) { - presence = roster.getPresence(address); - } - int type = parsePresence(presence); - */ - - int type = parsePresence(presence); - - Contact contact = getContact(xaddress.getAddress()); - Presence p = new Presence(type, status, null, null, - Presence.CLIENT_TYPE_DEFAULT); - p.setResource(resource); + @Override + public void entriesAdded(Collection addresses) { - if (contact == null) { - - contact = new Contact(xaddress, xaddress.getScreenName()); - - debug(TAG, "got presence updated for NEW user: " - + contact.getAddress().getAddress() + " presence:" + type); - //store the latest presence notification for this user in this queue - //unprocdPresence.put(user, presence); + try + { + if (mContactListManager.getState() == LISTS_LOADED) + { + + for (String address : addresses) + { + + Contact contact = getContact(address); + + requestPresenceRefresh(address); + + if (contact == null) + { + XmppAddress xAddr = new XmppAddress(address); + contact = new Contact(xAddr,xAddr.getUser()); + + } + + try + { + ContactList cl = mContactListManager.getDefaultContactList(); + if (!cl.containsContact(contact)) + cl.addExistingContact(contact); + + } + catch (Exception e) + { + debug(TAG,"could not add contact to list: " + e.getLocalizedMessage()); - } else { - debug(TAG, "Got presence update for EXISTING user: " - + contact.getAddress().getAddress() + " presence:" + type); - + } + + + } + + } + } + catch (Exception e) + { + Log.d(ImApp.LOG_TAG,"error adding contacts",e); + } } + }; - contact.setPresence(p); - Contact[] contacts = new Contact[] { contact }; - notifyContactsPresenceUpdated(contacts); - - // loadVCard(mContext.getContentResolver(),contact.getAddress().getAddress()); - - PacketExtension pe = presence.getExtension("x", NameSpace.VCARD_TEMP_X_UPDATE); - if (pe != null) { - DefaultPacketExtension dpe = (DefaultPacketExtension)pe; - String hash = dpe.getValue("photo"); - - if (hash != null) - loadVCard(mContext.getContentResolver(),contact.getAddress().getAddress(),true); - - } - } @Override protected ImConnection getConnection() { @@ -1815,33 +2240,46 @@ protected void doRemoveContactFromListAsync(Contact contact, ContactList list) { // FIXME synchronize this to executor thread if (mConnection == null) return; - Roster roster = mConnection.getRoster(); + String address = contact.getAddress().getAddress(); + + //otherwise, send unsub message and delete from local contact database + org.jivesoftware.smack.packet.Presence presence = new org.jivesoftware.smack.packet.Presence( + org.jivesoftware.smack.packet.Presence.Type.unsubscribe); + presence.setTo(address); + sendPacket(presence); + + presence = new org.jivesoftware.smack.packet.Presence( + org.jivesoftware.smack.packet.Presence.Type.unsubscribed); + presence.setTo(address); + sendPacket(presence); + try { - RosterGroup group = roster.getGroup(list.getName()); + RosterEntry entry = mRoster.getEntry(address); + RosterGroup group = mRoster.getGroup(list.getName()); + if (group == null) { debug(TAG, "could not find group " + list.getName() + " in roster"); - return; - } - RosterEntry entry = roster.getEntry(address); - if (entry == null) { - debug(TAG, "could not find entry " + address + " in group " + list.getName()); - return; + if (mRoster != null) + mRoster.removeEntry(entry); } + else + { + group.removeEntry(entry); + entry = mRoster.getEntry(address); + + // Remove from Roster if this is the last group + if (entry != null && entry.getGroups() != null && entry.getGroups().size() <= 1) + mRoster.removeEntry(entry); - // Remove from Roster if this is the last group - if (entry.getGroups().size() <= 1) - roster.removeEntry(entry); + } - group.removeEntry(entry); } catch (XMPPException e) { debug(TAG, "remove entry failed: " + e.getMessage()); throw new RuntimeException(e); } - org.jivesoftware.smack.packet.Presence response = new org.jivesoftware.smack.packet.Presence( - org.jivesoftware.smack.packet.Presence.Type.unsubscribed); - response.setTo(address); - sendPacket(response); + + notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_REMOVED, contact); } @@ -1865,56 +2303,128 @@ protected void doBlockContactAsync(String address, boolean block) { } @Override - protected void doAddContactToListAsync(String address, ContactList list) throws ImException { + protected void doAddContactToListAsync(Contact contact, ContactList list) throws ImException { debug(TAG, "add contact to " + list.getName()); - org.jivesoftware.smack.packet.Presence response = new org.jivesoftware.smack.packet.Presence( - org.jivesoftware.smack.packet.Presence.Type.subscribed); - response.setTo(address); - sendPacket(response); + if (mConnection.isConnected()) + { + org.jivesoftware.smack.packet.Presence reqSubscribe = new org.jivesoftware.smack.packet.Presence( + org.jivesoftware.smack.packet.Presence.Type.subscribe); + reqSubscribe.setTo(contact.getAddress().getBareAddress()); + sendPacket(reqSubscribe); + + org.jivesoftware.smack.packet.Presence reqSubscribed = new org.jivesoftware.smack.packet.Presence( + org.jivesoftware.smack.packet.Presence.Type.subscribed); + reqSubscribed.setTo(contact.getAddress().getBareAddress()); + sendPacket(reqSubscribed); - Roster roster = mConnection.getRoster(); - String[] groups = new String[] { list.getName() }; - try { - Contact contact = makeContact(address); - roster.createEntry(address, contact.getName(), groups); + + String[] groups = new String[] { list.getName() }; + try { + RosterEntry rEntry = mRoster.getEntry(contact.getAddress().getBareAddress()); + RosterGroup rGroup = mRoster.getGroup(list.getName()); + + if (rGroup == null) + { + if (rEntry == null) + mRoster.createEntry (contact.getAddress().getBareAddress(), contact.getName(), null); + + } + else if (rEntry == null) + { + mRoster.createEntry(contact.getAddress().getBareAddress(), contact.getName(), groups); + + } + + } catch (XMPPException e) { + + debug(TAG,"error updating remote roster",e); + throw new ImException("error updating remote roster"); + } catch (IllegalStateException e) { + String msg = "Not logged in to server while updating remote roster"; + debug(TAG, msg, e); + throw new ImException(msg); + } + + do_loadContactLists(); + notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_ADDED, contact); - // If contact exists locally, don't create another copy - - if (!containsContact(contact)) - notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_ADDED, contact); - else - debug(TAG, "skip adding existing contact locally " + contact.getName()); - } catch (XMPPException e) { - throw new RuntimeException(e); } } @Override - public void declineSubscriptionRequest(String contact) { + public void declineSubscriptionRequest(Contact contact) { debug(TAG, "decline subscription"); org.jivesoftware.smack.packet.Presence response = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.unsubscribed); - response.setTo(contact); + response.setTo(contact.getAddress().getBareAddress()); sendPacket(response); - mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact); + try { + mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact, mProviderId, mAccountId); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } @Override - public void approveSubscriptionRequest(String contact) { - debug(TAG, "approve subscription"); - try { - mContactListManager.doAddContactToListAsync(contact, getDefaultContactList()); - } catch (ImException e) { - debug(TAG, "failed to add " + contact + " to default list"); + public void approveSubscriptionRequest(final Contact contact) { + + + new Thread(new Runnable() + { + + public void run () + { + debug(TAG, "approve subscription: " + contact.getAddress().getAddress()); + + try + { + + doAddContactToListAsync(contact, getContactListManager().getDefaultContactList()); + mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact, mProviderId, mAccountId); + + + } catch (ImException e) { + debug (TAG, "error responding to subscription approval: " + e.getLocalizedMessage()); + + } + catch (RemoteException e) { + debug (TAG, "error responding to subscription approval: " + e.getLocalizedMessage()); + + } + } + }).start(); + + } + + @Override + public Contact[] createTemporaryContacts(String[] addresses) { + // debug(TAG, "create temporary " + address); + + Contact[] contacts = new Contact[addresses.length]; + + int i = 0; + + for (String address : addresses) + { + contacts[i++] = makeContact(address); } - mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact); + + notifyContactsPresenceUpdated(contacts); + return contacts; } @Override - public Contact createTemporaryContact(String address) { - debug(TAG, "create temporary " + address); - return makeContact(address); + protected void doSetContactName(String address, String name) throws ImException { + Roster roster = mConnection.getRoster(); + RosterEntry entry = roster.getEntry(address); + // confirm entry still exists + if (entry == null) { + return; + } + // set name + entry.setName(name); } } @@ -1937,34 +2447,35 @@ public void run() { // Runs in executor thread public void doHeartbeat(long heartbeatInterval) { heartbeatSequence++; - + + if (getState() == SUSPENDED) { + debug(TAG, "heartbeat during suspend"); + return; + } + if (mConnection == null && mRetryLogin) { debug(TAG, "reconnect with login"); do_login(); + return; } if (mConnection == null) return; - if (getState() == SUSPENDED) { - debug(TAG, "heartbeat during suspend"); - return; - } - if (mNeedReconnect) { reconnect(); } else if (!mConnection.isConnected() && getState() == LOGGED_IN) { // Smack failed to tell us about a disconnect - Log.w(TAG, "reconnect on unreported state change"); + debug(TAG, "reconnect on unreported state change"); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network disconnected")); force_reconnect(); } else if (getState() == LOGGED_IN) { if (PING_ENABLED) { // Check ping on every heartbeat. checkPing() will return true immediately if we already checked. if (!checkPing()) { - Log.w(TAG, "reconnect on ping failed"); + debug(TAG, "reconnect on ping failed: " + mUser.getAddress().getAddress()); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network timeout")); - force_reconnect(); + maybe_reconnect(); } else { // Send pings only at intervals configured by the user if (heartbeatSequence >= heartbeatInterval) { @@ -2021,40 +2532,59 @@ public static class MyXMPPConnection extends XMPPConnection { public MyXMPPConnection(ConnectionConfiguration config) { super(config); - //this.getConfiguration().setSocketFactory(arg0) + } + + + public void shutdown() { + if (socket != null) + { + try { + // Be forceful in shutting down since SSL can get stuck + try { + socket.shutdownInput(); + } catch (Exception e) { } - } + socket.close(); + shutdown(new org.jivesoftware.smack.packet.Presence( + org.jivesoftware.smack.packet.Presence.Type.unavailable)); - public void shutdown() { - try { - // Be forceful in shutting down since SSL can get stuck - try { - socket.shutdownInput(); } catch (Exception e) { + Log.e(TAG, "error on shutdown()", e); } - socket.close(); - shutdown(new org.jivesoftware.smack.packet.Presence( - org.jivesoftware.smack.packet.Presence.Type.unavailable)); - - } catch (Exception e) { - Log.e(TAG, "error on shutdown()", e); } } } @Override public void networkTypeChanged() { + super.networkTypeChanged(); + + execute(new Runnable() { + @Override + public void run() { + if (mState == SUSPENDED || mState == SUSPENDING) + { + debug(TAG, "network type changed"); + mNeedReconnect = false; + setState(LOGGING_IN, null); + reconnect(); + } + } + }); + } /* * Force a shutdown and reconnect, unless we are already reconnecting. - * + * * Runs in executor thread */ private void force_reconnect() { - debug(TAG, "force_reconnect need=" + mNeedReconnect); + debug(TAG, "force_reconnect mNeedReconnect=" + mNeedReconnect + " state=" + getState() + + " connection?=" + (mConnection != null)); + if (mConnection == null) return; if (mNeedReconnect) @@ -2075,12 +2605,12 @@ private void force_reconnect() { /* * Reconnect unless we are already in the process of doing so. - * + * * Runs in executor thread. */ private void maybe_reconnect() { debug(TAG, "maybe_reconnect mNeedReconnect=" + mNeedReconnect + " state=" + getState() - + " connection?=" + (mConnection != null)); + + " connection?=" + (mConnection != null)); // This is checking whether we are already in the process of reconnecting. If we are, // doHeartbeat will take care of reconnecting. @@ -2099,7 +2629,7 @@ private void maybe_reconnect() { /* * Retry connecting - * + * * Runs in executor thread */ private void reconnect() { @@ -2108,11 +2638,6 @@ private void reconnect() { return; } - try { - Thread.sleep(2000); // Wait for network to settle - } catch (InterruptedException e) { /* ignore */ - } - if (mConnection != null) { // It is safe to ask mConnection whether it is connected, because either: // - We detected an error using ping and called force_reconnect, which did a shutdown @@ -2120,70 +2645,67 @@ private void reconnect() { // so there are no cases where mConnection can be confused about being connected here. // The only left over cases are reconnect() being called too many times due to errors // reported multiple times or errors reported during a forced reconnect. - + // The analysis above is incorrect in the case where Smack loses connectivity // while trying to log in. This case is handled in a future heartbeat // by checking ping responses. + clearPing(); if (mConnection.isConnected()) { - Log.w(TAG, "reconnect while already connected, assuming good"); + debug(TAG,"reconnect while already connected, assuming good: " + mConnection); mNeedReconnect = false; setState(LOGGED_IN, null); return; } - Log.i(TAG, "reconnect"); - clearPing(); + debug(TAG, "reconnect"); + try { if (mStreamHandler.isResumePossible()) { // Connect without binding, will automatically trigger a resume - debug(TAG, "resume"); + debug(TAG, "mStreamHandler resume"); mConnection.connect(false); initServiceDiscovery(); } else { - - //mConnection.disconnect(); - - mConnection = null; - - do_login(); - /* - debug(TAG, "no resume"); - mConnection.connect(); + debug(TAG, "reconnection on network change failed: " + mUser.getAddress().getAddress()); - if (!mConnection.isAuthenticated()) { - // This can happen if a reconnect failed and the smack connection now has wasAuthenticated = false. - // It can also happen if auth exception was swallowed by smack. - // Try to login manually. + mConnection = null; + mNeedReconnect = true; + setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, null)); - // Log.e(TAG, "authentication did not happen in connect() - login manually"); - // mConnection.login(mUsername, mPassword, mResource); + while (mNeedReconnect) + { + do_login(); - // Make sure - if (!mConnection.isAuthenticated()) - throw new XMPPException("manual auth failed"); - - // Manually set the state since manual auth doesn't notify listeners - mNeedReconnect = false; - setState(LOGGED_IN, null); + if (mNeedReconnect) + try { Thread.sleep(3000);} + catch (Exception e){} } - mStreamHandler.notifyInitialLogin(); - initServiceDiscovery(); - sendPresencePacket(); - */ + } } catch (Exception e) { - mStreamHandler.quickShutdown(); - Log.w(TAG, "reconnection attempt failed", e); + if (mStreamHandler != null) + mStreamHandler.quickShutdown(); + + mConnection = null; + debug(TAG, "reconnection attempt failed", e); // Smack incorrectly notified us that reconnection was successful, reset in case it fails - mNeedReconnect = true; + mNeedReconnect = false; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage())); + + //while (mNeedReconnect) + // do_login(); + } } else { - mNeedReconnect = true; - + mNeedReconnect = false; + mConnection = null; debug(TAG, "reconnection on network change failed"); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "reconnection on network change failed")); + + //while (mNeedReconnect) + // do_login(); + } } @@ -2191,15 +2713,36 @@ private void reconnect() { protected void setState(int state, ImErrorInfo error) { debug(TAG, "setState to " + state); super.setState(state, error); - } + + if (state == LOGGED_IN) + { + //update and send new presence packet out + mUserPresence = new Presence(Presence.AVAILABLE, "", Presence.CLIENT_TYPE_MOBILE); + sendPresencePacket(); + + //request presence of remote contact for all existing sessions + for (ChatSessionAdapter session : mSessionManager.getAdapter().getActiveChatSessions()) + { + requestPresenceRefresh(session.getAddress()); + } + mChatGroupManager.reconnectAll(); + } + } + public void debug(String tag, String msg) { - // if (Log.isLoggable(TAG, Log.DEBUG)) { + // if (Log.isLoggable(TAG, Log.DEBUG)) { if (Debug.DEBUG_ENABLED) { Log.d(tag, "" + mGlobalId + " : " + msg); } } + public void debug(String tag, String msg, Exception e) { + if (Debug.DEBUG_ENABLED) { + Log.e(tag, "" + mGlobalId + " : " + msg,e); + } + } + @Override public void handle(Callback[] arg0) throws IOException { @@ -2212,12 +2755,12 @@ public void handle(Callback[] arg0) throws IOException { /* public class MySASLDigestMD5Mechanism extends SASLMechanism { - + public MySASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) { super(saslAuthentication); } - + protected void authenticate() throws IOException, XMPPException { @@ -2228,7 +2771,7 @@ protected void authenticate() sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, this); super.authenticate(); } - + public void authenticate(String username, String host, String password) throws IOException, XMPPException { @@ -2242,7 +2785,7 @@ public void authenticate(String username, String host, String password) sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); super.authenticate(); } - + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { @@ -2253,12 +2796,12 @@ public void authenticate(String username, String host, CallbackHandler cbh) sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh); super.authenticate(); } - + protected String getName() { return "DIGEST-MD5"; } - + public void challengeReceived(String challenge) throws IOException { @@ -2276,11 +2819,11 @@ public void challengeReceived(String challenge) //authenticationText = Base64.encodeBytes(response, 8); //if(authenticationText.equals("")) //authenticationText = "="; - + if (response == null){ responseStanza = new Response(); } else { - responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES)); + responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES)); } //} //stanza.append(""); @@ -2302,20 +2845,23 @@ private void initServiceDiscovery() { sdm.addFeature(DISCO_FEATURE); if (!sdm.includesFeature(DeliveryReceipts.NAMESPACE)) sdm.addFeature(DeliveryReceipts.NAMESPACE); + } + private void onReconnectionSuccessful() { mNeedReconnect = false; setState(LOGGED_IN, null); + } - - + + private void addProviderManagerExtensions () { ProviderManager pm = ProviderManager.getInstance(); - - // Private Data Storage + + // Private Data Storage pm.addIQProvider("query","jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); // Time @@ -2333,7 +2879,7 @@ private void addProviderManagerExtensions () // Chat State pm.addExtensionProvider("active","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); - pm.addExtensionProvider("composing","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); + pm.addExtensionProvider("composing","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("paused","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("inactive","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("gone","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); @@ -2344,7 +2890,7 @@ private void addProviderManagerExtensions () // Group Chat Invitations pm.addExtensionProvider("x","jabber:x:conference", new GroupChatInvitation.Provider()); - // Service Discovery # Items + // Service Discovery # Items pm.addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info @@ -2356,49 +2902,49 @@ private void addProviderManagerExtensions () // MUC User pm.addExtensionProvider("x","http://jabber.org/protocol/muc#user", new MUCUserProvider()); - // MUC Admin + // MUC Admin pm.addIQProvider("query","http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); - // MUC Owner + // MUC Owner pm.addIQProvider("query","http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); - + // Delayed Delivery pm.addExtensionProvider("x","jabber:x:delay", new DelayInformationProvider()); - + // Version try { pm.addIQProvider("query","jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException e) { // Not sure what's happening here. } - + // VCard pm.addIQProvider("vCard","vcard-temp", new VCardProvider()); - + // Offline Message Requests pm.addIQProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); - + // Offline Message Indicator pm.addExtensionProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); - + // Last Activity pm.addIQProvider("query","jabber:iq:last", new LastActivity.Provider()); - + // User Search pm.addIQProvider("query","jabber:iq:search", new UserSearch.Provider()); - + // SharedGroupsInfo pm.addIQProvider("sharedgroup","http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); - + // JEP-33: Extended Stanza Addressing pm.addExtensionProvider("addresses","http://jabber.org/protocol/address", new MultipleAddressesProvider()); - + // FileTransfer pm.addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider()); - + pm.addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); - + // Privacy pm.addIQProvider("query","jabber:iq:privacy", new PrivacyProvider()); pm.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); @@ -2407,8 +2953,8 @@ private void addProviderManagerExtensions () pm.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadPayloadError()); pm.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadSessionIDError()); pm.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.SessionExpiredError()); - - } + + } class NameSpace { @@ -2437,26 +2983,278 @@ class NameSpace { } - - public boolean registerAccount (Imps.ProviderSettings.QueryMap providerSettings, String username, String password) throws Exception + + public boolean registerAccount (Imps.ProviderSettings.QueryMap providerSettings, String username, String password, Map params) throws Exception { - - initConnection(providerSettings); - + + initConnection(providerSettings, username); + if (mConnection.getAccountManager().supportsAccountCreation()) { - mConnection.getAccountManager().createAccount(username, password); + mConnection.getAccountManager().createAccount(username, password, params); + return true; - + } else { return false;//not supported } + + + } + + private Contact handlePresenceChanged(org.jivesoftware.smack.packet.Presence presence) { + + if (presence == null || presence.getFrom() == null) //our presence isn't really valid + return null; + + String from = presence.getFrom(); - + if (presence.getType() == Type.error) + { + if (mRoster == null) + return null; + + presence = mRoster.getPresence(from); + } + + if (TextUtils.isEmpty(from)) + return null; + + XmppAddress xaddress = new XmppAddress(from); + + if (mUser.getAddress().getBareAddress().equals(xaddress.getBareAddress())) //ignore presence from yourself + return null; + + String status = presence.getStatus(); + + Presence p = new Presence(parsePresence(presence), status, null, null, + Presence.CLIENT_TYPE_DEFAULT); + + //this is only persisted in memory + p.setPriority(presence.getPriority()); + + // Get presence from the Roster to handle priorities and such + // TODO: this causes bad network and performance issues + // if (presence.getType() == Type.available) //get the latest presence for the highest priority + Contact contact = mContactListManager.getContact(xaddress.getBareAddress()); + + String[] presenceParts = presence.getFrom().split("/"); + if (presenceParts.length > 1) + p.setResource(presenceParts[1]); + + if (contact == null && presence.getType() == Type.subscribe) { + + XmppAddress xAddr = new XmppAddress(presence.getFrom()); + + if (mRoster == null) + return null; + + RosterEntry rEntry = mRoster.getEntry(xAddr.getBareAddress()); + + String name = null; + + if (rEntry != null) + name = rEntry.getName(); + + if (name == null || name.length() == 0) + name = xAddr.getUser(); + + contact = new Contact(xAddr,name); + + try { + if (!mContactListManager.getDefaultContactList().containsContact(contact.getAddress())) + { + mContactListManager.getDefaultContactList().addExistingContact(contact); + + } + } catch (ImException e) { + + debug(TAG,"unable to add new contact to default list: " + e.getLocalizedMessage()); + + } + + + } + else if (contact == null) + { + return null; //do nothing if we don't have a contact + } + + if (presence.getType() == Type.subscribe) { + debug(TAG,"got subscribe request: " + presence.getFrom()); + + try + { + mContactListManager.getSubscriptionRequestListener().onSubScriptionRequest(contact, mProviderId, mAccountId); + } + catch (RemoteException e) + { + Log.e(TAG,"remote exception on subscription handling",e); + } + } + else if (presence.getType() == Type.subscribed) { + debug(TAG,"got subscribed confirmation request: " + presence.getFrom()); + try + { + mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact, mProviderId, mAccountId); + } + catch (RemoteException e) + { + Log.e(TAG,"remote exception on subscription handling",e); + } + } + else if (presence.getType() == Type.unsubscribe) { + debug(TAG,"got unsubscribe request: " + presence.getFrom()); + + //TBD how to handle this + // mContactListManager.getSubscriptionRequestListener().onUnSubScriptionRequest(contact); + } + else if (presence.getType() == Type.unsubscribed) { + debug(TAG,"got unsubscribe request: " + presence.getFrom()); + try + { + mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact, mProviderId, mAccountId); + + } + catch (RemoteException e) + { + Log.e(TAG,"remote exception on subscription handling",e); + } + + } + else + { + //this is typical presence, let's get the latest/highest priority + debug(TAG,"got presence:: " + presence.getFrom() + "=" + p.getStatusText()); + + if (contact.getPresence() != null) + { + Presence pOld = contact.getPresence(); + + if (pOld.getResource() != null && pOld.getResource().equals(p.getResource())) //if the same resource as the existing one, then update it + { + contact.setPresence(p); + } + else if (p.getPriority() >= pOld.getPriority()) //if priority is higher, then override + { + contact.setPresence(p); + + } + + if (p.getStatus() != Imps.Presence.AVAILABLE) + { + //if offline, let's check for another online presence + presence = mRoster.getPresence(presence.getFrom()); + p = new Presence(parsePresence(presence), status, null, null, + Presence.CLIENT_TYPE_DEFAULT); + + //this is only persisted in memory + p.setPriority(presence.getPriority()); + contact.setPresence(p); + + } + + + } + else + { + + //we don't have a presence yet so set one + contact.setPresence(p); + } + + } + + + + return contact; + } + + private void initPresenceProcessor () + { + mTimerPresence = new Timer(); + + mTimerPresence.scheduleAtFixedRate(new TimerTask() { + + public void run() { + + + if (qPresence.size() > 0) + { + ArrayList alUpdate = new ArrayList(); + + org.jivesoftware.smack.packet.Presence p = null; + Contact contact = null; + + while (qPresence.peek() != null) + { + p = qPresence.pop(); + contact = handlePresenceChanged(p); + if (contact != null) + { + alUpdate.add(contact); + } + + } + + //Log.d(ImApp.LOG_TAG,"XMPP processed presence q=" + alUpdate.size()); + mContactListManager.notifyContactsPresenceUpdated(alUpdate.toArray(new Contact[alUpdate.size()])); + loadVCardsAsync(); + + } + + } + + }, 1000, 5000); } - + Timer mTimerPackets = null; + private void initPacketProcessor () + { + mTimerPackets = new Timer(); + + mTimerPackets.scheduleAtFixedRate(new TimerTask() { + + public void run() { + + try + { + org.jivesoftware.smack.packet.Packet packet = null; + + if (qPacket.size() > 0) + while (qPacket.peek()!=null) + { + packet = qPacket.poll(); + + if (mConnection == null || (!mConnection.isConnected())) { + debug(TAG, "postponed packet to " + packet.getTo() + + " because we are not connected"); + postpone(packet); + return; + } + try { + mConnection.sendPacket(packet); + } catch (IllegalStateException ex) { + postpone(packet); + debug(TAG, "postponed packet to " + packet.getTo() + + " because socket is disconnected"); + } + } + + + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"error processing presence",e); + } + + + } + + }, 500, 500); + } + } diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppImPlugin.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppImPlugin.java index 1fb445220..b9174019f 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppImPlugin.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppImPlugin.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppPresenceMapping.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppPresenceMapping.java index 7d30e4c58..00006410b 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppPresenceMapping.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppPresenceMapping.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppStreamHandler.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppStreamHandler.java index be9ecb4d5..dd32e3bb3 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppStreamHandler.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/XmppStreamHandler.java @@ -58,7 +58,7 @@ public void quickShutdown() { mConnection.shutdown(); } } - + public void setMaxOutgoingQueueSize(int maxOutgoingQueueSize) { this.maxOutgoingQueueSize = maxOutgoingQueueSize; } @@ -66,7 +66,7 @@ public void setMaxOutgoingQueueSize(int maxOutgoingQueueSize) { public boolean isResumePossible() { return sessionId != null; } - + public boolean isResumePending() { return isResumePossible() && !isSmEnabled; } @@ -177,7 +177,7 @@ public boolean accept(Packet packet) { return true; } }); - + mConnection.addPacketListener(new PacketListener() { public void processPacket(Packet packet) { if (isSmEnabled && isStanza(packet)) { @@ -186,7 +186,7 @@ public void processPacket(Packet packet) { } else { trace("recv " + packet.toXML()); } - + if (packet instanceof StreamHandlingPacket) { StreamHandlingPacket shPacket = (StreamHandlingPacket) packet; String name = shPacket.getElementName(); @@ -219,16 +219,16 @@ public void processPacket(Packet packet) { // Removed acked packets removeOutgoingAcked(resumeStanzaCount); trace(outgoingQueue.size() + " in outgoing queue after resume"); - + // Resend any unacked packets for (Packet resendPacket : outgoingQueue) { mConnection.sendPacket(resendPacket); } - + // Enable only after resend, so that the interceptor does not // queue these again or increment outgoingStanzaCount. isSmEnabled = true; - + // Re-notify the listener - we are really ready for packets now // Before this point, isSuspendPending() was true, and the listener should have // ignored reconnectionSuccessful() from XMPPConnection. @@ -341,7 +341,7 @@ public String toXML() { } } - + /** Returns true if the packet is a Stanza as defined in RFC-6121 - a Message, IQ or Presence packet. */ public static boolean isStanza(Packet packet) { if (packet instanceof Message) diff --git a/src/info/guardianproject/otr/app/im/plugin/xmpp/auth/GTalkOAuth2.java b/src/info/guardianproject/otr/app/im/plugin/xmpp/auth/GTalkOAuth2.java index ae2f6c0ff..5678ce8e4 100644 --- a/src/info/guardianproject/otr/app/im/plugin/xmpp/auth/GTalkOAuth2.java +++ b/src/info/guardianproject/otr/app/im/plugin/xmpp/auth/GTalkOAuth2.java @@ -1,11 +1,10 @@ package info.guardianproject.otr.app.im.plugin.xmpp.auth; -import info.guardianproject.bouncycastle.util.encoders.Base64; - import java.io.IOException; import java.net.URLEncoder; +import org.apache.commons.codec.binary.Base64; import org.apache.harmony.javax.security.auth.callback.CallbackHandler; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.XMPPException; @@ -22,12 +21,11 @@ import android.content.Context; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.util.Log; public class GTalkOAuth2 extends SASLMechanism { - + public static final String NAME="X-GOOGLE-TOKEN";//"X-OAUTH2";// private static final String TOKEN_TYPE = "mail";// "https://www.googleapis.com/auth/googletalk";//"mail"; public static final String TYPE_GOOGLE_ACCT = "com.google"; @@ -46,7 +44,7 @@ protected String getName() { static void enable() { - Log.d(NAME,"Google OAuth2 enabled"); + // Log.d(NAME,"Google OAuth2 enabled"); } @Override @@ -69,18 +67,18 @@ public void challengeReceived(String arg0) throws IOException { protected void authenticate() throws IOException, XMPPException { //Log.d(NAME, "authId=" + authenticationId + "; password=" + password); - + String jidAndToken = "\0" + URLEncoder.encode( authenticationId, "utf-8" ) + "\0" + password; StringBuilder stanza = new StringBuilder(); stanza.append( "" ); - - stanza.append( new String(Base64.encode( jidAndToken.getBytes( "UTF-8" ) ) )); + + stanza.append( new String(Base64.encodeBase64( jidAndToken.getBytes( "UTF-8" ) ) )); stanza.append( "" ); getSASLAuthentication().send( new Auth2Mechanism(stanza.toString()) ); - + } /* @@ -89,22 +87,22 @@ protected void authenticate() throws IOException, XMPPException public static String getGoogleAuthTokenAllow(String name, Context context, Activity activity, Handler handler) { AccountManager aMgr = AccountManager.get(context); - + String retVal = null; Account account = getAccount(TYPE_GOOGLE_ACCT,name, aMgr); Bundle bundle = new Bundle(); - - AccountManagerFuture accFut = aMgr.getAuthToken(account, TOKEN_TYPE, bundle, activity, + + AccountManagerFuture accFut = aMgr.getAuthToken(account, TOKEN_TYPE, bundle, activity, new AccountManagerCallback() { @Override public void run(AccountManagerFuture bundle) { - + //this is the result bundle? - - + + }} ,handler); - + try { Bundle authTokenBundle = accFut.getResult(); @@ -127,12 +125,12 @@ public static String getGoogleAuthToken(String accountName, Context context) { Account account = getAccount(TYPE_GOOGLE_ACCT,accountName,aMgr); if (accountName == null) accountName = account.name; - + if (account != null) { try { - + //aMgr.updateCredentials(account, authTokenType, options, activity, callback, handler); - + return aMgr.blockingGetAuthToken(account, authTokenType, true); } catch (OperationCanceledException e) { Log.e(NAME, "auth canceled", e); @@ -150,10 +148,10 @@ public static String getGoogleAuthToken(String accountName, Context context) { //help method for getting proper account public static Account getAccount(String type, String name, AccountManager aMgr) { Account[] accounts = aMgr.getAccountsByType(type); - + if (name == null) return accounts[0]; - + for (Account account : accounts) { if (account.name.equals(name)) { return account; @@ -222,7 +220,7 @@ public void onSuccess(String result) { }}); * this.authorizer = new GlsAuthorizer.GlsAuthorizerFactory().getAuthorizer(activity, GlsAuthorizer.YOUTUBE_AUTH_TOKEN_TYPE); - + this.clientLoginToken = authorizer.getFreshAuthToken(youTubeName, clientLoginToken); */ diff --git a/src/info/guardianproject/otr/app/im/provider/Imps.java b/src/info/guardianproject/otr/app/im/provider/Imps.java index 8c86bff45..57e1c5e03 100644 --- a/src/info/guardianproject/otr/app/im/provider/Imps.java +++ b/src/info/guardianproject/otr/app/im/provider/Imps.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2007 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,22 +16,27 @@ package info.guardianproject.otr.app.im.provider; +import info.guardianproject.otr.app.im.app.ImApp; + +import java.util.HashMap; +import java.util.UUID; + import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.net.Uri.Builder; import android.os.Handler; import android.provider.BaseColumns; - -import java.util.HashMap; +import android.util.Log; /** * The IM provider stores all information about roster contacts, chat messages, * presence, etc. - * + * * @hide */ public class Imps { @@ -81,25 +86,27 @@ private Provider() { } public static final long getProviderIdForName(ContentResolver cr, String providerName) { - - + + String select = NAME + "=?"; String[] selectionArgs = {providerName}; Cursor cursor = cr.query(CONTENT_URI, PROVIDER_PROJECTION, select, selectionArgs, null); - + long retVal = 0; try { if (cursor.moveToFirst()) { retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID)); } } finally { - cursor.close(); + if (cursor != null) + cursor.close(); + } return retVal; } - + public static final String getProviderNameForId(ContentResolver cr, long providerId) { Cursor cursor = cr.query(CONTENT_URI, PROVIDER_PROJECTION, _ID + "=" + providerId, null, null); @@ -142,7 +149,7 @@ public static final String getProviderNameForId(ContentResolver cr, long provide public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/imps-providers"; /** The default sort order for this table */ - public static final String DEFAULT_SORT_ORDER = "name ASC"; + public static final String DEFAULT_SORT_ORDER = "providers._ID ASC"; } /** @@ -246,6 +253,11 @@ public static final String getPassword(ContentResolver cr, long accountId) { public static final Uri CONTENT_URI = Uri .parse("content://info.guardianproject.otr.app.im.provider.Imps/accounts"); + /** The content:// style URL for looking up by domain */ + public static final Uri BY_DOMAIN_URI = Uri + .parse("content://info.guardianproject.otr.app.im.provider.Imps/domainAccounts"); + + /** * The MIME type of {@link #CONTENT_URI} providing a directory of * account. @@ -404,7 +416,7 @@ public interface ContactsColumns { /** * Google Contact Extension attribute - * + * * Rejected: a boolean value indicating whether a subscription request * from this client was ever rejected by the user. "true" indicates that * it has. This is provided so that a client can block repeated @@ -513,7 +525,13 @@ private Contacts() { /** The default sort order for this table */ public static final String DEFAULT_SORT_ORDER = "subscriptionType DESC, last_message_date DESC," - + " mode DESC, nickname COLLATE UNICODE ASC"; + + " mode DESC, nickname COLLATE NOCASE ASC"; + + /** The default sort order for this table */ + public static final String ALPHA_SORT_ORDER = "nickname COLLATE NOCASE ASC"; + + /** The default sort order for this table */ + public static final String MODE_AND_ALPHA_SORT_ORDER = "mode DESC, nickname COLLATE NOCASE ASC"; public static final String CHATS_CONTACT = "chats_contact"; @@ -726,12 +744,12 @@ public interface MessageType { int OTR_TURNED_ON_BY_USER = 11; /* off the record status turned on by buddy */ int OTR_TURNED_ON_BY_BUDDY = 12; - + /* received message */ int INCOMING_ENCRYPTED = 13; /* received message */ int INCOMING_ENCRYPTED_VERIFIED = 14; - + /* received message */ int OUTGOING_ENCRYPTED = 15; /* received message */ @@ -789,6 +807,9 @@ public interface MessageColumns { /** Whether a delivery confirmation was received.

    Type: INTEGER

    */ String IS_DELIVERED = "is_delivered"; + + /** Mime type. If non-null, body is a URI. */ + String MIME_TYPE = "mime_type"; } /** This table contains messages. */ @@ -799,7 +820,7 @@ private Messages() { /** * Gets the Uri to query messages by thread id. - * + * * @param threadId the thread id of the message. * @return the Uri */ @@ -811,9 +832,9 @@ public static final Uri getContentUriByThreadId(long threadId) { /** * @deprecated - * + * * Gets the Uri to query messages by account and contact. - * + * * @param accountId the account id of the contact. * @param username the user name of the contact. * @return the Uri @@ -827,7 +848,7 @@ public static final Uri getContentUriByContact(long accountId, String username) /** * Gets the Uri to query messages by provider. - * + * * @param providerId the service provider id. * @return the Uri */ @@ -839,7 +860,7 @@ public static final Uri getContentUriByProvider(long providerId) { /** * Gets the Uri to query off the record messages by account. - * + * * @param accountId the account id. * @return the Uri */ @@ -851,7 +872,7 @@ public static final Uri getContentUriByAccount(long accountId) { /** * Gets the Uri to query off the record messages by thread id. - * + * * @param threadId the thread id of the message. * @return the Uri */ @@ -863,10 +884,10 @@ public static final Uri getOtrMessagesContentUriByThreadId(long threadId) { /** * @deprecated - * + * * Gets the Uri to query off the record messages by account * and contact. - * + * * @param accountId the account id of the contact. * @param username the user name of the contact. * @return the Uri @@ -880,7 +901,7 @@ public static final Uri getOtrMessagesContentUriByContact(long accountId, String /** * Gets the Uri to query off the record messages by provider. - * + * * @param providerId the service provider id. * @return the Uri */ @@ -892,7 +913,7 @@ public static final Uri getOtrMessagesContentUriByProvider(long providerId) { /** * Gets the Uri to query off the record messages by account. - * + * * @param accountId the account id. * @return the Uri */ @@ -909,6 +930,10 @@ public static final Uri getOtrMessagesContentUriByAccount(long accountId) { /** The content:// style URL for messages by thread id */ public static final Uri CONTENT_URI_MESSAGES_BY_THREAD_ID = Uri .parse("content://info.guardianproject.otr.app.im.provider.Imps/messagesByThreadId"); + + /** The content:// style URL for messages by thread id */ + public static final Uri CONTENT_URI_MESSAGES_BY_PACKET_ID = Uri + .parse("content://info.guardianproject.otr.app.im.provider.Imps/messagesByPacketId"); /** The content:// style URL for messages by account and contact */ public static final Uri CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT = Uri @@ -1114,6 +1139,7 @@ public interface CommonPresenceColumns { String PRESENCE_STATUS = "mode"; /** Presence Status definition */ + int OFFLINE = 0; int INVISIBLE = 1; int AWAY = 2; @@ -1122,7 +1148,7 @@ public interface CommonPresenceColumns { int AVAILABLE = 5; int NEW_ACCOUNT = -99; - + /** The user defined status line.

    Type: TEXT

    */ String PRESENCE_CUSTOM_STATUS = "status"; @@ -1278,7 +1304,7 @@ private SessionCookies() { public static interface ProviderSettingsColumns { /** * The id in database of the related provider - * + * *

    Type: INT

    */ String PROVIDER = "provider"; @@ -1292,7 +1318,8 @@ public static interface ProviderSettingsColumns { public static class ProviderSettings implements ProviderSettingsColumns { // Global settings are saved with this provider ID, for backward compatibility - public static final int PROVIDER_ID_FOR_GLOBAL_SETTINGS = 1; + + public static final long PROVIDER_ID_FOR_GLOBAL_SETTINGS = 1; private ProviderSettings() { } @@ -1363,12 +1390,15 @@ private ProviderSettings() { */ public static final String AUTOMATICALLY_START_SERVICE = "auto_start_service"; + public static final String LINKIFY_ON_TOR = "linkify_on_tor"; /** * Global setting which controls whether the offline contacts will be * hid. */ public static final String HIDE_OFFLINE_CONTACTS = "hide_offline_contacts"; + public static final String DELETE_UNSECURED_MEDIA = "delete_unsecured_media"; + /** Global setting which controls whether enable the IM notification */ public static final String ENABLE_NOTIFICATION = "enable_notification"; @@ -1404,17 +1434,17 @@ private ProviderSettings() { * rmq id received from the GTalk server */ public static final String LAST_RMQ_RECEIVED = "last_rmq_rec"; - + /** * use for status persistence */ public static final String PRESENCE_STATE = "presence_state"; public static final String PRESENCE_STATUS_MESSAGE = "presence_status_message"; - + /** * Query the settings of the provider specified by id - * + * * @param cr the relative content resolver * @param providerId the specified id of provider * @return a HashMap which contains all the settings for the specified @@ -1443,7 +1473,7 @@ public static HashMap queryProviderSettings(ContentResolver cr, /** * Get the string value of setting which is specified by provider id and * the setting name. - * + * * @param cr The ContentResolver to use to access the settings table. * @param providerId The id of the provider. * @param settingName The name of the setting. @@ -1460,11 +1490,11 @@ public static String getStringValue(ContentResolver cr, long providerId, String return ret; } - + /** * Get the string value of setting which is specified by provider id and * the setting name. - * + * * @param cr The ContentResolver to use to access the settings table. * @param providerId The id of the provider. * @param settingName The name of the setting. @@ -1473,7 +1503,7 @@ public static String getStringValue(ContentResolver cr, long providerId, String */ public static int getIntValue(ContentResolver cr, long providerId, String settingName) { int ret = -1; - + Cursor c = getSettingValue(cr, providerId, settingName); if (c != null) { ret = c.getInt(0); @@ -1486,7 +1516,7 @@ public static int getIntValue(ContentResolver cr, long providerId, String settin /** * Get the boolean value of setting which is specified by provider id * and the setting name. - * + * * @param cr The ContentResolver to use to access the settings table. * @param providerId The id of the provider. * @param settingName The name of the setting. @@ -1519,7 +1549,7 @@ private static Cursor getSettingValue(ContentResolver cr, long providerId, /** * Save a long value of setting in the table providerSetting. - * + * * @param cr The ContentProvider used to access the providerSetting * table. * @param providerId The id of the provider. @@ -1537,7 +1567,7 @@ public static void putLongValue(ContentResolver cr, long providerId, String name /** * Save a long value of setting in the table providerSetting. - * + * * @param cr The ContentProvider used to access the providerSetting * table. * @param providerId The id of the provider. @@ -1552,10 +1582,10 @@ public static void putIntValue(ContentResolver cr, long providerId, String name, cr.insert(CONTENT_URI, v); } - + /** * Save a boolean value of setting in the table providerSetting. - * + * * @param cr The ContentProvider used to access the providerSetting * table. * @param providerId The id of the provider. @@ -1574,7 +1604,7 @@ public static void putBooleanValue(ContentResolver cr, long providerId, String n /** * Save a string value of setting in the table providerSetting. - * + * * @param cr The ContentProvider used to access the providerSetting * table. * @param providerId The id of the provider. @@ -1589,14 +1619,14 @@ public static void putStringValue(ContentResolver cr, long providerId, String na v.put(VALUE, value); cr.insert(CONTENT_URI, v); - - + + } /** * A convenience method to set the domain name affiliated with an * account - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1608,7 +1638,7 @@ public static void setDomain(ContentResolver cr, long providerId, String domain) /** * A convenience method to set the XMPP Resource string - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1620,7 +1650,7 @@ public static void setXmppResource(ContentResolver cr, long providerId, String x /** * A convenience method to set the XMPP Resource priority - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1632,7 +1662,7 @@ public static void setXmppResourcePrio(ContentResolver cr, long providerId, int /** * A convenience method to set the TCP/IP port number to connect to - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1645,7 +1675,7 @@ public static void setPort(ContentResolver cr, long providerId, int port) { /** * A convenience method to set the hostname or IP of the server to * connect to - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1657,7 +1687,7 @@ public static void setServer(ContentResolver cr, long providerId, String server) /** * A convenience method to set whether to allow plain text auth - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1670,7 +1700,7 @@ public static void setAllowPlainAuth(ContentResolver cr, long providerId, /** * A convenience method to set whether to require TLS - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1682,7 +1712,7 @@ public static void setRequireTls(ContentResolver cr, long providerId, boolean re /** * A convenience method to set whether to verify the TLS cert - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1695,7 +1725,7 @@ public static void setTlsCertVerify(ContentResolver cr, long providerId, /** * A convenience method to set the mode of operation for the OTR Engine - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1707,7 +1737,7 @@ public static void setOtrMode(ContentResolver cr, long providerId, String otrMod /** * A convenience method to set whether to use Tor - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1720,7 +1750,7 @@ public static void setUseTor(ContentResolver cr, long providerId, boolean useTor /** * A convenience method to set whether to use DNS SRV lookups to find * the server - * + * * @param cr The ContentResolver to use to access the settings table * @param providerId used to identify the set of settings for a given * provider @@ -1733,7 +1763,7 @@ public static void setDoDnsSrv(ContentResolver cr, long providerId, boolean doDn /** * A convenience method to set whether or not the GTalk service should * be started automatically. - * + * * @param contentResolver The ContentResolver to use to access the * settings table * @param autoConnect Whether the GTalk service should be started @@ -1744,10 +1774,15 @@ public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver, putBooleanValue(contentResolver, providerId, AUTOMATICALLY_CONNECT_GTALK, autoConnect); } + public static void setLinkifyOnTor(ContentResolver contentResolver, long providerId, + boolean linkifyOnTor) { + putBooleanValue(contentResolver, providerId, LINKIFY_ON_TOR, linkifyOnTor); + } + /** * A convenience method to set whether or not the offline contacts * should be hided - * + * * @param contentResolver The ContentResolver to use to access the * setting table * @param hideOfflineContacts Whether the offline contacts should be @@ -1758,6 +1793,11 @@ public static void setHideOfflineContacts(ContentResolver contentResolver, long putBooleanValue(contentResolver, providerId, HIDE_OFFLINE_CONTACTS, hideOfflineContacts); } + public static void setDeleteUnsecuredMedia(ContentResolver contentResolver, long providerId, + boolean deleteUnsecuredMedia) { + putBooleanValue(contentResolver, providerId, DELETE_UNSECURED_MEDIA, deleteUnsecuredMedia); + } + public static void setUseForegroundPriority(ContentResolver contentResolver, long providerId, boolean flag) { putBooleanValue(contentResolver, providerId, USE_FOREGROUND_PRIORITY, flag); @@ -1766,7 +1806,7 @@ public static void setUseForegroundPriority(ContentResolver contentResolver, /** * A convenience method to set whether or not enable the IM * notification. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param enable Whether enable the IM notification @@ -1778,7 +1818,7 @@ public static void setEnableNotification(ContentResolver contentResolver, long p /** * A convenience method to set whether or not to vibrate. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param vibrate Whether or not to vibrate @@ -1790,7 +1830,7 @@ public static void setVibrate(ContentResolver contentResolver, long providerId, /** * A convenience method to set the Uri String of the ringtone. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param ringtoneUri The Uri String of the ringtone to be set. @@ -1802,7 +1842,7 @@ public static void setRingtoneURI(ContentResolver contentResolver, long provider /** * A convenience method to set whether or not to show mobile indicator. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param showMobileIndicator Whether or not to show mobile indicator. @@ -1815,7 +1855,7 @@ public static void setShowMobileIndicator(ContentResolver contentResolver, long /** * A convenience method to set whether or not to show as away when * device is idle. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param showAway Whether or not to show as away when device is idle. @@ -1827,7 +1867,7 @@ public static void setShowAwayOnIdle(ContentResolver contentResolver, long provi /** * A convenience method to set whether or not to upload heartbeat stat. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param uploadStat Whether or not to upload heartbeat stat. @@ -1840,7 +1880,7 @@ public static void setUploadHeartbeatStat(ContentResolver contentResolver, long /** * A convenience method to set the heartbeat interval last received from * the server. - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param interval The heartbeat interval last received from the server. @@ -1852,24 +1892,24 @@ public static void setHeartbeatInterval(ContentResolver contentResolver, long pr /** * A convenience method to user configure presence state and status - * + * * @param contentResolver The ContentResolver to use to access the * setting table. * @param interval The heartbeat interval last received from the server. */ public static void setPresence(ContentResolver contentResolver, long providerId, int state, String statusMessage) { - + if (state != -1) putIntValue(contentResolver, providerId, PRESENCE_STATE, state); - + if (statusMessage != null) putStringValue(contentResolver, providerId, PRESENCE_STATUS_MESSAGE, statusMessage); } - - - + + + /** A convenience method to set the jid resource. */ public static void setJidResource(ContentResolver contentResolver, long providerId, String jidResource) { @@ -1879,28 +1919,43 @@ public static void setJidResource(ContentResolver contentResolver, long provider public static class QueryMap extends ContentQueryMap { private ContentResolver mContentResolver; private long mProviderId; + private Exception mStacktrace; + /* public QueryMap(ContentResolver contentResolver, boolean keepUpdated, Handler handlerForUpdateNotifications) { this(contentResolver, ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, keepUpdated, handlerForUpdateNotifications); - } + }*/ + + //contentResolver.query(CONTENT_URI,new String[] {NAME, VALUE},PROVIDER + "=?",new String[] { Long.toString(providerId)},null) - public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated, + public QueryMap(Cursor cursor, ContentResolver contentResolver, long providerId, boolean keepUpdated, Handler handlerForUpdateNotifications) { - - super(contentResolver.query(CONTENT_URI,new String[] {NAME, VALUE},PROVIDER + "=?",new String[] { Long.toString(providerId)}, - null), // no sort order + + super(cursor, // no sort order NAME, keepUpdated, handlerForUpdateNotifications); - - + mContentResolver = contentResolver; mProviderId = providerId; + mStacktrace = new Exception(); + } + @Override + public synchronized void close() { + mStacktrace = null; + super.close(); } + @Override + protected void finalize() throws Throwable { + if (mStacktrace != null) { + Log.w("GB.Imps", "QueryMap cursor not closed before finalize", mStacktrace); + } + super.finalize(); + } /** * Set if the GTalk service should automatically connect to server. - * + * * @param autoConnect if the GTalk service should auto connect to * server. */ @@ -1912,7 +1967,7 @@ public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) { /** * Check if the GTalk service should automatically connect to * server. - * + * * @return if the GTalk service should automatically connect to * server. */ @@ -1933,7 +1988,15 @@ public void setXmppResource(String resource) { } public String getXmppResource() { - return getString(XMPP_RESOURCE, "Gibberbot"); + String currentResource = getString(XMPP_RESOURCE, ImApp.DEFAULT_XMPP_RESOURCE); + String defaultResource; + if (currentResource.equals(ImApp.DEFAULT_XMPP_RESOURCE)) { + defaultResource = ImApp.DEFAULT_XMPP_RESOURCE + "-" + + UUID.randomUUID().toString().substring(0, 8); + setXmppResource(defaultResource); + return defaultResource; + } + return currentResource; } public void setXmppResourcePrio(int prio) { @@ -1941,7 +2004,7 @@ public void setXmppResourcePrio(int prio) { } public int getXmppResourcePrio() { - return (int) getLong(XMPP_RESOURCE_PRIO, 20); + return (int) getLong(XMPP_RESOURCE_PRIO, ImApp.DEFAULT_XMPP_PRIORITY); } public void setPort(int port) { @@ -1990,7 +2053,15 @@ public void setOtrMode(String otrMode) { } public String getOtrMode() { - return getString(OTR_MODE, "auto" /* by default, try to use OTR */); + return getString(OTR_MODE, ImApp.DEFAULT_XMPP_OTR_MODE /* by default, try to use OTR */); + } + + public void setLinkifyOnTor(boolean value) { + ProviderSettings.setLinkifyOnTor(mContentResolver, mProviderId, value); + } + + public boolean getLinkifyOnTor() { + return getBoolean(LINKIFY_ON_TOR, false /* default do not linkify */); } public void setUseTor(boolean value) { @@ -2011,7 +2082,7 @@ public boolean getDoDnsSrv() { /** * Set whether or not the offline contacts should be hided. - * + * * @param hideOfflineContacts Whether or not the offline contacts * should be hided. */ @@ -2022,13 +2093,21 @@ public void setHideOfflineContacts(boolean hideOfflineContacts) { /** * Check if the offline contacts should be hided. - * + * * @return Whether or not the offline contacts should be hided. */ public boolean getHideOfflineContacts() { return getBoolean(HIDE_OFFLINE_CONTACTS, false /* default*/); } + public void setDeleteUnsecuredMedia(boolean deleteUnsecuredMedia) { + ProviderSettings.setDeleteUnsecuredMedia(mContentResolver, mProviderId, deleteUnsecuredMedia); + } + + public boolean getDeleteUnsecuredMedia() { + return getBoolean(DELETE_UNSECURED_MEDIA, false /* default */); + } + public void setUseForegroundPriority(boolean flag) { ProviderSettings.setUseForegroundPriority(mContentResolver, mProviderId, flag); } @@ -2039,7 +2118,7 @@ public boolean getUseForegroundPriority() { /** * Set whether or not enable the IM notification. - * + * * @param enable Whether or not enable the IM notification. */ public void setEnableNotification(boolean enable) { @@ -2048,7 +2127,7 @@ public void setEnableNotification(boolean enable) { /** * Check if the IM notification is enabled. - * + * * @return Whether or not enable the IM notification. */ public boolean getEnableNotification() { @@ -2057,7 +2136,7 @@ public boolean getEnableNotification() { /** * Set whether or not to vibrate on IM notification. - * + * * @param vibrate Whether or not to vibrate. */ public void setVibrate(boolean vibrate) { @@ -2066,16 +2145,16 @@ public void setVibrate(boolean vibrate) { /** * Gets whether or not to vibrate on IM notification. - * + * * @return Whether or not to vibrate. */ public boolean getVibrate() { - return getBoolean(NOTIFICATION_VIBRATE, false /* by default disable vibrate */); + return getBoolean(NOTIFICATION_VIBRATE, true /* by default enable vibrate */); } /** * Set the Uri for the ringtone. - * + * * @param ringtoneUri The Uri of the ringtone to be set. */ public void setRingtoneURI(String ringtoneUri) { @@ -2084,7 +2163,7 @@ public void setRingtoneURI(String ringtoneUri) { /** * Get the Uri String of the current ringtone. - * + * * @return The Uri String of the current ringtone. */ public String getRingtoneURI() { @@ -2093,7 +2172,7 @@ public String getRingtoneURI() { /** * Set whether or not to show mobile indicator to friends. - * + * * @param showMobile whether or not to show mobile indicator. */ public void setShowMobileIndicator(boolean showMobile) { @@ -2102,7 +2181,7 @@ public void setShowMobileIndicator(boolean showMobile) { /** * Gets whether or not to show mobile indicator. - * + * * @return Whether or not to show mobile indicator. */ public boolean getShowMobileIndicator() { @@ -2111,7 +2190,7 @@ public boolean getShowMobileIndicator() { /** * Set whether or not to show as away when device is idle. - * + * * @param showAway whether or not to show as away when device is * idle. */ @@ -2121,7 +2200,7 @@ public void setShowAwayOnIdle(boolean showAway) { /** * Get whether or not to show as away when device is idle. - * + * * @return Whether or not to show as away when device is idle. */ public boolean getShowAwayOnIdle() { @@ -2130,7 +2209,7 @@ public boolean getShowAwayOnIdle() { /** * Set whether or not to upload heartbeat stat. - * + * * @param uploadStat whether or not to upload heartbeat stat. */ public void setUploadHeartbeatStat(boolean uploadStat) { @@ -2139,7 +2218,7 @@ public void setUploadHeartbeatStat(boolean uploadStat) { /** * Get whether or not to upload heartbeat stat. - * + * * @return Whether or not to upload heartbeat stat. */ public boolean getUploadHeartbeatStat() { @@ -2167,7 +2246,7 @@ public long getHeartbeatInterval() { /** * Set the JID resource. - * + * * @param jidResource the jid resource to be stored. */ public void setJidResource(String jidResource) { @@ -2176,7 +2255,7 @@ public void setJidResource(String jidResource) { /** * Get the JID resource used for the Google Talk connection - * + * * @return the JID resource stored. */ public String getJidResource() { @@ -2186,7 +2265,7 @@ public String getJidResource() { /** * Convenience function for retrieving a single settings value as a * boolean. - * + * * @param name The name of the setting to retrieve. * @param def Value to return if the setting is not defined. * @return The setting's current value, or 'def' if it is not @@ -2200,7 +2279,7 @@ private boolean getBoolean(String name, boolean def) { /** * Convenience function for retrieving a single settings value as a * String. - * + * * @param name The name of the setting to retrieve. * @param def The value to return if the setting is not defined. * @return The setting's current value or 'def' if it is not @@ -2214,7 +2293,7 @@ private String getString(String name, String def) { /** * Convenience function for retrieving a single settings value as an * Integer. - * + * * @param name The name of the setting to retrieve. * @param def The value to return if the setting is not defined. * @return The setting's current value or 'def' if it is not @@ -2228,7 +2307,7 @@ private int getInteger(String name, int def) { /** * Convenience function for retrieving a single settings value as a * Long. - * + * * @param name The name of the setting to retrieve. * @param def The value to return if the setting is not defined. * @return The setting's current value or 'def' if it is not @@ -2286,7 +2365,7 @@ public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns /** * queryHighestRmqId - * + * * @param resolver the content resolver * @return the highest rmq id assigned to the rmq packet, or 0 if there * are no rmq packets in the OutgoingRmq table. @@ -2344,9 +2423,9 @@ public static final class LastRmqId implements BaseColumns, LastRmqIdColumns { /** * queryLastRmqId - * + * * queries the last rmq id saved in the LastRmqId table. - * + * * @param resolver the content resolver. * @return the last rmq id stored in the LastRmqId table, or 0 if not * found. @@ -2371,10 +2450,10 @@ public static final long queryLastRmqId(ContentResolver resolver) { /** * saveLastRmqId - * + * * saves the rmqId to the lastRmqId table. This will override the * existing row if any, as we only keep one row of data in this table. - * + * * @param resolver the content resolver. * @param rmqId the rmq id to be saved. */ @@ -2409,4 +2488,160 @@ public static final class ServerToDeviceRmqIds implements BaseColumns, .parse("content://info.guardianproject.otr.app.im.provider.Imps/s2dids"); } + public static boolean isUnlocked(Context context) + { + try { + Cursor cursor = null; + + Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + Builder builder = uri.buildUpon(); + builder = builder.appendQueryParameter(ImApp.NO_CREATE_KEY, "1"); + + uri = builder.build(); + + cursor = context.getContentResolver().query( + uri, null, Imps.Provider.CATEGORY + "=?" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + null); + + if (cursor != null) + { + cursor.close(); + return true; + } + else + { + return false; + } + + } catch (Exception e) { + // Only complain if we thought this password should succeed + + Log.e(ImApp.LOG_TAG, e.getMessage(), e); + + // needs to be unlocked + return false; + } + } + + + public static boolean isUnencrypted(Context context) { + try { + Cursor cursor = null; + + Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + Builder builder = uri.buildUpon(); + builder.appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, ""); + builder = builder.appendQueryParameter(ImApp.NO_CREATE_KEY, "1"); + + uri = builder.build(); + + cursor = context.getContentResolver().query( + uri, null, Imps.Provider.CATEGORY + "=?" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + null); + + if (cursor != null) + { + cursor.close(); + return true; + } + else + { + return false; + } + + } catch (Exception e) { + // Only complain if we thought this password should succeed + + Log.e(ImApp.LOG_TAG, e.getMessage(), e); + + // needs to be unlocked + return false; + } + } + public static boolean setEmptyPassphrase(Context ctx, boolean noCreate) { + String pkey = ""; + + Uri uri = Provider.CONTENT_URI_WITH_ACCOUNT; + + Builder builder = uri.buildUpon().appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pkey); + if (noCreate) { + builder.appendQueryParameter(ImApp.NO_CREATE_KEY, "1"); + } + uri = builder.build(); + + Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null); + if (cursor != null) { + cursor.close(); + return true; + } + return false; + } + + public static void clearPassphrase(Context ctx) { + Uri uri = Provider.CONTENT_URI_WITH_ACCOUNT; + + Builder builder = uri.buildUpon().appendQueryParameter(ImApp.CLEAR_PASSWORD_KEY, "1"); + uri = builder.build(); + + Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null); + if (cursor != null) { + throw new RuntimeException("Unexpected cursor returned"); + } + } + + public static Uri insertMessageInDb(ContentResolver resolver, + boolean isGroup, + long contactId, + boolean isEncrypted, + String nickname, + String body, + long time, + int type, + int errCode, + String id, + String mimeType) { + + ContentValues values = new ContentValues(); + values.put(Imps.Messages.BODY, body); + values.put(Imps.Messages.DATE, time); + values.put(Imps.Messages.TYPE, type); + values.put(Imps.Messages.ERROR_CODE, errCode); + if (isGroup) { + values.put(Imps.Messages.NICKNAME, nickname); + values.put(Imps.Messages.IS_GROUP_CHAT, 1); + } + values.put(Imps.Messages.IS_DELIVERED, 0); + values.put(Imps.Messages.MIME_TYPE, mimeType); + values.put(Imps.Messages.PACKET_ID, id); + + return resolver.insert(isEncrypted ? Messages.getOtrMessagesContentUriByThreadId(contactId) : Messages.getContentUriByThreadId(contactId), values); + } + + public static int updateMessageBody(ContentResolver resolver, String id, String body, String mimeType) { + + Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI.buildUpon(); + builder.appendPath(id); + + ContentValues values = new ContentValues(); + values.put(Imps.Messages.BODY, body); + values.put(Imps.Messages.MIME_TYPE, mimeType); + + return resolver.update(builder.build(), values, null, null); + } + + public static int updateConfirmInDb(ContentResolver resolver, String id, boolean isDelivered) { + Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI_BY_PACKET_ID.buildUpon(); + builder.appendPath(id); + + ContentValues values = new ContentValues(1); + values.put(Imps.Messages.IS_DELIVERED, isDelivered); + return resolver.update(builder.build(), values, null, null); + } + + + } diff --git a/src/info/guardianproject/otr/app/im/provider/ImpsAddressUtils.java b/src/info/guardianproject/otr/app/im/provider/ImpsAddressUtils.java index 8479e6000..084f13072 100644 --- a/src/info/guardianproject/otr/app/im/provider/ImpsAddressUtils.java +++ b/src/info/guardianproject/otr/app/im/provider/ImpsAddressUtils.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/provider/ImpsDatabaseHelper.java b/src/info/guardianproject/otr/app/im/provider/ImpsDatabaseHelper.java deleted file mode 100644 index ed681a5ce..000000000 --- a/src/info/guardianproject/otr/app/im/provider/ImpsDatabaseHelper.java +++ /dev/null @@ -1,5 +0,0 @@ -package info.guardianproject.otr.app.im.provider; - -public class ImpsDatabaseHelper { - -} diff --git a/src/info/guardianproject/otr/app/im/provider/ImpsErrorInfo.java b/src/info/guardianproject/otr/app/im/provider/ImpsErrorInfo.java index c1404a2aa..19c280711 100644 --- a/src/info/guardianproject/otr/app/im/provider/ImpsErrorInfo.java +++ b/src/info/guardianproject/otr/app/im/provider/ImpsErrorInfo.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/provider/ImpsProvider.java b/src/info/guardianproject/otr/app/im/provider/ImpsProvider.java index 6c6630766..4a54e0654 100644 --- a/src/info/guardianproject/otr/app/im/provider/ImpsProvider.java +++ b/src/info/guardianproject/otr/app/im/provider/ImpsProvider.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2007 The Android Open Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,14 +16,16 @@ package info.guardianproject.otr.app.im.provider; -import info.guardianproject.cacheword.CacheWordActivityHandler; -import info.guardianproject.cacheword.ICacheWordSubscriber; -import info.guardianproject.cacheword.SQLCipherOpenHelper; import info.guardianproject.otr.OtrAndroidKeyManagerImpl; +import info.guardianproject.otr.app.im.app.ContactView; import info.guardianproject.otr.app.im.app.ImApp; +import info.guardianproject.otr.app.im.provider.Imps.Contacts; +import info.guardianproject.otr.app.im.provider.Imps.Provider; +import info.guardianproject.util.Debug; import info.guardianproject.util.LogCleaner; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; @@ -31,6 +33,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import net.sqlcipher.database.SQLiteConstraintException; @@ -44,16 +47,18 @@ import android.content.UriMatcher; import android.content.res.Configuration; import android.database.Cursor; +import android.database.CursorWindow; import android.database.DatabaseUtils; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.text.TextUtils; -import android.util.Log; /** A content provider for IM */ -public class ImpsProvider extends ContentProvider implements ICacheWordSubscriber { +public class ImpsProvider extends ContentProvider { + private static final String PREV_DATABASE_OPEN_TRAIL_TAG = "prev_database_open"; + private static final String EMPTY_KEY_TRAIL_TAG = "empty_key"; + private static final String DATABASE_OPEN_TRAIL_TAG = "database_open"; private static final String LOG_TAG = "imProvider"; - private static final boolean DBG = false; private static final String AUTHORITY = "info.guardianproject.otr.app.im.provider.Imps"; @@ -82,14 +87,17 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe private static final String TABLE_LAST_RMQ_ID = "lastrmqid"; private static final String TABLE_S2D_RMQ_IDS = "s2dRmqIds"; - private static final String DATABASE_NAME = "impsenc.db"; - private static final int DATABASE_VERSION = 102; + private static final String ENCRYPTED_DATABASE_NAME = "impsenc.db"; + private static final String UNENCRYPTED_DATABASE_NAME = "imps.db"; + + private static final int DATABASE_VERSION = 105; protected static final int MATCH_PROVIDERS = 1; protected static final int MATCH_PROVIDERS_BY_ID = 2; protected static final int MATCH_PROVIDERS_WITH_ACCOUNT = 3; protected static final int MATCH_ACCOUNTS = 10; protected static final int MATCH_ACCOUNTS_BY_ID = 11; + protected static final int MATCH_ACCOUNTS_WITH_DOMAIN = 12; protected static final int MATCH_CONTACTS = 18; protected static final int MATCH_CONTACTS_JOIN_PRESENCE = 19; protected static final int MATCH_CONTACTS_BAREBONE = 20; @@ -129,7 +137,8 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe protected static final int MATCH_OTR_MESSAGES_BY_ACCOUNT = 60; protected static final int MATCH_OTR_MESSAGE = 61; protected static final int MATCH_OTR_MESSAGES_BY_PACKET_ID = 62; - + protected static final int MATCH_MESSAGES_BY_PACKET_ID = 63; + protected static final int MATCH_GROUP_MEMBERS = 65; protected static final int MATCH_GROUP_MEMBERS_BY_GROUP = 66; protected static final int MATCH_AVATARS = 70; @@ -157,9 +166,10 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe protected static final int MATCH_S2D_RMQ_IDS = 204; protected final UriMatcher mUrlMatcher = new UriMatcher(UriMatcher.NO_MATCH); - private final String mTransientDbName; + private String mTransientDbName; private static final HashMap sProviderAccountsProjectionMap; + private static final HashMap sAccountsByDomainProjectionMap; private static final HashMap sContactsProjectionMap; private static final HashMap sContactListProjectionMap; private static final HashMap sBlockedListProjectionMap; @@ -170,6 +180,11 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe + "(providers._id = accounts.provider AND accounts.active = 1) " + "LEFT OUTER JOIN accountStatus ON (accounts._id = accountStatus.account)"; + private static final String DOMAIN_JOIN_ACCOUNT_TABLE = "providerSettings JOIN accounts ON " + + "(providerSettings.provider = accounts.provider AND providerSettings.name = '" + Imps.ProviderSettings.DOMAIN + "' AND accounts.active = 1) " + + "LEFT OUTER JOIN accountStatus ON (accounts._id = accountStatus.account)"; + + private static final String CONTACT_JOIN_PRESENCE_TABLE = "contacts LEFT OUTER JOIN presence ON (contacts._id = presence.contact_id)"; private static final String CONTACT_JOIN_PRESENCE_CHAT_TABLE = CONTACT_JOIN_PRESENCE_TABLE @@ -205,9 +220,9 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe + Imps.Presence.CONTACT_ID; protected static DatabaseHelper mDbHelper; - private final String mDatabaseName; + private String mDatabaseName; private final int mDatabaseVersion; - + private final String[] BACKFILL_PROJECTION = { Imps.Chats._ID, Imps.Chats.SHORTCUT, Imps.Chats.LAST_MESSAGE_DATE }; @@ -241,9 +256,7 @@ public class ImpsProvider extends ContentProvider implements ICacheWordSubscribe // contact id query selection args 2 private String[] mQueryContactIdSelectionArgs2 = new String[2]; - - private CacheWordActivityHandler mCacheWord; - + private class DatabaseHelper extends SQLiteOpenHelper { @@ -252,10 +265,12 @@ private class DatabaseHelper extends SQLiteOpenHelper { boolean mInMemoryDB = false; String mKey = null; + + boolean doCleanup = false; DatabaseHelper(Context context, String key, boolean inMemoryDb) throws Exception { super(context, mDatabaseName, null, mDatabaseVersion); - + mInMemoryDB = inMemoryDb; mKey = key; } @@ -271,13 +286,31 @@ public SQLiteDatabase getWritableDatabase() { if (dbWrite == null) dbWrite = super.getWritableDatabase(mKey); + /* + if (doCleanup) + { + //clean up orphaned contacts + dbWrite.execSQL("DELETE FROM " + TABLE_CONTACTS + " WHERE " + TABLE_CONTACTS + '.' + Contacts._ID + + " IN (Select " + TABLE_CONTACTS + '.' + Contacts._ID + " from " + TABLE_CONTACTS + " LEFT JOIN " + TABLE_PROVIDERS + " ON " + Contacts.PROVIDER + "=" + TABLE_PROVIDERS + "." + Provider._ID + " WHERE " + TABLE_PROVIDERS + '.' + Provider._ID + " IS NULL)"); + + + dbWrite.execSQL("DELETE FROM " + TABLE_CONTACTS + " WHERE " + TABLE_CONTACTS + '.' + Contacts._ID + " NOT IN (" + + "SELECT min(" + Contacts._ID + ") FROM " + TABLE_CONTACTS + " group by " + Contacts.USERNAME + "," + Contacts.PROVIDER + "," + Contacts.ACCOUNT + + ")"); + + + + doCleanup = false; + } + */ + return dbWrite; } @Override public void onCreate(SQLiteDatabase db) { - if (DBG) + log("DatabaseHelper.onCreate"); db.execSQL("CREATE TABLE " + TABLE_PROVIDERS + " (" + "_id INTEGER PRIMARY KEY," @@ -332,6 +365,9 @@ public void onCreate(SQLiteDatabase db) { db.execSQL("create TABLE " + TABLE_S2D_RMQ_IDS + " (" + "_id INTEGER PRIMARY KEY," + "rmq_id INTEGER" + ");"); + + //DELETE FROM cache WHERE id IN (SELECT cache.id FROM cache LEFT JOIN main ON cache.id=main.id WHERE main.id IS NULL); + } @Override @@ -459,6 +495,52 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.endTransaction(); } + return; + case 101: + // This was a no-op upgrade when we added the encrypted DB option + return; + case 103: + + try { + db.beginTransaction(); + + Cursor c = db.query(TABLE_MESSAGES, null, null, null, null, null, null); + if (c.getColumnIndex("mime_type")==-1) + { + db.execSQL("ALTER TABLE " + TABLE_MESSAGES + + " ADD COLUMN mime_type TEXT;"); + } + c.close(); + + db.setTransactionSuccessful(); + } catch (Throwable ex) { + LogCleaner.error(LOG_TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + if (mInMemoryDB) { //this should actually be if mInMemoryDB = true, then update the table + + try { + + db.beginTransaction(); + db.execSQL("ALTER TABLE " + TABLE_IN_MEMORY_MESSAGES + + " ADD COLUMN mime_type TEXT;"); + db.setTransactionSuccessful(); + + } catch (Throwable ex) { + LogCleaner.error(LOG_TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + } + + return; + case 104: + + db.rawExecSQL("PRAGMA cipher_migrate;"); + return; case 1: if (newVersion <= 100) { @@ -504,7 +586,7 @@ private void destroyOldTables(SQLiteDatabase db) { } private void createContactsTables(SQLiteDatabase db) { - if (DBG) + log("createContactsTables"); StringBuilder buf = new StringBuilder(); @@ -587,7 +669,7 @@ private void createContactsTables(SQLiteDatabase db) { private void createMessageChatTables(SQLiteDatabase db, boolean addShowTsColumnForMessagesTable) { - if (DBG) + log("createMessageChatTables"); // message table @@ -609,12 +691,13 @@ private void createMessageChatTables(SQLiteDatabase db, buf.append(",show_ts INTEGER"); } buf.append(",is_delivered INTEGER"); + buf.append(",mime_type TEXT"); buf.append(");"); String sqlStatement = buf.toString(); - if (DBG) + log("create message table: " + sqlStatement); db.execSQL(sqlStatement); @@ -633,7 +716,7 @@ private void createMessageChatTables(SQLiteDatabase db, // chat sessions, including single person chats and group chats sqlStatement = buf.toString(); - if (DBG) + log("create chat table: " + sqlStatement); db.execSQL(sqlStatement); @@ -647,7 +730,7 @@ private void createMessageChatTables(SQLiteDatabase db, sqlStatement = buf.toString(); - if (DBG) + log("create trigger: " + sqlStatement); db.execSQL(sqlStatement); } @@ -663,7 +746,10 @@ private void createInMemoryMessageTables(SQLiteDatabase db, String tablePrefix) + // in millisec "type INTEGER," + "packet_id TEXT UNIQUE," + "err_code INTEGER NOT NULL DEFAULT 0," + "err_msg TEXT," - + "is_muc INTEGER," + "show_ts INTEGER," + "is_delivered INTEGER" + ");"); + + "is_muc INTEGER," + "show_ts INTEGER," + + "is_delivered INTEGER," + + "mime_type TEXT" + + ");"); } @@ -675,13 +761,13 @@ public void onOpen(SQLiteDatabase db) { return; } - if (DBG) + log("##### createTransientTables"); - + // Create transient tables String cpDbName; - + if (mInMemoryDB) { db.execSQL("ATTACH DATABASE ':memory:' AS " + mTransientDbName + ";"); @@ -691,7 +777,7 @@ public void onOpen(SQLiteDatabase db) { { cpDbName = ""; } - + // in-memory message table createInMemoryMessageTables(db, cpDbName); @@ -794,6 +880,9 @@ public synchronized void close() { sProviderAccountsProjectionMap.put(Imps.Provider.ACCOUNT_CONNECTION_STATUS, "accountStatus.connStatus AS account_connStatus"); + sAccountsByDomainProjectionMap = new HashMap(); + sAccountsByDomainProjectionMap.put(Imps.Account._ID, "accounts._id AS _id"); + // contacts projection map sContactsProjectionMap = new HashMap(); @@ -840,7 +929,7 @@ public synchronized void close() { // Avatars columns sContactsProjectionMap.put(Imps.Contacts.AVATAR_HASH, "avatars.hash AS avatars_hash"); - sContactsProjectionMap.put(Imps.Contacts.AVATAR_DATA, "avatars.data AS avatars_data"); + sContactsProjectionMap.put(Imps.Contacts.AVATAR_DATA, "quote(avatars.data) AS avatars_data"); // contactList projection map sContactListProjectionMap = new HashMap(); @@ -858,7 +947,7 @@ public synchronized void close() { sBlockedListProjectionMap.put(Imps.BlockedList.NICKNAME, "nickname"); sBlockedListProjectionMap.put(Imps.BlockedList.PROVIDER, "provider"); sBlockedListProjectionMap.put(Imps.BlockedList.ACCOUNT, "account"); - sBlockedListProjectionMap.put(Imps.BlockedList.AVATAR_DATA, "avatars.data AS avatars_data"); + sBlockedListProjectionMap.put(Imps.BlockedList.AVATAR_DATA, "quote(avatars.data) AS avatars_data"); // messages projection map sMessagesProjectionMap = new HashMap(); @@ -876,6 +965,8 @@ public synchronized void close() { sMessagesProjectionMap.put(Imps.Messages.DISPLAY_SENT_TIME, "messages.show_ts AS show_ts"); sMessagesProjectionMap.put(Imps.Messages.IS_DELIVERED, "messages.is_delivered AS is_delivered"); + sMessagesProjectionMap.put(Imps.Messages.MIME_TYPE, + "messages.mime_type AS mime_type"); // contacts columns sMessagesProjectionMap.put(Imps.Messages.CONTACT, "contacts.username AS contact"); sMessagesProjectionMap.put(Imps.Contacts.PROVIDER, "contacts.provider AS provider"); @@ -904,6 +995,8 @@ public synchronized void close() { "inMemoryMessages.show_ts AS show_ts"); sInMemoryMessagesProjectionMap.put(Imps.Messages.IS_DELIVERED, "inMemoryMessages.is_delivered AS is_delivered"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.MIME_TYPE, + "inMemoryMessages.mime_type AS mime_type"); // contacts columns sInMemoryMessagesProjectionMap.put(Imps.Messages.CONTACT, "contacts.username AS contact"); sInMemoryMessagesProjectionMap.put(Imps.Contacts.PROVIDER, "contacts.provider AS provider"); @@ -912,17 +1005,15 @@ public synchronized void close() { } public ImpsProvider() { - this(DATABASE_NAME, DATABASE_VERSION); + this(DATABASE_VERSION); + - setupImUrlMatchers(AUTHORITY); setupMcsUrlMatchers(AUTHORITY); } - protected ImpsProvider(String dbName, int dbVersion) { - mDatabaseName = dbName; + protected ImpsProvider(int dbVersion) { mDatabaseVersion = dbVersion; - mTransientDbName = "transient_" + dbName.replace(".", "_"); } private void setupImUrlMatchers(String authority) { @@ -931,6 +1022,7 @@ private void setupImUrlMatchers(String authority) { mUrlMatcher.addURI(authority, "providers/account", MATCH_PROVIDERS_WITH_ACCOUNT); mUrlMatcher.addURI(authority, "accounts", MATCH_ACCOUNTS); + mUrlMatcher.addURI(authority, "domainAccounts", MATCH_ACCOUNTS_WITH_DOMAIN); mUrlMatcher.addURI(authority, "accounts/#", MATCH_ACCOUNTS_BY_ID); mUrlMatcher.addURI(authority, "contacts", MATCH_CONTACTS); @@ -967,7 +1059,8 @@ private void setupImUrlMatchers(String authority) { mUrlMatcher.addURI(authority, "messagesByProvider/#", MATCH_MESSAGES_BY_PROVIDER); mUrlMatcher.addURI(authority, "messagesByAccount/#", MATCH_MESSAGES_BY_ACCOUNT); mUrlMatcher.addURI(authority, "messages/#", MATCH_MESSAGE); - + mUrlMatcher.addURI(authority, "messagesByPacketId/*", MATCH_MESSAGES_BY_PACKET_ID); + mUrlMatcher.addURI(authority, "otrMessages", MATCH_OTR_MESSAGES); mUrlMatcher.addURI(authority, "otrMessagesByAcctAndContact/#/*", MATCH_OTR_MESSAGES_BY_CONTACT); @@ -1014,23 +1107,39 @@ private void setupMcsUrlMatchers(String authority) { @Override public boolean onCreate() { - mCacheWord = new CacheWordActivityHandler(getContext(), (ICacheWordSubscriber)this); - mCacheWord.connectToService(); - - return true; } - private synchronized DatabaseHelper initDBHelper(String pkey) throws Exception { + private void setDatabaseName(boolean isEncrypted) { + mDatabaseName = isEncrypted ? ENCRYPTED_DATABASE_NAME : UNENCRYPTED_DATABASE_NAME; + mTransientDbName = "transient_" + mDatabaseName.replace(".", "_"); + } + private synchronized DatabaseHelper initDBHelper(String pkey, boolean noCreate) throws Exception { + if (mDbHelper == null) { + if (pkey != null) { + setDatabaseName(!pkey.isEmpty()); + Context ctx = getContext(); + String path = ctx.getDatabasePath(mDatabaseName).getPath(); + if (noCreate && !new File(path).exists()) { + LogCleaner.debug(ImApp.LOG_TAG, "no DB exists at " + path); + return null; + } - - if (mDbHelper == null && pkey != null) { - Context ctx = getContext(); + boolean inMemoryDb = false; - boolean inMemoryDb = false; - - mDbHelper = new DatabaseHelper(ctx, pkey, inMemoryDb); + mDbHelper = new DatabaseHelper(ctx, pkey, inMemoryDb); + LogCleaner.debug(LOG_TAG, "Opened DB with key - empty=" + pkey.isEmpty()); + + Debug.recordTrail(getContext(), EMPTY_KEY_TRAIL_TAG, "" + pkey.isEmpty()); + String prevOpen = Debug.getTrail(getContext(), DATABASE_OPEN_TRAIL_TAG); + if (prevOpen != null) { + Debug.recordTrail(getContext(), PREV_DATABASE_OPEN_TRAIL_TAG, prevOpen); + } + Debug.recordTrail(getContext(), DATABASE_OPEN_TRAIL_TAG, new Date()); + } else { + LogCleaner.warn(ImApp.LOG_TAG, "DB not open and no password provided"); + } } return mDbHelper; @@ -1038,19 +1147,7 @@ private synchronized DatabaseHelper initDBHelper(String pkey) throws Exception { } private DatabaseHelper getDBHelper() { - - if (mDbHelper == null) - { - //check if cacheword is open, and then init the mDbHelper - if (!mCacheWord.isLocked()) - { - onCacheWordOpened(); - } - else - { - //we need to exit somehow - } - } + return mDbHelper; } @@ -1064,61 +1161,109 @@ public final int update(final Uri url, final ContentValues values, final String final String[] selectionArgs) { DatabaseHelper dbHelper = getDBHelper(); - - + + int result = 0; - + if (dbHelper != null) { SQLiteDatabase db = dbHelper.getWritableDatabase(); - db.beginTransaction(); - try { - result = updateInternal(url, values, selection, selectionArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - if (result > 0) { - getContext().getContentResolver() - .notifyChange(url, null /* observer */, false /* sync */); + + synchronized (db) + { + if (db.isOpen()) + { + try { + db.beginTransaction(); + + result = updateInternal(url, values, selection, selectionArgs); + db.setTransactionSuccessful(); + db.endTransaction(); + } + catch (Exception e){ + + if (db.isOpen() && db.inTransaction()) + db.endTransaction(); + } + + + if (result > 0) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + } } } - + return result; } @Override public final int delete(final Uri url, final String selection, final String[] selectionArgs) { - int result; - SQLiteDatabase db = getDBHelper().getWritableDatabase(); - db.beginTransaction(); - try { - result = deleteInternal(url, selection, selectionArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - if (result > 0) { - getContext().getContentResolver() - .notifyChange(url, null /* observer */, false /* sync */); + + int result = -1; + + if (getDBHelper() != null) + { + SQLiteDatabase db = getDBHelper().getWritableDatabase(); + + synchronized (db) + { + if (db.isOpen()) //db can be closed if service sign out takes longer than app/cacheword lock + { + try { + db.beginTransaction(); + result = deleteInternal(url, selection, selectionArgs); + db.setTransactionSuccessful(); + db.endTransaction(); + } + catch (Exception e) + { + //could not delete + } + + if (result > 0) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + } + } } + return result; } @Override public final Uri insert(final Uri url, final ContentValues values) { - Uri result; - SQLiteDatabase db = getDBHelper().getWritableDatabase(); - db.beginTransaction(); - try { - result = insertInternal(url, values); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - if (result != null) { - getContext().getContentResolver() - .notifyChange(url, null /* observer */, false /* sync */); + Uri result = null; + + if (getDBHelper() != null) + { + try + { + SQLiteDatabase db = getDBHelper().getWritableDatabase(); + synchronized (db) + { + if (db.isOpen()) + { + db.beginTransaction(); + try { + result = insertInternal(url, values); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + if (result != null) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + } + } + } + catch (IllegalStateException ise) + { + log("database closed when insert attempted: " + url.toString()); + } } return result; } @@ -1130,18 +1275,18 @@ public final Cursor query(final Uri url, final String[] projection, final String } boolean mLoadedLibs = false; - + public Cursor queryInternal(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort) { - - - + + Debug.onServiceStart(); + if (!mLoadedLibs) { SQLiteDatabase.loadLibs(this.getContext().getApplicationContext()); mLoadedLibs = true; } - + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); StringBuilder whereClause = new StringBuilder(); if (selection != null) { @@ -1151,20 +1296,35 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, String limit = null; String pkey = url.getQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY); - + boolean noCreate = "1".equals(url.getQueryParameter(ImApp.NO_CREATE_KEY)); + boolean clearKey = "1".equals(url.getQueryParameter(ImApp.CLEAR_PASSWORD_KEY)); + + if (clearKey) { + if (mDbHelper != null) { + mDbHelper.close(); + mDbHelper = null; + } + return null; + } + try { - initDBHelper(pkey); + initDBHelper(pkey, noCreate); } catch (Exception e) { LogCleaner.error(ImApp.LOG_TAG, e.getMessage(), e); return null; } - + + if (mDbHelper == null) { + // Failed to open + return null; + } + if (pkey != null) { OtrAndroidKeyManagerImpl.setKeyStorePassword(pkey); - + } - + /* * String dbKey = null; @@ -1200,7 +1360,7 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, // Generate the body of the query int match = mUrlMatcher.match(url); - if (DBG) { + { //log("query " + url + ", match " + match + ", where " + selection); if (selectionArgs != null) { for (String selectionArg : selectionArgs) { @@ -1223,6 +1383,11 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, qb.setProjectionMap(sProviderAccountsProjectionMap); break; + case MATCH_ACCOUNTS_WITH_DOMAIN: + qb.setTables(DOMAIN_JOIN_ACCOUNT_TABLE); + qb.setProjectionMap(sAccountsByDomainProjectionMap); + break; + case MATCH_ACCOUNTS_BY_ID: appendWhere(whereClause, Imps.Account._ID, "=", url.getPathSegments().get(1)); // falls down @@ -1358,12 +1523,12 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, final SQLiteDatabase db = getDBHelper().getWritableDatabase(); String[] doubleArgs = null; if (selectionArgs != null) { - + doubleArgs = new String[ selectionArgs.length * 2];//Arrays.copyOf(selectionArgs, selectionArgs.length * 2); System.arraycopy(selectionArgs, 0, doubleArgs, 0, selectionArgs.length); System.arraycopy(selectionArgs, 0, doubleArgs, selectionArgs.length, selectionArgs.length); } - + Cursor c = db.rawQueryWithFactory(null, query, doubleArgs, TABLE_MESSAGES); if ((c != null) && !isTemporary()) { c.setNotificationUri(getContext().getContentResolver(), url); @@ -1516,10 +1681,21 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, return null; // run the query - final SQLiteDatabase db = getDBHelper().getReadableDatabase(); - Cursor c = null; + SQLiteDatabase db; + try { + db = getDBHelper().getReadableDatabase(); + } catch (net.sqlcipher.database.SQLiteException e) { + // Failed to actually open - the passphrase must have been wrong - reset the helper + mDbHelper = null; + throw e; + } + net.sqlcipher.Cursor c = null; + + if (!db.isOpen()) + return null; try { + qb.setDistinct(true); c = qb.query(db, projectionIn, whereClause.toString(), selectionArgs, groupBy, null, sort, limit); if (c != null) { @@ -1535,21 +1711,72 @@ public Cursor queryInternal(Uri url, String[] projectionIn, String selection, url = Imps.Contacts.CONTENT_URI; break; } - if (DBG) - log("set notify url " + url); - + log("set notify url " + url); + + c.setNotificationUri(getContext().getContentResolver(), url); } + + // c = new MyCrossProcessCursorWrapper(c); + return c; + } catch (Exception ex) { LogCleaner.error(LOG_TAG, "query exc db caught ", ex); + return null; } catch (Error ex) { LogCleaner.error(LOG_TAG, "query error db caught ", ex); + return null; } - - return c; + + } + + static class MyCrossProcessCursorWrapper extends net.sqlcipher.CrossProcessCursorWrapper { + public MyCrossProcessCursorWrapper(net.sqlcipher.Cursor cursor) { + super(cursor); + } + + @Override + public void fillWindow(int position, CursorWindow window) { + if (position < 0 || position > getCount()) { + return; + } + window.acquireReference(); + try { + moveToPosition(position - 1); + window.clear(); + window.setStartPosition(position); + int columnNum = getColumnCount(); + window.setNumColumns(columnNum); + boolean isFull = false; + int numRows = 10; + + while (!isFull && --numRows > 0 && moveToNext() && window.allocRow()) { + for (int i = 0; i < columnNum; i++) { + String field = getString(i); + if (field != null) { + if (!window.putString(field, getPosition(), i)) { + window.freeLastRow(); + isFull = true; + break; + } + } else { + if (!window.putNull(getPosition(), i)) { + window.freeLastRow(); + isFull = true; + break; + } + } + } + } + } catch (IllegalStateException e) { + // simply ignore it + } finally { + window.releaseReference(); + } + } } private void buildQueryContactsByProvider(SQLiteQueryBuilder qb, StringBuilder whereClause, @@ -1665,7 +1892,7 @@ public String getType(Uri url) { // package scope for testing. boolean insertBulkContacts(ContentValues values) { - //if (DBG) log("insertBulkContacts: begin"); + // log("insertBulkContacts: begin"); ArrayList usernames = getStringArrayList(values, Imps.Contacts.USERNAME); ArrayList nicknames = getStringArrayList(values, Imps.Contacts.NICKNAME); @@ -1703,6 +1930,13 @@ boolean insertBulkContacts(ContentValues values) { ContentValues presenceValues = new ContentValues(); presenceValues.put(Imps.Presence.PRESENCE_STATUS, Imps.Presence.OFFLINE); + StringBuffer whereClause = new StringBuffer(); + whereClause.append(Contacts.USERNAME); + whereClause.append("=?"); + whereClause.append(" AND "); + whereClause.append(Contacts.ACCOUNT); + whereClause.append("=?"); + for (int i = 0; i < usernameCount; i++) { String username = usernames.get(i); String nickname = nicknames.get(i); @@ -1731,7 +1965,7 @@ boolean insertBulkContacts(ContentValues values) { } /* - if (DBG) log("insertBulkContacts[" + i + "] username=" + + log("insertBulkContacts[" + i + "] username=" + username + ", nickname=" + nickname + ", type=" + type + ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" + subscriptionType + ", qc=" + quickContact); @@ -1760,7 +1994,7 @@ boolean insertBulkContacts(ContentValues values) { try { rowId = db.insertOrThrow(TABLE_CONTACTS, USERNAME, contactValues); } catch (android.database.sqlite.SQLiteConstraintException ex) { - if (DBG) log("insertBulkContacts: insert " + username + " caught " + ex); + log("insertBulkContacts: insert " + username + " caught " + ex); // append username to the selection clause updateSelection.delete(0, updateSelection.length()); @@ -1776,23 +2010,37 @@ boolean insertBulkContacts(ContentValues values) { } } */ - - rowId = db.insert(TABLE_CONTACTS, USERNAME, contactValues); - if (rowId > 0) { - sum++; - - // seed the presence for the new contact - if (DBG) - log("### seedPresence for contact id " + rowId); - presenceValues.put(Imps.Presence.CONTACT_ID, rowId); - - try { - db.insert(TABLE_PRESENCE, null, presenceValues); - } catch (android.database.sqlite.SQLiteConstraintException ex) { - LogCleaner.warn(LOG_TAG, "insertBulkContacts: seeding presence caught " + ex); + + String[] columns = {"_id, username"}; + String[] whereArgs = {username,account+""}; + + Cursor c = db.query(TABLE_CONTACTS, columns, whereClause.toString(), whereArgs, null,null,null,null); + boolean contactExists = (c != null && c.getCount() > 0); + if (c != null) c.close(); + + if (contactExists) + { + int rowsUpdated = db.update(TABLE_CONTACTS, contactValues, whereClause.toString(), whereArgs); + } + else + { + rowId = db.insert(TABLE_CONTACTS, USERNAME, contactValues); + if (rowId > 0) { + sum++; + + // seed the presence for the new contact + + log("### seedPresence for contact id " + rowId); + presenceValues.put(Imps.Presence.CONTACT_ID, rowId); + + try { + db.insert(TABLE_PRESENCE, null, presenceValues); + } catch (android.database.sqlite.SQLiteConstraintException ex) { + LogCleaner.warn(LOG_TAG, "insertBulkContacts: seeding presence caught " + ex); + } } } - + // yield the lock if anyone else is trying to // perform a db operation here. db.yieldIfContended(); @@ -1804,7 +2052,7 @@ boolean insertBulkContacts(ContentValues values) { } // We know that we succeeded becuase endTransaction throws if the transaction failed. - if (DBG) + log("insertBulkContacts: added " + sum + " contacts!"); return true; } @@ -1885,7 +2133,7 @@ int updateBulkContacts(ContentValues values, String userWhere) { LogCleaner.error(LOG_TAG, "insertBulkContacts: caught ",ex); } - if (DBG) + log("updateBulkContacts[" + i + "] username=" + username + ", nickname=" + nickname + ", type=" + type + ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" + subscriptionType + ", qc=" @@ -1927,7 +2175,7 @@ int updateBulkContacts(ContentValues values, String userWhere) { db.endTransaction(); } - if (DBG) + log("updateBulkContacts: " + sum + " entries updated"); return sum; } @@ -1938,11 +2186,11 @@ int updateBulkContacts(ContentValues values, String userWhere) { * this method does not remove presences for which the corresponding * contacts no longer exist. That's probably ok since presence is kept in * memory, so it won't stay around for too long. Here is the algorithm. - * + * * 1. for all presence that have a corresponding contact, make it OFFLINE. * This is one sqlite call. 2. query for all the contacts that don't have a * presence, and add a presence row for them. - * + * * TODO simplify the presence management! The desire is to have a presence * row for each TODO contact in the database, so later we can just call * update() on the presence rows TODO instead of checking for the existence @@ -1951,7 +2199,7 @@ int updateBulkContacts(ContentValues values, String userWhere) { * complicated. One possible solution is to use insert_or_replace the * presence rows TODO when updating the presence. That way we don't always * need to maintain an empty presence TODO row for each contact. - * + * * @param account the account of the contacts for which we want to create * seed presence rows. */ @@ -1984,19 +2232,19 @@ private void seedInitialPresenceByAccount(long account) { buf.append("=?) "); String selection = buf.toString(); - if (DBG) + log("seedInitialPresence: reset presence selection=" + selection); int count = db.update(TABLE_PRESENCE, presenceValues, selection, mQueryContactIdSelectionArgs1); - if (DBG) + log("seedInitialPresence: reset " + count + " presence rows to OFFLINE"); // for in-memory presence table, add a presence row for each contact that // doesn't have a presence. in-memory presence table isn't reliable, and goes away // when device reboot or IMProvider process dies, so we can't rely on each contact // have a corresponding presence. - if (DBG) { + { log("seedInitialPresence: contacts_with_no_presence_selection => " + CONTACTS_WITH_NO_PRESENCE_SELECTION); } @@ -2004,7 +2252,7 @@ private void seedInitialPresenceByAccount(long account) { c = qb.query(db, CONTACT_ID_PROJECTION, CONTACTS_WITH_NO_PRESENCE_SELECTION, mQueryContactIdSelectionArgs1, null, null, null, null); - if (DBG) + log("seedInitialPresence: found " + c.getCount() + " contacts w/o presence"); count = 0; @@ -2020,13 +2268,13 @@ private void seedInitialPresenceByAccount(long account) { } catch (SQLiteConstraintException ex) { // we could possibly catch this exception, since there could be a presence // row with the same contact_id. That's fine, just ignore the error - if (DBG) + log("seedInitialPresence: insert presence for contact_id " + id + " failed, caught " + ex); } } - if (DBG) + log("seedInitialPresence: added " + count + " new presence rows"); db.setTransactionSuccessful(); @@ -2038,7 +2286,7 @@ private void seedInitialPresenceByAccount(long account) { } } - private int updateBulkPresence(ContentValues values, String userWhere, String[] whereArgs) { + private int updateBulkPresence(ContentValues values, SQLiteDatabase db, String userWhere, String[] whereArgs) { ArrayList usernames = getStringArrayList(values, Imps.Contacts.USERNAME); int count = usernames.size(); Long account = values.getAsLong(Imps.Contacts.ACCOUNT); @@ -2050,6 +2298,7 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] ArrayList clientTypeArray = getStringArrayList(values, Imps.Presence.CLIENT_TYPE); ArrayList resourceArray = getStringArrayList(values, Imps.Presence.JID_RESOURCE); + // append username to the selection clause StringBuilder buf = new StringBuilder(); @@ -2069,7 +2318,10 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] // use username LIKE ? for case insensitive comparison buf.append(Imps.Contacts.USERNAME); - buf.append(" LIKE ?) AND ("); + buf.append(" LIKE ?)"); + + /* + AND ("); buf.append(Imps.Presence.PRIORITY); buf.append("<=? OR "); @@ -2077,13 +2329,14 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] buf.append(" IS NULL OR "); buf.append(Imps.Presence.JID_RESOURCE); buf.append("=?)"); + */ String selection = buf.toString(); - if (DBG) + log("updateBulkPresence: selection => " + selection); - int numArgs = (whereArgs != null ? whereArgs.length + 4 : 4); + int numArgs = (whereArgs != null ? whereArgs.length + 2 : 2); String[] selectionArgs = new String[numArgs]; int selArgsIndex = 0; @@ -2093,8 +2346,6 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] } } - final SQLiteDatabase db = getDBHelper().getWritableDatabase(); - db.beginTransaction(); int sum = 0; @@ -2122,44 +2373,37 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] } catch (NumberFormatException ex) { LogCleaner.error(LOG_TAG, "[ImProvider] updateBulkPresence: caught",ex); } + + log("updateBulkPresence[" + i + "] account=" + account + " username=" + username + ", priority=" + + priority + ", mode=" + mode + ", status=" + status + ", resource=" + + jidResource + ", clientType=" + clientType); - if (DBG) { - log("updateBulkPresence[" + i + "] username=" + username + ", priority=" - + priority + ", mode=" + mode + ", status=" + status + ", resource=" - + jidResource + ", clientType=" + clientType); - } - - if (modeArray != null) { - presenceValues.put(Imps.Presence.PRESENCE_STATUS, mode); - } - if (priorityArray != null) { - presenceValues.put(Imps.Presence.PRIORITY, priority); - } + presenceValues.put(Imps.Presence.PRESENCE_STATUS, mode); + presenceValues.put(Imps.Presence.PRIORITY, priority); presenceValues.put(Imps.Presence.PRESENCE_CUSTOM_STATUS, status); - if (clientTypeArray != null) { - presenceValues.put(Imps.Presence.CLIENT_TYPE, clientType); - } - - if (!TextUtils.isEmpty(jidResource)) { - presenceValues.put(Imps.Presence.JID_RESOURCE, jidResource); - } + presenceValues.put(Imps.Presence.CLIENT_TYPE, clientType); + presenceValues.put(Imps.Presence.JID_RESOURCE, jidResource); // fill in the selection args int idx = selArgsIndex; selectionArgs[idx++] = String.valueOf(account); selectionArgs[idx++] = username; - selectionArgs[idx++] = String.valueOf(priority); - selectionArgs[idx] = jidResource; - + + //selectionArgs[idx++] = String.valueOf(priority); + //selectionArgs[idx] = jidResource; + int numUpdated = db .update(TABLE_PRESENCE, presenceValues, selection, selectionArgs); - if (numUpdated == 0) { - // this is really generating a lot of log output that doesn't seem necessary - // LogCleaner.warn(LOG_TAG, "[ImProvider] updateBulkPresence: failed for " + username); - } else { + + if (numUpdated == 0) + { + LogCleaner.debug(LOG_TAG, "[ImProvider] updateBulkPresence: " + username + " updated " + numUpdated); + } + else + { sum += numUpdated; } - + // yield the lock if anyone else is trying to // perform a db operation here. db.yieldIfContended(); @@ -2170,8 +2414,7 @@ private int updateBulkPresence(ContentValues values, String userWhere, String[] db.endTransaction(); } - if (DBG) - log("updateBulkPresence: " + sum + " entries updated"); + log("updateBulkPresence: " + sum + " entries updated"); return sum; } @@ -2192,7 +2435,7 @@ private Uri insertInternal(Uri url, ContentValues initialValues) { final SQLiteDatabase db = getDBHelper().getWritableDatabase(); int match = mUrlMatcher.match(url); - if (DBG) + log("insert to " + url + ", match " + match); switch (match) { case MATCH_PROVIDERS: @@ -2219,7 +2462,7 @@ private Uri insertInternal(Uri url, ContentValues initialValues) { case MATCH_CONTACTS: case MATCH_CONTACTS_BAREBONE: // Insert into the contacts table - rowID = db.insert(TABLE_CONTACTS, "username", initialValues); + rowID = db.insert(TABLE_CONTACTS, USERNAME, initialValues); if (rowID > 0) { resultUri = Uri.parse(Imps.Contacts.CONTENT_URI + "/" + rowID); } @@ -2501,7 +2744,7 @@ private Uri insertInternal(Uri url, ContentValues initialValues) { } if (notifyProviderAccountContentUri) { - if (DBG) + log("notify insert for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); } @@ -2708,10 +2951,10 @@ private void performContactRemovalCleanup(long contactId) { private void deleteWithSelection(SQLiteDatabase db, String tableName, String selection, String[] selectionArgs) { - if (DBG) + log("deleteWithSelection: table " + tableName + ", selection => " + selection); int count = db.delete(tableName, selection, selectionArgs); - if (DBG) + log("deleteWithSelection: deleted " + count + " rows"); } @@ -2956,7 +3199,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { buildContactIdSelection(Imps.Messages.THREAD_ID, Imps.Contacts.PROVIDER + "='" + provider + "'")); - if (DBG) + log("delete (MATCH_OTR_MESSAGES_BY_PROVIDER) sel => " + whereClause); notifyMessagesContentUri = true; break; @@ -2970,7 +3213,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { buildContactIdSelection(Imps.Messages.THREAD_ID, Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); - if (DBG) + log("delete (MATCH_OTR_MESSAGES_BY_ACCOUNT) sel => " + whereClause); notifyMessagesContentUri = true; break; @@ -3029,7 +3272,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { buildContactIdSelection(Imps.Chats.CONTACT_ID, Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); - if (DBG) + log("delete (MATCH_CHATS_BY_ACCOUNT) sel => " + whereClause); changedItemId = null; @@ -3062,7 +3305,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { buildContactIdSelection(Imps.Presence.CONTACT_ID, Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); - if (DBG) + log("delete (MATCH_PRESENCE_BY_ACCOUNT): sel => " + whereClause); changedItemId = null; break; @@ -3122,7 +3365,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { appendWhere(whereClause, idColumnName, "=", changedItemId); } - if (DBG) + log("delete from " + url + " WHERE " + whereClause); int count = db.delete(tableToChange, whereClause.toString(), whereArgs); @@ -3167,7 +3410,7 @@ private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { } if (notifyProviderAccountContentUri) { - if (DBG) + log("notify delete for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); } @@ -3348,13 +3591,23 @@ private int updateInternal(Uri url, ContentValues values, String userWhere, Stri case MATCH_OTR_MESSAGES_BY_PACKET_ID: packetId = decodeURLSegment(url.getPathSegments().get(1)); - tableToChange = TABLE_MESSAGES; // FIXME these should be going to memory but they do not + tableToChange = TABLE_IN_MEMORY_MESSAGES; // FIXME these should be going to memory but they do not appendWhere(whereClause, Imps.Messages.PACKET_ID, "=", packetId); notifyMessagesContentUri = true; // Try updating OTR message count += db.update(TABLE_IN_MEMORY_MESSAGES, values, whereClause.toString(), whereArgs); break; + + case MATCH_MESSAGES_BY_PACKET_ID: + packetId = decodeURLSegment(url.getPathSegments().get(1)); + tableToChange = TABLE_MESSAGES; // FIXME these should be going to memory but they do not + appendWhere(whereClause, Imps.Messages.PACKET_ID, "=", packetId); + notifyMessagesContentUri = true; + + // Try updating OTR message + count += db.update(TABLE_MESSAGES, values, whereClause.toString(), whereArgs); + break; case MATCH_OTR_MESSAGE: tableToChange = TABLE_IN_MEMORY_MESSAGES; @@ -3390,7 +3643,7 @@ private int updateInternal(Uri url, ContentValues values, String userWhere, Stri break; case MATCH_PRESENCE: - //if (DBG) log("update presence: where='" + userWhere + "'"); + log("update presence: where='" + userWhere + "'"); tableToChange = TABLE_PRESENCE; break; @@ -3401,15 +3654,21 @@ private int updateInternal(Uri url, ContentValues values, String userWhere, Stri break; case MATCH_PRESENCE_BULK: - count = updateBulkPresence(values, userWhere, whereArgs); + + tableToChange = null; + + count = updateBulkPresence(values, db, userWhere, whereArgs); // notify change using the "content://im/contacts" url, // so the change will be observed by listeners interested // in contacts changes. - if (count > 0) { + + if (count > 0) + { + getContext().getContentResolver().notifyChange(Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY, null); getContext().getContentResolver().notifyChange(Imps.Contacts.CONTENT_URI, null); + notifyContactListContentUri = true; } - - return count; + break; case MATCH_INVITATION: tableToChange = TABLE_INVITATIONS; @@ -3459,23 +3718,23 @@ private int updateInternal(Uri url, ContentValues values, String userWhere, Stri appendWhere(whereClause, idColumnName, "=", changedItemId); } - if (DBG) - log("update " + url + " WHERE " + whereClause); + log("update " + url + " WHERE " + whereClause); - count += db.update(tableToChange, values, whereClause.toString(), whereArgs); + if (tableToChange != null) + count += db.update(tableToChange, values, whereClause.toString(), whereArgs); if (count > 0) { ContentResolver resolver = getContext().getContentResolver(); // In most case, we query contacts with presence and chats joined, thus // we should also notify that contacts changes when presence or chats changed. - if (match == MATCH_CHATS || match == MATCH_CHATS_ID || match == MATCH_PRESENCE + if (match == MATCH_CHATS || match == MATCH_CHATS_ID || match == MATCH_PRESENCE || match == MATCH_PRESENCE_BULK || match == MATCH_PRESENCE_ID || match == MATCH_CONTACTS_BAREBONE) { resolver.notifyChange(Imps.Contacts.CONTENT_URI, null); } if (notifyMessagesContentUri) { - if (DBG) + log("notify change for " + Imps.Messages.CONTENT_URI); resolver.notifyChange(Imps.Messages.CONTENT_URI, null); } @@ -3495,7 +3754,7 @@ private int updateInternal(Uri url, ContentValues values, String userWhere, Stri } if (notifyProviderAccountContentUri) { - if (DBG) + log("notify change for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); } @@ -3537,37 +3796,7 @@ private static String decodeURLSegment(String segment) { } static void log(String message) { - LogCleaner.debug(LOG_TAG, message); - } - - @Override - public void onCacheWordUninitialized() { - // TODO Auto-generated method stub - + LogCleaner.debug(LOG_TAG, message); } - @Override - public void onCacheWordLocked() { - // TODO Auto-generated method stub - - } - - @Override - public void onCacheWordOpened() { - - String pkey = SQLCipherOpenHelper.encodeRawKey(mCacheWord.getEncryptionKey()); - - if (pkey != null) - { - - try { - this.initDBHelper(pkey); - } catch (Exception e) { - Log.e(ImApp.LOG_TAG,"unable to init cacheword in IMPSprovider",e); - } - - - } - - } } diff --git a/src/info/guardianproject/otr/app/im/provider/SQLCipherOpenHelper.java b/src/info/guardianproject/otr/app/im/provider/SQLCipherOpenHelper.java new file mode 100644 index 000000000..fa7b3b619 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/provider/SQLCipherOpenHelper.java @@ -0,0 +1,163 @@ + +package info.guardianproject.otr.app.im.provider; + +import info.guardianproject.cacheword.CacheWordHandler; + +import java.lang.reflect.Method; +import java.nio.CharBuffer; + +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; +import net.sqlcipher.database.SQLiteException; +import net.sqlcipher.database.SQLiteOpenHelper; +import android.content.Context; +import android.util.Log; + +/** + * A helper class to manage database creation and version management. You create + * a subclass implementing {@link #onCreate}, {@link #onUpgrade} and optionally + * {@link #onOpen}, and this class takes care of opening the database if it + * exists, creating it if it does not, and upgrading it as necessary. + * Transactions are used to make sure the database is always in a sensible + * state. + *

    + * For an example, see the NotePadProvider class in the NotePad sample + * application, in the samples/ directory of the SDK. + *

    + */ +public abstract class SQLCipherOpenHelper extends SQLiteOpenHelper { + + private static final String TAG = "SQLCipherOpenHelper"; + + protected Context mContext; // shame we have to duplicate this here + private CacheWordHandler mHandler; + + public SQLCipherOpenHelper(CacheWordHandler cacheWord, Context context, String name, + CursorFactory factory, int version) { + super(context, name, factory, version, new SQLCipherV3MigrationHook(context)); + if (cacheWord == null) + throw new IllegalArgumentException("CacheWordHandler is null"); + mHandler = cacheWord; + } + + /** + * Create and/or open a database that will be used for reading and writing. + * Once opened successfully, the database is cached, so you can call this + * method every time you need to write to the database. Make sure to call + * {@link #close} when you no longer need it. + *

    + * Errors such as bad permissions or a full disk may cause this operation to + * fail, but future attempts may succeed if the problem is fixed. + *

    + * + * @throws SQLiteException if the database cannot be opened for writing + * @return a read/write database object valid until {@link #close} is called + */ + public synchronized SQLiteDatabase getWritableDatabase() { + if (mHandler.isLocked()) + throw new SQLiteException("Database locked. Decryption key unavailable."); + + return super.getWritableDatabase(encodeRawKey(mHandler.getEncryptionKey())); + } + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + * @throws SQLiteException if the database cannot be opened + * @return a database object valid until {@link #getWritableDatabase} or + * {@link #close} is called. + */ + public synchronized SQLiteDatabase getReadableDatabase() { + if (mHandler.isLocked()) + throw new SQLiteException("Database locked. Decryption key unavailable."); + + return super.getReadableDatabase(encodeRawKey(mHandler.getEncryptionKey())); + } + + /** + * Formats a byte sequence into the literal string format expected by + * SQLCipher: hex'HEX ENCODED BYTES' The key data must be 256 bits (32 + * bytes) wide. The key data will be formatted into a 64 character hex + * string with a special prefix and suffix SQLCipher uses to distinguish raw + * key data from a password. + * + * @link http://sqlcipher.net/sqlcipher-api/#key + * @param raw_key a 32 byte array + * @return the encoded key + */ + public static char[] encodeRawKey(byte[] raw_key) { + if (raw_key.length != 32) + throw new IllegalArgumentException("provided key not 32 bytes (256 bits) wide"); + + final String kPrefix; + final String kSuffix; + + if (sqlcipher_uses_native_key) { + Log.d(TAG, "sqlcipher uses native method to set key"); + kPrefix = "x'"; + kSuffix = "'"; + } else { + Log.d(TAG, "sqlcipher uses PRAGMA to set key - SPECIAL HACK IN PROGRESS"); + kPrefix = "x''"; + kSuffix = "''"; + } + final char[] key_chars = encodeHex(raw_key, HEX_DIGITS_LOWER); + if (key_chars.length != 64) + throw new IllegalStateException("encoded key is not 64 bytes wide"); + + char[] kPrefix_c = kPrefix.toCharArray(); + char[] kSuffix_c = kSuffix.toCharArray(); + CharBuffer cb = CharBuffer.allocate(kPrefix_c.length + kSuffix_c.length + key_chars.length); + cb.put(kPrefix_c); + cb.put(key_chars); + cb.put(kSuffix_c); + + return cb.array(); + } + + /** + * @see #encodeRawKey(byte[]) + */ + public static String encodeRawKeyToStr(byte[] raw_key) { + return new String(encodeRawKey(raw_key)); + } + + /* + * Special hack for detecting whether or not we're using a new SQLCipher for + * Android library The old version uses the PRAGMA to set the key, which + * requires escaping of the single quote characters. The new version calls a + * native method to set the key instead. + * @see https://github.com/sqlcipher/android-database-sqlcipher/pull/95 + */ + private static final boolean sqlcipher_uses_native_key = check_sqlcipher_uses_native_key(); + + private static boolean check_sqlcipher_uses_native_key() { + + for (Method method : SQLiteDatabase.class.getDeclaredMethods()) { + if (method.getName().equals("native_key")) + return true; + } + return false; + } + + private static final char[] HEX_DIGITS_LOWER = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + private static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } +} diff --git a/src/info/guardianproject/otr/app/im/provider/SQLCipherV3MigrationHook.java b/src/info/guardianproject/otr/app/im/provider/SQLCipherV3MigrationHook.java new file mode 100644 index 000000000..0c47699a1 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/provider/SQLCipherV3MigrationHook.java @@ -0,0 +1,58 @@ + +package info.guardianproject.otr.app.im.provider; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +import info.guardianproject.cacheword.Constants; + +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteDatabaseHook; + +/** + * This hook handles the v2 -> v3 migration for SQLCipher databases + */ +public class SQLCipherV3MigrationHook implements SQLiteDatabaseHook { + private Context mContext; + + public SQLCipherV3MigrationHook(Context context) { + mContext = context; + } + + @Override + public void preKey(SQLiteDatabase database) { + // nop for now + } + + @Override + public void postKey(SQLiteDatabase database) { + /* V2 - V3 migration */ + if (!isMigratedV3(mContext, database)) { + database.rawExecSQL("PRAGMA cipher_migrate;"); + setMigratedV3(mContext, database, true); + } + + } + + public static void setMigratedV3(Context context, SQLiteDatabase database, boolean migrated) { + SharedPreferences prefs = context.getSharedPreferences( + Constants.SHARED_PREFS_SQLCIPHER_V3_MIGRATE, Context.MODE_PRIVATE); + Editor editor = prefs.edit().putBoolean(database.getPath(), migrated); + int i = 0; + boolean commited = false; + /* make sure it is committed, but only try 10 times */ + while (!commited) { + commited = editor.commit(); + i++; + if (i > 10) + break; + } + } + + public static boolean isMigratedV3(Context context, SQLiteDatabase database) { + SharedPreferences prefs = context.getSharedPreferences( + Constants.SHARED_PREFS_SQLCIPHER_V3_MIGRATE, Context.MODE_PRIVATE); + return prefs.getBoolean(database.getPath(), false); + } +} diff --git a/src/info/guardianproject/otr/app/im/service/AndroidHeartBeatService.java b/src/info/guardianproject/otr/app/im/service/AndroidHeartBeatService.java deleted file mode 100644 index ef49d1307..000000000 --- a/src/info/guardianproject/otr/app/im/service/AndroidHeartBeatService.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package info.guardianproject.otr.app.im.service; - -import info.guardianproject.otr.app.im.engine.HeartbeatService; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.PowerManager; -import android.os.SystemClock; -import android.util.SparseArray; - -public class AndroidHeartBeatService extends BroadcastReceiver implements HeartbeatService { - - private static final String WAKELOCK_TAG = "IM_HEARTBEAT"; - - private static final String HEARTBEAT_INTENT_ACTION = "info.guardianproject.otr.app.im.intent.action.HEARTBEAT"; - private static final Uri HEARTBEAT_CONTENT_URI = Uri.parse("content://im/heartbeat"); - private static final String HEARTBEAT_CONTENT_TYPE = "vnd.android.im/heartbeat"; - - private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); - - private final Context mContext; - private final AlarmManager mAlarmManager; - /*package*/PowerManager.WakeLock mWakeLock; - - static class Alarm { - public PendingIntent mAlaramSender; - public Callback mCallback; - } - - private final SparseArray mAlarms; - - public AndroidHeartBeatService(Context context) { - mContext = context; - mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); - mAlarms = new SparseArray(); - } - - public synchronized void startHeartbeat(Callback callback, long triggerTime) { - Alarm alarm = findAlarm(callback); - if (alarm == null) { - alarm = new Alarm(); - int id = nextId(); - alarm.mCallback = callback; - Uri data = ContentUris.withAppendedId(HEARTBEAT_CONTENT_URI, id); - Intent i = new Intent(HEARTBEAT_INTENT_ACTION).setDataAndType(data, - HEARTBEAT_CONTENT_TYPE); - alarm.mAlaramSender = PendingIntent.getBroadcast(mContext, 0, i, 0); - if (mAlarms.size() == 0) { - - try { - mContext.registerReceiver(this, - IntentFilter.create(HEARTBEAT_INTENT_ACTION, HEARTBEAT_CONTENT_TYPE)); - } catch (Exception e) { - mContext.unregisterReceiver(this); - - mContext.registerReceiver(this, - IntentFilter.create(HEARTBEAT_INTENT_ACTION, HEARTBEAT_CONTENT_TYPE)); - } - } - mAlarms.append(id, alarm); - } - setAlarm(alarm, triggerTime); - } - - public synchronized void stopHeartbeat(Callback callback) { - Alarm alarm = findAlarm(callback); - if (alarm != null) { - cancelAlarm(alarm); - } - } - - public synchronized void stopAll() { - for (int i = 0; i < mAlarms.size(); i++) { - Alarm alarm = mAlarms.valueAt(i); - cancelAlarm(alarm); - } - } - - @Override - public void onReceive(Context context, Intent intent) { - int id = (int) ContentUris.parseId(intent.getData()); - Alarm alarm = mAlarms.get(id); - if (alarm == null) { - return; - } - sExecutor.execute(new Worker(alarm)); - } - - private class Worker implements Runnable { - private final Alarm mAlarm; - - public Worker(Alarm alarm) { - mAlarm = alarm; - } - - public void run() { - mWakeLock.acquire(); - try { - Callback callback = mAlarm.mCallback; - long nextSchedule = callback.sendHeartbeat(); - if (nextSchedule <= 0) { - cancelAlarm(mAlarm); - } else { - setAlarm(mAlarm, nextSchedule); - } - } finally { - mWakeLock.release(); - } - } - } - - private Alarm findAlarm(Callback callback) { - for (int i = 0; i < mAlarms.size(); i++) { - Alarm alarm = mAlarms.valueAt(i); - if (alarm.mCallback == callback) { - return alarm; - } - } - return null; - } - - /*package*/synchronized void setAlarm(Alarm alarm, long offset) { - long triggerAtTime = SystemClock.elapsedRealtime() + offset; - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, alarm.mAlaramSender); - } - - /*package*/synchronized void cancelAlarm(Alarm alarm) { - mAlarmManager.cancel(alarm.mAlaramSender); - int index = mAlarms.indexOfValue(alarm); - if (index >= 0) { - mAlarms.delete(mAlarms.keyAt(index)); - } - - // Unregister the BroadcastReceiver if there isn't a alarm anymore. - if (mAlarms.size() == 0) { - mContext.unregisterReceiver(this); - } - } - - private static int sNextId = 0; - - private static synchronized int nextId() { - return sNextId++; - } -} diff --git a/src/info/guardianproject/otr/app/im/service/AndroidSystemService.java b/src/info/guardianproject/otr/app/im/service/AndroidSystemService.java deleted file mode 100644 index c7a6a1533..000000000 --- a/src/info/guardianproject/otr/app/im/service/AndroidSystemService.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source - * Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package info.guardianproject.otr.app.im.service; - -import info.guardianproject.otr.app.im.engine.HeartbeatService; -import info.guardianproject.otr.app.im.engine.SystemService; -import android.content.Context; - -public class AndroidSystemService extends SystemService { - private static AndroidSystemService sInstance; - - private AndroidSystemService() { - } - - public static AndroidSystemService getInstance() { - if (sInstance == null) { - sInstance = new AndroidSystemService(); - } - return sInstance; - } - - private Context mContext; - private AndroidHeartBeatService mHeartbeatServcie; - - public void initialize(Context context) { - mContext = context; - } - - public Context getContext() { - return mContext; - } - - public void shutdown() { - if (mHeartbeatServcie != null) { - mHeartbeatServcie.stopAll(); - } - - } - - @Override - public HeartbeatService getHeartbeatService() { - if (mContext == null) { - throw new IllegalStateException("Hasn't been initialized yet"); - } - if (mHeartbeatServcie == null) { - mHeartbeatServcie = new AndroidHeartBeatService(mContext); - } - return mHeartbeatServcie; - } - -} diff --git a/src/info/guardianproject/otr/app/im/service/ChatSessionAdapter.java b/src/info/guardianproject/otr/app/im/service/ChatSessionAdapter.java index 0fa5d5a0b..473c3ada5 100644 --- a/src/info/guardianproject/otr/app/im/service/ChatSessionAdapter.java +++ b/src/info/guardianproject/otr/app/im/service/ChatSessionAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,33 +18,39 @@ package info.guardianproject.otr.app.im.service; import info.guardianproject.otr.IOtrChatSession; -import info.guardianproject.otr.IOtrKeyManager; import info.guardianproject.otr.OtrChatListener; import info.guardianproject.otr.OtrChatManager; import info.guardianproject.otr.OtrChatSessionAdapter; -import info.guardianproject.otr.OtrKeyManagerAdapter; +import info.guardianproject.otr.OtrDataHandler; +import info.guardianproject.otr.OtrDataHandler.Transfer; +import info.guardianproject.otr.OtrDebugLogger; import info.guardianproject.otr.app.im.IChatListener; +import info.guardianproject.otr.app.im.IDataListener; +import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.otr.app.im.engine.ChatGroup; import info.guardianproject.otr.app.im.engine.ChatGroupManager; import info.guardianproject.otr.app.im.engine.ChatSession; import info.guardianproject.otr.app.im.engine.Contact; +import info.guardianproject.otr.app.im.engine.ContactListManager; import info.guardianproject.otr.app.im.engine.GroupListener; import info.guardianproject.otr.app.im.engine.GroupMemberListener; import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.engine.ImEntity; import info.guardianproject.otr.app.im.engine.ImErrorInfo; -import info.guardianproject.otr.app.im.engine.Message; import info.guardianproject.otr.app.im.engine.MessageListener; import info.guardianproject.otr.app.im.engine.Presence; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.SystemServices; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import net.java.otr4j.session.SessionStatus; + import org.jivesoftware.smack.packet.Packet; -import net.java.otr4j.session.SessionID; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -53,6 +59,9 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.BaseColumns; +import android.util.Log; + +import com.google.common.collect.Maps; public class ChatSessionAdapter extends info.guardianproject.otr.app.im.IChatSession.Stub { @@ -67,12 +76,8 @@ public class ChatSessionAdapter extends info.guardianproject.otr.app.im.IChatSes ImConnectionAdapter mConnection; ChatSessionManagerAdapter mChatSessionManager; - //all the otr bits that work per session - OtrChatManager mOtrChatManager; - OtrKeyManagerAdapter mOtrKeyManager; - OtrChatSessionAdapter mOtrChatSession; - ChatSession mAdaptee; + ChatSession mChatSession; ListenerAdapter mListenerAdapter; boolean mIsGroupChat; StatusBarNotifier mStatusBarNotifier; @@ -81,7 +86,6 @@ public class ChatSessionAdapter extends info.guardianproject.otr.app.im.IChatSes /*package*/Uri mChatURI; private Uri mMessageURI; - private Uri mOtrMessageURI; private boolean mConvertingToGroupChat; @@ -93,8 +97,23 @@ public class ChatSessionAdapter extends info.guardianproject.otr.app.im.IChatSes private RemoteImService service = null; - public ChatSessionAdapter(ChatSession adaptee, ImConnectionAdapter connection) { - mAdaptee = adaptee; + OtrChatSessionAdapter mOtrChatSession; + private OtrDataHandler mDataHandler; + + private IDataListener mDataListener; + private DataHandlerListenerImpl mDataHandlerListener; + + private boolean mAcceptTransfer = false; + private boolean mWaitingForResponse = false; + private boolean mAcceptAllTransfer = false; + private String mLastFileUrl = null; + + + private long mContactId; + + public ChatSessionAdapter(ChatSession chatSession, ImConnectionAdapter connection, boolean isNewSession) { + + mChatSession = chatSession; mConnection = connection; service = connection.getContext(); @@ -102,76 +121,114 @@ public ChatSessionAdapter(ChatSession adaptee, ImConnectionAdapter connection) { mStatusBarNotifier = service.getStatusBarNotifier(); mChatSessionManager = (ChatSessionManagerAdapter) connection.getChatSessionManager(); - String localUserId = mConnection.getLoginUser().getAddress().getAddress(); - String remoteUserId = mAdaptee.getParticipant().getAddress().getAddress(); - - mOtrChatManager = service.getOtrChatManager(); - mOtrChatSession = new OtrChatSessionAdapter(localUserId, remoteUserId, mOtrChatManager); - - mOtrKeyManager = new OtrKeyManagerAdapter(mOtrChatManager, localUserId, remoteUserId); - mListenerAdapter = new ListenerAdapter(); - // add OtrChatListener as the intermediary to mListenerAdapter so it can filter OTR msgs - mAdaptee.addMessageListener(new OtrChatListener(mOtrChatManager, mListenerAdapter)); - mAdaptee.setOtrChatManager(mOtrChatManager); + initOtrChatSession();//setup first time - ImEntity participant = mAdaptee.getParticipant(); + ImEntity participant = mChatSession.getParticipant(); if (participant instanceof ChatGroup) { - init((ChatGroup) participant); + init((ChatGroup) participant,isNewSession); } else { - init((Contact) participant); + init((Contact) participant,isNewSession); } + mDataHandler.setChatId(getId()); } - public IOtrKeyManager getOtrKeyManager() { + private void initOtrChatSession () + { + try + { + if (mConnection != null) + { + mDataHandler = new OtrDataHandler(mChatSession); + mDataHandlerListener = new DataHandlerListenerImpl(); + mDataHandler.setDataListener(mDataHandlerListener); + + String localUser = mConnection.getLoginUser().getAddress().getAddress(); + String remoteUser = mChatSession.getParticipant().getAddress().getAddress(); + + OtrChatManager cm = service.getOtrChatManager(); - return mOtrKeyManager; + mOtrChatSession = new OtrChatSessionAdapter(localUser, remoteUser, cm); + + // add OtrChatListener as the intermediary to mListenerAdapter so it can filter OTR msgs + mChatSession.setMessageListener(new OtrChatListener(cm, mListenerAdapter)); + // mChatSession.setOtrChatManager(cm); + } + } + catch (NullPointerException npe) + { + Log.e(ImApp.LOG_TAG,"error init OTR session",npe); + } } - public IOtrChatSession getOtrChatSession() { + public synchronized IOtrChatSession getOtrChatSession() { + + if (mOtrChatSession == null) + initOtrChatSession(); return mOtrChatSession; } - private void init(ChatGroup group) { + private void init(ChatGroup group, boolean isNewSession) { + mIsGroupChat = true; - long groupId = insertGroupContactInDb(group); + + mContactId = insertOrUpdateGroupContactInDb(group); group.addMemberListener(mListenerAdapter); - mMessageURI = Imps.Messages.getContentUriByThreadId(groupId); - mOtrMessageURI = Imps.Messages.getOtrMessagesContentUriByThreadId(groupId); + try { + mChatSessionManager.getChatGroupManager().joinChatGroupAsync(group.getAddress()); - mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, groupId); - insertOrUpdateChat(null); - - for (Contact c : group.getMembers()) { - mContactStatusMap.put(c.getName(), c.getPresence().getStatus()); + mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId); + + mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId); + + if (isNewSession) + insertOrUpdateChat(null); + + for (Contact c : group.getMembers()) { + mContactStatusMap.put(c.getName(), c.getPresence().getStatus()); + } + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); } + + } - private void init(Contact contact) { + private void init(Contact contact, boolean isNewSession) { mIsGroupChat = false; ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection .getContactListManager(); - long contactId = listManager.queryOrInsertContact(contact); + + mContactId = listManager.queryOrInsertContact(contact); - mMessageURI = Imps.Messages.getContentUriByThreadId(contactId); - mOtrMessageURI = Imps.Messages.getOtrMessagesContentUriByThreadId(contactId); + mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId); - mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, contactId); - insertOrUpdateChat(null); + mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId); + + if (isNewSession) + insertOrUpdateChat(null); mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus()); } + public void reInit () + { + // insertOrUpdateChat(null); + + } + private ChatGroupManager getGroupManager() { return mConnection.getAdaptee().getChatGroupManager(); } public ChatSession getAdaptee() { - return mAdaptee; + return mChatSession; } public Uri getChatUri() { @@ -181,7 +238,7 @@ public Uri getChatUri() { public String[] getParticipants() { if (mIsGroupChat) { Contact self = mConnection.getLoginUser(); - ChatGroup group = (ChatGroup) mAdaptee.getParticipant(); + ChatGroup group = (ChatGroup) mChatSession.getParticipant(); List members = group.getMembers(); String[] result = new String[members.size() - 1]; int index = 0; @@ -192,7 +249,8 @@ public String[] getParticipants() { } return result; } else { - return new String[] { mAdaptee.getParticipant().getAddress().getAddress() }; + + return new String[] { mChatSession.getParticipant().getAddress().getAddress() }; } } @@ -200,17 +258,17 @@ public String[] getParticipants() { * Convert this chat session to a group chat. If it's already a group chat, * nothing will happen. The method works in async mode and the registered * listener will be notified when it's converted to group chat successfully. - * + * * Note that the method is not thread-safe since it's always called from the * UI and Android uses single thread mode for UI. */ - public void convertToGroupChat() { + public void convertToGroupChat(String nickname) { if (mIsGroupChat || mConvertingToGroupChat) { return; } mConvertingToGroupChat = true; - new ChatConvertor().convertToGroupChat(); + new ChatConvertor().convertToGroupChat(nickname); } public boolean isGroupChatSession() { @@ -218,11 +276,11 @@ public boolean isGroupChatSession() { } public String getName() { - return mAdaptee.getParticipant().getAddress().getScreenName(); + return mChatSession.getParticipant().getAddress().getUser(); } public String getAddress() { - return mAdaptee.getParticipant().getAddress().getAddress(); + return mChatSession.getParticipant().getAddress().getAddress(); } public long getId() { @@ -239,25 +297,27 @@ public void inviteContact(String contact) { if (invitee == null) { ImErrorInfo error = new ImErrorInfo(ImErrorInfo.ILLEGAL_CONTACT_ADDRESS, "Cannot find contact with address: " + contact); - mListenerAdapter.onError((ChatGroup) mAdaptee.getParticipant(), error); + mListenerAdapter.onError((ChatGroup) mChatSession.getParticipant(), error); } else { - getGroupManager().inviteUserAsync((ChatGroup) mAdaptee.getParticipant(), invitee); + getGroupManager().inviteUserAsync((ChatGroup) mChatSession.getParticipant(), invitee); } } public void leave() { if (mIsGroupChat) { - getGroupManager().leaveChatGroupAsync((ChatGroup) mAdaptee.getParticipant()); + getGroupManager().leaveChatGroupAsync((ChatGroup) mChatSession.getParticipant()); } mContentResolver.delete(mMessageURI, null, null); mContentResolver.delete(mChatURI, null, null); mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress()); mChatSessionManager.closeChatSession(this); + + } public void leaveIfInactive() { - if (mAdaptee.getHistoryMessages().isEmpty()) { + if (mChatSession.getHistoryMessages().isEmpty()) { leave(); } } @@ -265,34 +325,59 @@ public void leaveIfInactive() { public void sendMessage(String text) { if (mConnection.getState() == ImConnection.SUSPENDED) { // connection has been suspended, save the message without send it - insertMessageInDb(null, text, -1, Imps.MessageType.POSTPONED); + long now = System.currentTimeMillis(); + insertMessageInDb(null, text, now, Imps.MessageType.POSTPONED); return; } - Message msg = new Message(text); - // TODO OTRCHAT move setFrom() to ChatSession.sendMessageAsync() + info.guardianproject.otr.app.im.engine.Message msg = new info.guardianproject.otr.app.im.engine.Message(text); + msg.setFrom(mConnection.getLoginUser().getAddress()); - msg.setType(Imps.MessageType.OUTGOING); - - mAdaptee.sendMessageAsync(msg); - + + int newType = mChatSession.sendMessageAsync(msg); + long now = System.currentTimeMillis(); - insertMessageInDb(null, text, now, msg.getType(), 0, msg.getID()); + + insertMessageInDb(null, text, now, newType, 0, msg.getID()); + } + + public boolean offerData(String offerId, String url, String type) { + if (mConnection.getState() == ImConnection.SUSPENDED) { + // TODO send later + return false; + } + + HashMap headers = null; + if (type != null) { + headers = Maps.newHashMap(); + headers.put("Mime-Type", type); + } + + try + { + mDataHandler.offerData(offerId, mConnection.getLoginUser().getAddress(), url, headers); + return true; + } + catch (IOException ioe) + { + Log.w(ImApp.LOG_TAG,"unable to offer data",ioe); + return false; + } } /** * Sends a message to other participant(s) in this session without adding it * to the history. - * + * * @param msg the message to send. */ /* public void sendMessageWithoutHistory(String text) { - + Message msg = new Message(text); // TODO OTRCHAT use a lower level method - mAdaptee.sendMessageAsync(msg); + mChatSession.sendMessageAsync(msg); }*/ void sendPostponedMessages() { @@ -308,27 +393,28 @@ void sendPostponedMessages() { return; } + ArrayList messages = new ArrayList(); + while (c.moveToNext()) { String body = c.getString(1); - String id = c.getString(2); - Message msg = new Message(body); - // TODO OTRCHAT move setFrom() to ChatSession.sendMessageAsync() - msg.setFrom(mConnection.getLoginUser().getAddress()); - msg.setID(id); - msg.setType(Imps.MessageType.OUTGOING); - - mAdaptee.sendMessageAsync(msg); - - updateMessageInDb(id, msg.getType(), System.currentTimeMillis()); - + messages.add(body); } - //c.commitUpdates(); + c.close(); + + removeMessageInDb(Imps.MessageType.POSTPONED); + + for (String body : messages) + sendMessage(body); + } public void registerChatListener(IChatListener listener) { if (listener != null) { mRemoteListeners.register(listener); + + if (mDataHandlerListener != null) + mDataHandlerListener.checkLastTransferRequest (); } } @@ -340,42 +426,51 @@ public void unregisterChatListener(IChatListener listener) { public void markAsRead() { if (mHasUnreadMessages) { + + /** + * we want to keep the last message now ContentValues values = new ContentValues(1); values.put(Imps.Chats.LAST_UNREAD_MESSAGE, (String) null); mConnection.getContext().getContentResolver().update(mChatURI, values, null, null); - - mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress()); +*/ + String baseUsername = mChatSession.getParticipant().getAddress().getBareAddress(); + mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), baseUsername); mHasUnreadMessages = false; } } String getNickName(String username) { - ImEntity participant = mAdaptee.getParticipant(); + ImEntity participant = mChatSession.getParticipant(); if (mIsGroupChat) { + ChatGroup group = (ChatGroup) participant; List members = group.getMembers(); for (Contact c : members) { if (username.equals(c.getAddress().getAddress())) { - return c.getName(); + + return c.getAddress().getResource(); + } } + // not found, impossible - return username; + String[] parts = username.split("/"); + return parts[parts.length-1]; } else { return ((Contact) participant).getName(); } } void onConvertToGroupChatSuccess(ChatGroup group) { - Contact oldParticipant = (Contact) mAdaptee.getParticipant(); + Contact oldParticipant = (Contact) mChatSession.getParticipant(); String oldAddress = getAddress(); - mAdaptee.setParticipant(group); + mChatSession.setParticipant(group); mChatSessionManager.updateChatSession(oldAddress, this); Uri oldChatUri = mChatURI; Uri oldMessageUri = mMessageURI; - init(group); + init(group,false); copyHistoryMessages(oldParticipant); mContentResolver.delete(oldMessageUri, NON_CHAT_MESSAGE_SELECTION, null); @@ -386,11 +481,11 @@ void onConvertToGroupChatSuccess(ChatGroup group) { } private void copyHistoryMessages(Contact oldParticipant) { - List historyMessages = mAdaptee.getHistoryMessages(); + List historyMessages = mChatSession.getHistoryMessages(); int total = historyMessages.size(); int start = total > MAX_HISTORY_COPY_COUNT ? total - MAX_HISTORY_COPY_COUNT : 0; for (int i = start; i < total; i++) { - Message msg = historyMessages.get(i); + info.guardianproject.otr.app.im.engine.Message msg = historyMessages.get(i); boolean incoming = msg.getFrom().equals(oldParticipant.getAddress()); String contact = incoming ? oldParticipant.getName() : null; long time = msg.getDateTime().getTime(); @@ -400,43 +495,57 @@ private void copyHistoryMessages(Contact oldParticipant) { } void insertOrUpdateChat(String message) { + ContentValues values = new ContentValues(2); values.put(Imps.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis()); - values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message); - // ImProvider.insert() will replace the chat if it already exist. - mContentResolver.insert(mChatURI, values); + values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message); + values.put(Imps.Chats.GROUP_CHAT, mIsGroupChat); + // ImProvider.insert() will replace the chat if it already exist. + mContentResolver.insert(mChatURI, values); + + } - private long insertGroupContactInDb(ChatGroup group) { + private long insertOrUpdateGroupContactInDb(ChatGroup group) { // Insert a record in contacts table ContentValues values = new ContentValues(4); values.put(Imps.Contacts.USERNAME, group.getAddress().getAddress()); values.put(Imps.Contacts.NICKNAME, group.getName()); - values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.FAKE_TEMPORARY_LIST_ID); + values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.LOCAL_GROUP_LIST_ID); values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_GROUP); Uri contactUri = ContentUris.withAppendedId( ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mConnection.mProviderId), mConnection.mAccountId); - long id = ContentUris.parseId(mContentResolver.insert(contactUri, values)); - - ArrayList memberValues = new ArrayList(); - Contact self = mConnection.getLoginUser(); - for (Contact member : group.getMembers()) { - if (!member.equals(self)) { // avoid to insert the user himself - ContentValues memberValue = new ContentValues(2); - memberValue.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress()); - memberValue.put(Imps.GroupMembers.NICKNAME, member.getName()); - memberValues.add(memberValue); + + ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection + .getContactListManager(); + + long id = listManager.queryGroup(group); + + if (id == -1) + { + id = ContentUris.parseId(mContentResolver.insert(contactUri, values)); + + ArrayList memberValues = new ArrayList(); + Contact self = mConnection.getLoginUser(); + for (Contact member : group.getMembers()) { + if (!member.equals(self)) { // avoid to insert the user himself + ContentValues memberValue = new ContentValues(2); + memberValue.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress()); + memberValue.put(Imps.GroupMembers.NICKNAME, member.getName()); + memberValues.add(memberValue); + } + } + if (!memberValues.isEmpty()) { + ContentValues[] result = new ContentValues[memberValues.size()]; + memberValues.toArray(result); + Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id); + mContentResolver.bulkInsert(memberUri, result); } } - if (!memberValues.isEmpty()) { - ContentValues[] result = new ContentValues[memberValues.size()]; - memberValues.toArray(result); - Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id); - mContentResolver.bulkInsert(memberUri, result); - } + return id; } @@ -513,67 +622,70 @@ Uri insertMessageInDb(String contact, String body, long time, int type) { } Uri insertMessageInDb(String contact, String body, long time, int type, int errCode, String id) { - - ContentValues values = new ContentValues(mIsGroupChat ? 4 : 3); - values.put(Imps.Messages.BODY, body); - values.put(Imps.Messages.DATE, time); - values.put(Imps.Messages.TYPE, type); - values.put(Imps.Messages.ERROR_CODE, errCode); - if (mIsGroupChat) { - values.put(Imps.Messages.NICKNAME, contact); - values.put(Imps.Messages.IS_GROUP_CHAT, 1); - } - values.put(Imps.Messages.IS_DELIVERED, 0); - values.put(Imps.Messages.PACKET_ID, id); - boolean isEncrypted = true; try { - isEncrypted = mOtrChatSession.isChatEncrypted(); + isEncrypted = getOtrChatSession().isChatEncrypted(); } catch (RemoteException e) { // Leave it as encrypted so it gets stored in memory - // TODO(miron) + // FIXME(miron) } - - return mContentResolver.insert(isEncrypted ? mOtrMessageURI : mMessageURI, values); - } - - int updateConfirmInDb(String id, int value) { - Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI_BY_PACKET_ID.buildUpon(); - builder.appendPath(id); - - ContentValues values = new ContentValues(1); - values.put(Imps.Messages.IS_DELIVERED, value); - return mContentResolver.update(builder.build(), values, null, null); + return Imps.insertMessageInDb(mContentResolver, mIsGroupChat, mContactId, isEncrypted, contact, body, time, type, errCode, id, null); } int updateMessageInDb(String id, int type, long time) { + + int result = -1; Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI_BY_PACKET_ID.buildUpon(); builder.appendPath(id); - + ContentValues values = new ContentValues(1); values.put(Imps.Messages.TYPE, type); - values.put(Imps.Messages.DATE, time); - return mContentResolver.update(builder.build(), values, null, null); + + if (time != -1) + values.put(Imps.Messages.DATE, time); + + result = mContentResolver.update(builder.build(), values, null, null); + + if (result == 0) + { + builder = Imps.Messages.CONTENT_URI_MESSAGES_BY_PACKET_ID.buildUpon(); + builder.appendPath(id); + + result = mContentResolver.update(builder.build(), values, null, null); + } + + + return result; + } + + class ListenerAdapter implements MessageListener, GroupMemberListener { - public boolean onIncomingMessage(ChatSession ses, final Message msg) { + public boolean onIncomingMessage(ChatSession ses, final info.guardianproject.otr.app.im.engine.Message msg) { String body = msg.getBody(); String username = msg.getFrom().getAddress(); + String bareUsername = msg.getFrom().getBareAddress(); String nickname = getNickName(username); long time = msg.getDateTime().getTime(); - + insertOrUpdateChat(body); - + insertMessageInDb(nickname, body, time, msg.getType()); + boolean wasMessageSeen = false; + int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { - listener.onIncomingMessage(ChatSessionAdapter.this, msg); + boolean wasSeen = listener.onIncomingMessage(ChatSessionAdapter.this, msg); + + if (wasSeen) + wasMessageSeen = wasSeen; + } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. @@ -581,18 +693,21 @@ public boolean onIncomingMessage(ChatSession ses, final Message msg) { } mRemoteListeners.finishBroadcast(); - if (N == 0) + // Due to the move to fragments, we could have listeners for ChatViews that are not visible on the screen. + // This is for fragments adjacent to the current one. Therefore we can't use the existence of listeners + // as a filter on notifications. + if (!wasMessageSeen) { //reinstated body display here in the notification; perhaps add preferences to turn that off mStatusBarNotifier.notifyChat(mConnection.getProviderId(), mConnection.getAccountId(), - getId(), username, nickname, body, false); + getId(), bareUsername, nickname, body, false); } - + mHasUnreadMessages = true; return true; } - public void onSendMessageError(ChatSession ses, final Message msg, final ImErrorInfo error) { + public void onSendMessageError(ChatSession ses, final info.guardianproject.otr.app.im.engine.Message msg, final ImErrorInfo error) { insertMessageInDb(null, null, System.currentTimeMillis(), Imps.MessageType.OUTGOING, error.getCode(), null); @@ -672,7 +787,7 @@ public void notifyChatSessionConverted() { @Override public void onIncomingReceipt(ChatSession ses, String id) { - updateConfirmInDb(id, 1); + Imps.updateConfirmInDb(mContentResolver, id, true); int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { @@ -699,7 +814,7 @@ public void onReceiptsExpected(ChatSession ses) { } @Override - public void onStatusChanged(ChatSession session) { + public void onStatusChanged(ChatSession session, SessionStatus status) { final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); @@ -707,11 +822,35 @@ public void onStatusChanged(ChatSession session) { listener.onStatusChanged(ChatSessionAdapter.this); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the - // dead listeners. + // dead listeners. // TODO Auto-generated method stub } } mRemoteListeners.finishBroadcast(); + mDataHandler.onOtrStatusChanged(status); + + if (status == SessionStatus.ENCRYPTED) + { + sendPostponedMessages (); + } + + } + + @Override + public void onIncomingDataRequest(ChatSession session, info.guardianproject.otr.app.im.engine.Message msg, byte[] value) { + mDataHandler.onIncomingRequest(msg.getFrom(),msg.getTo(), value); } + + @Override + public void onIncomingDataResponse(ChatSession session, info.guardianproject.otr.app.im.engine.Message msg, byte[] value) { + mDataHandler.onIncomingResponse(msg.getFrom(),msg.getTo(), value); + } + + @Override + public void onIncomingTransferRequest(final Transfer transfer) { + + } + + } class ChatConvertor implements GroupListener, GroupMemberListener { @@ -722,12 +861,12 @@ public ChatConvertor() { mGroupMgr = mConnection.mGroupManager; } - public void convertToGroupChat() { + public void convertToGroupChat(String nickname) { mGroupMgr.addGroupListener(this); mGroupName = "G" + System.currentTimeMillis(); try { - mGroupMgr.createChatGroupAsync(mGroupName); + mGroupMgr.createChatGroupAsync(mGroupName, nickname); } catch (Exception e){ e.printStackTrace(); @@ -738,12 +877,12 @@ public void onGroupCreated(ChatGroup group) { if (mGroupName.equalsIgnoreCase(group.getName())) { mGroupMgr.removeGroupListener(this); group.addMemberListener(this); - mGroupMgr.inviteUserAsync(group, (Contact) mAdaptee.getParticipant()); + mGroupMgr.inviteUserAsync(group, (Contact) mChatSession.getParticipant()); } } public void onMemberJoined(ChatGroup group, Contact contact) { - if (mAdaptee.getParticipant().equals(contact)) { + if (mChatSession.getParticipant().equals(contact)) { onConvertToGroupChatSuccess(group); } @@ -769,4 +908,229 @@ public void onMemberLeft(ChatGroup group, Contact contact) { mContactStatusMap.remove(contact.getName()); } } + + @Override + public void setDataListener(IDataListener dataListener) throws RemoteException { + + mDataListener = dataListener; + mDataHandler.setDataListener(mDataListener); + } + + @Override + public void setIncomingFileResponse (boolean acceptThis, boolean acceptAll) + { + + mAcceptTransfer = acceptThis; + mAcceptAllTransfer = acceptAll; + mWaitingForResponse = false; + + mDataHandler.acceptTransfer(mLastFileUrl); + + } + + class DataHandlerListenerImpl extends IDataListener.Stub { + + @Override + public void onTransferComplete(boolean outgoing, String offerId, String from, String url, String mimeType, String filePath) { + + + try { + + + if (outgoing) { + Imps.updateConfirmInDb(service.getContentResolver(), offerId, true); + } else { + + try + { + boolean isVerified = getOtrChatSession().isKeyVerified(from); + + int type = isVerified ? Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED : Imps.MessageType.INCOMING_ENCRYPTED; + + insertOrUpdateChat(filePath); + + Uri messageUri = Imps.insertMessageInDb(service.getContentResolver(), + false, getId(), + true, null, + filePath, System.currentTimeMillis(), type, + 0, offerId, mimeType); + + int percent = (int)(100); + + String[] path = url.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + + final int N = mRemoteListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IChatListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.onIncomingFileTransferProgress(sanitizedPath, percent); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + mRemoteListeners.finishBroadcast(); + + + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"Error updating file transfer progress",e); + } + + } + + /** + if (mimeType != null && mimeType.startsWith("audio")) + { + MediaPlayer mp = new MediaPlayer(); + try { + mp.setDataSource(file.getCanonicalPath()); + + mp.prepare(); + mp.start(); + + } catch (IOException e) { + // TODO Auto-generated catch block + //e.printStackTrace(); + } + }*/ + + } catch (Exception e) { + // mHandler.showAlert(service.getString(R.string.error_chat_file_transfer_title), service.getString(R.string.error_chat_file_transfer_body)); + OtrDebugLogger.log("error reading file", e); + } + + + } + + @Override + public synchronized void onTransferFailed(boolean outgoing, String offerId, String from, String url, String reason) { + + + String[] path = url.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + + final int N = mRemoteListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IChatListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.onIncomingFileTransferError(sanitizedPath, reason); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + mRemoteListeners.finishBroadcast(); + } + + @Override + public synchronized void onTransferProgress(boolean outgoing, String offerId, String from, String url, float percentF) { + + int percent = (int)(100*percentF); + + String[] path = url.split("/"); + String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); + + try + { + final int N = mRemoteListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IChatListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.onIncomingFileTransferProgress(sanitizedPath, percent); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + } + catch (Exception e) + { + Log.e(ImApp.LOG_TAG,"error broadcasting progress",e); + } + finally + { + mRemoteListeners.finishBroadcast(); + } + } + + + private String mLastTransferFrom; + private String mLastTransferUrl; + + public void checkLastTransferRequest () + { + if (mLastTransferFrom != null) + { + onTransferRequested(mLastTransferUrl,mLastTransferFrom,mLastTransferFrom,mLastTransferUrl); + mLastTransferFrom = null; + mLastTransferUrl = null; + } + } + + @Override + public synchronized boolean onTransferRequested(String offerId, String from, String to, String transferUrl) { + + mAcceptTransfer = false; + mWaitingForResponse = true; + mLastFileUrl = transferUrl; + + if (mAcceptAllTransfer) + { + mAcceptTransfer = true; + mWaitingForResponse = false; + mLastTransferFrom = from; + mLastTransferUrl = transferUrl; + + mDataHandler.acceptTransfer(mLastFileUrl); + } + else + { + try + { + final int N = mRemoteListeners.beginBroadcast(); + + if (N > 0) + { + for (int i = 0; i < N; i++) { + IChatListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.onIncomingFileTransfer(from, transferUrl); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + } + else + { + mLastTransferFrom = from; + mLastTransferUrl = transferUrl; + + //reinstated body display here in the notification; perhaps add preferences to turn that off + mStatusBarNotifier.notifyChat(mConnection.getProviderId(), mConnection.getAccountId(), + getId(), from, from, "Incoming file request", false); + } + } + finally + { + mRemoteListeners.finishBroadcast(); + } + + mAcceptTransfer = false; //for now, wait for the user callback + } + + return mAcceptTransfer; + + } + + + + } + + + + } diff --git a/src/info/guardianproject/otr/app/im/service/ChatSessionManagerAdapter.java b/src/info/guardianproject/otr/app/im/service/ChatSessionManagerAdapter.java index 4ccd2ca90..0cf906fbd 100644 --- a/src/info/guardianproject/otr/app/im/service/ChatSessionManagerAdapter.java +++ b/src/info/guardianproject/otr/app/im/service/ChatSessionManagerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,7 +17,6 @@ package info.guardianproject.otr.app.im.service; -import info.guardianproject.otr.OtrChatManager; import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.IChatSessionListener; import info.guardianproject.otr.app.im.app.ImApp; @@ -37,6 +36,8 @@ import java.util.HashMap; import java.util.List; +import org.jivesoftware.smackx.muc.MultiUserChat; + import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; @@ -46,37 +47,54 @@ public class ChatSessionManagerAdapter extends info.guardianproject.otr.app.im.IChatSessionManager.Stub { ImConnectionAdapter mConnection; - ChatSessionManager mChatSessionManager; - ChatGroupManager mGroupManager; - HashMap mActiveChatSessionAdapters; ChatSessionListenerAdapter mSessionListenerAdapter; final RemoteCallbackList mRemoteListeners = new RemoteCallbackList(); + static HashMap mActiveChatSessionAdapters; + public ChatSessionManagerAdapter(ImConnectionAdapter connection) { mConnection = connection; ImConnection connAdaptee = connection.getAdaptee(); - mChatSessionManager = connAdaptee.getChatSessionManager(); + + connAdaptee.getChatSessionManager().setAdapter(this); + mActiveChatSessionAdapters = new HashMap(); mSessionListenerAdapter = new ChatSessionListenerAdapter(); - mChatSessionManager.addChatSessionListener(mSessionListenerAdapter); - - if ((connAdaptee.getCapability() & ImConnection.CAPABILITY_GROUP_CHAT) != 0) { - mGroupManager = connAdaptee.getChatGroupManager(); - mGroupManager.addGroupListener(new ChatGroupListenerAdapter()); - } + getChatSessionManager().addChatSessionListener(mSessionListenerAdapter); + + } + public ChatGroupManager getChatGroupManager () + { + if ((mConnection.getAdaptee().getCapability() & ImConnection.CAPABILITY_GROUP_CHAT) != 0) { + ChatGroupManager groupManager = mConnection.getAdaptee().getChatGroupManager(); + groupManager.addGroupListener(new ChatGroupListenerAdapter()); + return groupManager; + } + else + return null; + } + public ChatSessionManager getChatSessionManager() { - return mChatSessionManager; + return mConnection.getAdaptee().getChatSessionManager(); } - public IChatSession createChatSession(String contactAddress) { + public IChatSession createChatSession(String contactAddress, boolean isNewSession) { + ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection .getContactListManager(); - Contact contact = listManager.getContactByAddress(contactAddress); + + Contact contact = listManager.getContactByAddress(Address.stripResource(contactAddress)); if (contact == null) { try { - contact = listManager.createTemporaryContact(contactAddress); + + contact = new Contact (new XmppAddress(contactAddress),contactAddress); + long contactId = listManager.queryOrInsertContact(contact); + + // String[] address = {Address.stripResource(contactAddress)}; + //contact = listManager.createTemporaryContacts(address)[0]; + } catch (IllegalArgumentException e) { mSessionListenerAdapter.notifyChatSessionCreateFailed(contactAddress, new ImErrorInfo(ImErrorInfo.ILLEGAL_CONTACT_ADDRESS, @@ -84,30 +102,42 @@ public IChatSession createChatSession(String contactAddress) { return null; } } - - ChatSession session = mChatSessionManager.createChatSession(contact); - - return getChatSessionAdapter(session); + + ChatSession session = getChatSessionManager().createChatSession(contact, isNewSession); + + return getChatSessionAdapter(session, isNewSession); + } + + public String getDefaultMultiUserChatServer () + { + ChatGroupManager groupMan = mConnection.getAdaptee().getChatGroupManager(); + + return groupMan.getDefaultMultiUserChatServer(); } - public IChatSession createMultiUserChatSession(String roomAddress) + public IChatSession createMultiUserChatSession(String roomAddress, String nickname, boolean isNewChat) { - + ChatGroupManager groupMan = mConnection.getAdaptee().getChatGroupManager(); - + try { - groupMan.createChatGroupAsync(roomAddress); - + groupMan.createChatGroupAsync(roomAddress, nickname); + Address address = new XmppAddress(roomAddress); //TODO hard coding XMPP for now + + // ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection + // .getContactListManager(); + + // long contactId = listManager.queryOrInsertContact(new Contact (new XmppAddress(roomAddress),roomAddress)); ChatGroup chatGroup = groupMan.getChatGroup(address); - + if (chatGroup != null) { - ChatSession session = mChatSessionManager.createChatSession(chatGroup); - - return getChatSessionAdapter(session); + ChatSession session = getChatSessionManager().createChatSession(chatGroup,isNewChat); + + return getChatSessionAdapter(session, isNewChat); } else { @@ -124,8 +154,10 @@ public IChatSession createMultiUserChatSession(String roomAddress) public void closeChatSession(ChatSessionAdapter adapter) { synchronized (mActiveChatSessionAdapters) { ChatSession session = adapter.getAdaptee(); - mChatSessionManager.closeChatSession(session); - mActiveChatSessionAdapters.remove(adapter.getAddress()); + getChatSessionManager().closeChatSession(session); + + String key = Address.stripResource(adapter.getAddress()); + mActiveChatSessionAdapters.remove(key); } } @@ -142,17 +174,17 @@ public void closeAllChatSessions() { public void updateChatSession(String oldAddress, ChatSessionAdapter adapter) { synchronized (mActiveChatSessionAdapters) { mActiveChatSessionAdapters.remove(oldAddress); - mActiveChatSessionAdapters.put(adapter.getAddress(), adapter); + mActiveChatSessionAdapters.put(Address.stripResource(adapter.getAddress()), adapter); } } public IChatSession getChatSession(String address) { synchronized (mActiveChatSessionAdapters) { - return mActiveChatSessionAdapters.get(address); + return mActiveChatSessionAdapters.get(Address.stripResource(address)); } } - public List getActiveChatSessions() { + public List getActiveChatSessions() { synchronized (mActiveChatSessionAdapters) { return new ArrayList(mActiveChatSessionAdapters.values()); } @@ -176,23 +208,24 @@ public void unregisterChatSessionListener(IChatSessionListener listener) { } } - ChatSessionAdapter getChatSessionAdapter(ChatSession session) { - synchronized (mActiveChatSessionAdapters) { - Address participantAddress = session.getParticipant().getAddress(); - String key = participantAddress.getAddress(); - ChatSessionAdapter adapter = mActiveChatSessionAdapters.get(key); - if (adapter == null) { - adapter = new ChatSessionAdapter(session, mConnection); - mActiveChatSessionAdapters.put(key, adapter); - } - return adapter; + public synchronized ChatSessionAdapter getChatSessionAdapter(ChatSession session, boolean isNewSession) { + + Address participantAddress = session.getParticipant().getAddress(); + String key = Address.stripResource(participantAddress.getAddress()); + ChatSessionAdapter adapter = mActiveChatSessionAdapters.get(key); + + if (adapter == null) { + adapter = new ChatSessionAdapter(session, mConnection, isNewSession); + mActiveChatSessionAdapters.put(key, adapter); } + + return adapter; } class ChatSessionListenerAdapter implements ChatSessionListener { public void onChatSessionCreated(ChatSession session) { - final IChatSession sessionAdapter = getChatSessionAdapter(session); + final IChatSession sessionAdapter = getChatSessionAdapter(session, false); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatSessionListener listener = mRemoteListeners.getBroadcastItem(i); @@ -236,7 +269,7 @@ public void onGroupError(int errorType, String name, ImErrorInfo error) { } public void onJoinedGroup(ChatGroup group) { - mChatSessionManager.createChatSession(group); + getChatSessionManager().createChatSession(group,false); } public void onLeftGroup(ChatGroup group) { diff --git a/src/info/guardianproject/otr/app/im/service/ContactListAdapter.java b/src/info/guardianproject/otr/app/im/service/ContactListAdapter.java index 06d2c5f05..4232a978e 100644 --- a/src/info/guardianproject/otr/app/im/service/ContactListAdapter.java +++ b/src/info/guardianproject/otr/app/im/service/ContactListAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -53,7 +53,7 @@ public int addContact(String address) { } catch (IllegalArgumentException e) { return ImErrorInfo.ILLEGAL_CONTACT_ADDRESS; } catch (ImException e) { - return e.getImError().getCode(); + return ImErrorInfo.NETWORK_ERROR; } return ImErrorInfo.NO_ERROR; diff --git a/src/info/guardianproject/otr/app/im/service/ContactListManagerAdapter.java b/src/info/guardianproject/otr/app/im/service/ContactListManagerAdapter.java index 60840a5a3..6decc8264 100644 --- a/src/info/guardianproject/otr/app/im/service/ContactListManagerAdapter.java +++ b/src/info/guardianproject/otr/app/im/service/ContactListManagerAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -22,6 +22,7 @@ import info.guardianproject.otr.app.im.ISubscriptionListener; import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.Address; +import info.guardianproject.otr.app.im.engine.ChatGroup; import info.guardianproject.otr.app.im.engine.Contact; import info.guardianproject.otr.app.im.engine.ContactList; import info.guardianproject.otr.app.im.engine.ContactListListener; @@ -29,7 +30,6 @@ import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.ImException; import info.guardianproject.otr.app.im.engine.Presence; -import info.guardianproject.otr.app.im.engine.SubscriptionRequestListener; import info.guardianproject.otr.app.im.provider.Imps; import java.io.ByteArrayOutputStream; @@ -68,7 +68,7 @@ public class ContactListManagerAdapter extends final RemoteCallbackList mRemoteContactListeners = new RemoteCallbackList(); final RemoteCallbackList mRemoteSubscriptionListeners = new RemoteCallbackList(); - HashMap mContactLists; + HashMap mContactLists; // Temporary contacts are created when a peer is encountered, and that peer // is not yet on any contact list. HashMap mTemporaryContacts; @@ -87,6 +87,7 @@ public class ContactListManagerAdapter extends private Uri mContactUrl; static final long FAKE_TEMPORARY_LIST_ID = -1; + static final long LOCAL_GROUP_LIST_ID = 9999; static final String[] CONTACT_LIST_ID_PROJECTION = { Imps.ContactList._ID }; RemoteImService mContext; @@ -99,12 +100,12 @@ public ContactListManagerAdapter(ImConnectionAdapter conn) { new Thread(this).start(); } - + public void run () { mContactListListenerAdapter = new ContactListListenerAdapter(); mSubscriptionListenerAdapter = new SubscriptionRequestListenerAdapter(); - mContactLists = new HashMap(); + mContactLists = new HashMap(); mTemporaryContacts = new HashMap(); mOfflineContacts = new HashMap(); mValidatedContacts = new HashSet(); @@ -128,22 +129,27 @@ public void run () ContentUris.appendId(builder, mAccountId); mContactUrl = builder.build(); - + seedInitialPresences(); - loadOfflineContacts(); + // loadOfflineContacts(); } - + private void loadOfflineContacts() { - Cursor contactCursor = mResolver.query(mContactUrl, new String[] { Imps.Contacts.USERNAME }, + Cursor contactCursor = mResolver.query(mContactUrl, new String[] { Imps.Contacts.USERNAME, Imps.Contacts.NICKNAME }, null, null, null); - + + String[] addresses = new String[contactCursor.getCount()]; + int i = 0; while (contactCursor.moveToNext()) { - String address = contactCursor.getString(0); - mOfflineContacts.put(address, mAdaptee.createTemporaryContact(address)); + addresses[i++] = contactCursor.getString(0); + } - - + + Contact[] contacts = mAdaptee.createTemporaryContacts(addresses); + for (Contact contact : contacts) + mOfflineContacts.put(contact.getAddress().getBareAddress(), contact); + contactCursor.close(); } @@ -168,20 +174,14 @@ public int deleteContactList(String name) { } public List getContactLists() { - synchronized (mContactLists) { - return new ArrayList(mContactLists.values()); - } + return new ArrayList(mContactLists.values()); } public int removeContact(String address) { - if (isTemporary(address)) { - // For temporary contact, just close the session and delete him in - // database. - closeChatSession(address); - String selection = Imps.Contacts.USERNAME + "=?"; - String[] selectionArgs = { address }; - mResolver.delete(mContactUrl, selection, selectionArgs); + closeChatSession(address); + + if (isTemporary(address)) { synchronized (mTemporaryContacts) { mTemporaryContacts.remove(address); } @@ -198,17 +198,42 @@ public int removeContact(String address) { return resCode; } } + } } + String selection = Imps.Contacts.USERNAME + "=?"; + String[] selectionArgs = { address }; + mResolver.delete(mContactUrl, selection, selectionArgs); + return ImErrorInfo.NO_ERROR; } - public void approveSubscription(String address) { + public int setContactName(String address, String name) { + // update the server + try { + mAdaptee.setContactName(address,name); + } catch (ImException e) { + return e.getImError().getCode(); + } + // update locally + String selection = Imps.Contacts.USERNAME + "=?"; + String[] selectionArgs = { address }; + ContentValues values = new ContentValues(1); + values.put( Imps.Contacts.NICKNAME, name); + int updated = mResolver.update(mContactUrl, values, selection, selectionArgs); + if( updated != 1 ) { + return ImErrorInfo.ILLEGAL_CONTACT_ADDRESS; + } + + return ImErrorInfo.NO_ERROR; + } + + public void approveSubscription(Contact address) { mAdaptee.approveSubscriptionRequest(address); } - public void declineSubscription(String address) { + public void declineSubscription(Contact address) { mAdaptee.declineSubscriptionRequest(address); } @@ -274,7 +299,8 @@ public void loadContactLists() { if (mAdaptee.getState() == ContactListManager.LISTS_NOT_LOADED) { clearValidatedContactsAndLists(); mAdaptee.loadContactListsAsync(); - } + } + } public int getState() { @@ -283,9 +309,12 @@ public int getState() { public Contact getContactByAddress(String address) { if (mAdaptee.getState() == ContactListManager.LISTS_NOT_LOADED) { - return mOfflineContacts.get(address); + if (mOfflineContacts != null) + return mOfflineContacts.get(address); + else + return null; } - + Contact c = mAdaptee.getContact(address); if (c == null) { synchronized (mTemporaryContacts) { @@ -296,16 +325,18 @@ public Contact getContactByAddress(String address) { } } - public Contact createTemporaryContact(String address) { - Contact c = mAdaptee.createTemporaryContact(address); - insertTemporary(c); - return c; + public Contact[] createTemporaryContacts(String[] addresses) { + Contact[] contacts = mAdaptee.createTemporaryContacts(addresses); + + for (Contact c : contacts) + insertTemporary(c); + return contacts; } public long queryOrInsertContact(Contact c) { long result; - String username = c.getAddress().getAddress(); + String username = mAdaptee.normalizeAddress(c.getAddress().getAddress()); String selection = Imps.Contacts.USERNAME + "=?"; String[] selectionArgs = { username }; String[] projection = { Imps.Contacts._ID }; @@ -323,12 +354,32 @@ public long queryOrInsertContact(Contact c) { } return result; } + + public long queryGroup(ChatGroup c) { + long result = -1; + + String username = mAdaptee.normalizeAddress(c.getAddress().getAddress()); + String selection = Imps.Contacts.USERNAME + "=?"; + String[] selectionArgs = { username }; + String[] projection = { Imps.Contacts._ID }; + + Cursor cursor = mResolver.query(mContactUrl, projection, selection, selectionArgs, null); + + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getLong(0); + } + + if (cursor != null) { + cursor.close(); + } + return result; + } private long insertTemporary(Contact c) { synchronized (mTemporaryContacts) { - mTemporaryContacts.put(c.getAddress().getAddress(), c); + mTemporaryContacts.put(mAdaptee.normalizeAddress(c.getAddress().getAddress()), c); } - Uri uri = insertContactContent(c, FAKE_TEMPORARY_LIST_ID); + Uri uri = insertContactContent(c, FAKE_TEMPORARY_LIST_ID, Imps.Contacts.TYPE_TEMPORARY); return ContentUris.parseId(uri); } @@ -336,7 +387,7 @@ private long insertTemporary(Contact c) { * Tells if a contact is a temporary one which is not in the list of * contacts that we subscribe presence for. Usually created because of the * user is having a chat session with this contact. - * + * * @param address the address of the contact. * @return true if it's a temporary contact; false * otherwise. @@ -423,6 +474,14 @@ private void removeObsoleteContactsAndLists() { } + interface ContactListBroadcaster { + void broadcast(IContactListListener listener) throws RemoteException; + } + + interface SubscriptionBroadcaster { + void broadcast(ISubscriptionListener listener) throws RemoteException; + } + final class ContactListListenerAdapter implements ContactListListener { private boolean mAllContactsLoaded; @@ -441,6 +500,22 @@ private class StoredContactChange { private Vector mDelayedContactChanges = new Vector(); + private void broadcast(ContactListBroadcaster callback) { + synchronized (mRemoteContactListeners) { + final int N = mRemoteContactListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IContactListListener listener = mRemoteContactListeners.getBroadcastItem(i); + try { + callback.broadcast(listener); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + mRemoteContactListeners.finishBroadcast(); + } + } + public void onContactsPresenceUpdate(final Contact[] contacts) { // The client listens only to presence updates for now. Update // the avatars first to ensure it can get the new avatar when @@ -449,17 +524,11 @@ public void onContactsPresenceUpdate(final Contact[] contacts) { // updateAvatarsContent(contacts); updatePresenceContent(contacts); - final int N = mRemoteContactListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IContactListListener listener = mRemoteContactListeners.getBroadcastItem(i); - try { + broadcast(new ContactListBroadcaster() { + public void broadcast(IContactListListener listener) throws RemoteException { listener.onContactsPresenceUpdate(contacts); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. } - } - mRemoteContactListeners.finishBroadcast(); + }); } public void onContactChange(final int type, final ContactList list, final Contact contact) { @@ -467,8 +536,10 @@ public void onContactChange(final int type, final ContactList list, final Contac String notificationText = null; switch (type) { - case LIST_LOADED: case LIST_CREATED: + break; + + case LIST_LOADED: addContactListContent(list); break; @@ -486,22 +557,30 @@ public void onContactChange(final int type, final ContactList list, final Contac break; case LIST_CONTACT_ADDED: - long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); - String contactAddress = contact.getAddress().getAddress(); - if (isTemporary(contactAddress)) { - moveTemporaryContactToList(contactAddress, listId); - } else { - insertContactContent(contact, listId); - } - notificationText = mContext.getResources().getString(R.string.add_contact_success, - contact.getName()); - // handle case where a contact is added before mAllContactsLoaded - if (!mAllContactsLoaded) { - // if a contact is added to a cached contact list before the actual contact - // list is downloaded from the server, we will have to add the contact to - // the contact list once mAllContactsLoaded is true - if (!mValidatedContactLists.contains(list.getName())) { - mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); + + ContactListAdapter cla = getContactListAdapter(list.getAddress()); + if (cla != null) + { + long listId = cla.getDataBaseId(); + if (isTemporary(mAdaptee.normalizeAddress(contact.getAddress().getAddress()))) { + moveTemporaryContactToList(mAdaptee.normalizeAddress(contact.getAddress().getAddress()), listId); + } else { + + boolean exists = updateContact(contact, listId); + + if (!exists) + insertContactContent(contact, listId, Imps.Contacts.TYPE_NORMAL); + } + notificationText = mContext.getResources().getString(R.string.add_contact_success, + contact.getName()); + // handle case where a contact is added before mAllContactsLoaded + if (!mAllContactsLoaded) { + // if a contact is added to a cached contact list before the actual contact + // list is downloaded from the server, we will have to add the contact to + // the contact list once mAllContactsLoaded is true + if (!mValidatedContactLists.contains(list.getName())) { + mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); + } } } break; @@ -519,7 +598,7 @@ public void onContactChange(final int type, final ContactList list, final Contac } // Clear ChatSession if any. - String address = contact.getAddress().getAddress(); + String address = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); closeChatSession(address); notificationText = mContext.getResources().getString( @@ -541,7 +620,7 @@ public void onContactChange(final int type, final ContactList list, final Contac case CONTACT_BLOCKED: insertBlockedContactToDataBase(contact); - address = contact.getAddress().getAddress(); + address = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); updateContactType(address, Imps.Contacts.TYPE_BLOCKED); closeChatSession(address); notificationText = mContext.getResources().getString( @@ -574,36 +653,25 @@ public void onContactChange(final int type, final ContactList list, final Contac } else { listAdapter = (list == null) ? null : getContactListAdapter(list.getAddress()); } - final int N = mRemoteContactListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IContactListListener listener = mRemoteContactListeners.getBroadcastItem(i); - try { + + broadcast(new ContactListBroadcaster() { + public void broadcast(IContactListListener listener) throws RemoteException { listener.onContactChange(type, listAdapter, contact); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. } - } - mRemoteContactListeners.finishBroadcast(); + }); if (mAllContactsLoaded && notificationText != null) { - mContext.showToast(notificationText, Toast.LENGTH_SHORT); + // mContext.showToast(notificationText, Toast.LENGTH_SHORT); } } public void onContactError(final int errorType, final ImErrorInfo error, final String listName, final Contact contact) { - final int N = mRemoteContactListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IContactListListener listener = mRemoteContactListeners.getBroadcastItem(i); - try { + broadcast(new ContactListBroadcaster() { + public void broadcast(IContactListListener listener) throws RemoteException { listener.onContactError(errorType, error, listName, contact); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. } - } - mRemoteContactListeners.finishBroadcast(); + }); } public void handleDelayedContactChanges() { @@ -614,79 +682,91 @@ public void handleDelayedContactChanges() { public void onAllContactListsLoaded() { mAllContactsLoaded = true; - + handleDelayedContactChanges(); - removeObsoleteContactsAndLists(); - final int N = mRemoteContactListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IContactListListener listener = mRemoteContactListeners.getBroadcastItem(i); - try { + // removeObsoleteContactsAndLists(); + + broadcast(new ContactListBroadcaster() { + public void broadcast(IContactListListener listener) throws RemoteException { listener.onAllContactListsLoaded(); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. } - } - mRemoteContactListeners.finishBroadcast(); + }); } } - final class SubscriptionRequestListenerAdapter implements SubscriptionRequestListener { + final class SubscriptionRequestListenerAdapter extends ISubscriptionListener.Stub { - public void onSubScriptionRequest(final Contact from) { - String username = from.getAddress().getAddress(); + public void onSubScriptionRequest(final Contact from, long providerId, long accountId) { + + String username = mAdaptee.normalizeAddress(from.getAddress().getAddress()); String nickname = from.getName(); queryOrInsertContact(from); // FIXME Miron Uri uri = insertOrUpdateSubscription(username, nickname, Imps.Contacts.SUBSCRIPTION_TYPE_FROM, Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING); - mContext.getStatusBarNotifier().notifySubscriptionRequest(mProviderId, mAccountId, - ContentUris.parseId(uri), username, nickname); - final int N = mRemoteSubscriptionListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - ISubscriptionListener listener = mRemoteSubscriptionListeners.getBroadcastItem(i); - try { - listener.onSubScriptionRequest(from); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. + + boolean hadListener = broadcast(new SubscriptionBroadcaster() { + public void broadcast(ISubscriptionListener listener) throws RemoteException { + listener.onSubScriptionRequest(from, mProviderId, mAccountId); } + }); + + if (!hadListener) + { + mContext.getStatusBarNotifier().notifySubscriptionRequest(mProviderId, mAccountId, + ContentUris.parseId(uri), username, nickname); } - mRemoteSubscriptionListeners.finishBroadcast(); } - public void onSubscriptionApproved(final String contact) { - insertOrUpdateSubscription(contact, null, Imps.Contacts.SUBSCRIPTION_TYPE_NONE, - Imps.Contacts.SUBSCRIPTION_STATUS_NONE); + public void onUnSubScriptionRequest(final Contact from, long providerId, long accountId) { + String username = mAdaptee.normalizeAddress(from.getAddress().getAddress()); + String nickname = from.getName(); + + //to be implemented - should prompt user to approve unsubscribe? + } + - final int N = mRemoteSubscriptionListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - ISubscriptionListener listener = mRemoteSubscriptionListeners.getBroadcastItem(i); - try { - listener.onSubscriptionApproved(contact); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. + private boolean broadcast(SubscriptionBroadcaster callback) { + boolean hadListener = false; + + synchronized (mRemoteSubscriptionListeners) { + final int N = mRemoteSubscriptionListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + ISubscriptionListener listener = mRemoteSubscriptionListeners.getBroadcastItem(i); + try { + callback.broadcast(listener); + hadListener = true; + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } } + mRemoteSubscriptionListeners.finishBroadcast(); } - mRemoteSubscriptionListeners.finishBroadcast(); + + return hadListener; } - public void onSubscriptionDeclined(final String contact) { - insertOrUpdateSubscription(contact, null, Imps.Contacts.SUBSCRIPTION_TYPE_NONE, + public void onSubscriptionApproved(final Contact contact, long providerId, long accountId) { + insertOrUpdateSubscription(contact.getAddress().getBareAddress(), null, Imps.Contacts.SUBSCRIPTION_TYPE_NONE, Imps.Contacts.SUBSCRIPTION_STATUS_NONE); - final int N = mRemoteSubscriptionListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - ISubscriptionListener listener = mRemoteSubscriptionListeners.getBroadcastItem(i); - try { - listener.onSubscriptionDeclined(contact); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. + broadcast(new SubscriptionBroadcaster() { + public void broadcast(ISubscriptionListener listener) throws RemoteException { + listener.onSubscriptionApproved(contact, mProviderId, mAccountId); } - } - mRemoteSubscriptionListeners.finishBroadcast(); + }); + } + + public void onSubscriptionDeclined(final Contact contact, long providerId, long accountId) { + insertOrUpdateSubscription(contact.getAddress().getBareAddress(), null, Imps.Contacts.SUBSCRIPTION_TYPE_NONE, + Imps.Contacts.SUBSCRIPTION_STATUS_NONE); + + broadcast(new SubscriptionBroadcaster() { + public void broadcast(ISubscriptionListener listener) throws RemoteException { + listener.onSubscriptionDeclined(contact, mProviderId, mAccountId); + } + }); } public void onApproveSubScriptionError(final String contact, final ImErrorInfo error) { @@ -721,7 +801,7 @@ void insertBlockedContactToDataBase(Contact contact) { ContentUris.appendId(builder, mAccountId); Uri uri = builder.build(); - String username = contact.getAddress().getAddress(); + String username = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); ContentValues values = new ContentValues(2); values.put(Imps.BlockedList.USERNAME, username); values.put(Imps.BlockedList.NICKNAME, contact.getName()); @@ -732,7 +812,7 @@ void insertBlockedContactToDataBase(Contact contact) { } void removeBlockedContactFromDataBase(Contact contact) { - String address = contact.getAddress().getAddress(); + String address = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); @@ -768,7 +848,7 @@ void updateContactType(String address, int type) { /** * Insert or update subscription request from user into the database. - * + * * @param username * @param nickname * @param subscriptionType @@ -806,27 +886,88 @@ Uri insertOrUpdateSubscription(String username, String nickname, int subscriptio cursor.close(); return uri; } + + boolean isSubscribed (String username) + { + boolean result = false; + + Cursor cursor = mResolver.query(mContactUrl, new String[] { Imps.Contacts._ID,}, + Imps.Contacts.USERNAME + "=? AND " + Imps.Contacts.SUBSCRIPTION_STATUS + "=" + Imps.Contacts.SUBSCRIPTION_STATUS_NONE, new String[] { username }, null); + if (cursor == null) { + RemoteImService.debug("query contact " + username + " failed"); + return false; + } + + if (cursor.moveToFirst()) { + + result = true; + } + + cursor.close(); + return result; + } + + boolean updateContact(Contact contact, long listId) + { + String addr = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); + boolean exists = updateContact(addr,getContactContentValues(contact, listId)); + + if (exists) + { + String username = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); + String selection = Imps.Contacts.USERNAME + "=?"; + String[] selectionArgs = { username }; + String[] projection = { Imps.Contacts._ID }; - void updateContact(String username, ContentValues values) { + Cursor cursor = mResolver.query(mContactUrl, projection, selection, selectionArgs, null); + + if (cursor != null) + { + + while (cursor.moveToNext()) + { + long contactId = cursor.getLong(0); + ContentValues presenceValues = getPresenceValues(contact); + selection = Imps.Presence.CONTACT_ID + "=?"; + String[] selectionArgs2 = {contactId+""}; + mResolver.update(Imps.Presence.CONTENT_URI,presenceValues, selection, selectionArgs2); + } + + cursor.close(); + } + + + } + + return exists; + } + + boolean updateContact(String username, ContentValues values) { String selection = Imps.Contacts.USERNAME + "=?"; String[] selectionArgs = { username }; - mResolver.update(mContactUrl, values, selection, selectionArgs); + return (mResolver.update(mContactUrl, values, selection, selectionArgs)) > 0; } void updatePresenceContent(Contact[] contacts) { + + if (mAdaptee == null) + return; + ArrayList usernames = new ArrayList(); + ArrayList nicknames = new ArrayList(); ArrayList statusArray = new ArrayList(); ArrayList customStatusArray = new ArrayList(); ArrayList clientTypeArray = new ArrayList(); for (Contact c : contacts) { - String username = c.getAddress().getAddress(); + String username = mAdaptee.normalizeAddress(c.getAddress().getAddress()); Presence p = c.getPresence(); int status = convertPresenceStatus(p); String customStatus = p.getStatusText(); int clientType = translateClientType(p); usernames.add(username); + nicknames.add(c.getName()); statusArray.add(String.valueOf(status)); customStatusArray.add(customStatus); clientTypeArray.add(String.valueOf(clientType)); @@ -835,6 +976,7 @@ void updatePresenceContent(Contact[] contacts) { ContentValues values = new ContentValues(); values.put(Imps.Contacts.ACCOUNT, mAccountId); putStringArrayList(values, Imps.Contacts.USERNAME, usernames); + putStringArrayList(values, Imps.Contacts.NICKNAME, nicknames); putStringArrayList(values, Imps.Presence.PRESENCE_STATUS, statusArray); putStringArrayList(values, Imps.Presence.PRESENCE_CUSTOM_STATUS, customStatusArray); putStringArrayList(values, Imps.Presence.CONTENT_TYPE, clientTypeArray); @@ -852,7 +994,7 @@ void updateAvatarsContent(Contact[] contacts) { continue; } - String username = contact.getAddress().getAddress(); + String username = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); ContentValues values = new ContentValues(2); values.put(Imps.Avatars.CONTACT, username); @@ -919,19 +1061,17 @@ void addContactListContent(ContactList list) { mValidatedContactLists.add(list.getName()); - synchronized (mContactLists) { - mContactLists.put(list.getAddress(), new ContactListAdapter(list, listId)); - } + mContactLists.put(list.getAddress().getAddress(), new ContactListAdapter(list, listId)); Cursor contactCursor = mResolver.query(mContactUrl, new String[] { Imps.Contacts.USERNAME }, Imps.Contacts.CONTACTLIST + "=?", new String[] { "" + listId }, null); Set existingUsernames = new HashSet(); - - + + while (contactCursor.moveToNext()) existingUsernames.add(contactCursor.getString(0)); - - + + contactCursor.close(); Collection contacts = list.getContacts(); @@ -942,7 +1082,7 @@ void addContactListContent(ContactList list) { Iterator iter = contacts.iterator(); while (iter.hasNext()) { Contact c = iter.next(); - String address = c.getAddress().getAddress(); + String address = mAdaptee.normalizeAddress(c.getAddress().getAddress()); if (isTemporary(address)) { if (!existingUsernames.contains(address)) { moveTemporaryContactToList(address, listId); @@ -956,11 +1096,13 @@ void addContactListContent(ContactList list) { ArrayList nicknames = new ArrayList(); ArrayList contactTypeArray = new ArrayList(); for (Contact c : contacts) { - String username = c.getAddress().getAddress(); + + if (updateContact(c,listId)) + continue; //contact existed and was updated to this list + + String username = mAdaptee.normalizeAddress(c.getAddress().getAddress()); String nickname = c.getName(); - - if (existingUsernames.contains(username)) - continue; // FIXME update instead of skipping + int type = Imps.Contacts.TYPE_NORMAL; if (isTemporary(username)) { type = Imps.Contacts.TYPE_TEMPORARY; @@ -1010,12 +1152,29 @@ void updateListNameInDataBase(ContactList list) { } void deleteContactFromDataBase(Contact contact, ContactList list) { - String selection = Imps.Contacts.USERNAME + "=? AND " + Imps.Contacts.CONTACTLIST + "=?"; - long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); - String username = contact.getAddress().getAddress(); - String[] selectionArgs = { username, Long.toString(listId) }; - mResolver.delete(mContactUrl, selection, selectionArgs); + String username = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); + + //if list is provided, then delete from one list + if (list != null && list.getAddress() != null) + { + String selection = Imps.Contacts.USERNAME + "=? AND " + Imps.Contacts.CONTACTLIST + "=?"; + ContactListAdapter cla = getContactListAdapter(list.getAddress()); + + if (cla != null) + { + long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); + String[] selectionArgs = { username, Long.toString(listId) }; + mResolver.delete(mContactUrl, selection, selectionArgs); + } + } + else //if it is null, delete from all + { + String selection = Imps.Contacts.USERNAME + "=?"; + String[] selectionArgs = { username }; + mResolver.delete(mContactUrl, selection, selectionArgs); + + } // clear the history message if the contact doesn't exist in any list // anymore. @@ -1024,21 +1183,19 @@ void deleteContactFromDataBase(Contact contact, ContactList list) { } } - Uri insertContactContent(Contact contact, long listId) { + Uri insertContactContent(Contact contact, long listId, int type) { ContentValues values = getContactContentValues(contact, listId); - + values.put(Imps.Contacts.TYPE, type); Uri uri = mResolver.insert(mContactUrl, values); - ContentValues presenceValues = getPresenceValues(ContentUris.parseId(uri), - contact.getPresence()); - + ContentValues presenceValues = getPresenceValues(contact); mResolver.insert(Imps.Presence.CONTENT_URI, presenceValues); return uri; } private ContentValues getContactContentValues(Contact contact, long listId) { - final String username = contact.getAddress().getAddress(); + final String username = mAdaptee.normalizeAddress(contact.getAddress().getAddress()); final String nickname = contact.getName(); int type = Imps.Contacts.TYPE_NORMAL; if (isTemporary(username)) { @@ -1061,9 +1218,9 @@ void clearHistoryMessages(String contact) { mResolver.delete(uri, null, null); } - private ContentValues getPresenceValues(long contactId, Presence p) { - ContentValues values = new ContentValues(3); - values.put(Imps.Presence.CONTACT_ID, contactId); + private ContentValues getPresenceValues(Contact c) { + Presence p = c.getPresence(); + ContentValues values = new ContentValues(3); values.put(Imps.Contacts.PRESENCE_STATUS, convertPresenceStatus(p)); values.put(Imps.Contacts.PRESENCE_CUSTOM_STATUS, p.getStatusText()); values.put(Imps.Presence.CLIENT_TYPE, translateClientType(p)); @@ -1082,37 +1239,38 @@ private int translateClientType(Presence presence) { /** * Converts the presence status to the value defined for ImProvider. - * + * * @param presence The presence from the IM engine. * @return The status value defined in for ImProvider. */ public static int convertPresenceStatus(Presence presence) { switch (presence.getStatus()) { case Presence.AVAILABLE: - return Imps.Presence.AVAILABLE; + return Presence.AVAILABLE; case Presence.IDLE: - return Imps.Presence.IDLE; + return Presence.IDLE; case Presence.AWAY: - return Imps.Presence.AWAY; + return Presence.AWAY; case Presence.DO_NOT_DISTURB: - return Imps.Presence.DO_NOT_DISTURB; + return Presence.DO_NOT_DISTURB; case Presence.OFFLINE: - return Imps.Presence.OFFLINE; + return Presence.OFFLINE; } // impossible... RemoteImService.debug("Illegal presence status value " + presence.getStatus()); - return Imps.Presence.AVAILABLE; + return Presence.AVAILABLE; } + public void clearOnLogout() { clearValidatedContactsAndLists(); clearTemporaryContacts(); - clearPresence(); + // clearPresence(); } /** @@ -1125,9 +1283,9 @@ public void clearOnLogout() { */ private void clearValidatedContactsAndLists() { // clear the list of validated contacts, contact lists, and blocked contacts - mValidatedContacts.clear(); - mValidatedContactLists.clear(); - mValidatedBlockedContacts.clear(); + // mValidatedContacts.clear(); + // mValidatedContactLists.clear(); + // mValidatedBlockedContacts.clear(); } /** @@ -1136,8 +1294,8 @@ private void clearValidatedContactsAndLists() { * logout. */ private void clearTemporaryContacts() { - String selection = Imps.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; - mResolver.delete(mContactUrl, selection, null); + // String selection = Imps.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; + // mResolver.delete(mContactUrl, selection, null); } /** diff --git a/src/info/guardianproject/otr/app/im/service/ForegroundStarter.java b/src/info/guardianproject/otr/app/im/service/ForegroundStarter.java deleted file mode 100644 index 383edb5c9..000000000 --- a/src/info/guardianproject/otr/app/im/service/ForegroundStarter.java +++ /dev/null @@ -1,104 +0,0 @@ -package info.guardianproject.otr.app.im.service; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; -import android.util.Log; - -public class ForegroundStarter { - private static final String TAG = "ForegroundStarter"; - private static final Class[] mSetForegroundSignature = new Class[] { boolean.class }; - private static final Class[] mStartForegroundSignature = new Class[] { int.class, - Notification.class }; - private static final Class[] mStopForegroundSignature = new Class[] { boolean.class }; - - private NotificationManager mNM; - private Method mSetForeground; - private Method mStartForeground; - private Method mStopForeground; - private Object[] mSetForegroundArgs = new Object[1]; - private Object[] mStartForegroundArgs = new Object[2]; - private Object[] mStopForegroundArgs = new Object[1]; - Service mService; - private int mNotificationId; - - public ForegroundStarter(Service service) { - mService = service; - init(); - } - - void invokeMethod(Method method, Object[] args) { - try { - method.invoke(mService, args); - } catch (InvocationTargetException e) { - // Should not happen. - Log.w(TAG, "Unable to invoke method", e); - } catch (IllegalAccessException e) { - // Should not happen. - Log.w(TAG, "Unable to invoke method", e); - } - } - - /** - * This is a wrapper around the new startForeground method, using the older - * APIs if it is not available. - */ - void startForegroundCompat(int id, Notification notification) { - // If we have the new startForeground API, then use it. - if (mStartForeground != null) { - mStartForegroundArgs[0] = Integer.valueOf(id); - mStartForegroundArgs[1] = notification; - invokeMethod(mStartForeground, mStartForegroundArgs); - return; - } - - // Fall back on the old API. - mSetForegroundArgs[0] = Boolean.TRUE; - invokeMethod(mSetForeground, mSetForegroundArgs); - mNM.notify(id, notification); - mNotificationId = id; - } - - /** - * This is a wrapper around the new stopForeground method, using the older - * APIs if it is not available. - */ - void stopForegroundCompat() { - // If we have the new stopForeground API, then use it. - if (mStopForeground != null) { - mStopForegroundArgs[0] = Boolean.TRUE; - invokeMethod(mStopForeground, mStopForegroundArgs); - return; - } - - // Fall back on the old API. Note to cancel BEFORE changing the - // foreground state, since we could be killed at that point. - mNM.cancel(mNotificationId); - mSetForegroundArgs[0] = Boolean.FALSE; - invokeMethod(mSetForeground, mSetForegroundArgs); - } - - public void init() { - mNM = (NotificationManager) mService.getSystemService(Service.NOTIFICATION_SERVICE); - try { - mStartForeground = mService.getClass().getMethod("startForeground", - mStartForegroundSignature); - mStopForeground = mService.getClass().getMethod("stopForeground", - mStopForegroundSignature); - return; - } catch (NoSuchMethodException e) { - // Running on an older platform. - mStartForeground = mStopForeground = null; - } - try { - mSetForeground = mService.getClass() - .getMethod("setForeground", mSetForegroundSignature); - } catch (NoSuchMethodException e) { - throw new IllegalStateException( - "OS doesn't have Service.startForeground OR Service.setForeground!"); - } - } -} diff --git a/src/info/guardianproject/otr/app/im/service/HeartbeatService.java b/src/info/guardianproject/otr/app/im/service/HeartbeatService.java new file mode 100644 index 000000000..719dee2ed --- /dev/null +++ b/src/info/guardianproject/otr/app/im/service/HeartbeatService.java @@ -0,0 +1,152 @@ +package info.guardianproject.otr.app.im.service; + +import info.guardianproject.otr.app.im.app.NetworkConnectivityListener; +import info.guardianproject.otr.app.im.provider.Imps; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; + +/** + * This service exists because a foreground service receiving a wakeup alarm from the OS will cause + * the service process to lose its foreground status and be killed. This service runs in the UI process instead. + * + * @author devrandom + * + */ +public class HeartbeatService extends Service { + public static final String HEARTBEAT_ACTION = "info.guardianproject.otr.app.im.SERVICE.HEARTBEAT"; + public static final String NETWORK_STATE_ACTION = "info.guardianproject.otr.app.im.SERVICE.NETWORK_STATE"; + public static final String NETWORK_STATE_EXTRA = "state"; + public static final String NETWORK_INFO_EXTRA = "info"; + + private static final String TAG = "GB.HeartbeatService"; + private PendingIntent mPendingIntent; + private Intent mRelayIntent; + private ServiceHandler mServiceHandler; + private NetworkConnectivityListener mNetworkConnectivityListener; + private static final int EVENT_NETWORK_STATE_CHANGED = 200; + + + // Our heartbeat interval in seconds. + // The user controlled preference heartbeat interval is in these units (i.e. minutes). + private static final long HEARTBEAT_INTERVAL = 1000 * 60; + private long mHeartbeatInterval = HEARTBEAT_INTERVAL; + + + @Override + public void onCreate() { + super.onCreate(); + this.mPendingIntent = PendingIntent.getService(this, 0, new Intent(HEARTBEAT_ACTION, null, + this, HeartbeatService.class), 0); + this.mRelayIntent = new Intent(HEARTBEAT_ACTION, null, this, RemoteImService.class); + + Imps.ProviderSettings.QueryMap settings = getGlobalSettings(); + + if (settings != null) + { + mHeartbeatInterval = settings.getHeartbeatInterval() * HEARTBEAT_INTERVAL; + settings.close(); + } + + startHeartbeat(mHeartbeatInterval); + + mServiceHandler = new ServiceHandler(); + + mNetworkConnectivityListener = new NetworkConnectivityListener(); + NetworkConnectivityListener.registerHandler(mServiceHandler, EVENT_NETWORK_STATE_CHANGED); + mNetworkConnectivityListener.startListening(this); + + + } + + void startHeartbeat(long interval) { + AlarmManager alarmManager = (AlarmManager)this.getSystemService(ALARM_SERVICE); + alarmManager.cancel(mPendingIntent); + if (interval > 0) + { + //alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + interval, interval, mPendingIntent); + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + interval, interval, mPendingIntent); + } + } + + @Override + public void onDestroy() { + startHeartbeat(0); + NetworkConnectivityListener.unregisterHandler(mServiceHandler); + mNetworkConnectivityListener.stopListening(); + mNetworkConnectivityListener = null; + super.onDestroy(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null && HEARTBEAT_ACTION.equals(intent.getAction())) { + startHeartbeat(mHeartbeatInterval); + startService(mRelayIntent); + } + return START_STICKY; + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + public static void startBeating(Context context) { + context.startService(new Intent(context, HeartbeatService.class)); + } + + public static void stopBeating(Context context) { + context.stopService(new Intent(context, HeartbeatService.class)); + } + + void networkStateChanged() { + // Callback may be async + if (mNetworkConnectivityListener == null) + return; + + Intent intent = new Intent(NETWORK_STATE_ACTION, null, this, RemoteImService.class); + intent.putExtra(NETWORK_INFO_EXTRA, mNetworkConnectivityListener.getNetworkInfo()); + intent.putExtra(NETWORK_STATE_EXTRA, mNetworkConnectivityListener.getState().ordinal()); + startService(intent); + } + + private final class ServiceHandler extends Handler { + public ServiceHandler() { + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_NETWORK_STATE_CHANGED: + // Log.d(TAG, "network"); + networkStateChanged(); + break; + + default: + } + } + } + + private Imps.ProviderSettings.QueryMap getGlobalSettings() { + + ContentResolver contentResolver = getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + if (cursor == null) + return null; + + return new Imps.ProviderSettings.QueryMap(cursor, contentResolver, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, true, null); + + } +} diff --git a/src/info/guardianproject/otr/app/im/service/ImConnectionAdapter.java b/src/info/guardianproject/otr/app/im/service/ImConnectionAdapter.java index f5c06bda3..c750c5eb7 100644 --- a/src/info/guardianproject/otr/app/im/service/ImConnectionAdapter.java +++ b/src/info/guardianproject/otr/app/im/service/ImConnectionAdapter.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,6 +17,7 @@ package info.guardianproject.otr.app.im.service; +import info.guardianproject.otr.OtrChatManager; import info.guardianproject.otr.app.im.IChatSessionManager; import info.guardianproject.otr.app.im.IConnectionListener; import info.guardianproject.otr.app.im.IContactListManager; @@ -25,7 +26,6 @@ import info.guardianproject.otr.app.im.engine.ChatGroupManager; import info.guardianproject.otr.app.im.engine.ConnectionListener; import info.guardianproject.otr.app.im.engine.Contact; -import info.guardianproject.otr.app.im.engine.ContactListManager; import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.engine.ImErrorInfo; import info.guardianproject.otr.app.im.engine.ImException; @@ -33,6 +33,7 @@ import info.guardianproject.otr.app.im.engine.InvitationListener; import info.guardianproject.otr.app.im.engine.Presence; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.Debug; import java.util.HashMap; import java.util.Map; @@ -96,14 +97,22 @@ public RemoteImService getContext() { return mService; } + @Override public long getProviderId() { return mProviderId; } + @Override public long getAccountId() { return mAccountId; } + @Override + public boolean isUsingTor() { + return mConnection.isUsingTor(); + } + + @Override public int[] getSupportedPresenceStatus() { return mConnection.getSupportedPresenceStatus(); } @@ -112,7 +121,7 @@ public void networkTypeChanged() { mConnection.networkTypeChanged(); } - void reestablishSession() { + boolean reestablishSession() { mConnectionState = ImConnection.LOGGING_IN; ContentResolver cr = mService.getContentResolver(); @@ -122,12 +131,15 @@ void reestablishSession() { RemoteImService.debug("re-establish session"); try { mConnection.reestablishSessionAsync(cookie); + return true; } catch (IllegalArgumentException e) { RemoteImService.debug("Invalid session cookie, probably modified by others."); clearSessionCookie(cr); } } } + + return false; } private Uri getSessionCookiesUri() { @@ -138,15 +150,28 @@ private Uri getSessionCookiesUri() { return builder.build(); } - public void login(String passwordTemp, boolean autoLoadContacts, boolean retry) { - + @Override + public void login(final String passwordTemp, final boolean autoLoadContacts, final boolean retry) { + Debug.wrapExceptions(new Runnable() { + @Override + public void run() { + do_login(passwordTemp, autoLoadContacts, retry); + } + }); + } + + public void do_login(String passwordTemp, boolean autoLoadContacts, boolean retry) { + mAutoLoadContacts = autoLoadContacts; mConnectionState = ImConnection.LOGGING_IN; mConnection.loginAsync(mAccountId, passwordTemp, mProviderId, retry); - - + + } + + + private void loadSavedPresence () { @@ -170,7 +195,10 @@ private void loadSavedPresence () @Override public void sendHeartbeat() throws RemoteException { - mConnection.sendHeartbeat(mService.getHeartbeatInterval()); + + if (mConnection != null) + mConnection.sendHeartbeat(mService.getHeartbeatInterval()); + } @Override @@ -197,11 +225,14 @@ private HashMap querySessionCookie(ContentResolver cr) { return cookie; } + @Override public void logout() { + OtrChatManager.endSessionsForAccount(mConnection.getLoginUser()); mConnectionState = ImConnection.LOGGING_OUT; - mConnection.logoutAsync(); + mConnection.logout(); } + @Override public synchronized void cancelLogin() { if (mConnectionState >= ImConnection.LOGGED_IN) { // too late @@ -216,34 +247,40 @@ void suspend() { mConnection.suspend(); } + @Override public void registerConnectionListener(IConnectionListener listener) { if (listener != null) { mRemoteConnListeners.register(listener); } } + @Override public void unregisterConnectionListener(IConnectionListener listener) { if (listener != null) { mRemoteConnListeners.unregister(listener); } } + @Override public void setInvitationListener(IInvitationListener listener) { if (mInvitationListener != null) { mInvitationListener.mRemoteListener = listener; } } - - + + + @Override public IChatSessionManager getChatSessionManager() { return mChatSessionManager; } + @Override public IContactListManager getContactListManager() { return mContactListManager; } + @Override public int getChatSessionCount() { if (mChatSessionManager == null) { return 0; @@ -255,17 +292,19 @@ public Contact getLoginUser() { return mConnection.getLoginUser(); } + @Override public Presence getUserPresence() { return mConnection.getUserPresence(); } + @Override public int updateUserPresence(Presence newPresence) { try { - - + + mConnection.updateUserPresenceAsync(newPresence); - - + + } catch (ImException e) { return e.getImError().getCode(); } @@ -273,14 +312,17 @@ public int updateUserPresence(Presence newPresence) { return ImErrorInfo.NO_ERROR; } + @Override public int getState() { return mConnectionState; } + @Override public void rejectInvitation(long id) { handleInvitation(id, false); } + @Override public void acceptInvitation(long id) { handleInvitation(id, true); } @@ -375,6 +417,7 @@ private static int convertConnStateForDb(int state) { } final class ConnectionListenerAdapter implements ConnectionListener { + @Override public void onStateChanged(final int state, final ImErrorInfo error) { synchronized (this) { @@ -399,8 +442,8 @@ public void onStateChanged(final int state, final ImErrorInfo error) { saveSessionCookie(cr); } - if (mAutoLoadContacts - && mContactListManager.getState() != ContactListManager.LISTS_LOADED) { + if (mAutoLoadContacts) + { mContactListManager.loadContactLists(); } @@ -410,21 +453,16 @@ public void onStateChanged(final int state, final ImErrorInfo error) { } // mService.getStatusBarNotifier().notifyLoggedIn(mProviderId, mAccountId); - + loadSavedPresence(); + - } else if (state == ImConnection.LOGGING_OUT) { - // The engine has started to logout the connection, remove it - // from the active connection list. - mService.removeConnection(ImConnectionAdapter.this); } else if (state == ImConnection.DISCONNECTED) { - mService.removeConnection(ImConnectionAdapter.this); - clearSessionCookie(cr); // mContactListManager might still be null if we fail // immediately in loginAsync (say, an invalid host URL) - if (mContactListManager != null) { - mContactListManager.clearOnLogout(); + if (mContactListManager != null) { // n8fr8 2015-01-21 Why are we clearing this? + mContactListManager.clearOnLogout(); } mConnectionState = state; @@ -437,19 +475,29 @@ public void onStateChanged(final int state, final ImErrorInfo error) { updateAccountStatusInDb(); - final int N = mRemoteConnListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); - try { - listener.onStateChanged(ImConnectionAdapter.this, state, error); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. + synchronized (mRemoteConnListeners) { + + final int N = mRemoteConnListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); + try { + listener.onStateChanged(ImConnectionAdapter.this, state, error); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } } + mRemoteConnListeners.finishBroadcast(); + } + + if (state == ImConnection.DISCONNECTED) { + // NOTE: if this logic is changed, the logic in ImApp.MyConnListener must be changed to match + mService.removeConnection(ImConnectionAdapter.this); + } - mRemoteConnListeners.finishBroadcast(); } + @Override public void onUserPresenceUpdated() { updateAccountStatusInDb(); @@ -466,6 +514,7 @@ public void onUserPresenceUpdated() { mRemoteConnListeners.finishBroadcast(); } + @Override public void onUpdatePresenceError(final ImErrorInfo error) { final int N = mRemoteConnListeners.beginBroadcast(); for (int i = 0; i < N; i++) { @@ -484,14 +533,15 @@ public void onUpdatePresenceError(final ImErrorInfo error) { final class InvitationListenerAdapter implements InvitationListener { IInvitationListener mRemoteListener; + @Override public void onGroupInvitation(Invitation invitation) { - String sender = invitation.getSender().getScreenName(); + String sender = invitation.getSender().getUser(); ContentValues values = new ContentValues(7); values.put(Imps.Invitation.PROVIDER, mProviderId); values.put(Imps.Invitation.ACCOUNT, mAccountId); values.put(Imps.Invitation.INVITE_ID, invitation.getInviteID()); values.put(Imps.Invitation.SENDER, sender); - values.put(Imps.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName()); + values.put(Imps.Invitation.GROUP_NAME, invitation.getGroupAddress().getUser()); values.put(Imps.Invitation.NOTE, invitation.getReason()); values.put(Imps.Invitation.STATUS, Imps.Invitation.STATUS_PENDING); ContentResolver resolver = mService.getContentResolver(); diff --git a/src/info/guardianproject/otr/app/im/service/ImServiceConstants.java b/src/info/guardianproject/otr/app/im/service/ImServiceConstants.java index 2b0a9ace4..34e14fe69 100644 --- a/src/info/guardianproject/otr/app/im/service/ImServiceConstants.java +++ b/src/info/guardianproject/otr/app/im/service/ImServiceConstants.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/src/info/guardianproject/otr/app/im/service/RemoteImService.java b/src/info/guardianproject/otr/app/im/service/RemoteImService.java index 1a14587f0..04100e270 100644 --- a/src/info/guardianproject/otr/app/im/service/RemoteImService.java +++ b/src/info/guardianproject/otr/app/im/service/RemoteImService.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -17,58 +17,79 @@ package info.guardianproject.otr.app.im.service; +import info.guardianproject.cacheword.CacheWordActivityHandler; +import info.guardianproject.cacheword.CacheWordHandler; +import info.guardianproject.cacheword.ICacheWordSubscriber; import info.guardianproject.otr.IOtrKeyManager; import info.guardianproject.otr.OtrAndroidKeyManagerImpl; import info.guardianproject.otr.OtrChatManager; -import info.guardianproject.otr.OtrKeyManagerAdapter; +import info.guardianproject.otr.OtrDebugLogger; import info.guardianproject.otr.app.im.IConnectionCreationListener; import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.IRemoteImService; import info.guardianproject.otr.app.im.ImService; import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.app.ChatFileStore; +import info.guardianproject.otr.app.im.app.DummyActivity; import info.guardianproject.otr.app.im.app.ImApp; import info.guardianproject.otr.app.im.app.ImPluginHelper; import info.guardianproject.otr.app.im.app.NetworkConnectivityListener; import info.guardianproject.otr.app.im.app.NetworkConnectivityListener.State; import info.guardianproject.otr.app.im.app.NewChatActivity; import info.guardianproject.otr.app.im.engine.ConnectionFactory; -import info.guardianproject.otr.app.im.engine.HeartbeatService.Callback; import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.engine.ImException; import info.guardianproject.otr.app.im.plugin.ImPluginInfo; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.otr.app.im.provider.Imps.ProviderSettings.QueryMap; +import info.guardianproject.otr.app.im.provider.SQLCipherOpenHelper; import info.guardianproject.util.Debug; import info.guardianproject.util.LogCleaner; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Date; +import java.util.Hashtable; import java.util.List; import java.util.Map; -import java.util.Vector; import net.java.otr4j.OtrEngineListener; import net.java.otr4j.OtrKeyManager; +import net.java.otr4j.OtrKeyManagerListener; import net.java.otr4j.OtrPolicy; import net.java.otr4j.session.SessionID; -import net.java.otr4j.session.SessionStatus; +import android.annotation.TargetApi; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.NetworkInfo; +import android.net.Uri; +import android.net.Uri.Builder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; -public class RemoteImService extends Service implements OtrEngineListener, ImService { +public class RemoteImService extends Service implements OtrEngineListener, ImService, ICacheWordSubscriber { + private static final String PREV_CONNECTIONS_TRAIL_TAG = "prev_connections"; + private static final String CONNECTIONS_TRAIL_TAG = "connections"; + private static final String LAST_SWIPE_TRAIL_TAG = "last_swipe"; + private static final String SERVICE_DESTROY_TRAIL_TAG = "service_destroy"; + private static final String PREV_SERVICE_CREATE_TRAIL_TAG = "prev_service_create"; + private static final String SERVICE_CREATE_TRAIL_KEY = "service_create"; private static final String[] ACCOUNT_PROJECTION = { Imps.Account._ID, Imps.Account.PROVIDER, Imps.Account.USERNAME, Imps.Account.PASSWORD, }; @@ -76,18 +97,12 @@ public class RemoteImService extends Service implements OtrEngineListener, ImSer private static final int ACCOUNT_ID_COLUMN = 0; private static final int ACCOUNT_PROVIDER_COLUMN = 1; private static final int ACCOUNT_USERNAME_COLUMN = 2; - private static final int ACCOUNT_PASSOWRD_COLUMN = 3; + private static final int ACCOUNT_PASSOWRD_COLUMN = 3; private static final int EVENT_SHOW_TOAST = 100; - private static final int EVENT_NETWORK_STATE_CHANGED = 200; - - // Our heartbeat interval in seconds. - // The user controlled preference heartbeat interval is in these units (i.e. minutes). - private static final long HEARTBEAT_INTERVAL = 1000 * 60; private StatusBarNotifier mStatusBarNotifier; private Handler mServiceHandler; - NetworkConnectivityListener mNetworkConnectivityListener; private int mNetworkType; private boolean mNeedCheckAutoLogin; @@ -95,102 +110,159 @@ public class RemoteImService extends Service implements OtrEngineListener, ImSer private OtrChatManager mOtrChatManager; private ImPluginHelper mPluginHelper; - Vector mConnections; + private Hashtable mConnections; private Imps.ProviderSettings.QueryMap mGlobalSettings; private Handler mHandler; final RemoteCallbackList mRemoteListeners = new RemoteCallbackList(); - private ForegroundStarter mForegroundStarter; public long mHeartbeatInterval; + private WakeLock mWakeLock; + private NetworkConnectivityListener.State mNetworkState; - private static final String TAG = "Gibberbot.ImService"; + private CacheWordHandler mCacheWord = null; - public RemoteImService() { - mConnections = new Vector(); - mHandler = new Handler(); - } + private NotificationManager mNotifyManager; + NotificationCompat.Builder mNotifyBuilder; + private int mNumNotify = 0; + private final static int notifyId = 7777; + + private static final String TAG = "GB.ImService"; public long getHeartbeatInterval() { return mHeartbeatInterval; } public static void debug(String msg) { - LogCleaner.debug(TAG, msg); + LogCleaner.debug(TAG, msg); + // Log.d(TAG, msg); + } public static void debug(String msg, Exception e) { LogCleaner.error(TAG, msg, e); } - private synchronized void initOtr() { + private void updateOtrPolicy () + { + int otrPolicy = convertPolicy(); + mOtrChatManager.setPolicy(otrPolicy); + + } + + private synchronized OtrChatManager initOtrChatManager() { int otrPolicy = convertPolicy(); if (mOtrChatManager == null) { - try { + try + { OtrKeyManager otrKeyManager = OtrAndroidKeyManagerImpl.getInstance(this); + if (otrKeyManager != null) { - // TODO OTRCHAT add support for more than one connection type (this is a kludge) mOtrChatManager = OtrChatManager.getInstance(otrPolicy, this, otrKeyManager); mOtrChatManager.addOtrEngineListener(this); + + otrKeyManager.addListener(new OtrKeyManagerListener() { + public void verificationStatusChanged(SessionID session) { + boolean isVerified = mOtrChatManager.getKeyManager().isVerified(session); + String msg = session + ": verification status=" + isVerified; + + OtrDebugLogger.log(msg); + + } + + public void remoteVerifiedUs(SessionID session) { + String msg = session + ": remote verified us"; + OtrDebugLogger.log(msg); + + showToast(getString(R.string.remote_verified_us),Toast.LENGTH_SHORT); + // if (!isRemoteKeyVerified(session)) + // showWarning(session, mContext.getApplicationContext().getString(R.string.remote_verified_us)); + } + }); } - else - { - debug("OtrKeyManager NOT INIT'd; is key null?"); - } - } catch (Exception e) { - debug("can't get otr manager", e); + + } + catch (Exception e) + { + OtrDebugLogger.log("unable to init OTR manager", e); } + } else { mOtrChatManager.setPolicy(otrPolicy); } + + return mOtrChatManager; } private int convertPolicy() { int otrPolicy = OtrPolicy.OPPORTUNISTIC; + QueryMap gSettings = getGlobalSettings(); + if (gSettings != null) + { + String otrModeSelect = gSettings.getOtrMode(); - String otrModeSelect = getGlobalSettings().getOtrMode(); - - if (otrModeSelect.equals("auto")) { - otrPolicy = OtrPolicy.OPPORTUNISTIC; - } else if (otrModeSelect.equals("disabled")) { - otrPolicy = OtrPolicy.NEVER; + if (otrModeSelect.equals("auto")) { + otrPolicy = OtrPolicy.OPPORTUNISTIC; + } else if (otrModeSelect.equals("disabled")) { + otrPolicy = OtrPolicy.NEVER; - } else if (otrModeSelect.equals("force")) { - otrPolicy = OtrPolicy.OTRL_POLICY_ALWAYS; + } else if (otrModeSelect.equals("force")) { + otrPolicy = OtrPolicy.OTRL_POLICY_ALWAYS; - } else if (otrModeSelect.equals("requested")) { - otrPolicy = OtrPolicy.OTRL_POLICY_MANUAL; + } else if (otrModeSelect.equals("requested")) { + otrPolicy = OtrPolicy.OTRL_POLICY_MANUAL; + } } + return otrPolicy; } - private Imps.ProviderSettings.QueryMap getGlobalSettings() { + private synchronized Imps.ProviderSettings.QueryMap getGlobalSettings() { if (mGlobalSettings == null) { - mGlobalSettings = new Imps.ProviderSettings.QueryMap(getContentResolver(), true, - mHandler); + + ContentResolver contentResolver = getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + if (cursor == null) + return null; + + mGlobalSettings = new Imps.ProviderSettings.QueryMap(cursor, contentResolver, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, true, mHandler); } + return mGlobalSettings; } @Override public void onCreate() { debug("ImService started"); + final String prev = Debug.getTrail(this, SERVICE_CREATE_TRAIL_KEY); + if (prev != null) + Debug.recordTrail(this, PREV_SERVICE_CREATE_TRAIL_TAG, prev); + Debug.recordTrail(this, SERVICE_CREATE_TRAIL_KEY, new Date()); + final String prevConnections = Debug.getTrail(this, CONNECTIONS_TRAIL_TAG); + if (prevConnections != null) + Debug.recordTrail(this, PREV_CONNECTIONS_TRAIL_TAG, prevConnections); + Debug.recordTrail(this, CONNECTIONS_TRAIL_TAG, "0"); + + mConnections = new Hashtable(); + mHandler = new Handler(); + Debug.onServiceStart(); + PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "IM_WAKELOCK"); + // Clear all account statii to logged-out, since we just got started and we don't want // leftovers from any previous crash. clearConnectionStatii(); - + mStatusBarNotifier = new StatusBarNotifier(this); mServiceHandler = new ServiceHandler(); - mNetworkConnectivityListener = new NetworkConnectivityListener(); - mNetworkConnectivityListener.registerHandler(mServiceHandler, EVENT_NETWORK_STATE_CHANGED); - mNetworkConnectivityListener.startListening(this); - //mSettingsMonitor = new SettingsMonitor(); /* @@ -198,110 +270,201 @@ public void onCreate() { intentFilter.addAction(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); registerReceiver(mSettingsMonitor, intentFilter); */ - + // setBackgroundData(ImApp.getApplication().isNetworkAvailableAndConnected()); - + mPluginHelper = ImPluginHelper.getInstance(this); mPluginHelper.loadAvailablePlugins(); - AndroidSystemService.getInstance().initialize(this); - AndroidSystemService.getInstance().getHeartbeatService() - .startHeartbeat(new HeartbeatHandler(), HEARTBEAT_INTERVAL); // Have the heartbeat start autoLogin, unless onStart turns this off mNeedCheckAutoLogin = true; - // if (getGlobalSettings().getUseForegroundPriority()) - startForegroundCompat(); + HeartbeatService.startBeating(getApplicationContext()); + } + + private void connectToCacheWord () + { + mCacheWord = new CacheWordActivityHandler(this, (ICacheWordSubscriber)this); + mCacheWord.connectToService(); } private void startForegroundCompat() { - Notification notification = new Notification(R.drawable.notify_chatsecure, getString(R.string.app_name), - System.currentTimeMillis()); - notification.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; + + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);; + + mNotifyBuilder = new NotificationCompat.Builder(this) + .setContentTitle(getString(R.string.app_name)) + .setSmallIcon(R.drawable.notify_chatsecure); + + // note.setOnlyAlertOnce(true); + mNotifyBuilder.setOngoing(true); + mNotifyBuilder.setWhen( System.currentTimeMillis() ); + Intent notificationIntent = new Intent(this, NewChatActivity.class); - notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - notification.contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(this, getString(R.string.app_name), getString(R.string.presence_available), notification.contentIntent); + PendingIntent launchIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0); + + mNotifyBuilder.setContentIntent(launchIntent); + + mNotifyBuilder.setContentText(getString(R.string.app_unlocked)); - mForegroundStarter = new ForegroundStarter(this); - mForegroundStarter.startForegroundCompat(1000, notification); + startForeground(notifyId, mNotifyBuilder.build()); } - class HeartbeatHandler implements Callback { + public void sendHeartbeat() { + Debug.onHeartbeat(); + try { + if (mNeedCheckAutoLogin && mNetworkState != NetworkConnectivityListener.State.NOT_CONNECTED) { + debug("autoLogin from heartbeat"); + mNeedCheckAutoLogin = false; + autoLogin(); + } - @SuppressWarnings("finally") - @Override - public long sendHeartbeat() { - try { - if (mNeedCheckAutoLogin - && mNetworkConnectivityListener.getState() != State.NOT_CONNECTED) { - debug("autoLogin from heartbeat"); - mNeedCheckAutoLogin = false; - autoLogin(); - } - - mHeartbeatInterval = getGlobalSettings().getHeartbeatInterval(); - - for (Iterator iter = mConnections.iterator(); iter.hasNext();) { - ImConnectionAdapter conn = iter.next(); - conn.sendHeartbeat(); - } - } finally { - return HEARTBEAT_INTERVAL; + mHeartbeatInterval = getGlobalSettings().getHeartbeatInterval(); + debug("heartbeat interval: " + mHeartbeatInterval); + + for (ImConnectionAdapter conn : mConnections.values()) + { + conn.sendHeartbeat(); } + } finally { + return; } - } @Override public int onStartCommand(Intent intent, int flags, int startId) { - - return super.onStartCommand(intent, flags, startId); - } - - - @Override - public void onLowMemory() { - super.onLowMemory(); - } + //if the service restarted, then we need to reconnect/reinit to cacheword + if ((flags & START_FLAG_REDELIVERY)!=0) // if crash restart... + { + if (ImApp.mUsingCacheword) + connectToCacheWord(); + else + { + if (openEncryptedStores(null, false)) { + try + { + ChatFileStore.initWithoutPassword(this); + } + catch (Exception e) + { + Log.d(ImApp.LOG_TAG,"unable to mount VFS store"); //but let's not crash the whole app right now + } + } else { + connectToCacheWord(); //first time setup + } + } + } - @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); + if (intent != null) + { + if (HeartbeatService.HEARTBEAT_ACTION.equals(intent.getAction())) { + // Log.d(TAG, "HEARTBEAT"); + if (!mWakeLock.isHeld()) + { + try { + mWakeLock.acquire(); + sendHeartbeat(); + } finally { + mWakeLock.release(); + } + } + return START_REDELIVER_INTENT; + } + + if (HeartbeatService.NETWORK_STATE_ACTION.equals(intent.getAction())) { + NetworkInfo networkInfo = (NetworkInfo) intent + .getParcelableExtra(HeartbeatService.NETWORK_INFO_EXTRA); + NetworkConnectivityListener.State networkState = State.values()[intent.getIntExtra(HeartbeatService.NETWORK_STATE_EXTRA, 0)]; + + if (!mWakeLock.isHeld()) + { + try { + mWakeLock.acquire(); + networkStateChanged(networkInfo, networkState); + + } finally { + mWakeLock.release(); + } + } + else + { + networkStateChanged(networkInfo, networkState); + + } + + return START_REDELIVER_INTENT; + } + + + if (intent.hasExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN)) + mNeedCheckAutoLogin = intent.getBooleanExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, + false); + + } - if (intent != null && intent.hasExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN)) - mNeedCheckAutoLogin = intent.getBooleanExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, - false); - else - mNeedCheckAutoLogin = true; - debug("ImService.onStart, checkAutoLogin=" + mNeedCheckAutoLogin + " intent =" + intent + " startId =" + startId); // Check and login accounts if network is ready, otherwise it's checked // when the network becomes available. - if (mNeedCheckAutoLogin && mNetworkConnectivityListener.getState() != State.NOT_CONNECTED) { + if (mNeedCheckAutoLogin && mNetworkState != NetworkConnectivityListener.State.NOT_CONNECTED) { mNeedCheckAutoLogin = false; autoLogin(); } + + return START_STICKY; } - - + + + + @Override + public void onLowMemory() { + + } + + + @Override + public void onTrimMemory(int level) { + + /** + * TRIM_MEMORY_COMPLETE, TRIM_MEMORY_MODERATE, TRIM_MEMORY_BACKGROUND, + TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_CRITICAL, TRIM_MEMORY_RUNNING_LOW, or + TRIM_MEMORY_RUNNING_MODERATE. + */ + + switch (level) + { + case TRIM_MEMORY_BACKGROUND: + case TRIM_MEMORY_UI_HIDDEN: + + Log.w(TAG,"in the background/no UI, so we should be efficient"); + + return; + + case TRIM_MEMORY_RUNNING_LOW: + case TRIM_MEMORY_RUNNING_CRITICAL: + + Log.w(TAG,"memory is low or critical"); + + return; + + } + } + private void clearConnectionStatii() { ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(2); values.put(Imps.AccountStatus.PRESENCE_STATUS, Imps.Presence.OFFLINE); values.put(Imps.AccountStatus.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); - + try { - //insert on the "account_status" uri actually replaces the existing value + //insert on the "account_status" uri actually replaces the existing value cr.update(Imps.AccountStatus.CONTENT_URI, values, null, null); } catch (Exception e) @@ -312,14 +475,18 @@ private void clearConnectionStatii() { } - private void autoLogin() { - - + private boolean autoLogin() { + // Try empty passphrase. We can't autologin if this fails. + if (!Imps.setEmptyPassphrase(this, true)) { + debug("Cannot autologin with non-empty passphrase"); + return false; + } + if (!mConnections.isEmpty()) { // This can happen because the UI process may be restarted and may think that we need // to autologin, while we (the Service process) are already up. debug("Got autoLogin request, but we have one or more connections"); - return; + return false; } debug("Scanning accounts and login automatically"); @@ -331,19 +498,20 @@ private void autoLogin() { null); if (cursor == null) { Log.w(TAG, "Can't query account!"); - return; + return false; } while (cursor.moveToNext()) { long accountId = cursor.getLong(ACCOUNT_ID_COLUMN); long providerId = cursor.getLong(ACCOUNT_PROVIDER_COLUMN); - IImConnection conn = createConnection(providerId, accountId); - + IImConnection conn = do_createConnection(providerId, accountId); + try { if (conn.getState() != ImConnection.LOGGED_IN) { try { conn.login(null, true, true); + } catch (RemoteException e) { Log.w(TAG, "Logging error while automatically login!"); } @@ -354,6 +522,8 @@ private void autoLogin() { } } cursor.close(); + + return true; } private Map loadProviderSettings(long providerId) { @@ -394,34 +564,23 @@ private Map loadProviderSettings(long providerId) { @Override public void onDestroy() { + Debug.recordTrail(this, SERVICE_DESTROY_TRAIL_TAG, new Date()); + + if (mCacheWord != null) + mCacheWord.disconnect(); - if (mForegroundStarter != null) - mForegroundStarter.stopForegroundCompat(); + HeartbeatService.stopBeating(getApplicationContext()); Log.w(TAG, "ImService stopped."); - for (ImConnectionAdapter conn : mConnections) { + for (ImConnectionAdapter conn : mConnections.values()) { conn.logout(); } - - - AndroidSystemService.getInstance().shutdown(); - mNetworkConnectivityListener.unregisterHandler(mServiceHandler); - mNetworkConnectivityListener.stopListening(); - mNetworkConnectivityListener = null; - - // unregisterReceiver(mSettingsMonitor); + if (mUseForeground) + stopForeground(true); if (mGlobalSettings != null) mGlobalSettings.close(); - - /* - if (mKillProcessOnStop) - { - int pid = android.os.Process.myPid(); - Log.w(TAG, "ImService: killing process: " + pid); - android.os.Process.killProcess(pid); - }*/ } @@ -440,8 +599,7 @@ public StatusBarNotifier getStatusBarNotifier() { } public OtrChatManager getOtrChatManager() { - initOtr() ; - return mOtrChatManager; + return initOtrChatManager(); } public void scheduleReconnect(long delay) { @@ -457,32 +615,72 @@ public void run() { }, delay); } - IImConnection createConnection(long providerId, long accountId) { - + private boolean mUseForeground = false; + + private IImConnection do_createConnection(long providerId, long accountId) { + + //make sure OTR is init'd before you create your first connection + initOtrChatManager(); + + QueryMap gSettings = getGlobalSettings(); + + if (gSettings == null) + return null; + + if (mConnections.size() == 0) + { + mUseForeground = gSettings.getUseForegroundPriority(); + + if (mUseForeground) + startForegroundCompat(); + } + Map settings = loadProviderSettings(providerId); ConnectionFactory factory = ConnectionFactory.getInstance(); try { ImConnection conn = factory.createConnection(settings, this); - ImConnectionAdapter imConnectionAdapter = + conn.initUser(providerId, accountId); + ImConnectionAdapter imConnectionAdapter = new ImConnectionAdapter(providerId, accountId, conn, this); - mConnections.add(imConnectionAdapter); - - initOtr(); - mOtrChatManager.addConnection(imConnectionAdapter); - - final int N = mRemoteListeners.beginBroadcast(); - for (int i = 0; i < N; i++) { - IConnectionCreationListener listener = mRemoteListeners.getBroadcastItem(i); - try { - listener.onConnectionCreated(imConnectionAdapter); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing the - // dead listeners. + + + ContentResolver contentResolver = getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(providerId)},null); + + if (cursor == null) + throw new ImException ("unable to query the provider settings"); + + Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( + cursor, contentResolver, providerId, false, null); + String userName = Imps.Account.getUserName(contentResolver, accountId); + String domain = providerSettings.getDomain(); + providerSettings.close(); + + mConnections.put(userName + '@' + domain,imConnectionAdapter); + Debug.recordTrail(this, CONNECTIONS_TRAIL_TAG, "" + mConnections.size()); + + synchronized (mRemoteListeners) + { + try + { + final int N = mRemoteListeners.beginBroadcast(); + for (int i = 0; i < N; i++) { + IConnectionCreationListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.onConnectionCreated(imConnectionAdapter); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + } + } + } + finally + { + mRemoteListeners.finishBroadcast(); } } - mRemoteListeners.finishBroadcast(); - return imConnectionAdapter; } catch (ImException e) { debug("Error creating connection", e); @@ -491,79 +689,115 @@ IImConnection createConnection(long providerId, long accountId) { } void removeConnection(ImConnectionAdapter connection) { - mOtrChatManager.removeConnection(connection); + mConnections.remove(connection); + + if (mConnections.size() == 0) + if (getGlobalSettings().getUseForegroundPriority()) + stopForeground(true); } - private boolean isNetworkAvailable() { - return mNetworkConnectivityListener.getState() == State.CONNECTED; + boolean isNetworkAvailable () + { + return mNetworkState == NetworkConnectivityListener.State.CONNECTED; } - void networkStateChanged() { - if (mNetworkConnectivityListener == null) { - return; - } - NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo(); - NetworkInfo.State state = networkInfo.getState(); + void networkStateChanged(NetworkInfo networkInfo, NetworkConnectivityListener.State networkState) { + + mNetworkType = networkInfo != null ? networkInfo.getType() : -1; - debug("networkStateChanged:" + state); + debug("networkStateChanged: type=" + networkInfo + " state=" + networkState); - int oldType = mNetworkType; - mNetworkType = networkInfo.getType(); + if (mNetworkState != networkState) { - // Notify the connection that network type has changed. Note that this - // only work for connected connections, we need to reestablish if it's - // suspended. - if (mNetworkType != oldType && isNetworkAvailable()) { - for (ImConnectionAdapter conn : mConnections) { + mNetworkState = networkState; + + for (ImConnectionAdapter conn : mConnections.values()) conn.networkTypeChanged(); + + //update the notification + if (mNotifyBuilder != null) + { + String message = ""; + + if (!isNetworkAvailable()) + { + message = getString(R.string.error_suspended_connection); + mNotifyBuilder.setSmallIcon(R.drawable.notify_chatsecure_offline); + } + else + { + message = getString(R.string.app_unlocked); + mNotifyBuilder.setSmallIcon(R.drawable.notify_chatsecure); + } + + mNotifyBuilder.setContentText(message); + // Because the ID remains unchanged, the existing notification is + // updated. + mNotifyManager.notify( + notifyId, + mNotifyBuilder.build()); + } + + } - switch (state) { - case CONNECTED: - if (mNeedCheckAutoLogin) { - mNeedCheckAutoLogin = false; - autoLogin(); - break; - } - reestablishConnections(); - break; + + if (isNetworkAvailable()) + { + boolean reConnd = reestablishConnections(); + + if (!reConnd) + { + if (mNeedCheckAutoLogin) { + mNeedCheckAutoLogin = false; + autoLogin(); + } + } - case DISCONNECTED: - if (!isNetworkAvailable()) { - suspendConnections(); - } - break; } + else + { + suspendConnections(); + } + + } // package private for inner class access - void reestablishConnections() { + boolean reestablishConnections() { + if (!isNetworkAvailable()) { - return; + return false; } - for (ImConnectionAdapter conn : mConnections) { + for (ImConnectionAdapter conn : mConnections.values()) { int connState = conn.getState(); if (connState == ImConnection.SUSPENDED) { conn.reestablishSession(); } } + return mConnections.values().size() > 0; } private void suspendConnections() { - for (ImConnectionAdapter conn : mConnections) { - if (conn.getState() != ImConnection.LOGGED_IN) { - continue; - } - conn.suspend(); + for (ImConnectionAdapter conn : mConnections.values()) { + if (conn.getState() == ImConnection.LOGGED_IN || conn.getState() == ImConnection.LOGGING_IN) { + conn.suspend(); + } } } + + public ImConnectionAdapter getConnection(String username) { + return mConnections.get(username); + } + + private final IRemoteImService.Stub mBinder = new IRemoteImService.Stub() { @Override @@ -587,13 +821,13 @@ public void removeConnectionCreatedListener(IConnectionCreationListener listener @Override public IImConnection createConnection(long providerId, long accountId) { - return RemoteImService.this.createConnection(providerId, accountId); + return RemoteImService.this.do_createConnection(providerId, accountId); } @Override public List getActiveConnections() { ArrayList result = new ArrayList(mConnections.size()); - for (IImConnection conn : mConnections) { + for (IImConnection conn : mConnections.values()) { result.add(conn.asBinder()); } return result; @@ -608,27 +842,44 @@ public void dismissNotifications(long providerId) { public void dismissChatNotification(long providerId, String username) { mStatusBarNotifier.dismissChatNotification(providerId, username); } - + @Override - public boolean unlockOtrStore (String password) + public boolean unlockOtrStore (String password) { OtrAndroidKeyManagerImpl.setKeyStorePassword(password); return true; } @Override - public IOtrKeyManager getOtrKeyManager(String accountId) throws RemoteException { - return new OtrKeyManagerAdapter(mOtrChatManager, null, accountId); + public IOtrKeyManager getOtrKeyManager() + { + + OtrAndroidKeyManagerImpl keyMgr = OtrAndroidKeyManagerImpl.getInstance(RemoteImService.this); + return keyMgr; + } - + + @Override public void setKillProcessOnStop (boolean killProcessOnStop) { mKillProcessOnStop = killProcessOnStop; } - + + @Override + public void enableDebugLogging (boolean debug) + { + Debug.DEBUG_ENABLED = debug; + } + + @Override + public void updateStateFromSettings() throws RemoteException { + + updateOtrPolicy (); + + } }; - + private boolean mKillProcessOnStop = false; /* @@ -658,10 +909,6 @@ public void handleMessage(Message msg) { Toast.makeText(RemoteImService.this, (CharSequence) msg.obj, msg.arg1).show(); break; - case EVENT_NETWORK_STATE_CHANGED: - networkStateChanged(); - break; - default: } } @@ -670,21 +917,101 @@ public void handleMessage(Message msg) { @Override public void sessionStatusChanged(SessionID sessionID) { - SessionStatus sStatus = mOtrChatManager.getSessionStatus(sessionID); + //this method does nothing! + // Log.d(TAG,"OTR session status changed: " + sessionID.getRemoteUserId()); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public void onTaskRemoved(Intent rootIntent) { + Debug.recordTrail(this, LAST_SWIPE_TRAIL_TAG, new Date()); + Intent intent = new Intent(this, DummyActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= 11) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } + + @Override + public void onCacheWordLocked() { + //do nothing here? + } - String msg = ""; + @Override + public void onCacheWordOpened() { + + byte[] encryptionKey = mCacheWord.getEncryptionKey(); + openEncryptedStores(encryptionKey, true); - if (sStatus == SessionStatus.PLAINTEXT) { - msg = getString(R.string.otr_session_status_plaintext); + // this is no longer configurable + // int defaultTimeout = 60 * Integer.parseInt(mPrefs.getString("pref_cacheword_timeout",ImApp.DEFAULT_TIMEOUT_CACHEWORD)); + // mCacheWord.setTimeoutSeconds(defaultTimeout); + ChatFileStore.init(this, encryptionKey); - } else if (sStatus == SessionStatus.ENCRYPTED) { - msg = getString(R.string.otr_session_status_encrypted); + } - } else if (sStatus == SessionStatus.FINISHED) { - msg = getString(R.string.otr_session_status_finished); + @Override + public void onCacheWordUninitialized() { + // TODO Auto-generated method stub + + } + + private boolean openEncryptedStores(byte[] key, boolean allowCreate) { + String pkey = (key != null) ? new String(SQLCipherOpenHelper.encodeRawKey(key)) : ""; + + OtrAndroidKeyManagerImpl.setKeyStorePassword(pkey); + + if (cursorUnlocked(pkey, allowCreate)) { + + return true; + } else { + return false; } + } + + @SuppressWarnings("deprecation") + private boolean cursorUnlocked(String pKey, boolean allowCreate) { + try { + Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; + + Builder builder = uri.buildUpon(); + if (pKey != null) + builder.appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pKey); + if (!allowCreate) + builder = builder.appendQueryParameter(ImApp.NO_CREATE_KEY, "1"); + uri = builder.build(); + + String[] PROVIDER_PROJECTION = { Imps.Provider._ID}; + ContentResolver contentResolver = getContentResolver(); - //showToast(msg, Toast.LENGTH_SHORT); + Cursor providerCursor = contentResolver.query(uri,PROVIDER_PROJECTION, Imps.Provider.CATEGORY + "=?" /* selection */, + new String[] { ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + if (providerCursor != null) + { + ImPluginHelper.getInstance(this).loadAvailablePlugins(); + + providerCursor.moveToFirst(); + providerCursor.close(); + + return true; + } + else + { + return false; + } + + } catch (Exception e) { + // Only complain if we thought this password should succeed + if (allowCreate) { + Log.e(ImApp.LOG_TAG, e.getMessage(), e); + + } + + // needs to be unlocked + return false; + } } + } diff --git a/src/info/guardianproject/otr/app/im/service/StatusBarNotifier.java b/src/info/guardianproject/otr/app/im/service/StatusBarNotifier.java index 3d3687416..b7535788d 100644 --- a/src/info/guardianproject/otr/app/im/service/StatusBarNotifier.java +++ b/src/info/guardianproject/otr/app/im/service/StatusBarNotifier.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open * Source Project - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,9 +18,10 @@ package info.guardianproject.otr.app.im.service; import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.ContactListActivity; import info.guardianproject.otr.app.im.app.NewChatActivity; +import info.guardianproject.otr.app.im.app.WelcomeActivity; import info.guardianproject.otr.app.im.provider.Imps; +import info.guardianproject.util.SystemServices; import java.util.HashMap; @@ -29,9 +30,11 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; @@ -81,7 +84,13 @@ public void notifyChat(long providerId, long accountId, long chatId, String user Intent intent = new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId( Imps.Chats.CONTENT_URI, chatId)); intent.addCategory(info.guardianproject.otr.app.im.app.ImApp.IMPS_CATEGORY); - notify(username, title, snippet, msg, providerId, accountId, intent, lightWeightNotify); + notify(username, title, snippet, msg, providerId, accountId, intent, lightWeightNotify, R.drawable.ic_stat_status); + } + + public void notifyError(String username, String error) { + + Intent intent = new Intent(mContext, NewChatActivity.class); + notify(username, error, error, error, -1, -1, intent, true, R.drawable.ic_stat_status); } public void notifySubscriptionRequest(long providerId, long accountId, long contactId, @@ -97,7 +106,7 @@ public void notifySubscriptionRequest(long providerId, long accountId, long cont ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, contactId)); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); - notify(username, title, message, message, providerId, accountId, intent, false); + notify(username, title, message, message, providerId, accountId, intent, false, R.drawable.ic_stat_status); } public void notifyGroupInvitation(long providerId, long accountId, long invitationId, @@ -108,7 +117,7 @@ public void notifyGroupInvitation(long providerId, long accountId, long invitati String title = mContext.getString(R.string.notify_groupchat_label); String message = mContext.getString(R.string.group_chat_invite_notify_text, username); - notify(username, title, message, message, providerId, accountId, intent, false); + notify(username, title, message, message, providerId, accountId, intent, false, R.drawable.ic_stat_status); } public void notifyLoggedIn(long providerId, long accountId) { @@ -118,7 +127,18 @@ public void notifyLoggedIn(long providerId, long accountId) { String title = mContext.getString(R.string.app_name); String message = mContext.getString(R.string.presence_available); - notify(message, title, message, message, providerId, accountId, intent, false); + notify(message, title, message, message, providerId, accountId, intent, false, R.drawable.ic_stat_status); + } + + public void notifyLocked() { + + Intent intent = new Intent(mContext, WelcomeActivity.class); + + String title = mContext.getString(R.string.app_name); + String message = mContext.getString(R.string.account_setup_pers_now_title); + notify(message, title, message, message, -1, -1, intent, true, R.drawable.notify_chatsecure); + + } public void notifyDisconnected(long providerId, long accountId) { @@ -128,19 +148,27 @@ public void notifyDisconnected(long providerId, long accountId) { String title = mContext.getString(R.string.app_name); String message = mContext.getString(R.string.presence_offline); - notify(message, title, message, message, providerId, accountId, intent, false); + notify(message, title, message, message, providerId, accountId, intent, false, R.drawable.ic_stat_status); + } + + + public void notifyFile(long providerId, long accountId, long id, String username, + String nickname, String path, Uri uri, String type, boolean b) { + String title = nickname; + String message = mContext.getString(R.string.file_notify_text, path); + Intent intent = SystemServices.Viewer.getViewIntent(uri, type); + notify(message, title, message, message, providerId, accountId, intent, false, R.drawable.ic_stat_status); } - public void dismissNotifications(long providerId) { - /* + synchronized (mNotificationInfos) { NotificationInfo info = mNotificationInfos.get(providerId); if (info != null) { mNotificationManager.cancel(info.computeNotificationId()); mNotificationInfos.remove(providerId); } - }*/ + } } public void dismissChatNotification(long providerId, String username) { @@ -164,26 +192,35 @@ public void dismissChatNotification(long providerId, String username) { log("cancelNotify: new notification" + " mTitle=" + info.getTitle() + " mMessage=" + info.getMessage() + " mIntent=" + info.getIntent()); } - mNotificationManager.notify(info.computeNotificationId(), - info.createNotification("", true)); + mNotificationManager.cancel(info.computeNotificationId()); } } } + private Imps.ProviderSettings.QueryMap getGlobalSettings() { if (mGlobalSettings == null) { - mGlobalSettings = new Imps.ProviderSettings.QueryMap(mContext.getContentResolver(), - true, mHandler); + + ContentResolver contentResolver = mContext.getContentResolver(); + + Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS)},null); + + if (cursor == null) + return null; + + mGlobalSettings = new Imps.ProviderSettings.QueryMap(cursor, contentResolver, Imps.ProviderSettings.PROVIDER_ID_FOR_GLOBAL_SETTINGS, true, mHandler); } + return mGlobalSettings; } + private boolean isNotificationEnabled(long providerId) { return getGlobalSettings().getEnableNotification(); } private void notify(String sender, String title, String tickerText, String message, - long providerId, long accountId, Intent intent, boolean lightWeightNotify) { + long providerId, long accountId, Intent intent, boolean lightWeightNotify, int icon) { // intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); @@ -195,11 +232,13 @@ private void notify(String sender, String title, String tickerText, String messa info = new NotificationInfo(providerId, accountId); mNotificationInfos.put(sender, info); } - info.addItem(sender, title, message, intent); + info.addItem(sender, title, message, intent); } mNotificationManager.notify(info.computeNotificationId(), - info.createNotification(tickerText, lightWeightNotify)); + info.createNotification(tickerText, lightWeightNotify, icon)); + + } private void setRinger(long providerId, NotificationCompat.Builder builder) { @@ -227,7 +266,7 @@ class Item { String mTitle; String mMessage; Intent mIntent; - + public Item(String title, String message, Intent intent) { mTitle = title; mMessage = message; @@ -248,6 +287,8 @@ public NotificationInfo(long providerId, long accountId) { } public int computeNotificationId() { + if (lastItem == null) + return (int)mProviderId; return lastItem.mTitle.hashCode(); } @@ -274,33 +315,33 @@ public synchronized boolean removeItem(String sender) { return true; } - public Notification createNotification(String tickerText, boolean lightWeightNotify) { + public Notification createNotification(String tickerText, boolean lightWeightNotify, int icon) { NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); Intent intent = getIntent(); builder - .setSmallIcon(R.drawable.ic_stat_status) + .setSmallIcon(icon) .setTicker(lightWeightNotify ? null : tickerText) .setWhen(System.currentTimeMillis()) .setLights(0xff00ff00, 300, 1000) .setContentTitle(getTitle()) .setContentText(getMessage()) - .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0)) - .setAutoCancel(true) - ; + .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)) + .setAutoCancel(true); + if (!(lightWeightNotify || shouldSuppressSoundNotification())) { setRinger(mProviderId, builder); } - - return builder.getNotification(); + + return builder.build(); } private Intent getDefaultIntent() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType(Imps.Contacts.CONTENT_TYPE); - intent.setClass(mContext, ContactListActivity.class); + intent.setClass(mContext, NewChatActivity.class); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + // intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } @@ -311,7 +352,7 @@ private Intent getMultipleNotificationIntent() { intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_SHOW_MULTIPLE, true); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + // intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } diff --git a/src/info/guardianproject/otr/app/im/ui/AboutActivity.java b/src/info/guardianproject/otr/app/im/ui/AboutActivity.java index b513ce2c3..45b1fad69 100644 --- a/src/info/guardianproject/otr/app/im/ui/AboutActivity.java +++ b/src/info/guardianproject/otr/app/im/ui/AboutActivity.java @@ -1,7 +1,7 @@ package info.guardianproject.otr.app.im.ui; import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.AccountListActivity; +import info.guardianproject.otr.app.im.app.AccountWizardActivity; import info.guardianproject.otr.app.im.app.ThemeableActivity; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -12,7 +12,7 @@ import android.widget.TextView; public class AboutActivity extends ThemeableActivity implements OnClickListener { - + private int title[] = { R.string.about_welcome_title, R.string.about_otr_title, R.string.about_security_title }; @@ -49,7 +49,7 @@ public void onClick(View v) { @Override public void onClick(View v) { finish(); - Intent intent = new Intent(getBaseContext(), AccountListActivity.class); + Intent intent = new Intent(getBaseContext(), AccountWizardActivity.class); startActivity(intent); } } }, diff --git a/src/info/guardianproject/otr/app/im/ui/AppPassphraseActivity.java b/src/info/guardianproject/otr/app/im/ui/AppPassphraseActivity.java deleted file mode 100644 index 87e13c620..000000000 --- a/src/info/guardianproject/otr/app/im/ui/AppPassphraseActivity.java +++ /dev/null @@ -1,96 +0,0 @@ -package info.guardianproject.otr.app.im.ui; - -import info.guardianproject.otr.app.im.R; -import info.guardianproject.otr.app.im.app.ThemeableActivity; -import android.app.Activity; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.EditText; - -public class AppPassphraseActivity extends ThemeableActivity { - - private Dialog dl; - - private void gotCredentials(String usr, String pwd) { - - } - - private void foo() { - dl = new Dialog(this); - dl.setTitle("Information Prompt"); - - dl.setContentView(R.layout.auth_view); - EditText inputBox1 = (EditText) dl.findViewById(R.id.user); - inputBox1.setText(""); - EditText inputBox2 = (EditText) dl.findViewById(R.id.pwd); - inputBox2.setText(""); - - Button bOk = (Button) dl.findViewById(R.id.ok); - bOk.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - EditText inputBox1 = (EditText) dl.findViewById(R.id.user); - String usr = inputBox1.getText().toString(); - inputBox1.setText(""); - - EditText inputBox2 = (EditText) dl.findViewById(R.id.pwd); - String pwd = inputBox2.getText().toString(); - inputBox2.setText(""); - - dl.dismiss(); - - gotCredentials(usr, pwd); - } - }); - } - - private void showPasswordDialog() { - dl = new Dialog(this); - dl.setTitle("Enter Password Please"); - - dl.setContentView(R.layout.password_prompt); - EditText inputBox1 = (EditText) dl.findViewById(R.id.pwd); - inputBox1.setText(""); - - Button bOk = (Button) dl.findViewById(R.id.ok); - bOk.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - - EditText inputBox2 = (EditText) dl.findViewById(R.id.pwd); - String pwd = inputBox2.getText().toString(); - inputBox2.setText(""); - - dl.dismiss(); - - gotCredentials(null, pwd); - } - }); - } - - private int contentIdx = -1; - - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - } - - @Override - protected void onStart() { - - super.onStart(); - - setContentView(R.layout.passphrase_view); - - } - - @Override - protected void onResume() { - super.onResume(); - - } - -} diff --git a/src/info/guardianproject/otr/app/im/ui/ImageViewActivity.java b/src/info/guardianproject/otr/app/im/ui/ImageViewActivity.java new file mode 100644 index 000000000..cd29334ee --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/ImageViewActivity.java @@ -0,0 +1,97 @@ +package info.guardianproject.otr.app.im.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.os.Build; +import android.os.Bundle; +import android.view.Display; +import android.view.View; + +import info.guardianproject.iocipher.File; +import info.guardianproject.iocipher.FileInputStream; +import info.guardianproject.otr.app.im.R; + +import java.io.IOException; + +public class ImageViewActivity extends Activity { + + public static final String FILENAME = "filename"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image_view_activity); + } + + @Override + protected void onStart() { + super.onStart(); + final String filename = getIntent().getStringExtra(FILENAME); + + runOnUiThread( new Runnable() { + @Override + public void run() { + display(filename); + } + }); + } + + private void display( String filename ) { + try { + Bitmap bitmap = fitToScreen(filename); + PZSImageView imageView = (PZSImageView) findViewById(R.id.pzs_image_view); + imageView.setImageBitmap(bitmap); + } catch (Throwable t) { // may run Out Of Memory + findViewById(R.id.pzs_image_view).setVisibility(View.INVISIBLE); + findViewById(R.id.pzs_broken_image_view).setVisibility(View.VISIBLE); + } + } + + private Bitmap fitToScreen( String filename ) throws IOException { + // read in dimensions only + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inInputShareable = true; + options.inPurgeable = true; + + FileInputStream fis = new FileInputStream(new File(filename)); + BitmapFactory.decodeStream(fis, null, options); + fis.close(); + + if ((options.outWidth <= 0) || (options.outHeight <= 0)) + throw new IOException( "Image dimensions unknown"); + + // calculate down sampling ratio to fit screen + int imageWidth = options.outWidth; + int imageHeight = options.outHeight; + + options = new BitmapFactory.Options(); + Point screenDimensions = getScreenDimensions(); + if (imageHeight > imageWidth) { + options.inSampleSize = imageHeight / screenDimensions.y; + } else { + options.inSampleSize = imageWidth / screenDimensions.x; + } + + // read in downsampled image + fis = new FileInputStream(new File(filename)); + Bitmap scaledBitmap = BitmapFactory.decodeStream(fis, null, options); + fis.close(); + return scaledBitmap; + } + + @SuppressLint("NewApi") + private Point getScreenDimensions() { + Display display = getWindowManager().getDefaultDisplay(); + if (Build.VERSION.SDK_INT >= 13) { + Point size = new Point(); + display.getSize(size); + return size; + } + return new Point( display.getWidth(), display.getHeight()); + } + +} diff --git a/src/info/guardianproject/otr/app/im/ui/InstantAutoCompleteTextView.java b/src/info/guardianproject/otr/app/im/ui/InstantAutoCompleteTextView.java new file mode 100644 index 000000000..c7301a2ab --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/InstantAutoCompleteTextView.java @@ -0,0 +1,36 @@ +package info.guardianproject.otr.app.im.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.AutoCompleteTextView; + +public class InstantAutoCompleteTextView extends AutoCompleteTextView { + + public InstantAutoCompleteTextView(Context context) { + super(context); + } + + public InstantAutoCompleteTextView(Context arg0, AttributeSet arg1) { + super(arg0, arg1); + } + + public InstantAutoCompleteTextView(Context arg0, AttributeSet arg1, int arg2) { + super(arg0, arg1, arg2); + } + + @Override + public boolean enoughToFilter() { + return true; + } + + @Override + protected void onFocusChanged(boolean focused, int direction, + Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + if (focused) { + performFiltering(getText(), 0); + } + } + +} \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/ui/LetterAvatar.java b/src/info/guardianproject/otr/app/im/ui/LetterAvatar.java new file mode 100644 index 000000000..95996bffc --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/LetterAvatar.java @@ -0,0 +1,76 @@ +package info.guardianproject.otr.app.im.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.graphics.drawable.ColorDrawable; + +public class LetterAvatar extends ColorDrawable { + Paint paint = new Paint(); + Rect bounds = new Rect(); + + String pLetters; + private float ONE_DP = 0.0f; + private Resources pResources; + private int pPadding; + int pSize = 0; + float pMesuredTextWidth; + + int pBoundsTextwidth; + int pBoundsTextHeight; + + int backgroundColor; + + RectF rectBounds = new RectF(); + + public LetterAvatar (Context context, int color, String letter, int paddingInDp) { + super(Color.TRANSPARENT); + this.pLetters = letter; + this.pResources = context.getResources(); + ONE_DP = 1 * pResources.getDisplayMetrics().density; + this.pPadding = Math.round(paddingInDp * ONE_DP); + this.backgroundColor = color; + this.rectBounds = new RectF(); + + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + paint.setAntiAlias(true); + + paint.setColor(backgroundColor); + canvas.drawOval(rectBounds, paint); + + do { + paint.setTextSize(++pSize); + paint.getTextBounds(pLetters, 0, pLetters.length(), bounds); + + } while ((bounds.height() < (canvas.getHeight() - pPadding)) && (paint.measureText(pLetters) < (canvas.getWidth() - pPadding))); + + paint.setTextSize(pSize); + pMesuredTextWidth = paint.measureText(pLetters); + pBoundsTextHeight = bounds.height(); + + float xOffset = ((canvas.getWidth() - pMesuredTextWidth) / 2); + float yOffset = (int) (pBoundsTextHeight + (canvas.getHeight() - pBoundsTextHeight) / 2); + paint.setTypeface(Typeface.SANS_SERIF); + paint.setColor(0xffffffff); + + canvas.drawText(pLetters, xOffset, yOffset, paint); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + rectBounds.set(bounds); + + } + + } \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/ui/OtrChatView.java b/src/info/guardianproject/otr/app/im/ui/OtrChatView.java index 34aed3b93..e73414548 100644 --- a/src/info/guardianproject/otr/app/im/ui/OtrChatView.java +++ b/src/info/guardianproject/otr/app/im/ui/OtrChatView.java @@ -1,7 +1,6 @@ package info.guardianproject.otr.app.im.ui; import info.guardianproject.otr.IOtrChatSession; -import info.guardianproject.otr.IOtrKeyManager; import android.app.Activity; import android.content.ComponentName; @@ -17,16 +16,11 @@ public class OtrChatView { private final static String TAG = "OtrChatView"; private static IOtrChatSession mOtrChatSession = null; - private static IOtrKeyManager mOtrKeyManager = null; public static IOtrChatSession getOtrChatSession() { return mOtrChatSession; } - public static IOtrKeyManager getOtrKeyManager() { - return mOtrKeyManager; - } - /** Class for interacting with the main interface of the service. */ private ServiceConnection mOtrChatSessionConnection = new ServiceConnection() { diff --git a/src/info/guardianproject/otr/app/im/ui/PZSImageView.java b/src/info/guardianproject/otr/app/im/ui/PZSImageView.java new file mode 100644 index 000000000..e2c0190dd --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/PZSImageView.java @@ -0,0 +1,356 @@ +package info.guardianproject.otr.app.im.ui; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.FloatMath; +import android.view.MotionEvent; +import android.widget.ImageView; + +/** + * class Pinch Zoom & Swipe Image View. + * @author huewu.yang + * @date 2012. 08. 23 + */ +public class PZSImageView extends ImageView { + + private static final String TAG = "GalleryImageView"; //debug tag. + + //wrapped motion event code. + protected static final int PZS_ACTION_INIT = 100; + protected static final int PZS_ACTION_SCALE = 1001; + protected static final int PZS_ACTION_TRANSLATE = 1002; + protected static final int PZS_ACTION_SCALE_TO_TRANSLATE = 1003; + protected static final int PZS_ACTION_TRANSLATE_TO_SCALE = 1004; + protected static final int PZS_ACTION_FIT_CENTER = 1005; + protected static final int PZS_ACTION_CANCEL = -1; + + //TODO below 2 values should be able to set from attributes. + private final static float MAX_SCALE_TO_SCREEN = 2.f; + private final static float MIN_SCALE_TO_SCREEN = .5f; + + private static final long DOUBLE_TAP_MARGIN_TIME = 200; + private static final float MIN_SCALE_SPAN = 10.f; + + //calculated min / max scale ratio based on image & screen size. + private float mMinScaleFactor = 0.5f; + private float mMaxScaleFactor = 2.f; + + private boolean mIsFirstDraw = true; //check flag to calculate necessary init values. + private int mImageWidth; //current set image width + private int mImageHeight; //current set image height + + /** + * constructor + * @param context + */ + public PZSImageView(Context context) { + super(context); + init(); + } + + /** + * constructor + * @param context + */ + public PZSImageView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * constructor + * @param context + */ + public PZSImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + //should use matrix scale type. + setScaleType(ScaleType.MATRIX); + Matrix mat = getImageMatrix(); + mat.reset(); + setImageMatrix(mat); + } + + //TODO how to handle bitmaps that set as different ways. (by res or src attr) + //TODO in that case this view should be worked just as a normal image view. + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + + mIsFirstDraw = true; + mImageWidth = bm.getWidth(); + mImageHeight = bm.getHeight(); + } + + @Override + protected void onDraw(Canvas canvas) { + + if( mIsFirstDraw == true ){ + mIsFirstDraw = false; + fitCenter(); + calculateScaleFactorLimit(); + } + + setImageMatrix(mCurrentMatrix); + + super.onDraw(canvas); + } + + private void calculateScaleFactorLimit() { + + //set max / min scale factor. + mMaxScaleFactor = Math.max( getHeight() * MAX_SCALE_TO_SCREEN / mImageHeight, + getWidth() * MAX_SCALE_TO_SCREEN / mImageWidth); + + mMinScaleFactor = Math.min( getHeight() * MIN_SCALE_TO_SCREEN / mImageHeight, + getWidth() * MIN_SCALE_TO_SCREEN / mImageWidth); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + int action = parseMotionEvent(event); + + switch(action){ + case PZS_ACTION_INIT: + initGestureAction(event.getX(), event.getY()); + break; + case PZS_ACTION_SCALE: + handleScale(event); + break; + case PZS_ACTION_TRANSLATE: + handleTranslate(event); + break; + case PZS_ACTION_TRANSLATE_TO_SCALE: + initGestureAction(event.getX(), event.getY()); + break; + case PZS_ACTION_SCALE_TO_TRANSLATE: + int activeIndex = (event.getActionIndex() == 0 ? 1 : 0); + initGestureAction(event.getX(activeIndex), event.getY(activeIndex)); + break; + case PZS_ACTION_FIT_CENTER: + fitCenter(); + initGestureAction(event.getX(), event.getY()); + break; + case PZS_ACTION_CANCEL: + break; + } + + //check current position of bitmap. + validateMatrix(); + updateMatrix(); + return true; // indicate event was handled + } + + private int parseMotionEvent(MotionEvent ev) { + + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + if( isDoubleTap(ev) ) + return PZS_ACTION_FIT_CENTER; + else + return PZS_ACTION_INIT; + case MotionEvent.ACTION_POINTER_DOWN: + //more than one pointer is pressed... + return PZS_ACTION_TRANSLATE_TO_SCALE; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + if( ev.getPointerCount() == 2 ){ + return PZS_ACTION_SCALE_TO_TRANSLATE; + }else{ + return PZS_ACTION_INIT; + } + case MotionEvent.ACTION_MOVE: + if( ev.getPointerCount() == 1 ) + return PZS_ACTION_TRANSLATE; + else if( ev.getPointerCount() == 2 ) + return PZS_ACTION_SCALE; + return 0; + } + return 0; + } + + ///////////////////////////////////////////////// + // Related matrix calculation stuffs. + ///////////////////////////////////////////////// + + private Matrix mCurrentMatrix = new Matrix(); + private Matrix mSavedMatrix = new Matrix(); + + // Remember some things for zooming + private PointF mStartPoint = new PointF(); + private PointF mMidPoint = new PointF(); + private float mInitScaleSpan = 1f; + + protected void initGestureAction(float x, float y) { + mSavedMatrix.set(mCurrentMatrix); + mStartPoint.set(x, y); + mInitScaleSpan = 0.f; + } + + /** + * check user double tapped this view.. or not. + * @param current motion event. + * @return true if user double tapped this view. + */ + private long mLastTocuhDownTime = 0; + protected boolean isDoubleTap(MotionEvent ev){ + //if old pointer is tapped? + if( ev.getPointerCount() > 1){ + //if there are more than one pointer... reset + mLastTocuhDownTime = 0; + return false; + } + + long downTime = ev.getDownTime(); + long diff = downTime - mLastTocuhDownTime; + mLastTocuhDownTime = downTime; + + return diff < DOUBLE_TAP_MARGIN_TIME; + } + + protected void handleScale(MotionEvent event){ + float newSpan = spacing(event); + + //if two finger is too close, pointer index is bumped.. so just ignore it. + if( newSpan < MIN_SCALE_SPAN ) + return; + + if( mInitScaleSpan == 0.f ){ + //init values. scale gesture action is just started. + mInitScaleSpan = newSpan; + midPoint(mMidPoint, event); + }else{ + float scale = normalizeScaleFactor(mSavedMatrix, newSpan, mInitScaleSpan); + mCurrentMatrix.set(mSavedMatrix); + mCurrentMatrix.postScale(scale, scale, mMidPoint.x, mMidPoint.y); + } + } + + private float normalizeScaleFactor(Matrix curMat, float newSpan, float stdSpan) { + + float values[] = new float[9]; + curMat.getValues(values); + float scale = values[Matrix.MSCALE_X]; + + if( stdSpan == newSpan ){ + return scale; + } else { + float newScaleFactor = newSpan / stdSpan; + float candinateScale = scale * newScaleFactor; + + if( candinateScale > mMaxScaleFactor ){ + return mMaxScaleFactor / scale; + }else if( candinateScale < mMinScaleFactor ){ + return mMinScaleFactor / scale; + }else{ + return newScaleFactor; + } + } + } + + protected void handleTranslate(MotionEvent event){ + mCurrentMatrix.set(mSavedMatrix); + mCurrentMatrix.postTranslate(event.getX() - mStartPoint.x, event.getY() - mStartPoint.y); + } + + private RectF mTraslateLimitRect = new RectF(); //reuse instance. + private void validateMatrix(){ + float values[] = new float[9]; + mCurrentMatrix.getValues(values); + + //get current matrix values. + float scale = values[Matrix.MSCALE_X]; + float tranX = values[Matrix.MTRANS_X]; + float tranY = values[Matrix.MTRANS_Y]; + + int imageHeight = (int) (scale * mImageHeight); + int imageWidth = (int) (scale * mImageWidth); + + mTraslateLimitRect.setEmpty(); + //don't think about optimize code. first, just write code case by case. + + //check TOP & BOTTOM + if( imageHeight > getHeight() ){ + //image height is taller than view + mTraslateLimitRect.top = getHeight() - imageHeight - getPaddingTop() - getPaddingBottom(); + mTraslateLimitRect.bottom = 0.f; + }else{ + mTraslateLimitRect.top = mTraslateLimitRect.bottom = + (getHeight() - imageHeight - getPaddingTop() - getPaddingBottom() ) / 2.f; + } + + //check LEFT & RIGHT + if( imageWidth > getWidth() ){ + //image width is longer than view + mTraslateLimitRect.left = getWidth() - imageWidth - getPaddingRight() - getPaddingLeft(); + mTraslateLimitRect.right = 0.f; + }else{ + mTraslateLimitRect.left = mTraslateLimitRect.right = + (getWidth() - imageWidth - getPaddingLeft() - getPaddingRight()) / 2.f; + } + + float newTranX = tranX; + newTranX = Math.max(newTranX, mTraslateLimitRect.left); + newTranX = Math.min(newTranX, mTraslateLimitRect.right); + + float newTranY = tranY; + newTranY = Math.max(newTranY, mTraslateLimitRect.top); + newTranY = Math.min(newTranY, mTraslateLimitRect.bottom); + + values[Matrix.MTRANS_X] = newTranX; + values[Matrix.MTRANS_Y] = newTranY; + mCurrentMatrix.setValues(values); + + if( mTraslateLimitRect.contains(tranX, tranY) == false ){ + //set new start point. + mStartPoint.offset(tranX - newTranX, tranY - newTranY); + } + } + + protected void updateMatrix(){ + setImageMatrix(mCurrentMatrix); + } + + protected void fitCenter(){ + //move image to center.... + mCurrentMatrix.reset(); + + float scaleX = (getWidth() - getPaddingLeft() - getPaddingRight()) / (float)mImageWidth; + float scaleY = (getHeight() - getPaddingTop() - getPaddingBottom()) / (float)mImageHeight; + float scale = Math.min(scaleX, scaleY); + + float dx = (getWidth() - getPaddingLeft() - getPaddingRight() - mImageWidth * scale) / 2.f; + float dy = (getHeight() - getPaddingTop() - getPaddingBottom() - mImageHeight * scale) / 2.f; + + mCurrentMatrix.postScale(scale, scale); + mCurrentMatrix.postTranslate(dx, dy); + setImageMatrix(mCurrentMatrix); + } + + /** Determine the space between the first two fingers */ + private float spacing(MotionEvent event) { + // ... + float x = event.getX(0) - event.getX(1); + float y = event.getY(0) - event.getY(1); + return FloatMath.sqrt(x * x + y * y); + } + + /** Calculate the mid point of the first two fingers */ + private void midPoint(PointF point, MotionEvent event) { + // ... + float x = event.getX(0) + event.getX(1); + float y = event.getY(0) + event.getY(1); + point.set(x / 2, y / 2); + } + +}//end of class diff --git a/src/info/guardianproject/otr/app/im/ui/RoundedAvatarDrawable.java b/src/info/guardianproject/otr/app/im/ui/RoundedAvatarDrawable.java new file mode 100644 index 000000000..96bea6a7b --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/RoundedAvatarDrawable.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013 Evelio Tarazona Cáceres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package info.guardianproject.otr.app.im.ui; + +import info.guardianproject.otr.app.im.app.ImApp; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.Drawable; + +/** + * A Drawable that draws an oval with given {@link Bitmap} + */ +public class RoundedAvatarDrawable extends Drawable { + private final Bitmap mBitmap; + private final Paint mPaint; + private final RectF mRectF; + private final int mBitmapWidth; + private final int mBitmapHeight; + + private Paint mPaintBorder; + private int mBorderWidth = 4; + + public RoundedAvatarDrawable(Bitmap bitmap) { + mBitmap = bitmap; + mRectF = new RectF(); + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + mPaint.setShader(shader); + + // NOTE: we assume bitmap is properly scaled to current density + mBitmapWidth = mBitmap.getWidth(); + mBitmapHeight = mBitmap.getHeight(); + + mPaintBorder = new Paint(); + mPaintBorder.setColor(Color.LTGRAY); + mPaintBorder.setStyle(Paint.Style.STROKE); + mPaintBorder.setAntiAlias(true); + mPaintBorder.setStrokeWidth(mBorderWidth); + + } + + public void setBorderColor (int borderColor) + { + mPaintBorder.setColor(borderColor); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawOval(mRectF, mPaint); + + float halfWidth = mRectF.width()/2; + canvas.drawCircle(halfWidth, halfWidth, halfWidth-mBorderWidth/2, mPaintBorder); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + mRectF.set(bounds); + } + + @Override + public void setAlpha(int alpha) { + if (mPaint.getAlpha() != alpha) { + mPaint.setAlpha(alpha); + invalidateSelf(); + } + } + + @Override + public void setColorFilter(ColorFilter cf) { + mPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicWidth() { + return mBitmapWidth; + } + + @Override + public int getIntrinsicHeight() { + return mBitmapHeight; + } + + public void setAntiAlias(boolean aa) { + mPaint.setAntiAlias(aa); + invalidateSelf(); + } + + @Override + public void setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + invalidateSelf(); + } + + @Override + public void setDither(boolean dither) { + mPaint.setDither(dither); + invalidateSelf(); + } + + public Bitmap getBitmap() { + return mBitmap; + } + + // TODO allow set and use target density, mutate, constant state, changing configurations, etc. +} \ No newline at end of file diff --git a/src/info/guardianproject/otr/app/im/ui/SecureCameraActivity.java b/src/info/guardianproject/otr/app/im/ui/SecureCameraActivity.java new file mode 100644 index 000000000..da0b9f64f --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/SecureCameraActivity.java @@ -0,0 +1,116 @@ +package info.guardianproject.otr.app.im.ui; + +import info.guardianproject.iocipher.File; +import info.guardianproject.iocipher.FileInputStream; +import info.guardianproject.iocipher.FileOutputStream; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.app.ChatFileStore; +import info.guardianproject.otr.app.im.app.MessageView; + +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +public class SecureCameraActivity extends SurfaceGrabberActivity { + + private final static String TAG = SecureCameraActivity.class.getSimpleName(); + + public static final String FILENAME = "filename"; + public static final String THUMBNAIL = "thumbnail"; + public static final String MIMETYPE = "mimeType"; + + private String filename = null; + private String thumbnail = null; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + filename = getIntent().getStringExtra(FILENAME); + thumbnail = getIntent().getStringExtra(THUMBNAIL); + } + + @Override + protected int getLayout() { + return R.layout.secure_camera; + } + + @Override + protected int getCameraDirection() { + //return CameraInfo.CAMERA_FACING_FRONT; + return CameraInfo.CAMERA_FACING_BACK; + } + + @Override + public void onPictureTaken(final byte[] data, Camera camera) { + try { + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(filename))); + out.write(data); + out.flush(); + out.close(); + + if (thumbnail != null) { + Bitmap thumbnailBitmap = getThumbnail(getContentResolver(), filename); + FileOutputStream fos = new FileOutputStream(thumbnail); + thumbnailBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + } + + Intent intent = new Intent(); + intent.putExtra(FILENAME, filename); + intent.putExtra(THUMBNAIL, thumbnail); + intent.putExtra(MIMETYPE, "image/*"); + + setResult(Activity.RESULT_OK, intent); + + finish(); + } catch (Exception e) { + e.printStackTrace(); + setResult(Activity.RESULT_CANCELED); + finish(); + } + finish(); + } + + public final static int THUMBNAIL_SIZE = 800; + + public Bitmap getThumbnail(ContentResolver cr, String filename) throws IOException { + + File file = new File(filename); + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inInputShareable = true; + options.inPurgeable = true; + + FileInputStream fis = new FileInputStream(file); + BitmapFactory.decodeStream(fis, null, options); + fis.close(); + + if ((options.outWidth == -1) || (options.outHeight == -1)) + throw new IOException("Bad image " + file); + + int originalSize = (options.outHeight > options.outWidth) ? options.outHeight : options.outWidth; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = originalSize / THUMBNAIL_SIZE; + + fis = new FileInputStream(file); + Bitmap scaledBitmap = BitmapFactory.decodeStream(fis, null, opts); + return scaledBitmap; + } + +} diff --git a/src/info/guardianproject/otr/app/im/ui/SurfaceGrabberActivity.java b/src/info/guardianproject/otr/app/im/ui/SurfaceGrabberActivity.java new file mode 100644 index 000000000..497989899 --- /dev/null +++ b/src/info/guardianproject/otr/app/im/ui/SurfaceGrabberActivity.java @@ -0,0 +1,240 @@ +package info.guardianproject.otr.app.im.ui; + +import java.io.IOException; +import java.util.List; +import info.guardianproject.otr.app.im.R; + +import android.app.Activity; +import android.content.Context; +import android.graphics.ImageFormat; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.PictureCallback; +import android.hardware.Camera.Size; +import android.os.Bundle; +import android.util.Log; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; + +public class SurfaceGrabberActivity extends Activity implements OnClickListener, + SurfaceHolder.Callback, PictureCallback { + Button button; + TextView progress; + + SurfaceView view; + SurfaceHolder holder; + Camera camera; + CameraInfo cameraInfo; + + private boolean mPreviewing; + + private final static String LOG = "Camera"; + + private int mRotation = -1; + + @SuppressWarnings("deprecation") + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getLayout()); + + button = (Button) findViewById(R.id.surface_grabber_button); + button.setOnClickListener(this); + + /** + * progress = (TextView) findViewById(R.id.surface_grabber_progress); + * progress.setText(String.valueOf(baseImages.size())); + */ + view = (SurfaceView) findViewById(R.id.surface_grabber_holder); + holder = view.getHolder(); + holder.addCallback(this); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + } + + protected int getLayout() { + return R.layout.secure_camera; + } + + protected int getCameraDirection() { + return CameraInfo.CAMERA_FACING_BACK; + } + + /** + * Whether or not we can default to "other" direction if our preferred + * facing camera can't be opened + * + * @return true to try camera facing other way, false otherwise + */ + protected boolean canUseOtherDirection() { + return false; + } + + @Override + public void onResume() { + super.onResume(); + + if (!tryCreateCamera(getCameraDirection())) { + if (!canUseOtherDirection() + || !tryCreateCamera(getOtherDirection(getCameraDirection()))) { + finish(); + return; + } + } + + if (camera == null) + finish(); + + mRotation = setCameraDisplayOrientation(); + } + + private int getOtherDirection(int facing) { + return (facing == CameraInfo.CAMERA_FACING_BACK) ? CameraInfo.CAMERA_FACING_FRONT : CameraInfo.CAMERA_FACING_BACK; + } + + private boolean tryCreateCamera(int facing) { + Camera.CameraInfo info = new Camera.CameraInfo(); + for (int nCam = 0; nCam < Camera.getNumberOfCameras(); nCam++) { + Camera.getCameraInfo(nCam, info); + if (info.facing == facing) { + camera = Camera.open(nCam); + cameraInfo = info; + //Size size = choosePictureSize(camera.getParameters().getSupportedPictureSizes()); + + Camera.Parameters params = camera.getParameters(); + params.setPictureFormat(ImageFormat.JPEG); + //params.setPictureSize(size.width,size.height); + //params.setJpegThumbnailSize(128,128); + //params.setPreviewSize(size.width/2,size.height/2); + + if (this.getCameraDirection() == CameraInfo.CAMERA_FACING_BACK) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + } + + camera.setParameters(params); + + return true; + } + } + return false; + } + + @Override + public void onPause() { + if (camera != null) + camera.release(); + + super.onPause(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + camera.startPreview(); + mPreviewing = true; + } + + protected Size choosePictureSize(List localSizes) { + Size size = null; + + for (Size sz : localSizes) { + if (sz.width > 640 && sz.width <= 1024) + size = sz; + + if (size != null) + break; + } + + if (size == null) + size = localSizes.get(localSizes.size() - 1); + return size; + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + try { + camera.setPreviewDisplay(holder); + } catch (IOException e) { + Log.e(LOG, e.toString()); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + @Override + public void onClick(View view) { + if (view == button && mPreviewing) { + mPreviewing = false; + camera.takePicture(null, null, this); + } + } + + @Override + public void onPictureTaken(byte[] data, Camera camera) { + + try { + String pathToData = ""; + //data, new File(pathToData)); + + view.post(new Runnable() { + @Override + public void run() { + resumePreview(); + } + }); + } catch (Exception ioe) { + Log.e(LOG, "error saving picture to iocipher", ioe); + } + } + + protected void resumePreview() { + if (!mPreviewing) { + camera.startPreview(); + mPreviewing = true; + } + } + + public int setCameraDisplayOrientation() { + if (camera == null || cameraInfo == null) { + return -1; + } + + WindowManager winManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + int rotation = winManager.getDefaultDisplay().getRotation(); + + int degrees = 0; + + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + } + + int result; + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (cameraInfo.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { // back-facing + result = (cameraInfo.orientation - degrees + 360) % 360; + } + camera.setDisplayOrientation(result); + + return result; + } +} \ No newline at end of file diff --git a/src/info/guardianproject/util/AssetUtil.java b/src/info/guardianproject/util/AssetUtil.java index 774ca0bfe..6c38c37f7 100644 --- a/src/info/guardianproject/util/AssetUtil.java +++ b/src/info/guardianproject/util/AssetUtil.java @@ -23,7 +23,7 @@ public static Properties getProperties(String name, Context context) { properties.load(inputStream); return properties; } catch (IOException e) { - Log.e("Gibberbot", "exception reading properties", e); + Log.i("ChatSecure", "no chatsecure.properties available"); return null; } } diff --git a/src/info/guardianproject/util/AudioPlayer.java b/src/info/guardianproject/util/AudioPlayer.java new file mode 100644 index 000000000..8e6b0d294 --- /dev/null +++ b/src/info/guardianproject/util/AudioPlayer.java @@ -0,0 +1,76 @@ +package info.guardianproject.util; + +import android.content.Context; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; +import android.widget.Toast; + +public class AudioPlayer { + private static final String TAG = "AudioPlayer"; + + private Context mContext; + private String mFileName; + private String mMimeType; + + private MediaPlayer mediaPlayer; + private HttpMediaStreamer streamer; + + public AudioPlayer(Context context, String fileName, String mimeType) { + mContext = context.getApplicationContext(); + mFileName = fileName; + mMimeType = mimeType; + } + + public void play() { + try { + initPlayer(); + } catch (Exception e) { + Toast.makeText(mContext, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); + } + } + + public void pause() { + killPlayer(); + } + + private void killPlayer() { + if (mediaPlayer != null) { + mediaPlayer.stop(); + mediaPlayer.release(); + mediaPlayer = null; + } + + if (streamer != null) { + streamer.destroy(); + streamer = null; + } + } + + private void initPlayer() throws Exception { + streamer = new HttpMediaStreamer(mFileName, mMimeType); + Uri uri = streamer.getUri(); + + mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setOnPreparedListener(new OnPreparedListener() { + + @Override + public void onPrepared(MediaPlayer mp) { + mediaPlayer.start(); + } + }); + mediaPlayer.setOnCompletionListener(new OnCompletionListener() { + + @Override + public void onCompletion(MediaPlayer mp) { + killPlayer(); + } + }); + + mediaPlayer.setDataSource(mContext, uri); + mediaPlayer.prepareAsync(); + } +} diff --git a/src/info/guardianproject/util/BackgroundBitmapLoaderTask.java b/src/info/guardianproject/util/BackgroundBitmapLoaderTask.java new file mode 100644 index 000000000..9f997100b --- /dev/null +++ b/src/info/guardianproject/util/BackgroundBitmapLoaderTask.java @@ -0,0 +1,82 @@ +package info.guardianproject.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.os.AsyncTask; +import android.os.Build; +import android.widget.LinearLayout; + +import java.lang.ref.WeakReference; + +public class BackgroundBitmapLoaderTask extends AsyncTask { + private final Resources resources; + private final WeakReference linearLayoutReference; + + public BackgroundBitmapLoaderTask(Context context, LinearLayout linearLayout) { + resources = context.getResources(); + // Use a WeakReference to ensure the LinearLayout can be garbage collected + linearLayoutReference = new WeakReference(linearLayout); + } + + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, + int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + private Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, + int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); + } + + @Override + protected Bitmap doInBackground(Integer... resIds) { + return decodeSampledBitmapFromResource(resources, resIds[0], 100, 100); + } + + // Once complete, see if LinearLayout is still around and set bitmap. + @SuppressWarnings("deprecation") + @SuppressLint("NewApi") + @Override + protected void onPostExecute(Bitmap bitmap) { + if (linearLayoutReference != null && bitmap != null) { + final LinearLayout linearLayout = linearLayoutReference.get(); + if (linearLayout != null) { + if (Build.VERSION.SDK_INT >= 16) + linearLayout.setBackground(new BitmapDrawable(resources, bitmap)); + else + linearLayout.setBackgroundDrawable(new BitmapDrawable(resources, bitmap)); + } + } + } +} diff --git a/src/info/guardianproject/util/DNSUtil.java b/src/info/guardianproject/util/DNSUtil.java index 2aaec135d..03bf518fb 100644 --- a/src/info/guardianproject/util/DNSUtil.java +++ b/src/info/guardianproject/util/DNSUtil.java @@ -1,14 +1,14 @@ /** * $Revision: 1456 $ $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ - * + * * Copyright 2003-2005 Jive Software. - * + * * All rights reserved. Licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -29,7 +29,7 @@ /** * Utilty class to perform DNS lookups for XMPP services. - * + * * @author Matt Tucker */ public class DNSUtil { @@ -92,20 +92,20 @@ private static HostAddress resolveSRV(String domain) { * reached at for client-to-server communication. A DNS lookup for a SRV * record in the form "_xmpp-client._tcp.example.com" is attempted, * according to section 14.4 of RFC 3920. - * + * * If that lookup fails, it's assumed that the XMPP server lives at the host * resolved by a DNS lookup at the specified domain on the default port of * 5222.

    - * + * * As an example, a lookup for "example.com" may return * "im.example.com:5222". - * + * * Note on SRV record selection. We now check priority and weight, but we * still don't do this correctly. The missing behavior is this: if we fail * to reach a host based on its SRV record then we need to select another * host from the other SRV records. In Smack 3.1.1 we're not going to be * able to do the major system redesign to correct this. - * + * * @param domain the domain. * @return a HostAddress, which encompasses the hostname and port that the * XMPP server can be reached at for the specified domain. @@ -143,10 +143,10 @@ public static HostAddress resolveXMPPDomain(String domain) { * notation. If that lookup fails as well, it's assumed that the XMPP server * lives at the host resolved by a DNS lookup at the specified domain on the * default port of 5269.

    - * + * * As an example, a lookup for "example.com" may return * "im.example.com:5269". - * + * * @param domain the domain. * @return a HostAddress, which encompasses the hostname and port that the * XMPP server can be reached at for the specified domain. @@ -188,7 +188,7 @@ private HostAddress(String host, int port) { /** * Returns the hostname. - * + * * @return the hostname. */ public String getHost() { @@ -197,7 +197,7 @@ public String getHost() { /** * Returns the port. - * + * * @return the port. */ public int getPort() { diff --git a/src/info/guardianproject/util/Debug.java b/src/info/guardianproject/util/Debug.java index 1137e2c42..ba0f21b5f 100644 --- a/src/info/guardianproject/util/Debug.java +++ b/src/info/guardianproject/util/Debug.java @@ -1,15 +1,129 @@ package info.guardianproject.util; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; +import java.util.TimeZone; + +import android.annotation.SuppressLint; +import android.content.Context; +import java.io.PrintWriter; + +import org.apache.commons.io.output.StringBuilderWriter; + +import android.os.StrictMode; + public class Debug { - public static final boolean DEBUG_ENABLED = false; + + public static boolean DEBUG_ENABLED = false; + public static final boolean DEBUGGER_ATTACH_ENABLED = false; + public static final boolean DEBUG_INJECT_ERRORS = false; + private static int injectCount = 0; + + public static void recordTrail(Context context, String key, Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + recordTrail(context, key, sdf.format(date)); + } + + public static String getTrail(Context context) { + File trail = new File(context.getFilesDir(), "trail.properties"); + try { + BufferedReader reader = new BufferedReader(new FileReader(trail)); + String line; + StringBuilder builder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + builder.append(line); + builder.append("\n"); + } + reader.close(); + return builder.toString(); + } catch (IOException e) { + return "#notrail"; + } + } + + public static void recordTrail(Context context, String key, String value) { + File trail = new File(context.getFilesDir(), "trail.properties"); + Properties props = new Properties(); + try { + FileReader reader = new FileReader(trail); + props.load(reader); + reader.close(); + } catch (IOException e) { + // ignore + } + + try { + FileWriter writer = new FileWriter(trail); + props.put(key, value); + props.store(writer, "ChatSecure debug trail file"); + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String getTrail(Context context, String key) { + File trail = new File(context.getFilesDir(), "trail.properties"); + Properties props = new Properties(); + try { + FileReader reader = new FileReader(trail); + props.load(reader); + reader.close(); + return props.getProperty(key); + } catch (IOException e) { + return null; + } + } public static void onConnectionStart() { - if (DEBUG_ENABLED) - android.os.Debug.waitForDebugger(); + if (DEBUG_ENABLED) { + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } + } + + public static void onAppStart() { + // Same StrictMode policy + onConnectionStart(); } public static void onServiceStart() { - if (DEBUG_ENABLED) + if (DEBUGGER_ATTACH_ENABLED) android.os.Debug.waitForDebugger(); } + + public static void onHeartbeat() { + if (DEBUG_ENABLED) + System.gc(); + } + + public static String injectErrors(String body) { + if (!DEBUG_INJECT_ERRORS) + return body; + // Inject an error every few blocks + if (++injectCount % 5 == 0 && body.length() > 5) + body = body.substring(0, 5) + 'X' + body.substring(6); + return body; + } + + static public void wrapExceptions(Runnable runnable) { + try { + runnable.run(); + } catch (Throwable t) { + StringBuilderWriter writer = new StringBuilderWriter(); + PrintWriter pw = new PrintWriter(writer, true); + t.printStackTrace(pw); + writer.flush(); + throw new IllegalStateException("service throwable: " + writer.getBuilder().toString()); + } + } } diff --git a/src/info/guardianproject/util/FontUtils.java b/src/info/guardianproject/util/FontUtils.java index ccb75bb22..48517d4bc 100644 --- a/src/info/guardianproject/util/FontUtils.java +++ b/src/info/guardianproject/util/FontUtils.java @@ -76,7 +76,7 @@ private static Typeface getRobotoTypeface(Context context, Typeface originalType default: robotoFontType = FontType.NORMAL; break; - + } } @@ -90,7 +90,7 @@ private static Typeface getRobotoTypeface(Context context, Typeface originalType * @param view - root view to apply typeface to */ public static void setRobotoFont(Context context, View view) { - + if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { setRobotoFont(context, ((ViewGroup) view).getChildAt(i)); diff --git a/src/info/guardianproject/util/HttpMediaStreamer.java b/src/info/guardianproject/util/HttpMediaStreamer.java new file mode 100644 index 000000000..612c08457 --- /dev/null +++ b/src/info/guardianproject/util/HttpMediaStreamer.java @@ -0,0 +1,110 @@ +/** + * + */ +package info.guardianproject.util; + +import info.guardianproject.iocipher.File; +import info.guardianproject.iocipher.FileInputStream; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; + +import android.net.Uri; +import android.util.Log; + +/** + * Copyright (C) 2014. All rights reserved. + * + * @author liorsaar + * + */ +public class HttpMediaStreamer { + private static final String TAG = HttpMediaStreamer.class.getSimpleName(); + private Uri uri; + private ServerSocket serverSocket; + + public HttpMediaStreamer(String filename, String mimeType) throws IOException { + uri = create(filename, mimeType); + } + + public Uri getUri() { + return uri; + } + + public void destroy() { + try { + if (serverSocket != null) + serverSocket.close(); + } catch (Exception e) { + } + } + + private Uri create(final String filename, final String mimeType) throws IOException { + + // FIXME generate a random token for security + final File file = new File(filename); + if (!file.exists()) { + throw new IOException("File not found " + filename); + } + + try { + if (serverSocket != null) + serverSocket.close(); + } catch (Exception e) { + } + + serverSocket = new ServerSocket(0); // use random free port + new Thread() { + public void run() { + try { + while (true) { + Socket socket = serverSocket.accept(); + + byte[] b = new byte[8192]; + int len; + + InputStream is = socket.getInputStream(); + StringBuilder isb = new StringBuilder(); + len = is.read(b); + isb.append(new String(b)); + + //Log.i(TAG, "request: " + isb.toString()); + + StringBuilder sb = new StringBuilder(); + sb.append("HTTP/1.1 200\r\n"); + sb.append("Content-Type: " + mimeType + "\r\n"); + sb.append("Content-Length: " + file.length() + "\r\n\r\n"); + + BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); + + bos.write(sb.toString().getBytes()); + + FileInputStream fis = new FileInputStream(file); + + int idx = 0; + + while ((len = fis.read(b)) != -1) { + bos.write(b, 0, len); + idx += len; + Log.d(TAG, "sharing via stream: " + idx); + } + + fis.close(); + bos.flush(); + bos.close(); + + socket.close(); + } + } catch (IOException e) { + Log.d(TAG, "web share error", e); + } + } + }.start(); + + Uri uri = Uri.parse("http://localhost:" + serverSocket.getLocalPort() + file.getAbsolutePath()); + return uri; + } +} diff --git a/src/info/guardianproject/util/Languages.java b/src/info/guardianproject/util/Languages.java new file mode 100644 index 000000000..9fed7409c --- /dev/null +++ b/src/info/guardianproject/util/Languages.java @@ -0,0 +1,134 @@ +package info.guardianproject.util; + +import android.app.Activity; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import info.guardianproject.otr.app.im.R; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public class Languages { + private static final String TAG = "Languages"; + private static Languages singleton; + private static Map tmpMap = new TreeMap(); + private static Map nameMap; + public static final String USE_SYSTEM_DEFAULT = ""; + public static final Locale TIBETAN = new Locale("bo"); + static final Locale localesToTest[] = { Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, + Locale.ITALIAN, Locale.JAPANESE, Locale.KOREAN, + Locale.TRADITIONAL_CHINESE, Locale.SIMPLIFIED_CHINESE, + TIBETAN, new Locale("af"), new Locale("am"), + new Locale("ar"), new Locale("az"), new Locale("bg"), + new Locale("bn"), new Locale("ca"), new Locale("cs"), + new Locale("da"), new Locale("el"), new Locale("es"), + new Locale("et"), new Locale("eu"), new Locale("fa"), + new Locale("fi"), new Locale("gl"), new Locale("hi"), + new Locale("hr"), new Locale("hu"), new Locale("hy"), + new Locale("in"), new Locale("hy"), new Locale("in"), + new Locale("is"), new Locale("it"), new Locale("iw"), + new Locale("ka"), new Locale("kk"), new Locale("km"), + new Locale("kn"), new Locale("ky"), new Locale("lo"), + new Locale("lt"), new Locale("lv"), new Locale("mk"), + new Locale("ml"), new Locale("mn"), new Locale("mr"), + new Locale("ms"), new Locale("my"), new Locale("nb"), + new Locale("ne"), new Locale("nl"), new Locale("pl"), + new Locale("pt"), new Locale("rm"), new Locale("ro"), + new Locale("ru"), new Locale("si"), new Locale("sk"), + new Locale("sl"), new Locale("sn"), new Locale("sr"), + new Locale("sv"), new Locale("sw"), new Locale("ta"), + new Locale("te"), new Locale("th"), new Locale("tl"), + new Locale("tr"), new Locale("uk"), new Locale("ur"), + new Locale("uz"), new Locale("vi"), new Locale("zu"), }; + + private Languages(Activity activity) { + AssetManager assets = activity.getAssets(); + Configuration config = activity.getResources().getConfiguration(); + DisplayMetrics metrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + Resources resources; + Set localeSet = new LinkedHashSet(); + for (Locale locale : localesToTest) { + config.locale = locale; + resources = new Resources(assets, metrics, config); + if (!TextUtils.equals("Settings", resources.getString(R.string.menu_settings)) + || locale.equals(Locale.ENGLISH)) + localeSet.add(locale); + } + for (Locale locale : localeSet) { + if (locale.equals(TIBETAN)) { + // include English name for devices that don't support Tibetan font + tmpMap.put(TIBETAN.getLanguage(), "Tibetan བོད་སྐད།"); // Tibetan + } else if (locale.equals(Locale.SIMPLIFIED_CHINESE)) { + tmpMap.put(Locale.SIMPLIFIED_CHINESE.toString(), "中文 (中国)"); // Chinese (China) + } else if (locale.equals(Locale.TRADITIONAL_CHINESE)) { + tmpMap.put(Locale.TRADITIONAL_CHINESE.toString(), "中文 (台灣)"); // Chinese (Taiwan) + } else { + tmpMap.put(locale.getLanguage(), locale.getDisplayLanguage(locale)); + } + } + // TODO implement this completely, the menu item works, but doesn't work properly + /* USE_SYSTEM_DEFAULT is a fake one for displaying in a chooser menu. */ + //localeSet.add(null); + //tmpMap.put(USE_SYSTEM_DEFAULT, activity.getString(R.string.use_system_default)); + nameMap = Collections.unmodifiableMap(tmpMap); + } + + public static Languages get(Activity activity) { + if (singleton == null) + singleton = new Languages(activity); + return singleton; + } + + /** + * Return the name of the language based on the locale. + * + * @param locale + * @return + */ + public String getName(String locale) { + String ret = nameMap.get(locale); + // if no match, try to return a more general name (i.e. English for en_IN) + if (ret == null && locale.contains("_")) + ret = nameMap.get(locale.split("_")[0]); + return ret; + } + + /** + * Return an array of the names of all the supported languages, sorted to + * match what is returned by {@link Languages#getSupportedLocales()}. + * + * @return + */ + public String[] getAllNames() { + return nameMap.values().toArray(new String[nameMap.size()]); + } + + public int getPosition(Locale locale) { + String localeName = locale.getLanguage(); + int i = 0; + for (String key : nameMap.keySet()) + if(TextUtils.equals(key, localeName)) + return i; + else + i++; + return -1; + } + + /** + * Get sorted list of supported locales. + * + * @return + */ + public String[] getSupportedLocales() { + Set keys = nameMap.keySet(); + return keys.toArray(new String[keys.size()]); + } +} diff --git a/src/info/guardianproject/util/LinkifyHelper.java b/src/info/guardianproject/util/LinkifyHelper.java new file mode 100644 index 000000000..6b0e64c3e --- /dev/null +++ b/src/info/guardianproject/util/LinkifyHelper.java @@ -0,0 +1,82 @@ +package info.guardianproject.util; + +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.CharacterStyle; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.text.util.Linkify; +import android.text.util.Linkify.TransformFilter; +import android.widget.TextView; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LinkifyHelper { + + private static Pattern bitcoin = Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:;@&=]*)?"); + private static Pattern geo = Pattern.compile("geo:[-0-9.]+,[-0-9.]+[^ \t\n\"\':]*"); + private static Pattern market = Pattern.compile("market://[^ \t\n\"\':,<>]+"); + private static Pattern openpgp4fpr = Pattern.compile("openpgp4fpr:[A-Za-z0-9]{8,40}"); + private static Pattern xmpp = Pattern.compile("xmpp:[^ \t\n\"\':,<>]+"); + private static Pattern twitterHandle = Pattern.compile("@([A-Za-z0-9_-]+)"); + private static Pattern hashtag = Pattern.compile("#([A-Za-z0-9_-]+)"); + private static Pattern bridge = Pattern.compile("bridge:[^ \t\n\"\':,<>]+"); + + static TransformFilter returnMatchFilter = new TransformFilter() { + @Override + public final String transformUrl(final Matcher match, String url) { + return match.group(1); + } + }; + + /* Right now, if there is no app to handle */ + public static void addLinks(TextView text, SpanConverter converter) { + Linkify.addLinks(text, Linkify.ALL); + Linkify.addLinks(text, geo, null); + Linkify.addLinks(text, market, null); + Linkify.addLinks(text, openpgp4fpr, null); + Linkify.addLinks(text, xmpp, null); + Linkify.addLinks(text, twitterHandle, "https://twitter.com/", null, returnMatchFilter); + Linkify.addLinks(text, hashtag, "https://twitter.com/hashtag/", null, returnMatchFilter); + text.setText(replaceAll(text.getText(), URLSpan.class, converter)); + } + + /** + * These are clickable links that will always be safe to click on, whether + * or not ChatSecure is using Tor or not. + * + * @param text + */ + public static void addTorSafeLinks(TextView text) { + Linkify.addLinks(text, bridge, null); + } + + /** + * Do not create this static utility class. + */ + private LinkifyHelper() { + } + + // thanks to @commonsware https://stackoverflow.com/a/11417498 + public static Spannable replaceAll( + CharSequence original, Class sourceType, SpanConverter converter) { + SpannableString result = new SpannableString(original); + A[] spans = result.getSpans(0, result.length(), sourceType); + + for (A span : spans) { + int start = result.getSpanStart(span); + int end = result.getSpanEnd(span); + int flags = result.getSpanFlags(span); + + result.removeSpan(span); + result.setSpan(converter.convert(span), start, end, flags); + } + + return (result); + } + + public interface SpanConverter { + B convert(A span); + } +} diff --git a/src/info/guardianproject/util/LogCleaner.java b/src/info/guardianproject/util/LogCleaner.java index 0384bdb4c..3edce7df4 100644 --- a/src/info/guardianproject/util/LogCleaner.java +++ b/src/info/guardianproject/util/LogCleaner.java @@ -12,22 +12,24 @@ public static String clean (String msg) else return URLEncoder.encode(msg); } - + public static void warn (String tag, String msg) { - Log.w(tag, clean(msg)); + + Log.w(tag, clean(msg)); } - + public static void debug (String tag, String msg) { - Log.d(tag, clean(msg)); + if (Debug.DEBUG_ENABLED) + Log.d(tag, clean(msg)); } - + public static void error (String tag, String msg, Exception e) { Log.e(tag, clean(msg),e); } - + public static void error (String tag, String msg, Throwable e) { diff --git a/src/info/guardianproject/util/PRNGFixes.java b/src/info/guardianproject/util/PRNGFixes.java deleted file mode 100644 index 5aff55368..000000000 --- a/src/info/guardianproject/util/PRNGFixes.java +++ /dev/null @@ -1,327 +0,0 @@ -package info.guardianproject.util; - -import android.os.Build; -import android.os.Process; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.SecureRandomSpi; -import java.security.Security; - -/** - * Fixes for the output of the default PRNG having low entropy. - * - * The fixes need to be applied via {@link #apply()} before any use of Java - * Cryptography Architecture primitives. A good place to invoke them is in the - * application's {@code onCreate}. - */ -public final class PRNGFixes { - - private static final int VERSION_CODE_JELLY_BEAN = 16; - private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = - getBuildFingerprintAndDeviceSerial(); - - /** Hidden constructor to prevent instantiation. */ - private PRNGFixes() {} - - /** - * Applies all fixes. - * - * @throws SecurityException if a fix is needed but could not be applied. - */ - public static void apply() { - applyOpenSSLFix(); - installLinuxPRNGSecureRandom(); - } - - /** - * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the - * fix is not needed. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) - || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } - - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); - - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class.forName( - "org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException( - "Unexpected number of bytes read from Linux PRNG: " - + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } - - /** - * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the - * default. Does nothing if the implementation is already the default or if - * there is not need to install the implementation. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void installLinuxPRNGSecureRandom() - throws SecurityException { - if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { - // No need to apply the fix - return; - } - - // Install a Linux PRNG-based SecureRandom implementation as the - // default, if not yet installed. - Provider[] secureRandomProviders = - Security.getProviders("SecureRandom.SHA1PRNG"); - if ((secureRandomProviders == null) - || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class.equals( - secureRandomProviders[0].getClass()))) { - Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); - } - - // Assert that new SecureRandom() and - // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed - // by the Linux PRNG-based SecureRandom implementation. - SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng1.getProvider().getClass())) { - throw new SecurityException( - "new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); - } - - SecureRandom rng2; - try { - rng2 = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("SHA1PRNG not available", e); - } - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng2.getProvider().getClass())) { - throw new SecurityException( - "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" - + " Provider: " + rng2.getProvider().getClass()); - } - } - - /** - * {@code Provider} of {@code SecureRandom} engines which pass through - * all requests to the Linux PRNG. - */ - private static class LinuxPRNGSecureRandomProvider extends Provider { - - public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", - 1.0, - "A Linux-specific random number provider that uses" - + " /dev/urandom"); - // Although /dev/urandom is not a SHA-1 PRNG, some apps - // explicitly request a SHA1PRNG SecureRandom and we thus need to - // prevent them from getting the default implementation whose output - // may have low entropy. - put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); - put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - } - } - - /** - * {@link SecureRandomSpi} which passes all requests to the Linux PRNG - * ({@code /dev/urandom}). - */ - public static class LinuxPRNGSecureRandom extends SecureRandomSpi { - - /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed - * are passed through to the Linux PRNG (/dev/urandom). Instances of - * this class seed themselves by mixing in the current time, PID, UID, - * build fingerprint, and hardware serial number (where available) into - * Linux PRNG. - * - * Concurrency: Read requests to the underlying Linux PRNG are - * serialized (on sLock) to ensure that multiple threads do not get - * duplicated PRNG output. - */ - - private static final File URANDOM_FILE = new File("/dev/urandom"); - - private static final Object sLock = new Object(); - - /** - * Input stream for reading from Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static DataInputStream sUrandomIn; - - /** - * Output stream for writing to Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static OutputStream sUrandomOut; - - /** - * Whether this engine instance has been seeded. This is needed because - * each instance needs to seed itself if the client does not explicitly - * seed it. - */ - private boolean mSeeded; - - @Override - protected void engineSetSeed(byte[] bytes) { - try { - OutputStream out; - synchronized (sLock) { - out = getUrandomOutputStream(); - } - out.write(bytes); - out.flush(); - mSeeded = true; - } catch (IOException e) { - throw new SecurityException( - "Failed to mix seed into " + URANDOM_FILE, e); - } - } - - @Override - protected void engineNextBytes(byte[] bytes) { - if (!mSeeded) { - // Mix in the device- and invocation-specific seed. - engineSetSeed(generateSeed()); - } - - try { - DataInputStream in; - synchronized (sLock) { - in = getUrandomInputStream(); - } - synchronized (in) { - in.readFully(bytes); - } - } catch (IOException e) { - throw new SecurityException( - "Failed to read from " + URANDOM_FILE, e); - } - } - - @Override - protected byte[] engineGenerateSeed(int size) { - byte[] seed = new byte[size]; - engineNextBytes(seed); - return seed; - } - - private DataInputStream getUrandomInputStream() { - synchronized (sLock) { - if (sUrandomIn == null) { - // NOTE: Consider inserting a BufferedInputStream between - // DataInputStream and FileInputStream if you need higher - // PRNG output performance and can live with future PRNG - // output being pulled into this process prematurely. - try { - sUrandomIn = new DataInputStream( - new FileInputStream(URANDOM_FILE)); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for reading", e); - } - } - return sUrandomIn; - } - } - - private OutputStream getUrandomOutputStream() { - synchronized (sLock) { - if (sUrandomOut == null) { - try { - sUrandomOut = new FileOutputStream(URANDOM_FILE); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for writing", e); - } - } - return sUrandomOut; - } - } - } - - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = - new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } - - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } - - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } -} \ No newline at end of file diff --git a/src/info/guardianproject/util/SystemServices.java b/src/info/guardianproject/util/SystemServices.java new file mode 100644 index 000000000..a32da031b --- /dev/null +++ b/src/info/guardianproject/util/SystemServices.java @@ -0,0 +1,231 @@ +/** + * + */ +package info.guardianproject.util; + +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.app.ChatFileStore; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; +import android.webkit.MimeTypeMap; + +/** + * + * @author liorsaar + * + */ + +/* + * Usage: + * String filePath = writeFile() ; + * Uri fileUri = SystemService.Scanner.scan( context, filePath ) ; // scan that one file + * the notification will launch the target activity with the file uri + * SystemServices.Ntfcation.sent( context, fileUri, NewChatActivity.class ) ; + * in the target activity call: + * Uri uri = getIntent().getData() ; + * SystemServices.Viewer.viewImage( context, uri ) ; + */ +public class SystemServices { + static class Ntfcation { + public static void send(Context aContext, Uri aUri, Class aTargetActivityClass) { + NotificationManager mNotificationManager = (NotificationManager)aContext.getSystemService(Context.NOTIFICATION_SERVICE); + + int icon = R.drawable.ic_action_message; + CharSequence tickerText = "Secured download completed!"; // TODO string + long when = System.currentTimeMillis(); + + Notification notification = new Notification(icon, tickerText, when); + CharSequence contentTitle = "ChatSecure notification"; // TODO string + CharSequence contentText = "A secured file was successfuly downloaded."; // TODO string + Intent notificationIntent = new Intent(aContext, aTargetActivityClass); + notificationIntent.setData(aUri); // when the target activity is invoked, extract this uri and call viewImage() + PendingIntent contentIntent = PendingIntent.getActivity(aContext, 0, notificationIntent, 0); + notification.setLatestEventInfo(aContext, contentTitle, contentText, contentIntent); + mNotificationManager.notify(1, notification); + } + } + + public static class Scanner { + // after writing the file to sd, invoke this to scan a single file without callback + public static Uri scan(Context aContext, String aPath) { + File file = new File(aPath); + Uri uri = Uri.fromFile(file); + Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri); + aContext.sendBroadcast(scanFileIntent); + return uri; + } + } + + public static class Viewer { + public static void viewImage(Context aContext, Uri aUri) { + view(aContext, aUri, "image/*"); + } + + public static void view(Context aContext, Uri aUri, String aMime) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(aUri, aMime); + aContext.startActivity(intent); + } + + public static Intent getViewIntent(Uri uri, String type) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(uri, type); + return intent; + } + } + + public static String sanitize(String path) { + try { + return URLEncoder.encode(path, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static class FileInfo { + public String path; + public String type; + } + + public final static String MIME_TYPE_JPEG = "image/jpeg"; + public final static String MIME_TYPE_PNG = "image/png"; + + public static String getMimeType(String url) + { + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) { + MimeTypeMap mime = MimeTypeMap.getSingleton(); + type = mime.getMimeTypeFromExtension(extension); + } + + if (type == null) + if (url.endsWith("jpg")) + return MIME_TYPE_JPEG; + else if (url.endsWith("jpg")) + return MIME_TYPE_PNG; + + return type; + } + + public static FileInfo getFileInfoFromURI(Context aContext, Uri uri) throws IllegalArgumentException { + FileInfo info = new FileInfo(); + info.path = uri.toString(); + + if (ChatFileStore.isVfsUri(uri)) { + info.path = uri.getPath(); + info.type = getMimeType(uri.toString()); + return info; + } + if (uri.getScheme() != null && uri.getScheme().equals("file")) { + info.path = uri.getPath(); + info.type = getMimeType(uri.toString()); + return info; + } + + if (uri.toString().startsWith("content://org.openintents.filemanager/")) { + // Work around URI escaping brokenness + info.path = uri.toString().replaceFirst("content://org.openintents.filemanager", ""); + info.type = getMimeType(uri.toString()); + return info; + } + + Cursor cursor = aContext.getContentResolver().query(uri, null, null, null, null); + + if (cursor != null && cursor.getCount() > 0) + { + cursor.moveToFirst(); + + //need to check columns for different types + int dataIdx = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + if (dataIdx != -1) + { + info.path = cursor.getString(dataIdx); + info.type = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)); + + } + else + { + dataIdx = cursor.getColumnIndex(MediaStore.Video.Media.DATA); + + if (dataIdx != -1) + { + info.path = cursor.getString(dataIdx); + info.type = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE)); + } + else + { + dataIdx = cursor.getColumnIndex(MediaStore.Audio.Media.DATA); + + if (dataIdx != -1) + { + info.path = cursor.getString(dataIdx); + info.type = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE)); + } + else + { + dataIdx = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); + + if (dataIdx != -1) + { + info.path = cursor.getString(dataIdx); + info.type = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)); + + } + } + } + + + } + } + + if (cursor != null) + cursor.close(); + + if (info.type == null) + info.type = getMimeType(info.path); + + return info; + } + + public static FileInfo getContactAsVCardFile(Context context, Uri uri) { + AssetFileDescriptor fd; + try { + fd = context.getContentResolver().openAssetFileDescriptor(uri, "r"); + java.io.FileInputStream in = fd.createInputStream(); + byte[] buf = new byte[(int) fd.getDeclaredLength()]; + in.read(buf); + in.close(); + String vCardText = new String(buf); + Log.d("Vcard", vCardText); + List pathSegments = uri.getPathSegments(); + String targetPath = "/" + pathSegments.get(pathSegments.size() - 1) + ".vcf"; + ChatFileStore.copyToVfs(buf, targetPath); + FileInfo info = new FileInfo(); + info.path = ChatFileStore.vfsUri(targetPath).toString(); + info.type = "text/vcard"; + return info; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/info/guardianproject/util/XmppUriHelper.java b/src/info/guardianproject/util/XmppUriHelper.java new file mode 100644 index 000000000..c0af54753 --- /dev/null +++ b/src/info/guardianproject/util/XmppUriHelper.java @@ -0,0 +1,142 @@ +package info.guardianproject.util; + +import android.net.Uri; +import android.text.TextUtils; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Make it easy to build URIs that are compliant with RFC5122/XEP-0147 + * + * @author hans + * + */ +public class XmppUriHelper { + private static final String TAG = "XmppUriHelper"; + + public static final String SCHEME = "xmpp"; + + // http://xmpp.org/registrar/querytypes.html + public static final String ACTION_COMMAND = "command"; + public static final String ACTION_DISCO = "disco"; + public static final String ACTION_INVITE = "invite"; + public static final String ACTION_JOIN = "join"; + public static final String ACTION_MESSAGE = "message"; + public static final String ACTION_PUBSUB = "pubsub"; + public static final String ACTION_RECVFILE = "recvfile"; + public static final String ACTION_REGISTER = "register"; + public static final String ACTION_REMOVE = "remove"; + public static final String ACTION_ROSTER = "roster"; + public static final String ACTION_SENDFILE = "sendfile"; + public static final String ACTION_SUBSCRIBE = "subscribe"; + public static final String ACTION_UNREGISTER = "unregister"; + public static final String ACTION_UNSUBSCRIBE = "unsubscribe"; + public static final String ACTION_VCARD = "vcard"; + // + public static final String OTR_QUERY_PARAM = "otr-fingerprint"; + // + public static final String KEY_ACTION = "action"; + public static final String KEY_ADDRESS = "address"; + public static final String KEY_RESOURCE = "resource"; + public static final String KEY_FRAGMENT = "fragment"; + public static final String KEY_OTR_FINGERPRINT = OTR_QUERY_PARAM; + + /** + * This creates a format that aims to be as compatible and standards- + * compliant as possible. + * + * @param address + * @param otrFingerprint + * @return a String representation of the {@link Uri} + */ + public static String getUri(String address, String otrFingerprint) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(SCHEME); + String opaquePart = address + "?" + ACTION_SUBSCRIBE; + if (!TextUtils.isEmpty(otrFingerprint)) + opaquePart += ";" + OTR_QUERY_PARAM + "=" + otrFingerprint; + builder.encodedOpaquePart(opaquePart); + return builder.toString(); + + } + + public static String getOtrFingerprint(String uriString) { + return getOtrFingerprint(Uri.parse(uriString)); + } + + public static String getOtrFingerprint(Uri uri) { + return parse(uri).get(KEY_OTR_FINGERPRINT); + } + + public static final Map parse(Uri uri) { + Map map = new HashMap(); + try { + if (TextUtils.equals(uri.getScheme(), "im")) { + if (uri.isHierarchical()) + uri = Uri.parse(uri.toString().replaceAll("://*", ":")); + String opaquePart = uri.getSchemeSpecificPart(); + String query[] = opaquePart.split("[/\\?]"); + map.put(KEY_ADDRESS, query[0]); + String[] parameters = null; + if (query.length > 1) { + parameters = query[1].split("[;&]"); + for (String t : parameters) { + String[] parameter = t.split("="); + if (parameter.length > 1) + map.put(parameter[0], parameter[1]); + else + map.put(parameter[0], ""); + } + } + } else if (TextUtils.equals(uri.getScheme(), "xmpp")) { + if (uri.isOpaque()) { + String opaquePart = uri.getSchemeSpecificPart(); + if (!TextUtils.isEmpty(opaquePart)) { + String f[] = opaquePart.split("#"); + if (f.length > 1) + map.put(KEY_FRAGMENT, f[1]); + String s[] = f[0].split("\\?"); + String a[] = s[0].split("/"); // split off XMPP Resource + map.put(KEY_ADDRESS, a[0]); + if (a.length > 1) + map.put(KEY_RESOURCE, a[1]); + String[] parameters = null; + if (s.length > 1) { + parameters = s[1].split("[;&]"); + for (String t : parameters) { + String[] parameter = t.split("="); + if (parameter.length > 1) + map.put(parameter[0], parameter[1]); + else + map.put(parameter[0], ""); + } + } + } + } else if (uri.isHierarchical()) { + String authority = uri.getAuthority(); + List path = uri.getPathSegments(); + if (TextUtils.isEmpty(authority) || path.size() > 0) { + // totally ignore the "authority" part + map.put(KEY_ADDRESS, path.get(0)); + if (path.size() > 1) + map.put(KEY_RESOURCE, path.get(1)); + } else { + /* this supports the original ChatSecure xmpp://to@address.com URLs. + * Those URLs are technically incorrect according to RFC5122 since the + * "authority" part directly after the // is meant to be the local + * sending account, not the remote, receiving account. */ + map.put(KEY_ADDRESS, authority); + } + map.put(KEY_OTR_FINGERPRINT, + uri.getQueryParameter(XmppUriHelper.OTR_QUERY_PARAM)); + } else { + throw new Exception("Uri is neither opaque nor hierarchical!"); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return map; + } +} diff --git a/src/net/java/otr4j/OtrEngine.java b/src/net/java/otr4j/OtrEngine.java index 1466ac55c..e719768b5 100644 --- a/src/net/java/otr4j/OtrEngine.java +++ b/src/net/java/otr4j/OtrEngine.java @@ -11,6 +11,16 @@ /** @author George Politis */ public interface OtrEngine { + /** + * @param sessionID The session identifier. + * @param content The message content to be transformed. + * @param tlvs The TLVs + * @return The transformed message content. + * @throws OtrException + */ + public abstract String transformReceiving(SessionID sessionID, String content, List tlvs) + throws OtrException; + /** * @param sessionID The session identifier. * @param content The message content to be transformed. @@ -41,7 +51,7 @@ public abstract String transformSending(SessionID sessionID, String content, Lis /** * Starts an Off-the-Record session, if there is no active one. - * + * * @param sessionID The session identifier. * @throws OtrException */ @@ -52,7 +62,7 @@ public abstract String transformSending(SessionID sessionID, String content, Lis /** * Ends the Off-the-Record session, if exists. - * + * * @param sessionID The session identifier. * @throws OtrException */ @@ -60,7 +70,7 @@ public abstract String transformSending(SessionID sessionID, String content, Lis /** * Stops/Starts the Off-the-Record session. - * + * * @param sessionID The session identifier. * @throws OtrException */ diff --git a/src/net/java/otr4j/OtrEngineHost.java b/src/net/java/otr4j/OtrEngineHost.java index 33fca995e..bca33ccdd 100644 --- a/src/net/java/otr4j/OtrEngineHost.java +++ b/src/net/java/otr4j/OtrEngineHost.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j; @@ -12,7 +12,7 @@ /** * This interface should be implemented by the host application. It is required * for otr4j to work properly. - * + * * @author George Politis */ public abstract interface OtrEngineHost { diff --git a/src/net/java/otr4j/OtrEngineImpl.java b/src/net/java/otr4j/OtrEngineImpl.java index f172940dd..daacd150c 100644 --- a/src/net/java/otr4j/OtrEngineImpl.java +++ b/src/net/java/otr4j/OtrEngineImpl.java @@ -1,12 +1,15 @@ /* * otr4j, the open source java otr librar - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j; +import info.guardianproject.otr.OtrChatListener; + import java.security.PublicKey; +import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -26,22 +29,23 @@ public OtrEngineImpl(OtrEngineHost host) { throw new IllegalArgumentException("OtrEgineHost is required."); this.setHost(host); + + if (sessions == null) + sessions = new Hashtable(); + } private OtrEngineHost host; - private Map sessions; + private Map sessions; public Session getSession(SessionID sessionID) { if (sessionID == null || sessionID.equals(SessionID.Empty)) throw new IllegalArgumentException(); - if (sessions == null) - sessions = new Hashtable(); - - if (!sessions.containsKey(sessionID)) { + if (!sessions.containsKey(sessionID.toString())) { Session session = new SessionImpl(sessionID, getHost()); - sessions.put(sessionID, session); + sessions.put(sessionID.toString(), session); session.addOtrEngineListener(new OtrEngineListener() { @@ -52,7 +56,11 @@ public void sessionStatusChanged(SessionID sessionID) { }); return session; } else - return sessions.get(sessionID); + { + SessionImpl session = (SessionImpl)sessions.get(sessionID.toString()); + session.setSessionID(sessionID);//make sure latest instance is stored in session (in case JIDs get updated) + return session; + } } public SessionStatus getSessionStatus(SessionID sessionID) { @@ -63,6 +71,10 @@ public String transformReceiving(SessionID sessionID, String msgText) throws Otr return this.getSession(sessionID).transformReceiving(msgText); } + public String transformReceiving(SessionID sessionID, String msgText, List tlvs) throws OtrException { + return this.getSession(sessionID).transformReceiving(msgText, tlvs); + } + public String transformSending(SessionID sessionID, String msgText) throws OtrException { return this.getSession(sessionID).transformSending(msgText, null); } @@ -72,12 +84,24 @@ public String transformSending(SessionID sessionID, String msgText, List tl return this.getSession(sessionID).transformSending(msgText, tlvs); } + public String transformSending(SessionID sessionID, String msgText, boolean isResponse, byte[] data) + throws OtrException { + List tlvs = null; + if (data != null) { + tlvs = new ArrayList(1); + tlvs.add(new TLV(isResponse ? OtrChatListener.TLV_DATA_RESPONSE : OtrChatListener.TLV_DATA_REQUEST, data)); + } + return this.getSession(sessionID).transformSending(msgText, tlvs); + } + + public void endSession(SessionID sessionID) throws OtrException { - this.getSession(sessionID).endSession(); + getSession(sessionID).endSession(); + sessions.remove(sessionID.toString()); } public void startSession(SessionID sessionID) throws OtrException { - this.getSession(sessionID).startSession(); + this.getSession(sessionID).refreshSession(); } private void setHost(OtrEngineHost host) { diff --git a/src/net/java/otr4j/OtrEngineListener.java b/src/net/java/otr4j/OtrEngineListener.java index 656407cf3..d2829e6c1 100644 --- a/src/net/java/otr4j/OtrEngineListener.java +++ b/src/net/java/otr4j/OtrEngineListener.java @@ -5,7 +5,7 @@ /** * This interface should be implemented by the host application. It notifies * about session status changes. - * + * * @author George Politis */ public interface OtrEngineListener { diff --git a/src/net/java/otr4j/OtrException.java b/src/net/java/otr4j/OtrException.java index fa25d2f08..c6253ed23 100644 --- a/src/net/java/otr4j/OtrException.java +++ b/src/net/java/otr4j/OtrException.java @@ -5,7 +5,7 @@ public class OtrException extends Exception { public OtrException(Exception e) { super(e); } - + public OtrException(String m) { super(m); } diff --git a/src/net/java/otr4j/OtrKeyManager.java b/src/net/java/otr4j/OtrKeyManager.java index 4c7428d8a..55d160404 100644 --- a/src/net/java/otr4j/OtrKeyManager.java +++ b/src/net/java/otr4j/OtrKeyManager.java @@ -18,7 +18,7 @@ public abstract interface OtrKeyManager { /** The remote verified us with an SMP Q&A */ public abstract void remoteVerifiedUs(SessionID sessionID); - + public abstract boolean isVerified(SessionID sessionID); public abstract String getRemoteFingerprint(SessionID sessionID); diff --git a/src/net/java/otr4j/OtrKeyManagerImpl.java b/src/net/java/otr4j/OtrKeyManagerDefaultImpl.java similarity index 86% rename from src/net/java/otr4j/OtrKeyManagerImpl.java rename to src/net/java/otr4j/OtrKeyManagerDefaultImpl.java index e1404be71..f94bb36ed 100644 --- a/src/net/java/otr4j/OtrKeyManagerImpl.java +++ b/src/net/java/otr4j/OtrKeyManagerDefaultImpl.java @@ -1,7 +1,5 @@ package net.java.otr4j; -import info.guardianproject.bouncycastle.util.encoders.Base64; - import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -19,6 +17,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.Vector; @@ -26,12 +25,15 @@ import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.SessionID; +import android.util.Base64; + -public class OtrKeyManagerImpl implements OtrKeyManager { + +public class OtrKeyManagerDefaultImpl implements OtrKeyManager { private OtrKeyManagerStore store; - public OtrKeyManagerImpl(OtrKeyManagerStore store) { + public OtrKeyManagerDefaultImpl(OtrKeyManagerStore store) { this.store = store; } @@ -76,7 +78,7 @@ private void store() throws FileNotFoundException, IOException { } public void setProperty(String id, byte[] value) { - properties.setProperty(id, new String(Base64.encode(value))); + properties.setProperty(id, Base64.encodeToString(value,Base64.NO_WRAP)); try { this.store(); } catch (Exception e) { @@ -91,7 +93,7 @@ public void removeProperty(String id) { public byte[] getPropertyBytes(String id) { String value = properties.getProperty(id); - return Base64.decode(value); + return Base64.decode(value,Base64.NO_WRAP); } public boolean getPropertyBoolean(String id, boolean defaultValue) { @@ -103,7 +105,7 @@ public boolean getPropertyBoolean(String id, boolean defaultValue) { } } - public OtrKeyManagerImpl(String filepath) throws IOException { + public OtrKeyManagerDefaultImpl(String filepath) throws IOException { this.store = new DefaultPropertiesStore(filepath); } @@ -126,7 +128,7 @@ public void generateLocalKeyPair(SessionID sessionID) { if (sessionID == null) return; - String accountID = sessionID.getAccountID(); + String accountID = sessionID.getLocalUserId(); KeyPair keyPair; try { keyPair = KeyPairGenerator.getInstance("DSA").genKeyPair(); @@ -146,6 +148,7 @@ public void generateLocalKeyPair(SessionID sessionID) { PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privKey.getEncoded()); this.store.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec.getEncoded()); + } public String getLocalFingerprint(SessionID sessionID) { @@ -180,14 +183,14 @@ public boolean isVerified(SessionID sessionID) { if (sessionID == null) return false; - return this.store.getPropertyBoolean(sessionID.getUserID() + ".publicKey.verified", false); + return this.store.getPropertyBoolean(sessionID.getLocalUserId() + ".publicKey.verified", false); } public KeyPair loadLocalKeyPair(SessionID sessionID) { if (sessionID == null) return null; - String accountID = sessionID.getAccountID(); + String accountID = sessionID.getLocalUserId(); // Load Private Key. byte[] b64PrivKey = this.store.getPropertyBytes(accountID + ".privateKey"); if (b64PrivKey == null) @@ -226,7 +229,7 @@ public PublicKey loadRemotePublicKey(SessionID sessionID) { if (sessionID == null) return null; - String userID = sessionID.getUserID(); + String userID = sessionID.getRemoteUserId(); byte[] b64PubKey = this.store.getPropertyBytes(userID + ".publicKey"); if (b64PubKey == null) @@ -254,10 +257,18 @@ public void savePublicKey(SessionID sessionID, PublicKey pubKey) { X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey.getEncoded()); - String userID = sessionID.getUserID(); - this.store.setProperty(userID + ".publicKey", x509EncodedKeySpec.getEncoded()); + String userID = sessionID.getRemoteUserId(); - this.store.removeProperty(userID + ".publicKey.verified"); + byte[] keyEnc = x509EncodedKeySpec.getEncoded(); + String keyString = userID + ".publicKey"; + + byte[] keyExisting = store.getPropertyBytes(keyString); + + if (keyExisting != null && (!Arrays.equals(keyEnc, keyExisting))) + { + store.removeProperty(userID + ".publicKey.verified"); //remove any verified state + store.setProperty(userID + ".publicKey", keyEnc); + } } public void unverify(SessionID sessionID) { @@ -267,7 +278,7 @@ public void unverify(SessionID sessionID) { if (!isVerified(sessionID)) return; - this.store.removeProperty(sessionID.getUserID() + ".publicKey.verified"); + this.store.removeProperty(sessionID.getRemoteUserId() + ".publicKey.verified"); for (OtrKeyManagerListener l : listeners) l.verificationStatusChanged(sessionID); @@ -281,8 +292,8 @@ public void verify(SessionID sessionID) { if (this.isVerified(sessionID)) return; - this.store.setProperty(sessionID.getUserID() + ".publicKey.verified", true); - + store.setProperty(sessionID.getRemoteUserId() + ".publicKey.verified", true); + for (OtrKeyManagerListener l : listeners) l.verificationStatusChanged(sessionID); } diff --git a/src/net/java/otr4j/OtrPolicy.java b/src/net/java/otr4j/OtrPolicy.java index 99dd59f48..98e767cd3 100644 --- a/src/net/java/otr4j/OtrPolicy.java +++ b/src/net/java/otr4j/OtrPolicy.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j; diff --git a/src/net/java/otr4j/crypto/OtrCryptoEngine.java b/src/net/java/otr4j/crypto/OtrCryptoEngine.java index 0d6bbebb9..b3508870f 100644 --- a/src/net/java/otr4j/crypto/OtrCryptoEngine.java +++ b/src/net/java/otr4j/crypto/OtrCryptoEngine.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ diff --git a/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java b/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java index 8be90e3ec..501fa757f 100644 --- a/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java +++ b/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.crypto; @@ -33,6 +33,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; +import java.security.interfaces.DSAKey; import java.security.interfaces.DSAParams; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; @@ -264,16 +265,19 @@ public byte[] sign(byte[] b, PrivateKey privatekey) throws OtrCryptoException { byte[] sig = new byte[siglen]; Boolean writeR = false; Boolean writeS = false; + int shiftR = rslen - rb.length; + int shiftS = rslen - sb.length; + for (int i = 0; i < siglen; i++) { if (i < rslen) { if (!writeR) writeR = rb.length >= rslen - i; - sig[i] = (writeR) ? rb[i] : (byte) 0x0; + sig[i] = (writeR) ? rb[i - shiftR] : (byte) 0x0; } else { int j = i - rslen; // Rebase. if (!writeS) writeS = sb.length >= rslen - j; - sig[i] = (writeS) ? sb[j] : (byte) 0x0; + sig[i] = (writeS) ? sb[j - shiftS] : (byte) 0x0; } } return sig; diff --git a/src/net/java/otr4j/crypto/SM.java b/src/net/java/otr4j/crypto/SM.java index 5b03efdea..af20ada2b 100644 --- a/src/net/java/otr4j/crypto/SM.java +++ b/src/net/java/otr4j/crypto/SM.java @@ -1,16 +1,16 @@ /* * Java OTR library Copyright (C) 2008-2009 Ian Goldberg, Muhaimeen Ashraf, * Andrew Chung, Can Tang - * + * * This library is free software; you can redistribute it and/or modify it under * the terms of version 2.1 of the GNU Lesser General Public License as * published by the Free Software Foundation. - * + * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -43,7 +43,7 @@ public SMState() { g1 = new BigInteger(1, SM.GENERATOR_S); smProgState = SM.PROG_OK; } - + public boolean isReceivedQuestion() { return receivedQuestion; } @@ -163,8 +163,10 @@ public static BigInteger[] unserialize(byte[] bytes) throws SMException { ByteArrayInputStream in = new ByteArrayInputStream(bytes); OtrInputStream ois = new OtrInputStream(in); int len = ois.readInt(); - if (len > 100) + if (len > 100) { + ois.close(); throw new SMException("Too many ints"); + } BigInteger[] ints = new BigInteger[len]; for (int i = 0; i < len; i++) { ints[i] = ois.readBigInt(); @@ -181,7 +183,7 @@ public static BigInteger[] unserialize(byte[] bytes) throws SMException { * element */ public static boolean checkGroupElem(BigInteger g) { - return !(g.compareTo(BigInteger.valueOf(2)) > 0 && g.compareTo(SM.MODULUS_MINUS_2) < 0); + return g.compareTo(BigInteger.valueOf(2)) < 0 || g.compareTo(SM.MODULUS_MINUS_2) > 0; } /** @@ -189,12 +191,12 @@ public static boolean checkGroupElem(BigInteger g) { * exponent */ public static boolean checkExpon(BigInteger x) { - return !(x.compareTo(BigInteger.ONE) > 0 && x.compareTo(SM.ORDER_S) <= 0); + return x.compareTo(BigInteger.ONE) < 0 || x.compareTo(SM.ORDER_S) >= 0; } /** * Proof of knowledge of a discrete logarithm - * + * * @throws SMException */ public static BigInteger[] proofKnowLog(BigInteger g, BigInteger x, int version) @@ -213,7 +215,7 @@ public static BigInteger[] proofKnowLog(BigInteger g, BigInteger x, int version) /** * Verify a proof of knowledge of a discrete logarithm. Checks that c = * h(g^d x^c) - * + * * @throws SMException */ public static int checkKnowLog(BigInteger c, BigInteger d, BigInteger g, BigInteger x, @@ -229,7 +231,7 @@ public static int checkKnowLog(BigInteger c, BigInteger d, BigInteger g, BigInte /** * Proof of knowledge of coordinates with first components being equal - * + * * @throws SMException */ public static BigInteger[] proofEqualCoords(SMState state, BigInteger r, int version) @@ -261,7 +263,7 @@ public static BigInteger[] proofEqualCoords(SMState state, BigInteger r, int ver /** * Verify a proof of knowledge of coordinates with first components being * equal - * + * * @throws SMException */ public static int checkEqualCoords(BigInteger c, BigInteger d1, BigInteger d2, BigInteger p, @@ -294,7 +296,7 @@ public static int checkEqualCoords(BigInteger c, BigInteger d1, BigInteger d2, B /** * Proof of knowledge of logs with exponents being equal - * + * * @throws SMException */ public static BigInteger[] proofEqualLogs(SMState state, int version) throws SMException { @@ -317,7 +319,7 @@ public static BigInteger[] proofEqualLogs(SMState state, int version) throws SME /** * Verify a proof of knowledge of logs with exponents being equal - * + * * @throws SMException */ public static int checkEqualLogs(BigInteger c, BigInteger d, BigInteger r, SMState state, @@ -326,7 +328,7 @@ public static int checkEqualLogs(BigInteger c, BigInteger d, BigInteger r, SMSta /* Here, we recall the exponents used to create g3. * If we have previously seen g3o = g1^x where x is unknown * during the DH exchange to produce g3, then we may proceed with: - * + * * To verify, we test that hash(g1^d * g3o^c, qab^d * r^c) = c * If indeed c = hash(g1^r1, qab^r1), d = r1- x * c * And if indeed r = qab^x @@ -358,7 +360,7 @@ public static int checkEqualLogs(BigInteger c, BigInteger d, BigInteger r, SMSta * ZK proof of knowledge of g2a exponent [3] = g3a, Alice's half of DH * exchange to determine g3 [4] = c3, [5] = d3, Alice's ZK proof of * knowledge of g3a exponent - * + * * @throws SMException */ public static byte[] step1(SMState astate, byte[] secret) throws SMException { @@ -391,7 +393,7 @@ public static byte[] step1(SMState astate, byte[] secret) throws SMException { /** * Receive the first message in SMP exchange, which was generated by step1. * Input is saved until the user inputs their secret information. No output. - * + * * @throws SMException */ public static void step2a(SMState bstate, byte[] input, boolean received_question) @@ -443,7 +445,7 @@ public static void step2a(SMState bstate, byte[] input, boolean received_questio * knowledge of g3b exponent [6] = pb, [7] = qb, Bob's halves of the (Pa/Pb) * and (Qa/Qb) values [8] = cp, [9] = d5, [10] = d6, Bob's ZK proof that pb, * qb formed correctly - * + * * @throws SMException */ public static byte[] step2b(SMState bstate, byte[] secret) throws SMException { @@ -498,7 +500,7 @@ public static byte[] step2b(SMState bstate, byte[] secret) throws SMException { * that pa, qa formed correctly [5] = ra, calculated as (Qa/Qb)^x3 where x3 * is the exponent used in g3a [6] = cr, [7] = d7, Alice's ZK proof that ra * is formed correctly - * + * * @throws SMException */ public static byte[] step3(SMState astate, byte[] input) throws SMException { @@ -579,7 +581,7 @@ public static byte[] step3(SMState astate, byte[] input) throws SMException { * correctly This method also checks if Alice and Bob's secrets were the * same. If so, it returns NO_ERROR. If the secrets differ, an INV_VALUE * error is returned instead. - * + * * @throws SMException */ public static byte[] step4(SMState bstate, byte[] input) throws SMException { @@ -634,7 +636,7 @@ public static byte[] step4(SMState bstate, byte[] input) throws SMException { * Receives the final SMP message, which was generated in otrl_sm_step. This * method checks if Alice and Bob's secrets were the same. If so, it returns * NO_ERROR. If the secrets differ, an INV_VALUE error is returned instead. - * + * * @throws SMException */ public static void step5(SMState astate, byte[] input) throws SMException { diff --git a/src/net/java/otr4j/crypto/Util.java b/src/net/java/otr4j/crypto/Util.java index 5b9ee3099..07d4484fc 100644 --- a/src/net/java/otr4j/crypto/Util.java +++ b/src/net/java/otr4j/crypto/Util.java @@ -1,16 +1,16 @@ /* * Java OTR library Copyright (C) 2008-2009 Ian Goldberg, Muhaimeen Ashraf, * Andrew Chung, Can Tang - * + * * This library is free software; you can redistribute it and/or modify it under * the terms of version 2.1 of the GNU Lesser General Public License as * published by the Free Software Foundation. - * + * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA diff --git a/src/net/java/otr4j/io/OtrOutputStream.java b/src/net/java/otr4j/io/OtrOutputStream.java index 84379c594..421a4c581 100644 --- a/src/net/java/otr4j/io/OtrOutputStream.java +++ b/src/net/java/otr4j/io/OtrOutputStream.java @@ -12,10 +12,11 @@ import javax.crypto.interfaces.DHPublicKey; -import net.java.otr4j.io.messages.SignatureM; import net.java.otr4j.io.messages.MysteriousT; +import net.java.otr4j.io.messages.SignatureM; import net.java.otr4j.io.messages.SignatureX; + public class OtrOutputStream extends FilterOutputStream implements SerializationConstants { public OtrOutputStream(OutputStream out) { diff --git a/src/net/java/otr4j/io/SerializationConstants.java b/src/net/java/otr4j/io/SerializationConstants.java index ade9f16c6..e4023297c 100644 --- a/src/net/java/otr4j/io/SerializationConstants.java +++ b/src/net/java/otr4j/io/SerializationConstants.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io; diff --git a/src/net/java/otr4j/io/SerializationUtils.java b/src/net/java/otr4j/io/SerializationUtils.java index dc101e09f..c2fe873b6 100644 --- a/src/net/java/otr4j/io/SerializationUtils.java +++ b/src/net/java/otr4j/io/SerializationUtils.java @@ -1,11 +1,12 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io; -import info.guardianproject.bouncycastle.util.encoders.Base64; +import android.util.Base64; + import info.guardianproject.otr.OtrConstants; import java.io.ByteArrayInputStream; @@ -14,6 +15,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; +import java.nio.charset.Charset; import java.security.PublicKey; import java.util.List; import java.util.Vector; @@ -38,6 +40,17 @@ /** @author George Politis */ public class SerializationUtils { + + /** + * Charset for base64-encoded content. + */ + public static Charset ASCII = Charset.forName("US-ASCII"); + + /** + * Charset for message content according to OTR spec. + */ + public static Charset UTF8 = Charset.forName("UTF-8"); + // Mysterious X IO. public static SignatureX toMysteriousX(byte[] b) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(b); @@ -73,6 +86,7 @@ public static byte[] toByteArray(MysteriousT t) throws IOException { oos.writeMysteriousT(t); byte[] b = out.toByteArray(); out.close(); + oos.close(); return b; } @@ -83,6 +97,7 @@ public static byte[] writeData(byte[] b) throws IOException { oos.writeData(b); byte[] otrb = out.toByteArray(); out.close(); + oos.close(); return otrb; } @@ -138,6 +153,7 @@ public static String toString(AbstractMessage m) throws IOException { writer.write(" \\t \\t \\t "); } } + break; case AbstractMessage.MESSAGE_QUERY: QueryMessage query = (QueryMessage) m; @@ -205,7 +221,7 @@ public static String toString(AbstractMessage m) throws IOException { } writer.write(SerializationConstants.HEAD_ENCODED); - writer.write(new String(Base64.encode(o.toByteArray()))); + writer.write(Base64.encodeToString(o.toByteArray(),Base64.NO_WRAP)); writer.write("."); break; default: @@ -216,7 +232,7 @@ public static String toString(AbstractMessage m) throws IOException { } static final Pattern patternWhitespace = Pattern - .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t\\t \\t )?( \\t \\t \\t )?"); + .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t \\t \\t )?( \\t\\t \\t )?"); public static AbstractMessage toMessage(String s) throws IOException { if (s == null || s.length() <= 1) @@ -261,8 +277,7 @@ public static AbstractMessage toMessage(String s) throws IOException { String content = s.substring(SerializationConstants.HEAD.length() + 1); switch (contentType) { case SerializationConstants.HEAD_ENCODED: - ByteArrayInputStream bin = new ByteArrayInputStream(Base64.decode(content - .getBytes())); + ByteArrayInputStream bin = new ByteArrayInputStream(Base64.decode(content,Base64.NO_WRAP)); OtrInputStream otr = new OtrInputStream(bin); // We have an encoded message. int protocolVersion = otr.readShort(); @@ -327,7 +342,7 @@ public static AbstractMessage toMessage(String s) throws IOException { QueryMessage query = new QueryMessage(versions); return query; default: - throw new IOException("Uknown message type."); + throw new IOException("Unknown message type."); } } } diff --git a/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java b/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java index bffa5e3d7..b676d340c 100644 --- a/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java +++ b/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/AbstractMessage.java b/src/net/java/otr4j/io/messages/AbstractMessage.java index 1b46012a5..b9829b6af 100644 --- a/src/net/java/otr4j/io/messages/AbstractMessage.java +++ b/src/net/java/otr4j/io/messages/AbstractMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/DHCommitMessage.java b/src/net/java/otr4j/io/messages/DHCommitMessage.java index 15d8adf05..439bd8e8a 100644 --- a/src/net/java/otr4j/io/messages/DHCommitMessage.java +++ b/src/net/java/otr4j/io/messages/DHCommitMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/DHKeyMessage.java b/src/net/java/otr4j/io/messages/DHKeyMessage.java index 53d9c2546..50c47aca9 100644 --- a/src/net/java/otr4j/io/messages/DHKeyMessage.java +++ b/src/net/java/otr4j/io/messages/DHKeyMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/DataMessage.java b/src/net/java/otr4j/io/messages/DataMessage.java index 72ecc54c9..3be2a11fc 100644 --- a/src/net/java/otr4j/io/messages/DataMessage.java +++ b/src/net/java/otr4j/io/messages/DataMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/ErrorMessage.java b/src/net/java/otr4j/io/messages/ErrorMessage.java index 8b378b8f4..90358507f 100644 --- a/src/net/java/otr4j/io/messages/ErrorMessage.java +++ b/src/net/java/otr4j/io/messages/ErrorMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/PlainTextMessage.java b/src/net/java/otr4j/io/messages/PlainTextMessage.java index 739f409eb..386940e29 100644 --- a/src/net/java/otr4j/io/messages/PlainTextMessage.java +++ b/src/net/java/otr4j/io/messages/PlainTextMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/QueryMessage.java b/src/net/java/otr4j/io/messages/QueryMessage.java index 4e63b0ce8..53dddcb42 100644 --- a/src/net/java/otr4j/io/messages/QueryMessage.java +++ b/src/net/java/otr4j/io/messages/QueryMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/RevealSignatureMessage.java b/src/net/java/otr4j/io/messages/RevealSignatureMessage.java index aa7ad649e..3718a9cc9 100644 --- a/src/net/java/otr4j/io/messages/RevealSignatureMessage.java +++ b/src/net/java/otr4j/io/messages/RevealSignatureMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/SignatureM.java b/src/net/java/otr4j/io/messages/SignatureM.java index dcfd3ce15..a875a99ff 100644 --- a/src/net/java/otr4j/io/messages/SignatureM.java +++ b/src/net/java/otr4j/io/messages/SignatureM.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/io/messages/SignatureMessage.java b/src/net/java/otr4j/io/messages/SignatureMessage.java index 82a1a0601..b9715e2de 100644 --- a/src/net/java/otr4j/io/messages/SignatureMessage.java +++ b/src/net/java/otr4j/io/messages/SignatureMessage.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; @@ -46,7 +46,7 @@ public boolean verify(byte[] key) throws OtrException { byte[] xEncryptedMAC = new OtrCryptoEngineImpl().sha256Hmac160(xbEncrypted, key); // Verify signature. - return Arrays.equals(xEncryptedMAC, xEncryptedMAC); + return Arrays.equals(this.xEncryptedMAC, xEncryptedMAC); } @Override diff --git a/src/net/java/otr4j/io/messages/SignatureX.java b/src/net/java/otr4j/io/messages/SignatureX.java index c3a1d875e..a646bca50 100644 --- a/src/net/java/otr4j/io/messages/SignatureX.java +++ b/src/net/java/otr4j/io/messages/SignatureX.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io.messages; diff --git a/src/net/java/otr4j/session/AuthContext.java b/src/net/java/otr4j/session/AuthContext.java index 70ba6e81e..e3c40db69 100644 --- a/src/net/java/otr4j/session/AuthContext.java +++ b/src/net/java/otr4j/session/AuthContext.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; @@ -27,6 +27,7 @@ interface AuthContext { public static final byte M2_START = (byte) 0x03; public static final byte M1p_START = (byte) 0x04; public static final byte M2p_START = (byte) 0x05; + public static final byte EXTRA_SYMMETRIC_KEY = (byte) 0xFF; public abstract void reset(); @@ -47,4 +48,6 @@ interface AuthContext { public abstract PublicKey getRemoteLongTermPublicKey(); public abstract KeyPair getLocalLongTermKeyPair(); + + public abstract byte[] getExtraSymmetricKey() throws OtrException; } \ No newline at end of file diff --git a/src/net/java/otr4j/session/AuthContextImpl.java b/src/net/java/otr4j/session/AuthContextImpl.java index d17ba502a..32f056699 100644 --- a/src/net/java/otr4j/session/AuthContextImpl.java +++ b/src/net/java/otr4j/session/AuthContextImpl.java @@ -1,17 +1,19 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; +import info.guardianproject.otr.AndroidLogHandler; + import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.KeyPair; import java.security.PublicKey; +import java.security.SecureRandom; import java.util.Arrays; -import java.util.Random; import java.util.Vector; import java.util.logging.Logger; @@ -37,11 +39,14 @@ class AuthContextImpl implements AuthContext { public AuthContextImpl(Session session) { this.setSession(session); this.reset(); + logger.addHandler(new AndroidLogHandler()); + } private Session session; private int authenticationState; + SecureRandom secureRandom; private byte[] r; private DHPublicKey remoteDHPublicKey; @@ -66,6 +71,8 @@ public AuthContextImpl(Session session) { private Boolean isSecure = false; private int protocolVersion; + private static Logger logger = Logger.getLogger(AuthContextImpl.class.getName()); + private int getProtocolVersion() { return this.protocolVersion; } @@ -74,8 +81,6 @@ private void setProtocolVersion(int protoVersion) { this.protocolVersion = protoVersion; } - private static Logger logger = Logger.getLogger(AuthContextImpl.class.getName()); - class MessageFactory { private QueryMessage getQueryMessage() { @@ -194,10 +199,12 @@ private int getAuthenticationState() { } private byte[] getR() { + if (secureRandom == null) + secureRandom = new java.security.SecureRandom(); if (r == null) { logger.finest("Picking random key r."); r = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; - new Random().nextBytes(r); + secureRandom.nextBytes(r); } return r; } @@ -274,6 +281,10 @@ public BigInteger getS() throws OtrException { return s; } + public byte[] getExtraSymmetricKey() throws OtrException { + return h2(EXTRA_SYMMETRIC_KEY); + } + private byte[] getC() throws OtrException { if (c != null) return c; @@ -413,8 +424,8 @@ public void handleReceivingMessage(AbstractMessage m) throws OtrException { private void handleSignatureMessage(SignatureMessage m) throws OtrException { Session session = getSession(); SessionID sessionID = session.getSessionID(); - logger.finest(sessionID.getAccountID() + " received a signature message from " - + sessionID.getUserID() + " throught " + sessionID.getProtocolName() + "."); + logger.finest(sessionID.getLocalUserId() + " received a signature message from " + + sessionID.getRemoteUserId() + " throught " + sessionID.getProtocolName() + "."); if (!session.getSessionPolicy().getAllowV2()) { logger.finest("Policy does not allow OTRv2, ignoring message."); return; @@ -467,8 +478,8 @@ private void handleSignatureMessage(SignatureMessage m) throws OtrException { private void handleRevealSignatureMessage(RevealSignatureMessage m) throws OtrException { Session session = getSession(); SessionID sessionID = session.getSessionID(); - logger.finest(sessionID.getAccountID() + " received a reveal signature message from " - + sessionID.getUserID() + " throught " + sessionID.getProtocolName() + "."); + logger.finest(sessionID.getLocalUserId() + " received a reveal signature message from " + + sessionID.getRemoteUserId() + " throught " + sessionID.getProtocolName() + "."); if (!session.getSessionPolicy().getAllowV2()) { logger.finest("Policy does not allow OTRv2, ignoring message."); @@ -566,8 +577,8 @@ private void handleRevealSignatureMessage(RevealSignatureMessage m) throws OtrEx private void handleDHKeyMessage(DHKeyMessage m) throws OtrException { Session session = getSession(); SessionID sessionID = session.getSessionID(); - logger.finest(sessionID.getAccountID() + " received a D-H key message from " - + sessionID.getUserID() + " throught " + sessionID.getProtocolName() + "."); + logger.finest(sessionID.getLocalUserId() + " received a D-H key message from " + + sessionID.getRemoteUserId() + " throught " + sessionID.getProtocolName() + "."); if (!session.getSessionPolicy().getAllowV2()) { logger.finest("If ALLOW_V2 is not set, ignore this message."); @@ -607,8 +618,8 @@ private void handleDHKeyMessage(DHKeyMessage m) throws OtrException { private void handleDHCommitMessage(DHCommitMessage m) throws OtrException { Session session = getSession(); SessionID sessionID = session.getSessionID(); - logger.finest(sessionID.getAccountID() + " received a D-H commit message from " - + sessionID.getUserID() + " throught " + sessionID.getProtocolName() + "."); + logger.finest(sessionID.getLocalUserId() + " received a D-H commit message from " + + sessionID.getRemoteUserId() + " throught " + sessionID.getProtocolName() + "."); if (!session.getSessionPolicy().getAllowV2()) { logger.finest("ALLOW_V2 is not set, ignore this message."); diff --git a/src/net/java/otr4j/session/OtrAssembler.java b/src/net/java/otr4j/session/OtrAssembler.java new file mode 100644 index 000000000..6bf162736 --- /dev/null +++ b/src/net/java/otr4j/session/OtrAssembler.java @@ -0,0 +1,144 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +import java.net.ProtocolException; + +/** + * + * @author Felix Eckhofer + * + */ +public final class OtrAssembler { + + public OtrAssembler() { + discard(); + } + + /** + * Accumulated fragment thus far. + */ + private StringBuffer fragment; + + /** + * Number of last fragment received. This variable must be able to store an + * unsigned short value. + */ + private int fragmentCur; + + /** + * Total number of fragments in message. This variable must be able to store + * an unsigned short value. + */ + private int fragmentMax; + + private static final String HEAD_FRAGMENT_V2 = "?OTR,"; + private static final String HEAD_FRAGMENT_V3 = "?OTR|"; + + /** + * Appends a message fragment to the internal buffer and returns the full + * message if msgText was no fragmented message or all the fragments have + * been combined. Returns null, if there are fragments pending or an invalid + * fragment was received.

    A fragmented OTR message looks like this: (V2) + * ?OTR,k,n,piece-k, or (V3) + * ?OTR|sender_instance|receiver_instance,k,n,piece-k, + * + * @param msgText Message to be processed. + * + * @return String with the accumulated message or null if the message was + * incomplete or malformed + */ + public String accumulate(String msgText) throws ProtocolException { + // if it's a fragment, remove everything before "k,n,piece-k" + if (msgText.startsWith(HEAD_FRAGMENT_V2)) { + // v2 + msgText = msgText.substring(HEAD_FRAGMENT_V2.length()); + } else if (msgText.startsWith(HEAD_FRAGMENT_V3)) { + // v + msgText = msgText.substring(HEAD_FRAGMENT_V3.length()); + + // break away the v2 part + String[] instancePart = msgText.split(",", 2); + // split the two instance ids + String[] instances = instancePart[0].split("\\|", 2); + + if (instancePart.length != 2 || instances.length != 2) { + discard(); + throw new ProtocolException(); + } + + int receiverInstance; + try { + receiverInstance = Integer.parseInt(instances[1], 16); + } catch (NumberFormatException e) { + discard(); + throw new ProtocolException(); + } + + // continue with v2 part of fragment + msgText = instancePart[1]; + } else { + // not a fragmented message + discard(); + return msgText; + } + + String[] params = msgText.split(",", 4); + + int k, n; + try { + k = Integer.parseInt(params[0]); + n = Integer.parseInt(params[1]); + } catch (NumberFormatException e) { + discard(); + throw new ProtocolException(); + } catch (ArrayIndexOutOfBoundsException e) { + discard(); + throw new ProtocolException(); + } + + if (k == 0 || n == 0 || k > n || params.length != 4 || params[3].length() != 0) { + discard(); + throw new ProtocolException(); + } + + msgText = params[2]; + + if (k == 1) { + // first fragment + discard(); + fragmentCur = k; + fragmentMax = n; + fragment.append(msgText); + } else if (n == fragmentMax && k == fragmentCur + 1) { + // consecutive fragment + fragmentCur++; + fragment.append(msgText); + } else { + // out-of-order fragment + discard(); + throw new ProtocolException(); + } + + if (n == k && n > 0) { + String result = fragment.toString(); + discard(); + return result; + } else { + return null; // incomplete fragment + } + } + + /** + * Discard current fragment buffer and reset the counters. + */ + public void discard() { + fragment = new StringBuffer(); + fragmentCur = 0; + fragmentMax = 0; + } + +} diff --git a/src/net/java/otr4j/session/OtrSm.java b/src/net/java/otr4j/session/OtrSm.java index 282f1aec4..28b96c08a 100644 --- a/src/net/java/otr4j/session/OtrSm.java +++ b/src/net/java/otr4j/session/OtrSm.java @@ -18,6 +18,7 @@ import net.java.otr4j.crypto.SM.SMException; import net.java.otr4j.crypto.SM.SMState; import net.java.otr4j.io.OtrOutputStream; +import net.java.otr4j.io.SerializationUtils; public class OtrSm implements OtrTlvHandler { public static interface OtrSmEngineHost extends OtrEngineHost { @@ -33,7 +34,7 @@ public static interface OtrSmEngineHost extends OtrEngineHost { /** * Construct an OTR Socialist Millionaire handler object. - * + * * @param authContext The encryption context for encrypting the session. * @param keyManager The long-term key manager. * @param sessionId The session ID. @@ -79,13 +80,13 @@ private static byte[] computeSessionId(BigInteger s) throws SMException { /** * Respond to or initiate an SMP negotiation - * + * * @param question The question to present to the peer, if initiating. May * be null for no question. * @param secret The secret. * @param initiating Whether we are initiating or responding to an initial * request. - * + * * @return TLVs to send to the peer */ public List initRespondSmp(String question, String secret, boolean initiating) @@ -99,7 +100,12 @@ public List initRespondSmp(String question, String secret, boolean initiati * responder fingerprint (20 bytes), secure session id, input secret */ byte[] our_fp = Hex.decode(keyManager.getLocalFingerprint(sessionID)); - byte[] their_fp = Hex.decode(keyManager.getRemoteFingerprint(sessionID)); + + String remoteFingerprint = keyManager.getRemoteFingerprint(sessionID); + if (remoteFingerprint == null) + throw new OtrException("no fingerprint for remote user"); + + byte[] their_fp = Hex.decode(remoteFingerprint); byte[] sessionId; try { @@ -108,7 +114,8 @@ public List initRespondSmp(String question, String secret, boolean initiati throw new OtrException(ex); } - int combined_buf_len = 41 + sessionId.length + secret.length(); + byte[] bytes = secret.getBytes(SerializationUtils.UTF8); + int combined_buf_len = 41 + sessionId.length + bytes.length; byte[] combined_buf = new byte[combined_buf_len]; combined_buf[0] = 1; if (initiating) { @@ -119,7 +126,7 @@ public List initRespondSmp(String question, String secret, boolean initiati System.arraycopy(our_fp, 0, combined_buf, 21, 20); } System.arraycopy(sessionId, 0, combined_buf, 41, sessionId.length); - System.arraycopy(secret.getBytes(), 0, combined_buf, 41 + sessionId.length, secret.length()); + System.arraycopy(bytes, 0, combined_buf, 41 + sessionId.length, bytes.length); MessageDigest sha256; try { @@ -140,11 +147,12 @@ public List initRespondSmp(String question, String secret, boolean initiati throw new OtrException(ex); } - // If we've got a question, attach it to the smpmsg + // If we've got a question, attach it to the smpmsg if (question != null) { - byte[] qsmpmsg = new byte[question.length() + 1 + smpmsg.length]; - System.arraycopy(question.getBytes(), 0, qsmpmsg, 0, question.length()); - System.arraycopy(smpmsg, 0, qsmpmsg, question.length() + 1, smpmsg.length); + bytes = question.getBytes(SerializationUtils.UTF8); + byte[] qsmpmsg = new byte[bytes.length + 1 + smpmsg.length]; + System.arraycopy(bytes, 0, qsmpmsg, 0, bytes.length); + System.arraycopy(smpmsg, 0, qsmpmsg, bytes.length + 1, smpmsg.length); smpmsg = qsmpmsg; } @@ -156,7 +164,7 @@ public List initRespondSmp(String question, String secret, boolean initiati /** * Create an abort TLV and reset our state. - * + * * @return TLVs to send to the peer */ public List abortSmp() throws OtrException { @@ -170,6 +178,7 @@ public List getPendingTlvs() { } /** Process an incoming TLV and optionally send back TLVs to peer. */ + @Override public void processTlv(TLV tlv) throws OtrException { try { pendingTlvs = doProcessTlv(tlv); diff --git a/src/net/java/otr4j/session/Session.java b/src/net/java/otr4j/session/Session.java index 186b77c03..ce5b37fe8 100644 --- a/src/net/java/otr4j/session/Session.java +++ b/src/net/java/otr4j/session/Session.java @@ -23,6 +23,8 @@ public interface Session { public abstract OtrPolicy getSessionPolicy(); + public abstract String transformReceiving(String content, List tlvs) throws OtrException; + public abstract String transformReceiving(String content) throws OtrException; public abstract String transformSending(String content, List tlvs) throws OtrException; @@ -44,6 +46,8 @@ public interface Session { public abstract void removeTlvHandler(OtrTlvHandler handler); public abstract BigInteger getS(); - + public abstract void showWarning(String warning); + + public byte[] getExtraKey(); } \ No newline at end of file diff --git a/src/net/java/otr4j/session/SessionID.java b/src/net/java/otr4j/session/SessionID.java index a43a063e4..6719ddbf3 100644 --- a/src/net/java/otr4j/session/SessionID.java +++ b/src/net/java/otr4j/session/SessionID.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; @@ -10,44 +10,38 @@ /** @author George Politis */ public final class SessionID { - private String fullUserID; + private String mLocalUserId; + private String mRemoteUserId; + private String mProtocolName; - public SessionID(String accountID, String fullUserID, String protocolName) { - this.accountID = accountID; - this.userID = fullUserID == null ? null : Address.stripResource(fullUserID); - this.fullUserID = fullUserID; - this.protocolName = protocolName; - } - - private String accountID; - private String userID; - private String protocolName; - - private String sessionId; + private String mSessionId; public static final SessionID Empty = new SessionID(null, null, null); - public String getAccountID() { - return accountID; + public SessionID(String localUserId, String remoteUserId, String protocolName) { + mLocalUserId = localUserId; + mRemoteUserId = remoteUserId; + mProtocolName = protocolName; + mSessionId = Address.stripResource(mLocalUserId) + '_' + mProtocolName + '_' + Address.stripResource(mRemoteUserId); } - public String getUserID() { - return userID; - } - - public String getFullUserID() { - return fullUserID; + public String getLocalUserId () + { + return mLocalUserId; } - public String getProtocolName() { - return protocolName; + public String getRemoteUserId () + { + return mRemoteUserId; } - public synchronized String toString() { - - sessionId = getAccountID() + '_' + this.getProtocolName() + '_' + this.getFullUserID(); + public String getProtocolName () + { + return mProtocolName; + } - return sessionId; + public String toString() { + return mSessionId; } diff --git a/src/net/java/otr4j/session/SessionImpl.java b/src/net/java/otr4j/session/SessionImpl.java index 77e75e988..3e87c1c7c 100644 --- a/src/net/java/otr4j/session/SessionImpl.java +++ b/src/net/java/otr4j/session/SessionImpl.java @@ -1,16 +1,20 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; +import info.guardianproject.otr.app.im.app.ImApp; +import info.guardianproject.util.Debug; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.net.ProtocolException; import java.nio.ByteBuffer; import java.security.KeyPair; import java.security.PublicKey; @@ -18,7 +22,6 @@ import java.util.Arrays; import java.util.List; import java.util.Vector; -import java.util.logging.Logger; import javax.crypto.interfaces.DHPublicKey; @@ -40,22 +43,27 @@ import net.java.otr4j.io.messages.MysteriousT; import net.java.otr4j.io.messages.PlainTextMessage; import net.java.otr4j.io.messages.QueryMessage; +import android.util.Log; /** @author George Politis */ public class SessionImpl implements Session { + private static final int MIN_SESSION_START_INTERVAL = 5000; private SessionID sessionID; private OtrEngineHost host; private SessionStatus sessionStatus; private AuthContext authContext; private SessionKeys[][] sessionKeys; private Vector oldMacKeys; - private static Logger logger = Logger.getLogger(SessionImpl.class.getName()); + private List tlvHandlers = new ArrayList(); private BigInteger ess; private String lastSentMessage; private boolean doTransmitLastMessage = false; private boolean isLastMessageRetransmit = false; + private byte[] extraKey; + private long lastStart; + private OtrAssembler assembler; public SessionImpl(SessionID sessionID, OtrEngineHost listener) { @@ -67,34 +75,35 @@ public SessionImpl(SessionID sessionID, OtrEngineHost listener) { // -> setSessionStatus() fires statusChangedEvent // -> client application calls OtrEngine.getSessionStatus() this.sessionStatus = SessionStatus.PLAINTEXT; + assembler = new OtrAssembler(); } @Override - public void addTlvHandler(OtrTlvHandler handler) { + public synchronized void addTlvHandler(OtrTlvHandler handler) { tlvHandlers.add(handler); } @Override - public void removeTlvHandler(OtrTlvHandler handler) { + public synchronized void removeTlvHandler(OtrTlvHandler handler) { tlvHandlers.remove(handler); } - public BigInteger getS() { + public synchronized BigInteger getS() { return ess; } private SessionKeys getEncryptionSessionKeys() { - logger.finest("Getting encryption keys"); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Getting encryption keys"); return getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Current); } private SessionKeys getMostRecentSessionKeys() { - logger.finest("Getting most recent keys."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Getting most recent keys."); return getSessionKeysByIndex(SessionKeys.Current, SessionKeys.Current); } private SessionKeys getSessionKeysByID(int localKeyID, int remoteKeyID) { - logger.finest("Searching for session keys with (localKeyID, remoteKeyID) = (" + localKeyID + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Searching for session keys with (localKeyID, remoteKeyID) = (" + localKeyID + "," + remoteKeyID + ")"); for (int i = 0; i < getSessionKeys().length; i++) { @@ -102,7 +111,7 @@ private SessionKeys getSessionKeysByID(int localKeyID, int remoteKeyID) { SessionKeys current = getSessionKeysByIndex(i, j); if (current.getLocalKeyID() == localKeyID && current.getRemoteKeyID() == remoteKeyID) { - logger.finest("Matching keys found."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Matching keys found."); return current; } } @@ -121,16 +130,16 @@ private SessionKeys getSessionKeysByIndex(int localKeyIndex, int remoteKeyIndex) private void rotateRemoteSessionKeys(DHPublicKey pubKey) throws OtrException { - logger.finest("Rotating remote keys."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Rotating remote keys."); SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Current, SessionKeys.Previous); if (sess1.getIsUsedReceivingMACKey()) { - logger.finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); getOldMacKeys().add(sess1.getReceivingMACKey()); } SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Previous); if (sess2.getIsUsedReceivingMACKey()) { - logger.finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); getOldMacKeys().add(sess2.getReceivingMACKey()); } @@ -146,16 +155,16 @@ private void rotateRemoteSessionKeys(DHPublicKey pubKey) throws OtrException { private void rotateLocalSessionKeys() throws OtrException { - logger.finest("Rotating local keys."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Rotating local keys."); SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Current); if (sess1.getIsUsedReceivingMACKey()) { - logger.finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); getOldMacKeys().add(sess1.getReceivingMACKey()); } SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Previous); if (sess2.getIsUsedReceivingMACKey()) { - logger.finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); getOldMacKeys().add(sess2.getReceivingMACKey()); } @@ -170,7 +179,7 @@ private void rotateLocalSessionKeys() throws OtrException { } private byte[] collectOldMacKeys() { - logger.finest("Collecting old MAC keys to be revealed."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Collecting old MAC keys to be revealed."); int len = 0; for (int i = 0; i < getOldMacKeys().size(); i++) len += getOldMacKeys().get(i).length; @@ -183,71 +192,81 @@ private byte[] collectOldMacKeys() { return buff.array(); } - private void setSessionStatus(SessionStatus sessionStatus) throws OtrException { + private void setSessionStatus(SessionStatus sessionStatusNew) throws OtrException { + + boolean sessionStatusChanged = (sessionStatus != sessionStatusNew); + + sessionStatus = sessionStatusNew; switch (sessionStatus) { - case ENCRYPTED: - AuthContext auth = this.getAuthContext(); - ess = auth.getS(); - logger.finest("Setting most recent session keys from auth."); - for (int i = 0; i < this.getSessionKeys()[0].length; i++) { - SessionKeys current = getSessionKeysByIndex(0, i); - current.setLocalPair(auth.getLocalDHKeyPair(), 1); - current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); - current.setS(auth.getS()); - } + case ENCRYPTED: + AuthContext auth = this.getAuthContext(); + ess = auth.getS(); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Setting most recent session keys from auth."); + for (int i = 0; i < this.getSessionKeys()[0].length; i++) { + SessionKeys current = getSessionKeysByIndex(0, i); + current.setLocalPair(auth.getLocalDHKeyPair(), 1); + current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); + current.setS(auth.getS()); + } - KeyPair nextDH = new OtrCryptoEngineImpl().generateDHKeyPair(); - for (int i = 0; i < this.getSessionKeys()[1].length; i++) { - SessionKeys current = getSessionKeysByIndex(1, i); - current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); - current.setLocalPair(nextDH, 2); - } + KeyPair nextDH = new OtrCryptoEngineImpl().generateDHKeyPair(); + for (int i = 0; i < this.getSessionKeys()[1].length; i++) { + SessionKeys current = getSessionKeysByIndex(1, i); + current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); + current.setLocalPair(nextDH, 2); + } - this.setRemotePublicKey(auth.getRemoteLongTermPublicKey()); + this.setRemotePublicKey(auth.getRemoteLongTermPublicKey()); - auth.reset(); - - break; + auth.reset(); + + break; + case PLAINTEXT: + //nothing here + break; + + default: + //do nothing; } - SessionStatus oldSessionStatus = this.sessionStatus; - - // This must be set correctly for transformSending - this.sessionStatus = sessionStatus; - + if (sessionStatus == SessionStatus.ENCRYPTED && doTransmitLastMessage && lastSentMessage != null) { - String msg = this.transformSending((isLastMessageRetransmit ? "[resent] " : "") + lastSentMessage, null); + String retransmit = (isLastMessageRetransmit ? "[resent] " : ""); + String msg = transformSending(retransmit + lastSentMessage, null); getHost().injectMessage(getSessionID(), msg); } doTransmitLastMessage = false; isLastMessageRetransmit = false; lastSentMessage = null; - - if (sessionStatus != oldSessionStatus) { + + if (sessionStatusChanged) { + for (OtrEngineListener l : this.listeners) l.sessionStatusChanged(getSessionID()); } + + } /* * (non-Javadoc) - * + * * @see net.java.otr4j.session.ISession#getSessionStatus() */ - public SessionStatus getSessionStatus() { + public synchronized SessionStatus getSessionStatus() { return sessionStatus; } - private void setSessionID(SessionID sessionID) { + public void setSessionID(SessionID sessionID) { this.sessionID = sessionID; } /* * (non-Javadoc) - * + * * @see net.java.otr4j.session.ISession#getSessionID() */ public SessionID getSessionID() { @@ -280,19 +299,33 @@ private Vector getOldMacKeys() { return oldMacKeys; } + public String transformReceiving(String msgText) throws OtrException { + return transformReceiving(msgText, null); + } + /* * (non-Javadoc) - * + * * @see * net.java.otr4j.session.ISession#handleReceivingMessage(java.lang.String) */ - public String transformReceiving(String msgText) throws OtrException { + public String transformReceiving(String msgText, List tlvs) throws OtrException, NullPointerException { OtrPolicy policy = getSessionPolicy(); if (!policy.getAllowV1() && !policy.getAllowV2()) { - logger.finest("Policy does not allow neither V1 not V2, ignoring message."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Policy does not allow neither V1 not V2, ignoring message."); return msgText; } + try { + msgText = assembler.accumulate(msgText); + } catch (ProtocolException e) { + if (Debug.DEBUG_ENABLED) if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"An invalid message fragment was discarded."); + return null; + } + + if (msgText == null) + return null; // Not a complete message (yet). + AbstractMessage m; try { m = SerializationUtils.toMessage(msgText); @@ -305,7 +338,7 @@ public String transformReceiving(String msgText) throws OtrException { switch (m.messageType) { case AbstractEncodedMessage.MESSAGE_DATA: - return handleDataMessage((DataMessage) m); + return handleDataMessage((DataMessage) m, tlvs); case AbstractMessage.MESSAGE_ERROR: handleErrorMessage((ErrorMessage) m); return null; @@ -323,33 +356,33 @@ public String transformReceiving(String msgText) throws OtrException { if (auth.getIsSecure()) { this.setSessionStatus(SessionStatus.ENCRYPTED); - logger.finest("Gone Secure."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Gone Secure."); } return null; default: - throw new UnsupportedOperationException("Received an uknown message type."); + throw new UnsupportedOperationException("Received an unknown message type."); } } private void handleQueryMessage(QueryMessage queryMessage) throws OtrException { - logger.finest(getSessionID().getAccountID() + " received a query message from " - + getSessionID().getUserID() + " throught " + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,getSessionID().getLocalUserId() + " received a query message from " + + getSessionID().getRemoteUserId() + " throught " + getSessionID().getProtocolName() + "."); setSessionStatus(SessionStatus.PLAINTEXT); OtrPolicy policy = getSessionPolicy(); if (queryMessage.versions.contains(2) && policy.getAllowV2()) { - logger.finest("Query message with V2 support found."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Query message with V2 support found."); getAuthContext().respondV2Auth(); } else if (queryMessage.versions.contains(1) && policy.getAllowV1()) { - throw new UnsupportedOperationException(); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Query message with V1 support found - ignoring."); } } private void handleErrorMessage(ErrorMessage errorMessage) throws OtrException { - logger.finest(getSessionID().getAccountID() + " received an error message from " - + getSessionID().getUserID() + " throught " + getSessionID().getUserID() + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,getSessionID().getLocalUserId() + " received an error message from " + + getSessionID().getRemoteUserId() + " throught " + getSessionID().getRemoteUserId() + "."); OtrPolicy policy = getSessionPolicy(); @@ -357,7 +390,7 @@ private void handleErrorMessage(ErrorMessage errorMessage) throws OtrException { if (policy.getErrorStartAKE() && getSessionStatus() == SessionStatus.ENCRYPTED) { showWarning(errorMessage.error + " Initiating encryption."); - logger.finest("Error message starts AKE."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Error message starts AKE."); doTransmitLastMessage = true; isLastMessageRetransmit = true; @@ -368,20 +401,20 @@ private void handleErrorMessage(ErrorMessage errorMessage) throws OtrException { if (policy.getAllowV2()) versions.add(2); - logger.finest("Sending Query"); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Sending Query"); injectMessage(new QueryMessage(versions)); } else { showError(errorMessage.error); } } - private String handleDataMessage(DataMessage data) throws OtrException { - logger.finest(getSessionID().getAccountID() + " received a data message from " - + getSessionID().getUserID() + "."); + private String handleDataMessage(DataMessage data, List tlvs) throws OtrException { + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,getSessionID().getLocalUserId() + " received a data message from " + + getSessionID().getRemoteUserId() + "."); switch (this.getSessionStatus()) { case ENCRYPTED: - logger.finest("Message state is ENCRYPTED. Trying to decrypt message."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Message state is ENCRYPTED. Trying to decrypt message."); // Find matching session keys. int senderKeyID = data.senderKeyID; @@ -393,7 +426,7 @@ private String handleDataMessage(DataMessage data) throws OtrException { } // Verify received MAC with a locally calculated MAC. - logger.finest("Transforming T to byte[] to calculate it's HmacSHA1."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Transforming T to byte[] to calculate it's HmacSHA1."); byte[] serializedT; try { @@ -411,7 +444,7 @@ private String handleDataMessage(DataMessage data) throws OtrException { throw new OtrException("MAC verification failed, ignoring message"); } - logger.finest("Computed HmacSHA1 value matches sent one."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Computed HmacSHA1 value matches sent one."); // Mark this MAC key as old to be revealed. matchingKeys.setIsUsedReceivingMACKey(true); @@ -428,7 +461,8 @@ private String handleDataMessage(DataMessage data) throws OtrException { throw new OtrException(e); } - logger.finest("Decrypted message: \"" + decryptedMsgContent + "\""); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Decrypted message: \"" + decryptedMsgContent + "\""); + // FIXME extraKey = authContext.getExtraSymmetricKey(); // Rotate keys if necessary. SessionKeys mostRecent = this.getMostRecentSessionKeys(); @@ -439,7 +473,10 @@ private String handleDataMessage(DataMessage data) throws OtrException { this.rotateRemoteSessionKeys(data.nextDH); // Handle TLVs - List tlvs = null; + if (tlvs == null) { + tlvs = new ArrayList(); + } + int tlvIndex = decryptedMsgContent.indexOf((char) 0x0); if (tlvIndex > -1) { decryptedMsgContent = decryptedMsgContent.substring(0, tlvIndex); @@ -447,7 +484,6 @@ private String handleDataMessage(DataMessage data) throws OtrException { byte[] tlvsb = new byte[dmc.length - tlvIndex]; System.arraycopy(dmc, tlvIndex, tlvsb, 0, tlvsb.length); - tlvs = new Vector(); ByteArrayInputStream tin = new ByteArrayInputStream(tlvsb); while (tin.available() > 0) { int type; @@ -464,7 +500,7 @@ private String handleDataMessage(DataMessage data) throws OtrException { tlvs.add(new TLV(type, tdata)); } } - if (tlvs != null && tlvs.size() > 0) { + if (tlvs.size() > 0) { for (TLV tlv : tlvs) { switch (tlv.getType()) { case TLV.DISCONNECTED: @@ -503,21 +539,21 @@ public void injectMessage(AbstractMessage m) throws OtrException { } private String handlePlainTextMessage(PlainTextMessage plainTextMessage) throws OtrException { - logger.finest(getSessionID().getAccountID() + " received a plaintext message from " - + getSessionID().getUserID() + " throught " - + getSessionID().getProtocolName() + "."); + // if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,getSessionID().getLocalUserId() + " received a plaintext message from " + // + getSessionID().getRemoteUserId() + " throught " + // + getSessionID().getProtocolName() + "."); OtrPolicy policy = getSessionPolicy(); List versions = plainTextMessage.versions; if (versions == null || versions.size() < 1) { - logger.finest("Received plaintext message without the whitespace tag."); + //if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Received plaintext message without the whitespace tag."); switch (this.getSessionStatus()) { case ENCRYPTED: case FINISHED: // Display the message to the user, but warn him that the // message was received unencrypted. - showError("The message was received unencrypted."); - return plainTextMessage.cleanText; + //showError("The message was received unencrypted."); + return "[WARNING UNENCRYPTED: " + plainTextMessage.cleanText + "]"; case PLAINTEXT: // Simply display the message to the user. If // REQUIRE_ENCRYPTION @@ -529,7 +565,7 @@ private String handlePlainTextMessage(PlainTextMessage plainTextMessage) throws return plainTextMessage.cleanText; } } else { - logger.finest("Received plaintext message with the whitespace tag."); + // if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Received plaintext message with the whitespace tag."); switch (this.getSessionStatus()) { case ENCRYPTED: case FINISHED: @@ -547,10 +583,10 @@ private String handlePlainTextMessage(PlainTextMessage plainTextMessage) throws } if (policy.getWhitespaceStartAKE()) { - logger.finest("WHITESPACE_START_AKE is set"); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"WHITESPACE_START_AKE is set"); if (plainTextMessage.versions.contains(2) && policy.getAllowV2()) { - logger.finest("V2 tag found."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"V2 tag found."); getAuthContext().respondV2Auth(); } else if (plainTextMessage.versions.contains(1) && policy.getAllowV1()) { throw new UnsupportedOperationException(); @@ -579,8 +615,8 @@ public String transformSending(String msgText, List tlvs) throws OtrExcepti return msgText; case ENCRYPTED: this.lastSentMessage = msgText; - logger.finest(getSessionID().getAccountID() + " sends an encrypted message to " - + getSessionID().getUserID() + " through " + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,getSessionID().getLocalUserId() + " sends an encrypted message to " + + getSessionID().getRemoteUserId() + " through " + getSessionID().getProtocolName() + "."); // Get encryption keys. @@ -609,6 +645,7 @@ public String transformSending(String msgText, List tlvs) throws OtrExcepti try { eoos.writeShort(tlv.type); eoos.writeTlvData(tlv.value); + eoos.close(); } catch (IOException e) { throw new OtrException(e); } @@ -619,7 +656,7 @@ public String transformSending(String msgText, List tlvs) throws OtrExcepti byte[] data = out.toByteArray(); // Encrypt message. - logger.finest("Encrypting message with keyids (localKeyID, remoteKeyID) = (" + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Encrypting message with keyids (localKeyID, remoteKeyID) = (" + senderKeyID + ", " + receipientKeyID + ")"); byte[] encryptedMsg = otrCryptoEngine.aesEncrypt(encryptionKeys.getSendingAESKey(), ctr, data); @@ -635,7 +672,7 @@ public String transformSending(String msgText, List tlvs) throws OtrExcepti // Calculate T hash. byte[] sendingMACKey = encryptionKeys.getSendingMACKey(); - logger.finest("Transforming T to byte[] to calculate it's HmacSHA1."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Transforming T to byte[] to calculate it's HmacSHA1."); byte[] serializedT; try { serializedT = SerializationUtils.toByteArray(t); @@ -658,33 +695,40 @@ public String transformSending(String msgText, List tlvs) throws OtrExcepti case FINISHED: this.lastSentMessage = msgText; showError("Your message to " - + sessionID.getUserID() + + sessionID.getRemoteUserId() + " was not sent. Either end your private conversation, or restart it."); return null; default: - logger.finest("Uknown message state, not processing."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Unknown message state, not processing."); return msgText; } } /* * (non-Javadoc) - * + * * @see net.java.otr4j.session.ISession#startSession() */ public void startSession() throws OtrException { + // Throttle session starts + long now = System.currentTimeMillis(); + if (now - lastStart < MIN_SESSION_START_INTERVAL) { + return; + } + lastStart = now; + if (this.getSessionStatus() == SessionStatus.ENCRYPTED) return; if (!getSessionPolicy().getAllowV2()) - throw new UnsupportedOperationException(); + throw new OtrException("OTRv2 is not supported by this session"); this.getAuthContext().startV2Auth(); } /* * (non-Javadoc) - * + * * @see net.java.otr4j.session.ISession#endSession() */ public void endSession() throws OtrException { @@ -693,10 +737,9 @@ public void endSession() throws OtrException { case ENCRYPTED: Vector tlvs = new Vector(); tlvs.add(new TLV(1, null)); - String msg = this.transformSending(null, tlvs); getHost().injectMessage(getSessionID(), msg); - this.setSessionStatus(SessionStatus.PLAINTEXT); + setSessionStatus(SessionStatus.PLAINTEXT); break; case FINISHED: this.setSessionStatus(SessionStatus.PLAINTEXT); @@ -709,7 +752,7 @@ public void endSession() throws OtrException { /* * (non-Javadoc) - * + * * @see net.java.otr4j.session.ISession#refreshSession() */ public void refreshSession() throws OtrException { @@ -749,12 +792,16 @@ public OtrPolicy getSessionPolicy() { public KeyPair getLocalKeyPair() { return getHost().getKeyPair(this.getSessionID()); } - + public void showError(String warning) { getHost().showError(sessionID, warning); } - + public void showWarning(String warning) { getHost().showWarning(sessionID, warning); } + + public byte[] getExtraKey() { + return extraKey; + } } diff --git a/src/net/java/otr4j/session/SessionKeysImpl.java b/src/net/java/otr4j/session/SessionKeysImpl.java index a212fdb55..36deaf817 100644 --- a/src/net/java/otr4j/session/SessionKeysImpl.java +++ b/src/net/java/otr4j/session/SessionKeysImpl.java @@ -1,15 +1,17 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; +import info.guardianproject.otr.app.im.app.ImApp; +import info.guardianproject.util.Debug; + import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.KeyPair; import java.util.Arrays; -import java.util.logging.Logger; import javax.crypto.interfaces.DHPublicKey; @@ -17,11 +19,11 @@ import net.java.otr4j.crypto.OtrCryptoEngine; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.io.SerializationUtils; +import android.util.Log; /** @author George Politis */ class SessionKeysImpl implements SessionKeys { - private static Logger logger = Logger.getLogger(SessionKeysImpl.class.getName()); private String keyDescription; public SessionKeysImpl(int localKeyIndex, int remoteKeyIndex) { @@ -40,14 +42,14 @@ public SessionKeysImpl(int localKeyIndex, int remoteKeyIndex) { public void setLocalPair(KeyPair keyPair, int localPairKeyID) { this.localPair = keyPair; this.setLocalKeyID(localPairKeyID); - logger.finest(keyDescription + " current local key ID: " + this.getLocalKeyID()); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,keyDescription + " current local key ID: " + this.getLocalKeyID()); this.reset(); } public void setRemoteDHPublicKey(DHPublicKey pubKey, int remoteKeyID) { this.setRemoteKey(pubKey); this.setRemoteKeyID(remoteKeyID); - logger.finest(keyDescription + " current remote key ID: " + this.getRemoteKeyID()); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,keyDescription + " current remote key ID: " + this.getRemoteKeyID()); this.reset(); } @@ -55,7 +57,7 @@ public void setRemoteDHPublicKey(DHPublicKey pubKey, int remoteKeyID) { private byte[] receivingCtr = new byte[16]; public void incrementSendingCtr() { - logger.finest("Incrementing counter for (localkeyID, remoteKeyID) = (" + getLocalKeyID() + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Incrementing counter for (localkeyID, remoteKeyID) = (" + getLocalKeyID() + "," + getRemoteKeyID() + ")"); // logger.debug("Counter prior increament: " + // Utils.dump(sendingCtr, @@ -82,7 +84,7 @@ public void setReceivingCtr(byte[] ctr) { } private void reset() { - logger.finest("Resetting " + keyDescription + " session keys."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Resetting " + keyDescription + " session keys."); Arrays.fill(this.sendingCtr, (byte) 0x00); Arrays.fill(this.receivingCtr, (byte) 0x00); this.sendingAESKey = null; @@ -127,7 +129,7 @@ public byte[] getSendingAESKey() throws OtrException { byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; ByteBuffer buff = ByteBuffer.wrap(h1); buff.get(key); - logger.finest("Calculated sending AES key."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Calculated sending AES key."); this.sendingAESKey = key; return sendingAESKey; } @@ -145,7 +147,7 @@ public byte[] getReceivingAESKey() throws OtrException { byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; ByteBuffer buff = ByteBuffer.wrap(h1); buff.get(key); - logger.finest("Calculated receiving AES key."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Calculated receiving AES key."); this.receivingAESKey = key; return receivingAESKey; @@ -156,14 +158,14 @@ public byte[] getSendingMACKey() throws OtrException { return sendingMACKey; sendingMACKey = new OtrCryptoEngineImpl().sha1Hash(getSendingAESKey()); - logger.finest("Calculated sending MAC key."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Calculated sending MAC key."); return sendingMACKey; } public byte[] getReceivingMACKey() throws OtrException { if (receivingMACKey == null) { receivingMACKey = new OtrCryptoEngineImpl().sha1Hash(getReceivingAESKey()); - logger.finest("Calculated receiving AES key."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Calculated receiving AES key."); } return receivingMACKey; } @@ -172,7 +174,7 @@ private BigInteger getS() throws OtrException { if (s == null) { s = new OtrCryptoEngineImpl().generateSecret(getLocalPair().getPrivate(), getRemoteKey()); - logger.finest("Calculating shared secret S."); + if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG,"Calculating shared secret S."); } return s; } diff --git a/src/net/java/otr4j/session/SessionStatus.java b/src/net/java/otr4j/session/SessionStatus.java index 20f77ffe0..f8c544a4b 100644 --- a/src/net/java/otr4j/session/SessionStatus.java +++ b/src/net/java/otr4j/session/SessionStatus.java @@ -1,6 +1,6 @@ /* * otr4j, the open source java otr library. - * + * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.session; diff --git a/src/net/java/otr4j/session/UnknownInstanceException.java b/src/net/java/otr4j/session/UnknownInstanceException.java new file mode 100644 index 000000000..51308408c --- /dev/null +++ b/src/net/java/otr4j/session/UnknownInstanceException.java @@ -0,0 +1,12 @@ +package net.java.otr4j.session; + +import java.net.ProtocolException; + +@SuppressWarnings("serial") +public class UnknownInstanceException extends ProtocolException { + + public UnknownInstanceException(String host) { + super(host); + } + +} diff --git a/src/net/java/otr4j/test/io/IOTest.java b/src/net/java/otr4j/test/io/IOTest.java index 8e46eb15a..29fe8c3d2 100644 --- a/src/net/java/otr4j/test/io/IOTest.java +++ b/src/net/java/otr4j/test/io/IOTest.java @@ -46,12 +46,14 @@ public void testIOShort() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); OtrOutputStream oos = new OtrOutputStream(out); oos.writeShort(source); + oos.close(); byte[] converted = out.toByteArray(); ByteArrayInputStream bin = new ByteArrayInputStream(converted); OtrInputStream ois = new OtrInputStream(bin); int result = ois.readShort(); + ois.close(); assertEquals(source, result); } @@ -62,12 +64,14 @@ public void testIOData() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); OtrOutputStream oos = new OtrOutputStream(out); oos.writeData(source); + oos.close(); byte[] converted = out.toByteArray(); ByteArrayInputStream bin = new ByteArrayInputStream(converted); OtrInputStream ois = new OtrInputStream(bin); byte[] result = ois.readData(); + ois.close(); assertTrue(java.util.Arrays.equals(source, result)); } @@ -80,12 +84,14 @@ public void testIOBigInt() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); OtrOutputStream oos = new OtrOutputStream(out); oos.writeBigInt(source); + oos.close(); byte[] converted = out.toByteArray(); ByteArrayInputStream bin = new ByteArrayInputStream(converted); OtrInputStream ois = new OtrInputStream(bin); BigInteger result = ois.readBigInt(); + ois.close(); assertTrue(source.compareTo(result) == 0); } @@ -98,12 +104,14 @@ public void testIODHPublicKey() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); OtrOutputStream oos = new OtrOutputStream(out); oos.writeDHPublicKey(source); + oos.close(); byte[] converted = out.toByteArray(); ByteArrayInputStream bin = new ByteArrayInputStream(converted); OtrInputStream ois = new OtrInputStream(bin); DHPublicKey result = ois.readDHPublicKey(); + ois.close(); assertTrue(source.getY().compareTo(result.getY()) == 0); } diff --git a/tests/.classpath b/tests/.classpath index 2ea874070..9b48070c0 100644 --- a/tests/.classpath +++ b/tests/.classpath @@ -1,9 +1,10 @@ + + + + - - - diff --git a/tests/.project b/tests/.project index 7524e8c28..80513638b 100644 --- a/tests/.project +++ b/tests/.project @@ -1,9 +1,9 @@ - GibberbotTests + ChatSecureTest - Gibberbot + ChatSecure diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 4638e77cd..5252eb1df 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -4,7 +4,7 @@ android:versionCode="1" android:versionName="1.0" > - + Hello World! - GibberbotTestsTest + ChatSecureTestsTest - \ No newline at end of file + diff --git a/tests/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPinsTest.java b/tests/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPinsTest.java new file mode 100644 index 000000000..1e8d4d429 --- /dev/null +++ b/tests/src/info/guardianproject/otr/app/im/plugin/xmpp/XMPPCertPinsTest.java @@ -0,0 +1,165 @@ + +package info.guardianproject.otr.app.im.plugin.xmpp; + +import android.content.Context; +import android.os.Build; +import android.test.AndroidTestCase; +import android.util.Log; + +import info.guardianproject.cacheword.PRNGFixes; +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.app.AccountActivity; +import info.guardianproject.util.Debug; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; + +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.thoughtcrime.ssl.pinning.PinningTrustManager; +import org.thoughtcrime.ssl.pinning.SystemKeyStore; + +public class XMPPCertPinsTest extends AndroidTestCase { + private static final String TAG = "XMPPCertPinsTest"; + + SystemKeyStore systemKeyStore; + PinningTrustManager pinningTrustManager; + SSLContext sslContext; + SecureRandom secureRandom; + String domainsWithPins[]; + String domainsWithoutPins[] = { + // signed by cacert.org, can't be pinned with AndroidPinning + "jabber.ccc.de", + // "vodka-pomme.net", + // "jabber.cn" + }; + + @Override + public void setUp() { + Context c = getContext(); + PRNGFixes.apply(); + systemKeyStore = SystemKeyStore.getInstance(c); + pinningTrustManager = new PinningTrustManager(systemKeyStore, + XMPPCertPins.getPinList(), 0); + secureRandom = new java.security.SecureRandom(); + try { + sslContext = SSLContext.getInstance("TLS"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + assert true; + } + ArrayList domains = new ArrayList( + Arrays.asList(c.getResources().getStringArray(R.array.account_domains))); + domains.add(AccountActivity.DEFAULT_SERVER_FACEBOOK); + domains.add(AccountActivity.DEFAULT_SERVER_JABBERORG); + // currently fails here, needs SRV tricks + // domains.add(AccountActivity.DEFAULT_SERVER_GOOGLE); + domainsWithPins = domains.toArray(new String[domains.size()]); + } + + private ConnectionConfiguration getConfig(String domain) throws KeyManagementException { + ConnectionConfiguration config = new ConnectionConfiguration(domain, 5222); + config.setDebuggerEnabled(Debug.DEBUG_ENABLED); + config.setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()); + config.setSecurityMode(SecurityMode.required); + config.setVerifyChainEnabled(true); + config.setVerifyRootCAEnabled(true); + config.setExpiredCertificatesCheckEnabled(true); + config.setNotMatchingDomainCheckEnabled(true); + config.setSelfSignedCertificateEnabled(false); + + return config; + } + + public void testDomainsWithPins() { + XMPPConnection connection = null; + try { + for (String domain : domainsWithPins) { + Log.i(TAG, "TESTING DOMAINS WITH PINS: " + domain); + ConnectionConfiguration config = getConfig(domain); + sslContext.init(null, new javax.net.ssl.TrustManager[] { + pinningTrustManager + }, secureRandom); + config.setCustomSSLContext(sslContext); + connection = new XMPPConnection(config); + connection.addConnectionListener(new ConnectionListener() { + + @Override + public void reconnectionSuccessful() { + Log.i(TAG, "reconnectionSuccessful"); + assertTrue(false); + } + + @Override + public void reconnectionFailed(Exception e) { + Log.i(TAG, "reconnectionSuccessful"); + e.printStackTrace(); + assertTrue(false); + } + + @Override + public void reconnectingIn(int arg0) { + Log.i(TAG, "reconnectingIn " + arg0); + assertTrue(false); + } + + @Override + public void connectionClosedOnError(Exception e) { + Log.i(TAG, "connectionClosedOnError"); + e.printStackTrace(); + assertTrue(false); + } + + @Override + public void connectionClosed() { + Log.i(TAG, "connectionClosed"); + } + }); + connection.connect(); + assertTrue(connection.isConnected()); + } + } catch (KeyManagementException e) { + Log.e(TAG, "KeyManagementException"); + e.printStackTrace(); + assertTrue(false); + } catch (XMPPException e) { + Log.e(TAG, "XMPPException"); + e.printStackTrace(); + assertTrue(false); + } + if (connection != null) + connection.disconnect(); + } + + public void testSettingCipherSuites() { + try { + sslContext.init(null, new javax.net.ssl.TrustManager[] { + pinningTrustManager + }, secureRandom); + + sslContext.getDefaultSSLParameters().getCipherSuites(); + + if (Build.VERSION.SDK_INT >= 20) { + sslContext.getDefaultSSLParameters().setCipherSuites( + XMPPCertPins.SSL_IDEAL_CIPHER_SUITES_API_20); + } + else + { + sslContext.getDefaultSSLParameters().setCipherSuites( + XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); + } + } catch (KeyManagementException e) { + e.printStackTrace(); + assert true; + } + } +} diff --git a/tests/src/info/guardianproject/otr/app/im/ui/AboutActivityTest.java b/tests/src/info/guardianproject/otr/app/im/ui/AboutActivityTest.java new file mode 100644 index 000000000..0d98f1cad --- /dev/null +++ b/tests/src/info/guardianproject/otr/app/im/ui/AboutActivityTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package info.guardianproject.otr.app.im.ui; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; + +import info.guardianproject.otr.app.im.R; +import info.guardianproject.otr.app.im.ui.AboutActivity; + +/** + * @author devrandom + */ +public class AboutActivityTest extends ActivityInstrumentationTestCase2 { + public AboutActivityTest() { + super(AboutActivity.class); + } + + private AboutActivity mActivity; + private View mView; + + @Override + public void setUp() throws Exception { + super.setUp(); + setActivityInitialTouchMode(false); + mActivity = getActivity(); + mView = mActivity.findViewById(R.id.WizardTextBody); + } + + public void testPreconditions() { + assertNotNull(mView); + } +} diff --git a/tests/src/net/java/otr4j/crypto/SMTest.java b/tests/src/net/java/otr4j/crypto/SMTest.java new file mode 100644 index 000000000..64b8660f1 --- /dev/null +++ b/tests/src/net/java/otr4j/crypto/SMTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package net.java.otr4j.crypto; + +import android.test.InstrumentationTestCase; + +import net.java.otr4j.crypto.SM.SMState; + +public class SMTest extends InstrumentationTestCase { + private byte[] secret1; + private byte[] secret2; + private SMState state_a; + private SMState state_b; + + @Override + public void setUp() throws Exception { + secret1 = "abcdef".getBytes(); + secret2 = "abCdef".getBytes(); + this.state_a = new SMState(); + this.state_b = new SMState(); + } + + public void testSuccess() throws Exception { + byte[] msg1 = SM.step1(state_a, secret1); + SM.step2a(state_b, msg1, true); + byte[] msg2 = SM.step2b(state_b, secret1); + byte[] msg3 = SM.step3(state_a, msg2); + byte[] msg4 = SM.step4(state_b, msg3); + SM.step5(state_a, msg4); + assertEquals(SM.PROG_SUCCEEDED, state_a.smProgState); + assertEquals(SM.PROG_SUCCEEDED, state_b.smProgState); + } + + public void testFail() throws Exception { + byte[] msg1 = SM.step1(state_a, secret1); + SM.step2a(state_b, msg1, true); + byte[] msg2 = SM.step2b(state_b, secret2); + byte[] msg3 = SM.step3(state_a, msg2); + byte[] msg4 = SM.step4(state_b, msg3); + SM.step5(state_a, msg4); + assertEquals(SM.PROG_FAILED, state_a.smProgState); + assertEquals(SM.PROG_FAILED, state_b.smProgState); + } +} diff --git a/update-ant-build.bat b/update-ant-build.bat new file mode 100644 index 000000000..e7333bb0d --- /dev/null +++ b/update-ant-build.bat @@ -0,0 +1,22 @@ +@echo off +rem update-ant-build script for Windows Command prompt +rem make sure PATH environment variable includes Android SDK tools path + +set target=android-21 +set projectname=ChatSecure + +echo Updating Android target... +call android update lib-project --path external/MemorizingTrustManager --target %target% +call android update lib-project --path external/AndroidPinning --target %target% +call android update lib-project --path external/cacheword/cachewordlib --target %target% +call android update lib-project --path external/SlidingMenu/library --target %target% +call android update lib-project --path external/AndroidEmojiInput/library --target %target% + +call android update project --path . --name %projectname% --target %target% --subprojects + +echo Synchronizing jar files... +copy "libs\android-support-v4.jar" "external\SlidingMenu\library\libs\android-support-v4.jar" +copy "libs\android-support-v4.jar" "external\cacheword\cachewordlib\libs\android-support-v4.jar" +copy "libs\android-support-v4.jar" "external\ViewPagerIndicator\library\libs\android-support-v4.jar" +copy "libs\android-support-v4.jar" "external\AndroidEmojiInputlibs\android-support-v4.jar" + diff --git a/update-ant-build.sh b/update-ant-build.sh index 61c86e241..4c8ee1d31 100755 --- a/update-ant-build.sh +++ b/update-ant-build.sh @@ -1,22 +1,32 @@ #!/bin/sh -# make sure your Android SDK tools path is set in SDK_BASE -android update project --path . --name Gibberbot --subprojects -android update project --path external/ActionBarSherlock/actionbarsherlock -t android-17 -android update project --path external/MemorizingTrustManager --name MemorizingTrustManager -t android-17 --subprojects -android update project --path external/OnionKit/libonionkit --name OnionKit -t android-17 -android update project --path external/AndroidPinning --name libpinning -t android-17 -android update project --path external/cacheword/cachewordlib --name cacheword -t android-17 -android update project --path external/SlidingMenu/library --name sliding -t android-17 -android update project --path external/SlideListView/library --name slidelist -t android-17 -android update project --path external/NineOldAndroids/library --name nineold -t android-17 -android update project --path external/MessageBar/library --name messagebar -t android-17 -android update project --path external/ShowcaseView/library --name showcase -t android-17 -android update project --path external/AndroidEmojiInput/library --name emoji -t android-17 +set -e + +if ! which android > /dev/null; then + if [ -z $ANDROID_HOME ]; then + if [ -e ~/.android/bashrc ]; then + . ~/.android/bashrc + else + echo "'android' not found, ANDROID_HOME must be set!" + exit + fi + else + export PATH="${ANDROID_HOME}/tools:$PATH" + fi +fi + +# fetch target from project.properties +eval `grep '^target=' project.properties` + +projectname=`sed -n 's,.*name="app_name">\(.*\)<.*,\1,p' res/values/strings.xml` + +for lib in `sed -n 's,^android\.library\.reference\.[0-9][0-9]*=\(.*\),\1,p' project.properties`; do + android update lib-project --path $lib --target $target +done + +android update project --path . --name $projectname --target $target --subprojects -cp libs/android-support-v4.jar external/OnionKit/libonionkit/libs/android-support-v4.jar -cp libs/android-support-v4.jar external/ActionBarSherlock/actionbarsherlock/libs/android-support-v4.jar cp libs/android-support-v4.jar external/SlidingMenu/library/libs/android-support-v4.jar cp libs/android-support-v4.jar external/cacheword/cachewordlib/libs/android-support-v4.jar - -cp libs/sqlcipher.jar external/cacheword/cachewordlib/libs/sqlcipher.jar +cp libs/android-support-v4.jar external/ViewPagerIndicator/library/libs/android-support-v4.jar +cp libs/android-support-v4.jar external/AndroidEmojiInput/library/libs/android-support-v4.jar