diff options
58 files changed, 2061 insertions, 1444 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8c90d092..62ea4035 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.siacs.conversations" - android:versionCode="11" - android:versionName="0.2" > + android:versionCode="14" + android:versionName="0.2.3" > <uses-sdk android:minSdkVersion="14" @@ -32,13 +32,14 @@ <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> + <action android:name="android.intent.action.ACTION_SHUTDOWN" /> </intent-filter> </receiver> <activity android:name="eu.siacs.conversations.ui.ConversationActivity" android:configChanges="orientation|screenSize" - android:label="Conversations" + android:label="@string/title_activity_conversations" android:windowSoftInputMode="stateHidden" > <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -48,23 +49,23 @@ </activity> <activity android:name="eu.siacs.conversations.ui.SettingsActivity" - android:label="Settings" + android:label="@string/title_activity_settings" android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity" > </activity> <activity android:name="eu.siacs.conversations.ui.ManageAccountActivity" - android:label="Manage Accounts" + android:label="@string/title_activity_manage_accounts" android:configChanges="orientation|screenSize" android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity" > </activity> <activity android:name="eu.siacs.conversations.ui.MucDetailsActivity" - android:label="Conference Details" + android:label="@string/title_activity_conference_details" android:windowSoftInputMode="stateHidden" > </activity> <activity android:name="eu.siacs.conversations.ui.ContactDetailsActivity" - android:label="Contact Details" + android:label="@string/title_activity_contact_details" android:windowSoftInputMode="stateHidden" > </activity> <activity @@ -84,7 +85,7 @@ </activity> <activity android:name="eu.siacs.conversations.ui.ShareWithActivity" - android:label="Conversations" + android:label="@string/title_activity_conversations" android:theme="@android:style/Theme.Holo.Light.DialogWhenLarge" android:icon="@drawable/ic_launcher"> <intent-filter> diff --git a/CHANGELOG.md b/CHANGELOG.md index ecfd13f5..7e9aa5cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ###Changelog +####Version 0.2.3 +* regression fix with receiving encrypted images + +####Version 0.2.2 +* Ability to take photos directly +* Improved openPGP offline handling +* Various bug fixes +* Updated Translations + +####Version 0.2.1 +* Various bug fixes +* Updated Translations + ####Version 0.2 * Image file transfer * Better integration with OpenKeychain (PGP encryption) @@ -42,11 +42,14 @@ These XEPs are - as of now: (In order of appearance) ###Code -* Rene Treffer @rtreffer -* Andreas Straub @strb +* [Rene Treffer](https://github.com/rtreffer) +* [Andreas Straub](https://github.com/strb) ###Translations -* @beriain (Spanish and Basque) +* [Sergio Cárdenas](https://github.com/kruks23) (Spanish) +* [Benoit Bouvarel](https://github.com/BenoitBouvarel) (French) +* [Daniel Gultsch](https://github.com/iNPUTmice) (German) +* [Aitor Beriain](https://github.com/beriain) (Basque) ##FAQ ###General diff --git a/res/drawable-hdpi/ic_action_search.png b/res/drawable-hdpi/ic_action_search.png Binary files differnew file mode 100644 index 00000000..f594b4e4 --- /dev/null +++ b/res/drawable-hdpi/ic_action_search.png diff --git a/res/drawable-mdpi/ic_action_search.png b/res/drawable-mdpi/ic_action_search.png Binary files differnew file mode 100644 index 00000000..2e446ec0 --- /dev/null +++ b/res/drawable-mdpi/ic_action_search.png diff --git a/res/drawable-xhdpi/ic_action_search.png b/res/drawable-xhdpi/ic_action_search.png Binary files differnew file mode 100644 index 00000000..aad535e9 --- /dev/null +++ b/res/drawable-xhdpi/ic_action_search.png diff --git a/res/drawable-xxhdpi/ic_action_search.png b/res/drawable-xxhdpi/ic_action_search.png Binary files differnew file mode 100644 index 00000000..9c0ea3ca --- /dev/null +++ b/res/drawable-xxhdpi/ic_action_search.png diff --git a/res/layout/fragment_conversations_overview.xml b/res/layout-sw320dp/fragment_conversations_overview.xml index 5548cce6..fcb1949c 100644 --- a/res/layout/fragment_conversations_overview.xml +++ b/res/layout-sw320dp/fragment_conversations_overview.xml @@ -4,7 +4,7 @@ android:layout_height="match_parent" android:id="@+id/slidingpanelayout"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="336dp" + android:layout_width="288dp" android:layout_height="match_parent" android:orientation="vertical" android:background="#eeeeee" @@ -22,7 +22,7 @@ </LinearLayout> <LinearLayout android:id="@+id/selected_conversation" - android:layout_width="400dp" + android:layout_width="fill_parent" android:layout_height="match_parent" android:layout_weight="1" android:background="#e5e5e5" diff --git a/res/layout-sw360dp/fragment_conversations_overview.xml b/res/layout-sw360dp/fragment_conversations_overview.xml new file mode 100644 index 00000000..939950c2 --- /dev/null +++ b/res/layout-sw360dp/fragment_conversations_overview.xml @@ -0,0 +1,31 @@ +<android.support.v4.widget.SlidingPaneLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/slidingpanelayout"> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="324dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#eeeeee" + > + + <ListView + android:id="@+id/list" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="#b5b5b5" + android:dividerHeight="1dp" + android:background="#eeeeee" + /> + +</LinearLayout> +<LinearLayout + android:id="@+id/selected_conversation" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#e5e5e5" + android:orientation="vertical"> +</LinearLayout> +</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file diff --git a/res/layout-sw384dp/fragment_conversations_overview.xml b/res/layout-sw384dp/fragment_conversations_overview.xml new file mode 100644 index 00000000..e48cf9ec --- /dev/null +++ b/res/layout-sw384dp/fragment_conversations_overview.xml @@ -0,0 +1,31 @@ +<android.support.v4.widget.SlidingPaneLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/slidingpanelayout"> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="345dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#eeeeee" + > + + <ListView + android:id="@+id/list" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="#b5b5b5" + android:dividerHeight="1dp" + android:background="#eeeeee" + /> + +</LinearLayout> +<LinearLayout + android:id="@+id/selected_conversation" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#e5e5e5" + android:orientation="vertical"> +</LinearLayout> +</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file diff --git a/res/layout-sw600dp/fragment_conversations_overview.xml b/res/layout-sw600dp/fragment_conversations_overview.xml new file mode 100644 index 00000000..fac95f9c --- /dev/null +++ b/res/layout-sw600dp/fragment_conversations_overview.xml @@ -0,0 +1,31 @@ +<android.support.v4.widget.SlidingPaneLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/slidingpanelayout"> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="240dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#eeeeee" + > + + <ListView + android:id="@+id/list" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="#b5b5b5" + android:dividerHeight="1dp" + android:background="#eeeeee" + /> + +</LinearLayout> +<LinearLayout + android:id="@+id/selected_conversation" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#e5e5e5" + android:orientation="vertical"> +</LinearLayout> +</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file diff --git a/res/layout-sw720dp/fragment_conversations_overview.xml b/res/layout-sw720dp/fragment_conversations_overview.xml new file mode 100644 index 00000000..fcb1949c --- /dev/null +++ b/res/layout-sw720dp/fragment_conversations_overview.xml @@ -0,0 +1,31 @@ +<android.support.v4.widget.SlidingPaneLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/slidingpanelayout"> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="288dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#eeeeee" + > + + <ListView + android:id="@+id/list" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="#b5b5b5" + android:dividerHeight="1dp" + android:background="#eeeeee" + /> + +</LinearLayout> +<LinearLayout + android:id="@+id/selected_conversation" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#e5e5e5" + android:orientation="vertical"> +</LinearLayout> +</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file diff --git a/res/layout/account_row.xml b/res/layout/account_row.xml index 402cb965..6cfbe9e3 100644 --- a/res/layout/account_row.xml +++ b/res/layout/account_row.xml @@ -19,7 +19,6 @@ android:id="@+id/account_jid" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="inputmice@jabber.ccc.de" android:textSize="20sp" android:singleLine="true" android:scrollHorizontally="false"/> @@ -34,7 +33,7 @@ android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Status: " + android:text="@string/account_status" android:textStyle="bold" android:textSize="16sp" /> @@ -42,8 +41,9 @@ android:id="@+id/account_status" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingLeft="4dp" android:textColor="#669900" - android:text="unknown" + android:text="@string/account_status_unknown" android:textStyle="bold" android:textSize="16sp"/> diff --git a/res/layout/activity_muc_details.xml b/res/layout/activity_muc_details.xml index 73f21ad0..f866a485 100644 --- a/res/layout/activity_muc_details.xml +++ b/res/layout/activity_muc_details.xml @@ -13,7 +13,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dp" - android:text="Conference" /> + android:text="@string/muc_details_conference" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -28,7 +28,7 @@ android:layout_toLeftOf="@+id/muc_edit_subject" android:background="#eee" android:ems="10" - android:hint="Conferenece Subject" + android:hint="@string/muc_details_conference_subject" android:inputType="textAutoComplete" android:paddingBottom="12dp" android:paddingLeft="8dp" @@ -62,7 +62,7 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:paddingTop="8dp" - android:text="Your Nickname" /> + android:text="@string/muc_details_your_nickname" /> <RelativeLayout android:layout_width="match_parent" @@ -78,7 +78,7 @@ android:layout_toLeftOf="@+id/muc_edit_nick" android:background="#eee" android:ems="10" - android:hint="Your nickname" + android:hint="@string/muc_details_your_nickname" android:inputType="textEmailAddress" android:paddingBottom="12dp" android:paddingLeft="8dp" @@ -119,7 +119,7 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:paddingTop="8dp" - android:text="Other Members" /> + android:text="@string/muc_details_other_members" /> <LinearLayout android:id="@+id/muc_members" diff --git a/res/layout/activity_new_conversation.xml b/res/layout/activity_new_conversation.xml index a2b00af1..78500ead 100644 --- a/res/layout/activity_new_conversation.xml +++ b/res/layout/activity_new_conversation.xml @@ -23,7 +23,7 @@ android:layout_alignParentTop="true" android:background="#eee" android:ems="10" - android:hint="Search or enter Jabber ID" + android:hint="@string/search_jabber_id" android:inputType="textEmailAddress" android:paddingBottom="12dp" android:paddingLeft="8dp" diff --git a/res/layout/dialog_verify_otr.xml b/res/layout/dialog_verify_otr.xml index 41ce3b07..6dfe149d 100644 --- a/res/layout/dialog_verify_otr.xml +++ b/res/layout/dialog_verify_otr.xml @@ -20,7 +20,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="8dp" - android:text="julia@jabber.example.com" android:textSize="14sp" /> <TextView android:layout_width="wrap_content" @@ -35,7 +34,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="8dp" - android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:textSize="14sp" android:typeface="monospace"/> <TextView @@ -51,7 +49,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="8dp" - android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:textSize="14sp" android:typeface="monospace"/> </LinearLayout> diff --git a/res/layout/edit_account_dialog.xml b/res/layout/edit_account_dialog.xml index 0a86039d..12fbe468 100644 --- a/res/layout/edit_account_dialog.xml +++ b/res/layout/edit_account_dialog.xml @@ -9,7 +9,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" - android:text="Jabber ID:" /> + android:text="@string/account_settings_jabber_id" /> @@ -19,7 +19,7 @@ android:layout_height="wrap_content" android:ems="10" android:inputType="textEmailAddress" - android:hint="username@jabber.example.com"> + android:hint="@string/account_settings_example_jabber_id"> </EditText> @@ -30,7 +30,7 @@ android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Password:" + android:text="@string/account_settings_password" android:textSize="14sp"/> <EditText @@ -39,14 +39,14 @@ android:layout_height="wrap_content" android:ems="10" android:inputType="textPassword" - android:hint="Password" + android:hint="@string/password" android:fontFamily="sans-serif" /> <CheckBox android:id="@+id/edit_account_register_new" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Register a new account on server"/> + android:text="@string/register_account"/> <TextView @@ -54,7 +54,7 @@ android:id="@+id/account_confirm_password_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Confirm password:" + android:text="@string/account_settings_confirm_password" android:textSize="14sp" android:visibility="gone"/> @@ -64,7 +64,7 @@ android:layout_height="wrap_content" android:ems="10" android:inputType="textPassword" - android:hint="Confirm password" + android:hint="@string/confirm_password" android:visibility="gone" android:fontFamily="sans-serif" /> diff --git a/res/menu/attachment_choices.xml b/res/menu/attachment_choices.xml new file mode 100644 index 00000000..6a4f295a --- /dev/null +++ b/res/menu/attachment_choices.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/attach_choose_picture" + android:title="@string/attach_choose_picture"/> + <item + android:id="@+id/attach_take_picture" + android:title="@string/attach_take_picture"/> + <item + android:id="@+id/attach_record_voice" + android:title="@string/attach_record_voice" + android:visible="false"/> +</menu>
\ No newline at end of file diff --git a/res/menu/contact_details.xml b/res/menu/contact_details.xml index 5d4a8d56..43e8ea6a 100644 --- a/res/menu/contact_details.xml +++ b/res/menu/contact_details.xml @@ -5,13 +5,13 @@ android:orderInCategory="10" android:showAsAction="always" android:icon="@drawable/ic_action_edit" - android:title="Edit name" /> + android:title="@string/action_edit_contact" /> <item android:id="@+id/action_delete_contact" android:orderInCategory="10" android:showAsAction="always" android:icon="@drawable/ic_action_discard" - android:title="Delete from roster" /> + android:title="@string/action_delete_contact" /> <item android:id="@+id/action_accounts" android:orderInCategory="90" diff --git a/res/menu/encryption_choices.xml b/res/menu/encryption_choices.xml index ade17654..0596bfbc 100644 --- a/res/menu/encryption_choices.xml +++ b/res/menu/encryption_choices.xml @@ -4,14 +4,14 @@ <item android:id="@+id/encryption_choice_none" - android:title="Plain text" + android:title="@string/encryption_choice_none" /> <item android:id="@+id/encryption_choice_otr" - android:title="OTR" + android:title="@string/encryption_choice_otr" /> <item android:id="@+id/encryption_choice_pgp" - android:title="OpenPGP"/> + android:title="@string/encryption_choice_pgp"/> </group> </menu> diff --git a/res/menu/manageaccounts_context.xml b/res/menu/manageaccounts_context.xml index da8c8bf2..5f76b0e0 100644 --- a/res/menu/manageaccounts_context.xml +++ b/res/menu/manageaccounts_context.xml @@ -5,20 +5,20 @@ android:id="@+id/mgmt_account_edit" android:icon="@drawable/ic_action_edit" android:showAsAction="always" - android:title="Edit Account"/> + android:title="@string/mgmt_account_edit"/> <item android:id="@+id/mgmt_account_delete" android:icon="@drawable/ic_action_delete" android:showAsAction="always" - android:title="Delete"/> + android:title="@string/mgmt_account_delete"/> <item android:id="@+id/mgmt_account_disable" android:showAsAction="never" - android:title="Temporarily disable"/> + android:title="@string/mgmt_account_disable"/> <item android:id="@+id/mgmt_account_enable" android:showAsAction="never" - android:title="Enable" + android:title="@string/mgmt_account_enable" android:visible="false"/> <item android:id="@+id/mgmt_account_announce_pgp" diff --git a/res/menu/newconversation.xml b/res/menu/newconversation.xml index ca6a57e1..4f9b6da0 100644 --- a/res/menu/newconversation.xml +++ b/res/menu/newconversation.xml @@ -1,12 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item - android:id="@+id/action_refresh_contacts" - android:orderInCategory="10" - android:showAsAction="always" - android:icon="@drawable/ic_action_refresh" - android:title="Refresh contact list" /> - <item android:id="@+id/action_accounts" android:orderInCategory="90" android:showAsAction="never" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 719d769c..292a81e8 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -4,12 +4,15 @@ <string name="app_name">Conversations</string> <string name="action_settings">Einstellungen</string> <string name="action_add">Neue Unterhaltung</string> - <string name="action_accounts">Accounts verwalten</string> + <string name="action_accounts">Konten verwalten</string> + <string name="action_refresh">Kontaktliste neu laden</string> <string name="action_end_conversation">Unterhaltung beenden</string> <string name="action_contact_details">Kontaktdetails</string> <string name="action_muc_details">Konferenzdetails</string> <string name="action_secure">Verschlüsselte Unterhaltung</string> <string name="action_add_account">Account hinzufügen</string> + <string name="action_edit_contact">Name bearbeiten</string> + <string name="action_delete_contact">Aus Kontaktliste entfernen</string> <string name="title_activity_contacts">Kontakte</string> <string name="just_now">gerade</string> <string name="sending">senden…</string> @@ -24,7 +27,7 @@ <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste enfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> <string name="untrusted_cert_hint">Der Server %s hat Dir ein unbekanntes, möglicherweise selbstsigniertes Zertifikat geschickt.</string> <string name="account_info">Server Info</string> - <string name="register_account">Neuen Account auf dem Server erstellen</string> + <string name="register_account">Neues Konto auf dem Server erstellen</string> <string name="share_with">Teile mit…</string> <string name="ask_again"><u>Klick um noch einmal zu fragen</u></string> <string name="show_otr_key">OTR Fingerabdruck</string> @@ -41,12 +44,12 @@ <string name="account_offline">Account offline</string> <string name="cant_invite_while_offline">Du musst online sein um andere Leute zu einer Konferenz einzuladen</string> <string name="crash_report_title">Conversations ist abgestürzt</string> - <string name="crash_report_message">By sending in stack traces you are helping the ongoing development of Conversations\n<b>Warning:</b> This will use your XMPP account to send the stack trace to the developer.</string> + <string name="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird einen von deinen XMPP Konten benutzen um den Entwickler zu kontaktieren.</string> <string name="send_now">Jetzt abschicken</string> <string name="send_never">Nie mehr nachfragen</string> - <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Account</string> - <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Accounts</string> - <string name="touch_to_fix">Drücke hier um den Account zu verwalten</string> + <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string> + <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konto</string> + <string name="touch_to_fix">Drücke hier um das Konto zu verwalten</string> <string name="attach_file">Datei anfügen</string> <string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string> <string name="add_contact">Kontakt hinzufügen</string> @@ -69,11 +72,11 @@ <string name="error_loading_image">Fehler beim laden des Bildes. (Datei wurde nicht gefunden)</string> <string name="image_offered_for_download"><i>Bild Datei zum Download angeboten</i></string> <string name="not_connected">Nicht verbunden</string> - <string name="you_are_offline">Du musst online sein um %s zu verschicken. Dein Account der mit dieser Unterhaltung verbunden ist aber gerade offline.</string> + <string name="you_are_offline">Du musst online sein um %s zu verschicken. Das mit dieser Unterhaltung verbundene Konto ist gerade offline.</string> <string name="you_are_offline_blank">Du kannst diese Aktion nicht ausführen so lange du offline bist.</string> <string name="files">Dateien</string> <string name="otr_messages">OTR-verschlüsselte Nachrichten</string> - <string name="manage_account">Account verwalten</string> + <string name="manage_account">Konto verwalten</string> <string name="contact_offline">Dein Kontakt ist offline</string> <string name="contact_offline_otr">Dein Kontakt muss online sein um OTR-verschlüsselte Nachrichten zu empfangen. Möchtest Du die Nachricht gerne im Klartext übertragen?</string> <string name="contact_offline_file">Der Kontakt muss online sein um Datein zu empfangen.</string> @@ -127,5 +130,43 @@ <string name="keys">Schlüssel</string> <string name="send_presence_updates">Sende Online Status</string> <string name="receive_presence_updates">Empfange Online Status</string> - <string name="preemptively_grant">Preemptively grant subscription request</string> -</resources> + <string name="ask_for_presence_updates">Frage um Erlaubnis den Online Status sehen zu dürfen</string> + <string name="attach_choose_picture">Photo auswählen</string> + <string name="attach_take_picture">Photo aufnehmen</string> + <string name="preemptively_grant">Erlaube Statusanfrage vorab</string> + <string name="error_not_an_image_file">Die ausgewählte Datei ist kein Bild</string> + <string name="error_compressing_image">Fehler beim umwandeln des Bildes</string> + <string name="error_file_not_found">Datei nicht gefunden</string> + <string name="error_io_exception">Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr?</string> + <string name="error_security_exception_during_image_copy">Die App mit der du das Bild ausgesucht hast, hat uns keine Rechte eingeräumt das Bild zu betrachten.\n\n<small>Benutze einen anderen Dateimanager</small></string> + <string name="account_status">Status:</string> + <string name="account_status_unknown">Unbekannt</string> + <string name="account_status_disabled">Vorübergehend abgeschaltet</string> + <string name="account_status_online">Online</string> + <string name="account_status_connecting">Verbinde\u2026</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Ungültige Zugangsdaten</string> + <string name="account_status_not_found">Server nicht gefunden</string> + <string name="account_status_no_internet">Keine Internetverbindung</string> + <string name="account_status_requires_tls">Server benötigt TLS</string> + <string name="account_status_error">Unbekanntes Zerfifikat</string> + <string name="account_status_regis_fail">Registrierung fehlgeschlagen</string> + <string name="account_status_regis_conflict">Benutzername wird bereits verwendet</string> + <string name="account_status_regis_success">Registrierung abgeschlossen</string> + <string name="account_status_regis_not_sup">Der Server unterstützt keine Registrierung</string> + <string name="certif_no_trust">Nicht verbinden</string> + <string name="certif_trust">Dem Zertifikat vertrauen</string> + <string name="encryption_choice_none">Klartext</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">openPGP</string> + <string name="mgmt_account_edit">Konto bearbeiten</string> + <string name="mgmt_account_delete">Löschen</string> + <string name="mgmt_account_disable">Vorübergehend abschalten</string> + <string name="mgmt_account_enable">Anschalten</string> + <string name="attach_record_voice">Sprache aufzeichnen</string> + <string name="account_settings">Kontoeinstellungen</string> + <string name="save">Speichern</string> + <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> + <string name="invalid_jid">Ungültige Jabber ID</string> + +</resources>
\ No newline at end of file diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 12e255e8..ce4db1fb 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -5,35 +5,62 @@ <string name="action_settings">Ajustes</string> <string name="action_add">Nueva conversación</string> <string name="action_accounts">Gestionar cuentas</string> + <string name="action_refresh">Actualizar lista de contactos</string> <string name="action_end_conversation">Terminar conversación</string> <string name="action_contact_details">Detalles del contacto</string> <string name="action_muc_details">Detalles de la conferencia</string> <string name="action_secure">Conversación segura</string> <string name="action_add_account">Añadir cuenta</string> + <string name="action_edit_contact">Editar contacto</string> + <string name="action_delete_contact">Eliminar contacto de la lista</string> + <string name="action_add_phone_book">Añadir a contactos del teléfono</string> <string name="title_activity_contacts">Contactos</string> + <string name="title_activity_manage_accounts">Gestionar Cuentas</string> + <string name="title_activity_settings">Ajustes</string> + <string name="title_activity_conference_details">Detalles de Conferencia</string> + <string name="title_activity_contact_details">Detalles de Contacto</string> + <string name="title_activity_conversations">Conversations</string> <string name="just_now">ahora</string> + <string name="minutes_ago">min</string> + <string name="unread_conversations">conversaciones por leer</string> <string name="sending">enviando…</string> <string name="announce_pgp">Renovar anuncio PGP</string> <string name="encrypted_message">Desencriptando mensaje. Espera por favor…</string> <string name="conference_details">Detalles de la conferencia</string> <string name="nick_in_use">El apodo ya está en uso</string> + <string name="admin">Administrador</string> + <string name="owner">Propietario</string> <string name="moderator">Moderador</string> <string name="participant">Participante</string> <string name="visitor">Visitante</string> <string name="enter_new_name">Introduce un nuevo nombre:</string> - <string name="remove_contact_text">¿Quieres eliminar a %s de tu lista?. La conversación asociada a esta cuenta no se eliminará.</string> + <string name="remove_contact_text">¿Quieres eliminar a %s de tu lista? La conversación asociada a esta cuenta no se eliminará.</string> <string name="untrusted_cert_hint">El servidor %s presenta un certificado no confiable, posiblemente auto firmado.</string> <string name="account_info">Información del servidor</string> <string name="register_account">Registrar nueva cuenta en servidor</string> <string name="share_with">Compartir con</string> <string name="ask_again"><u>Pulsa para preguntar otra vez</u></string> <string name="show_otr_key">Huella dactilar OTR</string> - <string name="no_otr_fingerprint">No se ha generado una huella dactilar OTR. Simplemente continúa y comienza una conversación encriptada</string> + <string name="no_otr_fingerprint">No se ha generado una huella dactilar OTR. Continúa y comienza una conversación encriptada</string> <string name="start_conversation">Comenzar conversación</string> <string name="invite_contacts">Invitar contactos</string> <string name="invite_contacts_to_existing">Invitar a conferencia existente</string> <string name="new_conference">Crear nueva conferencia</string> + <string name="new_contact">Crear nuevo contacto</string> + <string name="contacts">Contactos</string> + <string name="search_jabber_id">Busca o escribe identificador Jabber</string> + <string name="choose_account">Seleccionar cuenta</string> + <string name="multi_user_conference">Conferencia multiusuario</string> + <string name="trying_join_conference">¿Estás intentando unirte a una conferencia?</string> <string name="cancel">Cancelar</string> + <string name="add">Añadir</string> + <string name="edit">Editar</string> + <string name="delete">Eliminar</string> + <string name="save">Guardar</string> + <string name="yes">Sí</string> + <string name="no">No</string> + <string name="ok">Ok</string> + <string name="done">Hecho</string> <string name="create_invite">Crear \u0026 Invitar</string> <string name="new_conference_explained">¿Quieres crear una nueva conferencia con una dirección generada aleatoriamente e invitar a los contactos seleccionados a ella?</string> <string name="no_open_mucs">No hay conferencias existentes</string> @@ -47,7 +74,7 @@ <string name="problem_connecting_to_account">No se ha podido conectar a la cuenta</string> <string name="problem_connecting_to_accounts">No se ha podido conectar a múltiples cuentas</string> <string name="touch_to_fix">Pulsa aquí para gestionar tus cuentas</string> - <string name="attach_file">Enviar archivo</string> + <string name="attach_file">Adjuntar</string> <string name="not_in_roster">El contacto no está en tu lista. ¿Quieres añadirlo?</string> <string name="add_contact">Añadir contacto</string> <string name="send_failed">Error al enviar</string> @@ -74,7 +101,7 @@ <string name="files">archivos</string> <string name="otr_messages">Mensajes encriptados con OTR</string> <string name="manage_account">Gestionar cuenta</string> - <string name="contact_offline">Tu contacto está desconectado</string> + <string name="contact_offline">El contacto está desconectado</string> <string name="contact_offline_otr">Desgraciadamente no es posible enviar mensajes encriptados con OTR a un contacto desconectado.\n¿Quieres enviar el mensaje en texto plano?</string> <string name="contact_offline_file">Desgraciadamente no es posible enviar archivos a un contacto desconectado.</string> <string name="send_unencrypted">Enviar sin encriptar</string> @@ -85,9 +112,9 @@ <string name="install">Instalar</string> <string name="offering">ofreciendo…</string> <string name="no_pgp_key">Clave openPGP no encontrada</string> - <string name="contact_has_no_pgp_key">Conversations no ha podido encriptar tus mensajes porque tu contacto no está anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure openPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaje encriptado recibido. Pulsa para desencriptar y ver.</i></string> - <string name="encrypted_image_received"><i>Imagen encriptada recibida. Pulsa para desencriptar y ver.</i></string> + <string name="contact_has_no_pgp_key">Conversations no ha podido encriptar tus mensajes porque el contacto no está anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure openPGP.</small></string> + <string name="encrypted_message_received"><i>Mensaje encriptado recibido. Pulsa para ver.</i></string> + <string name="encrypted_image_received"><i>Imagen encriptada recibida. Pulsa para ver.</i></string> <string name="image_file"><i>Imagen recibida. Pulsa para ver</i></string> <string name="otr_file_transfer">Encriptación con OTR no disponible</string> <string name="otr_file_transfer_msg">Desafortunadamente la encriptación con OTR no está disponible para transferencia de archivos. Puedes selecionar encriptación con openPGP o no usar encriptación.</string> @@ -95,7 +122,7 @@ <string name="pref_xmpp_resource">Recurso</string> <string name="pref_xmpp_resource_summary">El nombre que identifica el cliente que estás utilizando</string> <string name="pref_accept_files">Aceptar archivos</string> - <string name="pref_accept_files_summary">Automáticamente aceptar archivos menores que…</string> + <string name="pref_accept_files_summary">De forma automática aceptar archivos menores de…</string> <string name="pref_notification_settings">Ajustes de notificación</string> <string name="pref_notifications">Notificaciones</string> <string name="pref_notifications_summary">Notifica cuando llega un nuevo mensaje</string> @@ -108,7 +135,7 @@ <string name="pref_notification_grace_period">Notificaciones Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita las notificaciones durante un corto periodo de tiempo después de recibir la copia del mensaje carbon</string> <string name="pref_ui_options">Opciones de interfaz</string> - <string name="pref_use_phone_self_picture">Usar foto de contacto del teléfono</string> + <string name="pref_use_phone_self_picture">Usar foto del teléfono</string> <string name="pref_use_phone_sefl_picture_summary">Podrías no ser capaz de distinguir que cuenta está utlizando en una conversación</string> <string name="pref_conference_name">Nombre de la conferencia</string> <string name="pref_conference_name_summary">Usa el nombre de la sala para identificar Conferencias</string> @@ -120,4 +147,70 @@ <string name="error_copying_image_file">Error copiando archivo de imagen.</string> <string name="accept">Aceptar</string> <string name="error">Ha ocurrido un error</string> + <string name="pref_grant_presence_updates">Suscripción de presencia</string> + <string name="pref_grant_presence_updates_summary">Por defecto solicitar y conceder suscripciones de presencia de los contactos que has creado</string> + <string name="subscriptions">Suscripciones</string> + <string name="subscription_updated">Suscripción actualizada</string> + <string name="your_account">Tu cuenta</string> + <string name="keys">Claves</string> + <string name="send_presence_updates">Enviar actualizaciones de presencia</string> + <string name="receive_presence_updates">Recibir actualizaciones de presencia</string> + <string name="ask_for_presence_updates">Solicitar actualizaciones de presencia</string> + <string name="asked_for_presence_updates">Solictida actualizaciones de presencia</string> + <string name="attach_choose_picture">Seleccionar imagen</string> + <string name="attach_take_picture">Hacer foto</string> + <string name="preemptively_grant">Por defecto conceder solicitud de suscripción</string> + <string name="error_not_an_image_file">El archivo seleccionado no es una imagen</string> + <string name="error_compressing_image">Error convirtiendo el archivo de imagen</string> + <string name="error_file_not_found">Archivo no encontrado</string> + <string name="error_io_exception">Error I/O general. ¿Puede que te hayas quedado sin espacio en disco?</string> + <string name="error_security_exception_during_image_copy">La aplicación que usas para seleccionar imágenes no proporciona suficientes permisos para leer el archivo.\n\n<small>Utiliza un explorador de ficheros diferente para seleccionar la imagen</small></string> + <string name="account_status">Estado:</string> + <string name="account_status_unknown">Desconocido</string> + <string name="account_status_disabled">Deshabilitado temporalmente</string> + <string name="account_status_online">Conectado</string> + <string name="account_status_connecting">Conectando\u2026</string> + <string name="account_status_offline">Desconectado</string> + <string name="account_status_unauthorized">No autorizado</string> + <string name="account_status_not_found">Servidor no encontrado</string> + <string name="account_status_no_internet">Sin conectividad</string> + <string name="account_status_requires_tls">El servidor requiere TLS</string> + <string name="account_status_error">Certificado no confiable</string> + <string name="account_status_regis_fail">Error en el registro</string> + <string name="account_status_regis_conflict">El identificador ya está en uso</string> + <string name="account_status_regis_success">Registro completado</string> + <string name="account_status_regis_not_sup">El servidor no soporta registros</string> + <string name="certif_no_trust">No conectar</string> + <string name="certif_trust">Confiar en el certificado</string> + <string name="encryption_choice_none">Texto plano</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">openPGP</string> + <string name="mgmt_account_edit">Editar cuenta</string> + <string name="mgmt_account_delete">Eliminar cuenta</string> + <string name="mgmt_account_disable">Deshabilitar temporalmente</string> + <string name="mgmt_account_enable">Habilitar</string> + <string name="mgmt_account_are_you_sure">¿Estás seguro?</string> + <string name="mgmt_account_delete_confirm_text">Si eliminas tu cuenta tu historial completo de conversaciones se perderá</string> + <string name="attach_record_voice">Grabar audio</string> + <string name="account_settings">Configuración de cuenta</string> + <string name="account_settings_jabber_id">Identificador Jabber:</string> + <string name="account_settings_password">Contraseña:</string> + <string name="account_settings_example_jabber_id">usuario@ejemplo.com</string> + <string name="account_settings_confirm_password">Confirmar contraseña:</string> + <string name="password">Contraseña</string> + <string name="confirm_password">Confirmar contraseña</string> + <string name="passwords_do_not_match">Las contraseñas no coinciden</string> + <string name="invalid_jid">El identificador no es un identificador de Jabber válido</string> + <string name="error_out_of_memory">Sin memoria. La imagen es demasiado grande</string> + <string name="add_phone_book_text">¿Te gustaría añadir a %s a tus contactos del teléfono?</string> + <string name="contact_status_online">Disponible</string> + <string name="contact_status_free_to_chat">Hablador</string> + <string name="contact_status_away">Ausente</string> + <string name="contact_status_extended_away">Ausencia ext.</string> + <string name="contact_status_do_not_disturb">No molestar</string> + <string name="contact_status_offline">Desconectado</string> + <string name="muc_details_conference">Conferencia</string> + <string name="muc_details_conference_subject">Ausnto de la Conferencia</string> + <string name="muc_details_your_nickname">Tu apodo</string> + <string name="muc_details_other_members">Otros Miembros</string> </resources>
\ No newline at end of file diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 78327d66..daa4fa78 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -5,15 +5,18 @@ <string name="action_settings">Paramètres</string> <string name="action_add">Nouvelle conversation</string> <string name="action_accounts">Gérer les comptes</string> + <string name="action_refresh">Rafraichir la liste de contacts</string> <string name="action_end_conversation">Terminer cette conversation</string> <string name="action_contact_details">Détails du contact</string> <string name="action_muc_details">Détails de la conférence</string> <string name="action_secure">Conversation sécurisée</string> + <string name="action_edit_contact">Edit name</string> + <string name="action_delete_contact">Delete from roster</string> <string name="action_add_account">Ajouter un compte</string> <string name="title_activity_contacts">Contacts</string> <string name="just_now">À l\'instant</string> <string name="sending">envoi…</string> - <string name="announce_pgp">Renouveller les annonces PGP</string> + <string name="announce_pgp">Renouveler les annonces PGP</string> <string name="encrypted_message">Déchiffrement du message. Patientez…</string> <string name="conference_details">Détails de la conférence</string> <string name="nick_in_use">Cet identifiant est déjà utilisé.</string> @@ -26,7 +29,7 @@ <string name="account_info">Informations du serveur</string> <string name="register_account">Créer un nouveau compte sur le serveur</string> <string name="share_with">Partager avec</string> - <string name="ask_again"><u>Appuyez pour demander à nouveau</u></string> + <string name="ask_again"><u>Appuyez pour demander à nouveau.</u></string> <string name="show_otr_key">Empreinte OTR</string> <string name="no_otr_fingerprint">Empreinte OTR non générée. Essayez de démarrer une conversation sécurisée.</string> <string name="start_conversation">Démarrer une conversation</string> @@ -41,16 +44,16 @@ <string name="account_offline">Compte hors-ligne</string> <string name="cant_invite_while_offline">Vous devez être en ligne pour inviter des participants à une conférence.</string> <string name="crash_report_title">Conversations s\'est arreté</string> - <string name="crash_report_message">En envoyant des logs vous aidez au développement de Conversations\n\n<b>Attention:</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> + <string name="crash_report_message">En envoyant des logs vous aidez au développement de Conversations.\n\n<b>Attention:</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> <string name="send_now">Envoyer</string> <string name="send_never">Ne plus me demander</string> - <string name="problem_connecting_to_account">Impossible de se connecter au compte</string> - <string name="problem_connecting_to_accounts">Impossible de se connecter aux comptes</string> - <string name="touch_to_fix">Appuyez pour gérer vos comptes</string> + <string name="problem_connecting_to_account">Impossible de se connecter au compte.</string> + <string name="problem_connecting_to_accounts">Impossible de se connecter aux comptes.</string> + <string name="touch_to_fix">Appuyez pour gérer vos comptes.</string> <string name="attach_file">Lier un fichier</string> <string name="not_in_roster">Le contact n\'est pas dans votre carnet d\'adresses. Voulez-vous l\'y ajouter?</string> <string name="add_contact">Ajouter un contact</string> - <string name="send_failed">Echec de l\'envoi</string> + <string name="send_failed">Echec de l\'envoi.</string> <string name="send_rejected">Rejeté</string> <string name="receiving_image">Réception d\'une image. Patientez…</string> <string name="preparing_image">Préparation de la transmission de l\'image. Patientez…</string> @@ -65,14 +68,14 @@ <string name="send_otr_message">Envoyer un message sécurisé par OTR</string> <string name="send_pgp_message">Envoyer un message sécurisé par openPGP</string> <string name="your_nick_has_been_changed">Votre identifiant a été changé</string> - <string name="download_image">Télecharger l\'image</string> + <string name="download_image">Télécharger l\'image</string> <string name="error_loading_image">Impossible de télécharger l\'image (Fichier non trouvé)</string> <string name="image_offered_for_download"><i>Image proposée au téléchargement.</i></string> <string name="not_connected">Non connecté</string> <string name="you_are_offline">Vous devez être en ligne pour envoyer %s mais votre compte utilisé dans cette conversation est hors-ligne.</string> <string name="you_are_offline_blank">Vous devez être en ligne pour réaliser cette action.</string> <string name="files">Fichiers</string> - <string name="otr_messages">Message chiffrés par OTR</string> + <string name="otr_messages">Messages chiffrés par OTR</string> <string name="manage_account">Gérer les comptes</string> <string name="contact_offline">Votre correspondant est hors-ligne.</string> <string name="contact_offline_otr">Envoyer un message chiffré via OTR à un correspondant hors-ligne n\'est malheureusement pas possible.\nVoulez-vous envoyer ce message sans chiffrement?</string> @@ -83,4 +86,81 @@ <string name="openkeychain_required_long">Conversations requiert une application tierce nommée <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n<small>(Merci de redémarrer Conversations apres l\'installation du logiciel)</small></string> <string name="restart">Redémarrer</string> <string name="install">Installer</string> + <string name="offering">Proposition…</string> + <string name="no_pgp_key">Aucune clef openPGP trouvée.</string> + <string name="contact_has_no_pgp_key">Conversations ne peut chiffrer vos messages car votre correspondant n\'a pas communiqué sa clef publique.\n\n<small>Merci de demander à votre correspondant de configurer openPGP.</small></string> + <string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour le déchiffrer.</i></string> + <string name="encrypted_image_received"><i>Image chiffrée reçue. Appuyez pour la déchiffrer.</i></string> + <string name="image_file"><i>Image reçue. Appuyez pour visualiser.</i></string> + <string name="otr_file_transfer">Chiffrement OTR non disponible</string> + <string name="otr_file_transfer_msg">Malheureusement le chiffrement OTR n\'est pas disponible pour le transfert de fichiers. Vous pouvez utiliser openPGP ou envoyer vos fichiers non chiffrés.</string> + <string name="use_pgp_encryption">Utiliser le chiffrement openPGP</string> + <string name="pref_xmpp_resource">Ressource XMPP</string> + <string name="pref_xmpp_resource_summary">Nom permettant d\'identifier ce client XMPP</string> + <string name="pref_accept_files">Accepter les fichiers</string> + <string name="pref_accept_files_summary">Accepter automatiquement les fichiers plus petits que…</string> + <string name="pref_notification_settings">Paramètres de notification</string> + <string name="pref_notifications">Notifications</string> + <string name="pref_notifications_summary">Notifier l\'arrivée d\'un message</string> + <string name="pref_vibrate">Vibration</string> + <string name="pref_vibrate_summary">Vibrer lors de l\'arrivée d\'un message</string> + <string name="pref_sound">Sonore</string> + <string name="pref_sound_summary">Jouer une sonnerie lors de l\'arrivée d\'un message</string> + <string name="pref_conference_notifications">Notifications lors des conférences</string> + <string name="pref_conference_notifications_summary">Toujours notifier l\'arrivée d\'un message provenant d\'une conférence.</string> + <string name="pref_notification_grace_period">Période sans notification</string> + <string name="pref_notification_grace_period_summary">Désactiver momentanément les notifications après l\'arrivée d\'une copie carbone.</string> + <string name="pref_ui_options">Options d\'affichage</string> + <string name="pref_use_phone_self_picture">Utiliser les images des contacts</string> + <string name="pref_use_phone_sefl_picture_summary">Vous pourriez ne plus être capable de distinguer quel compte vous utilisez dans une conversation.</string> + <string name="pref_conference_name">Nom de la conférence</string> + <string name="pref_conference_name_summary">Identifier la conférence par son sujet</string> + <string name="pref_advanced_options">Options avancées</string> + <string name="pref_never_send_crash">Ne jamais envoyer de rapports d\'erreurs</string> + <string name="pref_never_send_crash_summary">En envoyant des logs vous aidez au développement de Conversations.</string> + <string name="openpgp_error">Une erreur s\'est produite via OpenKeychain</string> + <string name="error_decrypting_file">Erreur d\'E/S lors du déchiffrement du fichier</string> + <string name="error_copying_image_file">Erreur lors de la copie du fichier</string> + <string name="accept">Accepter</string> + <string name="error">Une erreur s\'est produite</string> + <string name="pref_grant_presence_updates">Accepter les mises à jour de présence</string> + <string name="pref_grant_presence_updates_summary">Demander et accepter par avance les mises à jour de présence des contacts créés.</string> + <string name="subscriptions">Publications</string> + <string name="your_account">Votre compte</string> + <string name="keys">Clefs</string> + <string name="send_presence_updates">Envoyer les mises à jour de présence</string> + <string name="receive_presence_updates">Recevoir les mises à jour de présence</string> + <string name="ask_for_presence_updates">Demander les mises à jour de présence</string> + <string name="attach_choose_picture">Choisir une image</string> + <string name="attach_take_picture">Prendre une photo</string> + <string name="preemptively_grant">Accepter par avance les demandes de publication.</string> + <string name="error_not_an_image_file">Le fichier choisi n\'est pas une image</string> + <string name="error_compressing_image">Une erreur s\'est produite en convertissant l\'image</string> + <string name="error_file_not_found">Fichier non trouvé</string> + <string name="error_io_exception">Erreur générale d\'E/S. Avez-vous encore de l\'espace libre?</string> + <string name="error_security_exception_during_image_copy">L\'application utilisée empêche la lecture de l\'image.\n\n<small>Choisissez l\'image depuis une autre application.</small></string> + <string name="account_status">Statut :</string> + <string name="account_status_unknown">Inconnu</string> + <string name="account_status_disabled">Temporarily disabled</string> + <string name="account_status_online">En ligne</string> + <string name="account_status_connecting">Connexion\u2026</string> + <string name="account_status_offline">Hors-ligne</string> + <string name="account_status_unauthorized">Non autorisé</string> + <string name="account_status_not_found">Serveur non trouvé</string> + <string name="account_status_no_internet">Aucune connectivité</string> + <string name="account_status_requires_tls">Le serveur requiert TLS</string> + <string name="account_status_error">Certificat non certifié</string> + <string name="account_status_regis_fail">Enregistrement échoué</string> + <string name="account_status_regis_conflict">Identifiant déjà utilisé</string> + <string name="account_status_regis_success">Enregistrement réussi</string> + <string name="account_status_regis_not_sup">Le serveur ne permet pas l\'enregistrement</string> + <string name="certif_no_trust">Annuler</string> + <string name="certif_trust">Croire ce certificat</string> + <string name="encryption_choice_none">Texte clair</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">openPGP</string> + <string name="mgmt_account_edit">Modifier le compte</string> + <string name="mgmt_account_delete">Supprimer</string> + <string name="mgmt_account_disable">Désactiver temporairement</string> + <string name="mgmt_account_enable">Activer</string> </resources> diff --git a/res/values-gl/arrays.xml b/res/values-gl/arrays.xml new file mode 100644 index 00000000..318b9d5c --- /dev/null +++ b/res/values-gl/arrays.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <array name="resources"> + <item>Móvil</item> + <item>Teléfono</item> + <item>Tablet</item> + <item>Conversations</item> + <item>Android</item> + </array> + <string-array name="filesizes"> + <item>nunca</item> + <item>256 KB</item> + <item>512 KB</item> + <item>1 MB</item> + </string-array> + <string-array name="filesizes_values"> + <item>0</item> + <item>262144</item> + <item>524288</item> + <item>1048576</item> + </string-array> +</resources> diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml new file mode 100644 index 00000000..fe1d4fd4 --- /dev/null +++ b/res/values-gl/strings.xml @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Conversations</string> + <string name="action_settings">Axustes</string> + <string name="action_add">Nova conversa</string> + <string name="action_accounts">Xestionar contas</string> + <string name="action_refresh">Actualizar lista de contactos</string> + <string name="action_end_conversation">Terminar conversa</string> + <string name="action_contact_details">Detalles do contacto</string> + <string name="action_muc_details">Detalles da conferencia</string> + <string name="action_secure">Conversa segura</string> + <string name="action_add_account">Engadir conta</string> + <string name="action_edit_contact">Editar contacto</string> + <string name="action_delete_contact">Eliminar contacto da lista</string> + <string name="title_activity_contacts">Contactos</string> + <string name="just_now">agora</string> + <string name="minutes_ago">min</string> + <string name="unread_conversations">conversas sen ler</string> + <string name="sending">enviando…</string> + <string name="announce_pgp">Renovar anuncio PGP</string> + <string name="encrypted_message">Descifrando mensaxe. Agarda uns intres…</string> + <string name="conference_details">Detalles da conferencia</string> + <string name="nick_in_use">O apodo xa está en uso</string> + <string name="moderator">Moderador</string> + <string name="participant">Participante</string> + <string name="visitor">Visitante</string> + <string name="enter_new_name">Introduce un novo nome:</string> + <string name="remove_contact_text">¿Queres eliminar a %s da túa lista?. A conversa asociada a esta conta non se eliminará.</string> + <string name="untrusted_cert_hint">O servidor %s presenta un certificado non confiable, posiblemente auto firmado.</string> + <string name="account_info">Información do servidor</string> + <string name="register_account">Rexistrar nova conta no servidor</string> + <string name="share_with">Compartir con</string> + <string name="ask_again"><u>Pulsa para preguntar outra vez</u></string> + <string name="show_otr_key">Pegada dactilar OTR</string> + <string name="no_otr_fingerprint">Non hai pegadas dactilares OTR. Continúa e comeza unha conversación cifrada</string> + <string name="start_conversation">Comeza conversa</string> + <string name="invite_contacts">Invitar contactos</string> + <string name="invite_contacts_to_existing">Invitar a conferencia existente</string> + <string name="new_conference">Crear nova conferencia</string> + <string name="cancel">Cancelar</string> + <string name="create_invite">Crear \u0026 Invitar</string> + <string name="new_conference_explained">¿Queres crear unha nueva conferencia con unha dirección xerada aleatoriamente e invitar aos contactos seleccionados a ela?</string> + <string name="no_open_mucs">Non hai conferencias existentes</string> + <string name="invitation_sent">Invitación enviada</string> + <string name="account_offline">Conta desconectada</string> + <string name="cant_invite_while_offline">Debes estar conectado para invitar contactos á conferencia</string> + <string name="crash_report_title">Conversations deteuse.</string> + <string name="crash_report_message">Enviando volcados de pilas axudas ao desenrolo de Conversations\n<b>Aviso:</b> Isto empregará a túa conta XMPP para enviar o volcado de pila ao desenrolador.</string> + <string name="send_now">Enviar agora</string> + <string name="send_never">Non preguntar de novo</string> + <string name="problem_connecting_to_account">Erro na conexión á conta</string> + <string name="problem_connecting_to_accounts">Erro na conexión a múltiples contas</string> + <string name="touch_to_fix">Pulsa aquí para xestionar as túas contass</string> + <string name="attach_file">Adxuntar</string> + <string name="not_in_roster">O contacto non está na túa lista. ¿Queres engadilo?</string> + <string name="add_contact">Engadir contacto</string> + <string name="send_failed">Erro ao enviar</string> + <string name="send_rejected">rechazado</string> + <string name="receiving_image">Recibindo arquivo de imaxe. Agarda por favor…</string> + <string name="preparing_image">Preparando imaxe para enviar</string> + <string name="action_clear_history">Limpar historial</string> + <string name="clear_conversation_history">Limpar historial de conversa</string> + <string name="clear_histor_msg">¿Queres borrar todas as mensaxes desta conversa?\n\n<b>Ollo:</b> Isto non afectará ás mensaxes gardadas noutros dispositivos ou servidores.</string> + <string name="delete_messages">Borrar mensaxes</string> + <string name="also_end_conversation">Terminar esta conversa máis tarde</string> + <string name="choose_presence">Selecciona recurso del contacto</string> + <string name="send_message_to_conference">Enviar mensaxe a conferencia</string> + <string name="send_plain_text_message">Enviar mensaxe de texto</string> + <string name="send_otr_message">Enviar mensaxe cifrado con OTR</string> + <string name="send_pgp_message">Enviar mensaxe cifrado con openPGP</string> + <string name="your_nick_has_been_changed">Modificouse o teu apodo</string> + <string name="download_image">Descargar imaxe</string> + <string name="error_loading_image">Erro na carga da imaxen (Arquivo non atopado)</string> + <string name="image_offered_for_download"><i>Arquivo de imaxe ofrecido para descarga</i></string> + <string name="not_connected">Non conectado</string> + <string name="you_are_offline">Debes estar conectado para enviar %s pero a túa conta asociada a esta conversa está desconectada.</string> + <string name="you_are_offline_blank">Non podes executar esta acción estando desconectado</string> + <string name="files">arquivos</string> + <string name="otr_messages">Mensaxes cifrados con OTR</string> + <string name="manage_account">Xestionar conta</string> + <string name="contact_offline">O teu contacto está desconectado</string> + <string name="contact_offline_otr">É unha pena, pero non é posible enviar mensaxes encriptados con OTR a un contacto desconectado.\n¿Queres enviar a mensaxe sen cifrar?</string> + <string name="contact_offline_file">É unha pena, pero non se pode enviar arquivos a un contacto desconectado.</string> + <string name="send_unencrypted">Enviar sen cifrar</string> + <string name="decryption_failed">Fallou o descifrado. Quizábeis non teñas a clave privada apropiada.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations emprega unha aplicación de terceiros chamada <b>OpenKeychain</b> para cifrar e descifrar mensaxes e xestionar as túas claves públicas.\n\nOpenKeychain está publicado baixo licencia GPLv3 e disponible en F-Droid e Google Play.\n\n<small>(Por favor, reinicie Conversations despois.)</small></string> + <string name="restart">Reiniciar</string> + <string name="install">Instalar</string> + <string name="offering">ofrecendo…</string> + <string name="no_pgp_key">Clave openPGP non atopada</string> + <string name="contact_has_no_pgp_key">Conversations non foi quen de cifrar as túas mensaxes porque o teu contactos non está anunciando a súa clave pública.\n\n<small>Por favor, pídelle ao teu contacto que configure openPGP.</small></string> + <string name="encrypted_message_received"><i>Mensaxe cifrado recibido. Pulsa para ver.</i></string> + <string name="encrypted_image_received"><i>Imaxe cifrada recibida. Pulsa para ver.</i></string> + <string name="image_file"><i>Imaxe recibida. Pulsa para ver</i></string> + <string name="otr_file_transfer">Cifrado con OTR non disponible</string> + <string name="otr_file_transfer_msg">É unha pena, pero o cifrado con OTR non está disponible para transferencia de arquivos. Podes selecionar cifrado con openPGP ou enviar os datos sen cifrar.</string> + <string name="use_pgp_encryption">Usa cifrado con openPGP</string> + <string name="pref_xmpp_resource">Recurso</string> + <string name="pref_xmpp_resource_summary">O nome que identifica o cliente que estás a empregar</string> + <string name="pref_accept_files">Aceptar arquivos</string> + <string name="pref_accept_files_summary">De forma automática aceptar arquivos menores de…</string> + <string name="pref_notification_settings">Axustes de notificación</string> + <string name="pref_notifications">Notificacións</string> + <string name="pref_notifications_summary">Notifica cuando chega unha nova mensaxe</string> + <string name="pref_vibrate">Tremer</string> + <string name="pref_vibrate_summary">Treme cando chega unha novo mensaxe</string> + <string name="pref_sound">Son</string> + <string name="pref_sound_summary">Reproduce un ton ca notificación</string> + <string name="pref_conference_notifications">Notificacións de conferencia</string> + <string name="pref_conference_notifications_summary">Siempre notifica cuando chega unha mensaxe de conferencia e non solo cuando chega unha mensaxe destacada</string> + <string name="pref_notification_grace_period">Notificacións Carbons</string> + <string name="pref_notification_grace_period_summary">Deshabilita as notificacións durante un corto periodo de tiempo despois de recibir a copia da mensaxe carbón</string> + <string name="pref_ui_options">Opcións de interfaz</string> + <string name="pref_use_phone_self_picture">Usar a foto do teléfono</string> + <string name="pref_use_phone_sefl_picture_summary">Poderías non ser capaz de distinguir que conta estase a utlizar nunha conversa</string> + <string name="pref_conference_name">Nome dea conferencia</string> + <string name="pref_conference_name_summary">Usa o nome da sala para identificar Conferencias</string> + <string name="pref_advanced_options">Opcións avanzadas</string> + <string name="pref_never_send_crash">Nunca enviar informe de erros</string> + <string name="pref_never_send_crash_summary">Enviando volcados de pilas axudas al desenrolo de Conversations</string> + <string name="openpgp_error">OpenKeychain reportou un erro</string> + <string name="error_decrypting_file">I/O Erro descifrando arquivo</string> + <string name="error_copying_image_file">Erro copiando arquivo de imaxe.</string> + <string name="accept">Aceptar</string> + <string name="error">Produciuse un erro</string> + <string name="pref_grant_presence_updates">Suscripción de presencia</string> + <string name="pref_grant_presence_updates_summary">Por defecto otorgar e pedir suscripcións de presencia dos contactos que creaches</string> + <string name="subscriptions">Suscripcións</string> + <string name="your_account">A túa conta</string> + <string name="keys">Chaves</string> + <string name="send_presence_updates">Enviar actualizacións de presencia</string> + <string name="receive_presence_updates">Recibir actualizacións de presencia</string> + <string name="ask_for_presence_updates">Solicitar actualizacións de presencia</string> + <string name="attach_choose_picture">Seleccionar imaxe</string> + <string name="attach_take_picture">Facer foto</string> + <string name="preemptively_grant">Por defecto otorgar peticiones de suscripción</string> + <string name="error_not_an_image_file">O arquivo seleccionado non é unha imaxe</string> + <string name="error_compressing_image">Erro convertindo o arquivo de imaxe</string> + <string name="error_file_not_found">Arquivo non atopado</string> + <string name="error_io_exception">Erro xeral de I/O. ¿Quedaches sen espazo no disco?</string> + <string name="error_security_exception_during_image_copy">A aplicación que usas para seleccionar imaxes non proporciona suficientes permisos para leer o arquivo.\n\n<small>Utiliza un explorador de arquivos diferente para seleccionar a imaxe</small></string> + <string name="account_status">Estado:</string> + <string name="account_status_unknown">Descoñecido</string> + <string name="account_status_disabled">Deshabilitado temporalmente</string> + <string name="account_status_online">Conectado</string> + <string name="account_status_connecting">Conectando\u2026</string> + <string name="account_status_offline">Desconectado</string> + <string name="account_status_unauthorized">Non autorizado</string> + <string name="account_status_not_found">Servidor non atopado</string> + <string name="account_status_no_internet">Sen conectividade</string> + <string name="account_status_requires_tls">O servidor require TLS</string> + <string name="account_status_error">Certificado non confiable</string> + <string name="account_status_regis_fail">Erro no rexistro</string> + <string name="account_status_regis_conflict">O identificador xa está en uso</string> + <string name="account_status_regis_success">Rexistro completado</string> + <string name="account_status_regis_not_sup">O servidor non soporta rexistros</string> + <string name="certif_no_trust">Non conectar</string> + <string name="certif_trust">Confiar no certificado</string> + <string name="encryption_choice_none">Texto plano</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">openPGP</string> + <string name="mgmt_account_edit">Editar conta</string> + <string name="mgmt_account_delete">Eliminar conta</string> + <string name="mgmt_account_disable">Deshabilitar temporalmente</string> + <string name="mgmt_account_enable">Habilitar</string> + <string name="attach_record_voice">Grabar audio</string> + <string name="account_settings">Configuración de conta</string> + <string name="save">Gardar</string> + <string name="passwords_do_not_match">As contrasinais non coinciden</string> + <string name="invalid_jid">O identificador non é un identificador de Jabber válido</string> +</resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 10dfb592..13e43f5f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5,18 +5,31 @@ <string name="action_settings">Settings</string> <string name="action_add">New conversation</string> <string name="action_accounts">Manage accounts</string> + <string name="action_refresh">Refresh contact list</string> <string name="action_end_conversation">End this conversation</string> <string name="action_contact_details">Contact details</string> <string name="action_muc_details">Conferenece details</string> <string name="action_secure">Secure conversation</string> <string name="action_add_account">Add account</string> + <string name="action_edit_contact">Edit name</string> + <string name="action_add_phone_book">Add to phone book</string> + <string name="action_delete_contact">Delete from roster</string> <string name="title_activity_contacts">Contacts</string> + <string name="title_activity_manage_accounts">Manage Accounts</string> + <string name="title_activity_settings">Settings</string> + <string name="title_activity_conference_details">Conference Details</string> + <string name="title_activity_contact_details">Contact Details</string> + <string name="title_activity_conversations">Conversations</string> <string name="just_now">just now</string> + <string name="minutes_ago">min ago</string> + <string name="unread_conversations">unread Conversations</string> <string name="sending">sending…</string> <string name="announce_pgp">Renew PGP announcement</string> <string name="encrypted_message">Decrypting message. Please wait…</string> <string name="conference_details">Conference Details</string> <string name="nick_in_use">Nickname is already in use</string> + <string name="admin">Admin</string> + <string name="owner">Owner</string> <string name="moderator">Moderator</string> <string name="participant">Participant</string> <string name="visitor">Visitor</string> @@ -33,7 +46,21 @@ <string name="invite_contacts">Invite Contacts</string> <string name="invite_contacts_to_existing">Invite to existing conference</string> <string name="new_conference">Create new conference</string> + <string name="new_contact">Create new contact</string> + <string name="contacts">Contacts</string> + <string name="search_jabber_id">Search or enter Jabber ID</string> + <string name="choose_account">Choose account</string> + <string name="multi_user_conference">Multi User Conference</string> + <string name="trying_join_conference">Are you trying to join a conference?</string> <string name="cancel">Cancel</string> + <string name="add">Add</string> + <string name="edit">Edit</string> + <string name="delete">Delete</string> + <string name="save">Save</string> + <string name="yes">Yes</string> + <string name="no">No</string> + <string name="ok">Ok</string> + <string name="done">Done</string> <string name="create_invite">Create \u0026 Invite</string> <string name="new_conference_explained">Do you want to create a new conference with a randomly generated address and invite the selected contacts to it?</string> <string name="no_open_mucs">No existing conferences</string> @@ -123,9 +150,68 @@ <string name="pref_grant_presence_updates">Grant presence updates</string> <string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string> <string name="subscriptions">Subscriptions</string> + <string name="subscription_updated">Subscription updated</string> <string name="your_account">Your account</string> <string name="keys">Keys</string> <string name="send_presence_updates">Send presence updates</string> <string name="receive_presence_updates">Receive presence updates</string> <string name="ask_for_presence_updates">Ask for presence updates</string> -</resources> + <string name="asked_for_presence_updates">Asked for presence updates</string> + <string name="attach_choose_picture">Choose picture</string> + <string name="attach_take_picture">Take picture</string> + <string name="preemptively_grant">Preemptively grant subscription request</string> + <string name="error_not_an_image_file">The file you selected is not an image</string> + <string name="error_compressing_image">Error while converting the image file</string> + <string name="error_file_not_found">File not found</string> + <string name="error_io_exception">General I/O error. Maybe you ran out of storage space?</string> + <string name="error_security_exception_during_image_copy">The app you used to select this image did not provide us with enough permissions to read the file.\n\n<small>Use a different file manager to choose an image</small></string> + <string name="account_status">Status:</string> + <string name="account_status_unknown">Unknown</string> + <string name="account_status_disabled">Temporarily disabled</string> + <string name="account_status_online">Online</string> + <string name="account_status_connecting">Connecting\u2026</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Unauthorized</string> + <string name="account_status_not_found">Server not found</string> + <string name="account_status_no_internet">No connectivity</string> + <string name="account_status_requires_tls">Server requires TLS</string> + <string name="account_status_error">Untrusted cerficate</string> + <string name="account_status_regis_fail">Registration failed</string> + <string name="account_status_regis_conflict">Username already in use</string> + <string name="account_status_regis_success">Registration completed</string> + <string name="account_status_regis_not_sup">Server does not support registration</string> + <string name="certif_no_trust">Don\'t connect</string> + <string name="certif_trust">Trust certificate</string> + <string name="encryption_choice_none">Plain text</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">openPGP</string> + <string name="mgmt_account_edit">Edit account</string> + <string name="mgmt_account_delete">Delete</string> + <string name="mgmt_account_disable">Temporarily disable</string> + <string name="mgmt_account_enable">Enable</string> + <string name="mgmt_account_are_you_sure">Are you sure?</string> + <string name="mgmt_account_delete_confirm_text">If you delete your account your entire conversation history will be lost</string> + <string name="attach_record_voice">Record voice</string> + <string name="account_settings">Account Settings</string> + <string name="account_settings_jabber_id">Jabber ID:</string> + <string name="account_settings_password">Password:</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Confirm password:</string> + <string name="password">Password</string> + <string name="confirm_password">Confirm password</string> + <string name="passwords_do_not_match">Passwords do not match</string> + <string name="invalid_jid">This is not a valid Jabber ID</string> + <string name="error_out_of_memory">Ouf of memory. Image is to large</string> + <string name="add_phone_book_text">Do you want to add %s to your phones contact list?</string> + <string name="contact_status_online">online</string> + <string name="contact_status_free_to_chat">free to chat</string> + <string name="contact_status_away">away</string> + <string name="contact_status_extended_away">extended away</string> + <string name="contact_status_do_not_disturb">do not disturb</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Conference</string> + <string name="muc_details_conference_subject">Conference Subject</string> + <string name="muc_details_your_nickname">Your nickname</string> + <string name="muc_details_other_members">Other Members</string> + <string name="subscription_not_updated_offline">Account offline. Could not update subscription</string> +</resources>
\ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index bad2574b..18b23acd 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -68,7 +68,7 @@ android:defaultValue="true"/> </PreferenceCategory> <PreferenceCategory - android:title="Advanced Options"> + android:title="@string/pref_advanced_options"> <CheckBoxPreference android:key="never_send" android:title="@string/pref_never_send_crash" diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java index 48750e24..0f2aeff4 100644 --- a/src/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/eu/siacs/conversations/crypto/PgpEngine.java @@ -221,7 +221,6 @@ public class PgpEngine { return 0; } case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - Log.d("xmppService","openpgp user interaction requeried"); return 0; case OpenPgpApi.RESULT_CODE_ERROR: Log.d("xmppService","openpgp error: "+((OpenPgpError) result diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index 35870aaa..b9c87eac 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -65,6 +65,8 @@ public class Account extends AbstractEntity{ private String otrFingerprint; + private Roster roster = null; + public Account() { this.uuid = "0"; } @@ -135,7 +137,7 @@ public class Account extends AbstractEntity{ } public boolean hasErrorStatus() { - return getStatus() > STATUS_NO_INTERNET; + return getStatus() > STATUS_NO_INTERNET && (getXmppConnection().getAttempt() >= 2); } public void setResource(String resource) { @@ -287,4 +289,11 @@ public class Account extends AbstractEntity{ return null; } } + + public Roster getRoster() { + if (this.roster==null) { + this.roster = new Roster(this); + } + return this.roster; + } } diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java index 599aa8de..cff0dd73 100644 --- a/src/eu/siacs/conversations/entities/Contact.java +++ b/src/eu/siacs/conversations/entities/Contact.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.entities; -import java.io.Serializable; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; @@ -10,27 +9,25 @@ import org.json.JSONException; import org.json.JSONObject; import eu.siacs.conversations.xml.Element; - import android.content.ContentValues; import android.database.Cursor; import android.util.Log; -public class Contact extends AbstractEntity implements Serializable { - private static final long serialVersionUID = -4570817093119419962L; - +public class Contact { public static final String TABLENAME = "contacts"; - public static final String DISPLAYNAME = "name"; + public static final String SYSTEMNAME = "systemname"; + public static final String SERVERNAME = "servername"; public static final String JID = "jid"; - public static final String SUBSCRIPTION = "subscription"; + public static final String OPTIONS = "options"; public static final String SYSTEMACCOUNT = "systemaccount"; public static final String PHOTOURI = "photouri"; public static final String KEYS = "pgpkey"; - public static final String PRESENCES = "presences"; public static final String ACCOUNT = "accountUuid"; protected String accountUuid; - protected String displayName; + protected String systemName; + protected String serverName; protected String jid; protected int subscription = 0; protected String systemAccount; @@ -42,26 +39,12 @@ public class Contact extends AbstractEntity implements Serializable { protected boolean inRoster = true; - public Contact(Account account, String displayName, String jid, - String photoUri) { - if (account == null) { - this.accountUuid = null; - } else { - this.accountUuid = account.getUuid(); - } - this.account = account; - this.displayName = displayName; - this.jid = jid; - this.photoUri = photoUri; - this.uuid = java.util.UUID.randomUUID().toString(); - } - - public Contact(String uuid, String account, String displayName, String jid, - int subscription, String photoUri, String systemAccount, - String keys, String presences) { - this.uuid = uuid; + public Contact(String account, String systemName, String serverName, + String jid, int subscription, String photoUri, + String systemAccount, String keys) { this.accountUuid = account; - this.displayName = displayName; + this.systemName = systemName; + this.serverName = serverName; this.jid = jid; this.subscription = subscription; this.photoUri = photoUri; @@ -74,11 +57,20 @@ public class Contact extends AbstractEntity implements Serializable { } catch (JSONException e) { this.keys = new JSONObject(); } - this.presences = Presences.fromJsonString(presences); + } + + public Contact(String jid) { + this.jid = jid; } public String getDisplayName() { - return this.displayName; + if (this.systemName != null) { + return this.systemName; + } else if (this.serverName != null) { + return this.serverName; + } else { + return this.jid.split("@")[0]; + } } public String getProfilePhoto() { @@ -90,35 +82,32 @@ public class Contact extends AbstractEntity implements Serializable { } public boolean match(String needle) { - return (jid.toLowerCase().contains(needle.toLowerCase()) || (displayName + return (jid.toLowerCase().contains(needle.toLowerCase()) || (getDisplayName() .toLowerCase().contains(needle.toLowerCase()))); } - @Override public ContentValues getContentValues() { ContentValues values = new ContentValues(); - values.put(UUID, uuid); values.put(ACCOUNT, accountUuid); - values.put(DISPLAYNAME, displayName); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); values.put(JID, jid); - values.put(SUBSCRIPTION, subscription); + values.put(OPTIONS, subscription); values.put(SYSTEMACCOUNT, systemAccount); values.put(PHOTOURI, photoUri); values.put(KEYS, keys.toString()); - values.put(PRESENCES, presences.toJsonString()); return values; } public static Contact fromCursor(Cursor cursor) { - return new Contact(cursor.getString(cursor.getColumnIndex(UUID)), - cursor.getString(cursor.getColumnIndex(ACCOUNT)), - cursor.getString(cursor.getColumnIndex(DISPLAYNAME)), + return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), + cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), + cursor.getString(cursor.getColumnIndex(SERVERNAME)), cursor.getString(cursor.getColumnIndex(JID)), - cursor.getInt(cursor.getColumnIndex(SUBSCRIPTION)), + cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getString(cursor.getColumnIndex(PHOTOURI)), cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), - cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(PRESENCES))); + cursor.getString(cursor.getColumnIndex(KEYS))); } public int getSubscription() { @@ -138,10 +127,6 @@ public class Contact extends AbstractEntity implements Serializable { return this.account; } - public void setUuid(String uuid) { - this.uuid = uuid; - } - public boolean couldBeMuc() { String[] split = this.getJid().split("@"); if (split.length != 2) { @@ -154,8 +139,8 @@ public class Contact extends AbstractEntity implements Serializable { return (domainParts[0].equals("conf") || domainParts[0].equals("conference") || domainParts[0].equals("muc") - || domainParts[0].equals("sala") - || domainParts[0].equals("salas")); + || domainParts[0].equals("sala") || domainParts[0] + .equals("salas")); } } } @@ -188,8 +173,12 @@ public class Contact extends AbstractEntity implements Serializable { this.photoUri = uri; } - public void setDisplayName(String name) { - this.displayName = name; + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public void setSystemName(String systemName) { + this.systemName = systemName; } public String getSystemAccount() { @@ -249,58 +238,71 @@ public class Contact extends AbstractEntity implements Serializable { } } - public void setSubscriptionOption(int option) { + public void setOption(int option) { this.subscription |= 1 << option; } - public void resetSubscriptionOption(int option) { + public void resetOption(int option) { this.subscription &= ~(1 << option); } - public boolean getSubscriptionOption(int option) { + public boolean getOption(int option) { return ((this.subscription & (1 << option)) != 0); } + public boolean showInRoster() { + return (this.getOption(Contact.Options.IN_ROSTER) && (!this + .getOption(Contact.Options.DIRTY_DELETE))) + || (this.getOption(Contact.Options.DIRTY_PUSH)); + } + public void parseSubscriptionFromElement(Element item) { String ask = item.getAttribute("ask"); String subscription = item.getAttribute("subscription"); if (subscription != null) { if (subscription.equals("to")) { - this.resetSubscriptionOption(Contact.Subscription.FROM); - this.setSubscriptionOption(Contact.Subscription.TO); + this.resetOption(Contact.Options.FROM); + this.setOption(Contact.Options.TO); } else if (subscription.equals("from")) { - this.resetSubscriptionOption(Contact.Subscription.TO); - this.setSubscriptionOption(Contact.Subscription.FROM); + this.resetOption(Contact.Options.TO); + this.setOption(Contact.Options.FROM); } else if (subscription.equals("both")) { - this.setSubscriptionOption(Contact.Subscription.TO); - this.setSubscriptionOption(Contact.Subscription.FROM); + this.setOption(Contact.Options.TO); + this.setOption(Contact.Options.FROM); + } else if (subscription.equals("none")) { + this.resetOption(Contact.Options.FROM); + this.resetOption(Contact.Options.TO); } } - if ((ask != null) && (ask.equals("subscribe"))) { - this.setSubscriptionOption(Contact.Subscription.ASKING); - } else { - this.resetSubscriptionOption(Contact.Subscription.ASKING); + // do NOT override asking if pending push request + if (!this.getOption(Contact.Options.DIRTY_PUSH)) { + if ((ask != null) && (ask.equals("subscribe"))) { + this.setOption(Contact.Options.ASKING); + } else { + this.resetOption(Contact.Options.ASKING); + } + } + } + + public Element asElement() { + Element item = new Element("item"); + item.setAttribute("jid", this.jid); + if (this.serverName != null) { + item.setAttribute("name", this.serverName); } + return item; } - public class Subscription { + public class Options { public static final int TO = 0; public static final int FROM = 1; public static final int ASKING = 2; - public static final int PREEMPTIVE_GRANT = 4; - } - - public void flagAsNotInRoster() { - this.inRoster = false; - } - - public boolean isInRoster() { - return this.inRoster; - } - - public String getAccountUuid() { - return this.accountUuid; + public static final int PREEMPTIVE_GRANT = 3; + public static final int IN_ROSTER = 4; + public static final int PENDING_SUBSCRIPTION_REQUEST = 5; + public static final int DIRTY_PUSH = 6; + public static final int DIRTY_DELETE = 7; } } diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index 5674f84a..37a230df 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -10,11 +10,11 @@ import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.util.Log; public class Conversation extends AbstractEntity { @@ -49,7 +49,6 @@ public class Conversation extends AbstractEntity { private transient List<Message> messages = null; private transient Account account = null; - private transient Contact contact; private transient SessionImpl otrSession; @@ -129,19 +128,13 @@ public class Conversation extends AbstractEntity { public String getName(boolean useSubject) { if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null) && useSubject) { return getMucOptions().getSubject(); - } else if (this.contact != null) { - return this.contact.getDisplayName(); } else { - return this.name; + return this.getContact().getDisplayName(); } } public String getProfilePhotoString() { - if (this.contact == null) { - return null; - } else { - return this.contact.getProfilePhoto(); - } + return this.getContact().getProfilePhoto(); } public String getAccountUuid() { @@ -153,14 +146,7 @@ public class Conversation extends AbstractEntity { } public Contact getContact() { - return this.contact; - } - - public void setContact(Contact contact) { - this.contact = contact; - if (contact != null) { - this.contactUuid = contact.getUuid(); - } + return this.account.getRoster().getContact(this.contactJid); } public void setAccount(Account account) { @@ -254,6 +240,7 @@ public class Conversation extends AbstractEntity { public void endOtrIfNeeded() { if (this.otrSession != null) { if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + Log.d("xmppService","ending otr session with "+getContactJid()); try { this.otrSession.endSession(); this.resetOtrSession(); @@ -265,16 +252,7 @@ public class Conversation extends AbstractEntity { } public boolean hasValidOtrSession() { - if (this.otrSession == null) { - return false; - } else { - String foreignPresence = this.otrSession.getSessionID().getUserID(); - if (!getContact().getPresences().containsKey(foreignPresence)) { - this.resetOtrSession(); - return false; - } - return true; - } + return this.otrSession != null; } public String getOtrFingerprint() { @@ -341,7 +319,11 @@ public class Conversation extends AbstractEntity { } public String getNextMessage() { - return this.nextMessage; + if (this.nextMessage==null) { + return ""; + } else { + return this.nextMessage; + } } public void setNextMessage(String message) { diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java index 33f7a8d4..950e349e 100644 --- a/src/eu/siacs/conversations/entities/Message.java +++ b/src/eu/siacs/conversations/entities/Message.java @@ -29,6 +29,7 @@ public class Message extends AbstractEntity { public static final int TYPE_TEXT = 0; public static final int TYPE_IMAGE = 1; + public static final int TYPE_AUDIO = 2; public static String CONVERSATION = "conversationUuid"; public static String COUNTERPART = "counterpart"; diff --git a/src/eu/siacs/conversations/entities/Presences.java b/src/eu/siacs/conversations/entities/Presences.java index acbaafca..fd8af573 100644 --- a/src/eu/siacs/conversations/entities/Presences.java +++ b/src/eu/siacs/conversations/entities/Presences.java @@ -4,10 +4,6 @@ import java.util.Hashtable; import java.util.Iterator; import java.util.Map.Entry; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import eu.siacs.conversations.xml.Element; public class Presences { @@ -47,39 +43,6 @@ public class Presences { return status; } - public String toJsonString() { - JSONArray json = new JSONArray(); - Iterator<Entry<String, Integer>> it = presences.entrySet().iterator(); - - while (it.hasNext()) { - Entry<String, Integer> entry = it.next(); - JSONObject jObj = new JSONObject(); - try { - jObj.put("resource", entry.getKey()); - jObj.put("status", entry.getValue()); - } catch (JSONException e) { - - } - json.put(jObj); - } - return json.toString(); - } - - public static Presences fromJsonString(String jsonString) { - Presences presences = new Presences(); - try { - JSONArray json = new JSONArray(jsonString); - for (int i = 0; i < json.length(); ++i) { - JSONObject jObj = json.getJSONObject(i); - presences.updatePresence(jObj.getString("resource"), - jObj.getInt("status")); - } - } catch (JSONException e1) { - - } - return presences; - } - public static int parseShow(Element show) { if (show == null) { return Presences.ONLINE; diff --git a/src/eu/siacs/conversations/entities/Roster.java b/src/eu/siacs/conversations/entities/Roster.java new file mode 100644 index 00000000..1b4bc954 --- /dev/null +++ b/src/eu/siacs/conversations/entities/Roster.java @@ -0,0 +1,74 @@ +package eu.siacs.conversations.entities; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Roster { + Account account; + HashMap<String, Contact> contacts = new HashMap<String, Contact>(); + private String version = null; + + public Roster(Account account) { + this.account = account; + } + + public boolean hasContact(String jid) { + String cleanJid = jid.split("/")[0]; + return contacts.containsKey(cleanJid); + } + + public Contact getContact(String jid) { + String cleanJid = jid.split("/")[0]; + if (contacts.containsKey(cleanJid)) { + return contacts.get(cleanJid); + } else { + Contact contact = new Contact(cleanJid); + contact.setAccount(account); + contacts.put(cleanJid, contact); + return contact; + } + } + + public void clearPresences() { + for(Contact contact : getContacts()) { + contact.clearPresences(); + } + } + + public void markAllAsNotInRoster() { + for(Contact contact : getContacts()) { + contact.resetOption(Contact.Options.IN_ROSTER); + } + } + + public void clearSystemAccounts() { + for(Contact contact : getContacts()) { + contact.setPhotoUri(null); + contact.setSystemName(null); + contact.setSystemAccount(null); + } + } + + public List<Contact> getContacts() { + return new ArrayList<Contact>(this.contacts.values()); + } + + public void initContact(Contact contact) { + contact.setAccount(account); + contact.setOption(Contact.Options.IN_ROSTER); + contacts.put(contact.getJid(),contact); + } + + public void setVersion(String version) { + this.version = version; + } + + public String getVersion() { + return this.version; + } + + public Account getAccount() { + return this.account; + } +} diff --git a/src/eu/siacs/conversations/utils/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index 52b18f66..3733767f 100644 --- a/src/eu/siacs/conversations/utils/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.utils; +package eu.siacs.conversations.parser; import java.util.List; @@ -15,29 +15,34 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageParser { protected static final String LOGTAG = "xmppService"; + private XmppConnectionService mXmppConnectionService; - public static Message parsePlainTextChat(MessagePacket packet, Account account, XmppConnectionService service) { + public MessageParser(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public Message parsePlainTextChat(MessagePacket packet, Account account) { String[] fromParts = packet.getFrom().split("/"); - Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, fromParts[0],false); String body = packet.getBody(); return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED); } - public static Message parsePgpChat(String pgpBody, MessagePacket packet, Account account, XmppConnectionService service) { + public Message parsePgpChat(String pgpBody, MessagePacket packet, Account account) { String[] fromParts = packet.getFrom().split("/"); - Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, fromParts[0],false); return new Message(conversation, packet.getFrom(), pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED); } - public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) { + public Message parseOtrChat(MessagePacket packet, Account account) { boolean properlyAddressed = (packet.getTo().split("/").length == 2) || (account.countPresences() == 1); String[] fromParts = packet.getFrom().split("/"); - Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, fromParts[0],false); String body = packet.getBody(); if (!conversation.hasValidOtrSession()) { if (properlyAddressed) { Log.d("xmppService","starting new otr session with "+packet.getFrom()+" because no valid otr session has been found"); - conversation.startOtrSession(service.getApplicationContext(), fromParts[1],false); + conversation.startOtrSession(mXmppConnectionService.getApplicationContext(), fromParts[1],false); } else { Log.d("xmppService",account.getJid()+": ignoring otr session with "+fromParts[0]); return null; @@ -48,7 +53,7 @@ public class MessageParser { conversation.resetOtrSession(); if (properlyAddressed) { Log.d("xmppService","replacing otr session with "+packet.getFrom()); - conversation.startOtrSession(service.getApplicationContext(), fromParts[1],false); + conversation.startOtrSession(mXmppConnectionService.getApplicationContext(), fromParts[1],false); } else { return null; } @@ -69,15 +74,15 @@ public class MessageParser { Message msg = messages.get(i); if ((msg.getStatus() == Message.STATUS_UNSEND) && (msg.getEncryption() == Message.ENCRYPTION_OTR)) { - MessagePacket outPacket = service.prepareMessagePacket( + MessagePacket outPacket = mXmppConnectionService.prepareMessagePacket( account, msg, otrSession); msg.setStatus(Message.STATUS_SEND); - service.databaseBackend.updateMessage(msg); + mXmppConnectionService.databaseBackend.updateMessage(msg); account.getXmppConnection() .sendMessagePacket(outPacket); } } - service.updateUi(conversation, false); + mXmppConnectionService.updateUi(conversation, false); } else if ((before != after) && (after == SessionStatus.FINISHED)) { conversation.resetOtrSession(); Log.d(LOGTAG,"otr session stoped"); @@ -93,13 +98,13 @@ public class MessageParser { } } - public static Message parseGroupchat(MessagePacket packet, Account account, XmppConnectionService service) { + public Message parseGroupchat(MessagePacket packet, Account account) { int status; String[] fromParts = packet.getFrom().split("/"); - Conversation conversation = service.findOrCreateConversation(account, fromParts[0],true); + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, fromParts[0],true); if (packet.hasChild("subject")) { conversation.getMucOptions().setSubject(packet.findChild("subject").getContent()); - service.updateUi(conversation, false); + mXmppConnectionService.updateUi(conversation, false); return null; } if ((fromParts.length == 1)) { @@ -107,15 +112,18 @@ public class MessageParser { } String counterPart = fromParts[1]; if (counterPart.equals(conversation.getMucOptions().getNick())) { - status = Message.STATUS_SEND; + if (mXmppConnectionService.markMessage(conversation, packet.getId(), Message.STATUS_SEND)) { + return null; + } else { + status = Message.STATUS_SEND; + } } else { status = Message.STATUS_RECIEVED; } return new Message(conversation, counterPart, packet.getBody(), Message.ENCRYPTION_NONE, status); } - public static Message parseCarbonMessage(MessagePacket packet, - Account account, XmppConnectionService service) { + public Message parseCarbonMessage(MessagePacket packet,Account account) { int status; String fullJid; Element forwarded; @@ -142,16 +150,16 @@ public class MessageParser { fullJid = message.getAttribute("to"); } String[] parts = fullJid.split("/"); - Conversation conversation = service.findOrCreateConversation(account, parts[0],false); + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, parts[0],false); return new Message(conversation,fullJid, message.findChild("body").getContent(), Message.ENCRYPTION_NONE,status); } - public static void parseError(MessagePacket packet, Account account, XmppConnectionService service) { + public void parseError(MessagePacket packet, Account account) { String[] fromParts = packet.getFrom().split("/"); - service.markMessage(account, fromParts[0], packet.getId(), Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(account, fromParts[0], packet.getId(), Message.STATUS_SEND_FAILED); } - public static String getPgpBody(MessagePacket packet) { + public String getPgpBody(MessagePacket packet) { for(Element child : packet.getChildren()) { if (child.getName().equals("x")&&child.getAttribute("xmlns").equals("jabber:x:encrypted")) { return child.getContent(); diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index 5a34dac6..26d09378 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -2,20 +2,17 @@ package eu.siacs.conversations.persistance; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.entities.Presences; -import android.content.ContentValues; +import eu.siacs.conversations.entities.Roster; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.os.Bundle; import android.util.Log; public class DatabaseBackend extends SQLiteOpenHelper { @@ -23,7 +20,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 3; + private static final int DATABASE_VERSION = 5; + + private static String CREATE_CONTATCS_STATEMENT = "create table " + + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + Contact.SERVERNAME + " TEXT, " + + Contact.SYSTEMNAME + " TEXT," + Contact.JID + " TEXT," + + Contact.KEYS + " TEXT," + Contact.PHOTOURI + " TEXT," + + Contact.OPTIONS + " NUMBER," + Contact.SYSTEMACCOUNT + + " NUMBER, " + "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, UNIQUE("+Contact.ACCOUNT+", "+Contact.JID+") ON CONFLICT REPLACE);"; public DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -36,7 +41,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT," + Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT," + Account.ROSTERVERSION + " TEXT," + Account.OPTIONS - + " NUMBER, "+Account.KEYS+" TEXT)"); + + " NUMBER, " + Account.KEYS + " TEXT)"); db.execSQL("create table " + Conversation.TABLENAME + " (" + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME + " TEXT, " + Conversation.CONTACT + " TEXT, " @@ -50,31 +55,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, " + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART + " TEXT, " + Message.BODY + " TEXT, " + Message.ENCRYPTION - + " NUMBER, " + Message.STATUS + " NUMBER," +Message.TYPE +" NUMBER, FOREIGN KEY(" - + Message.CONVERSATION + ") REFERENCES " - + Conversation.TABLENAME + "(" + Conversation.UUID - + ") ON DELETE CASCADE);"); - db.execSQL("create table " + Contact.TABLENAME + "(" + Contact.UUID - + " TEXT PRIMARY KEY, " + Contact.ACCOUNT + " TEXT, " - + Contact.DISPLAYNAME + " TEXT," + Contact.JID + " TEXT," - + Contact.PRESENCES + " TEXT, " + Contact.KEYS - + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.SUBSCRIPTION - + " NUMBER," + Contact.SYSTEMACCOUNT + " NUMBER, " - + "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " - + Account.TABLENAME + "(" + Account.UUID - + ") ON DELETE CASCADE);"); + + " NUMBER, " + Message.STATUS + " NUMBER," + Message.TYPE + + " NUMBER, FOREIGN KEY(" + Message.CONVERSATION + + ") REFERENCES " + Conversation.TABLENAME + "(" + + Conversation.UUID + ") ON DELETE CASCADE);"); + + db.execSQL(CREATE_CONTATCS_STATEMENT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 2 && newVersion >= 2) { - // enable compression by default. - db.execSQL("update " + Account.TABLENAME - + " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); + db.execSQL("update " + Account.TABLENAME + " set " + + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); } if (oldVersion < 3 && newVersion >= 3) { - //add field type to message - db.execSQL("ALTER TABLE "+Message.TABLENAME+" ADD COLUMN "+Message.TYPE+" NUMBER");; + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.TYPE + " NUMBER"); + } + if (oldVersion < 5 && newVersion >= 5) { + db.execSQL("DROP TABLE "+Contact.TABLENAME); + db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL("UPDATE "+Account.TABLENAME+ " SET "+Account.ROSTERVERSION+" = NULL"); } } @@ -99,7 +101,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getWritableDatabase(); db.insert(Account.TABLENAME, null, account.getContentValues()); } - + public void createContact(Contact contact) { SQLiteDatabase db = this.getWritableDatabase(); db.insert(Contact.TABLENAME, null, contact.getContentValues()); @@ -145,10 +147,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { public Conversation findConversation(Account account, String contactJid) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { account.getUuid(), contactJid+"%" }; + String[] selectionArgs = { account.getUuid(), contactJid + "%" }; Cursor cursor = db.query(Conversation.TABLENAME, null, - Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID + " like ?", - selectionArgs, null, null, null); + Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID + + " like ?", selectionArgs, null, null, null); if (cursor.getCount() == 0) return null; cursor.moveToFirst(); @@ -200,87 +202,32 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); } - - public void updateContact(Contact contact, boolean updatePresences) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { contact.getUuid() }; - ContentValues values = contact.getContentValues(); - if (!updatePresences) { - values.remove(Contact.PRESENCES); - } else { - values.remove(Contact.DISPLAYNAME); - values.remove(Contact.PHOTOURI); - values.remove(Contact.SYSTEMACCOUNT); - } - db.update(Contact.TABLENAME, contact.getContentValues(), Contact.UUID - + "=?", args); - } - - public void clearPresences(Account account) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { account.getUuid() }; - ContentValues values = new ContentValues(); - values.put(Contact.PRESENCES,"[]"); - db.update(Contact.TABLENAME, values, Contact.ACCOUNT - + "=?", args); - } - - public void mergeContacts(List<Contact> contacts) { - SQLiteDatabase db = this.getWritableDatabase(); - for (int i = 0; i < contacts.size(); i++) { - Contact contact = contacts.get(i); - String[] columns = {Contact.UUID, Contact.PRESENCES}; - String[] args = {contact.getAccount().getUuid(), contact.getJid()}; - Cursor cursor = db.query(Contact.TABLENAME, columns,Contact.ACCOUNT+"=? AND "+Contact.JID+"=?", args, null, null, null); - if (cursor.getCount()>=1) { - cursor.moveToFirst(); - contact.setUuid(cursor.getString(0)); - updateContact(contact,false); - } else { - contact.setUuid(UUID.randomUUID().toString()); - createContact(contact); - } - } - } - public List<Contact> getContactsByAccount(Account account) { - List<Contact> list = new ArrayList<Contact>(); + public void readRoster(Roster roster) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; - if (account==null) { - cursor = db.query(Contact.TABLENAME, null, null, null, null, - null, null); - } else { - String args[] = {account.getUuid()}; - cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT+"=?", args, null, - null, null); - } + String args[] = { roster.getAccount().getUuid() }; + cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", + args, null, null, null); while (cursor.moveToNext()) { - list.add(Contact.fromCursor(cursor)); + roster.initContact(Contact.fromCursor(cursor)); } - return list; } - public List<Contact> getContacts(String where) { - List<Contact> list = new ArrayList<Contact>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(Contact.TABLENAME, null, where, null, null, null, null); - while (cursor.moveToNext()) { - list.add(Contact.fromCursor(cursor)); + public void writeRoster(Roster roster) { + Account account = roster.getAccount(); + SQLiteDatabase db = this.getWritableDatabase(); + for(Contact contact : roster.getContacts()) { + if (contact.getOption(Contact.Options.IN_ROSTER)) { + db.insert(Contact.TABLENAME, null, contact.getContentValues()); + } else { + String where = Contact.ACCOUNT + "=? AND "+Contact.JID+"=?"; + String[] whereArgs = {account.getUuid(), contact.getJid()}; + db.delete(Contact.TABLENAME, where, whereArgs); + } } - return list; - } - - public Contact findContact(Account account, String jid) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { account.getUuid(), jid }; - Cursor cursor = db.query(Contact.TABLENAME, null, - Contact.ACCOUNT + "=? AND " + Contact.JID + "=?", - selectionArgs, null, null, null); - if (cursor.getCount() == 0) - return null; - cursor.moveToFirst(); - return Contact.fromCursor(cursor); + account.setRosterVersion(roster.getVersion()); + updateAccount(account); } public void deleteMessage(Message message) { @@ -288,34 +235,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { message.getUuid() }; db.delete(Message.TABLENAME, Message.UUID + "=?", args); } - + public void deleteMessagesInConversation(Conversation conversation) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = { conversation.getUuid() }; db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); } - public void deleteContact(Contact contact) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { contact.getUuid() }; - db.delete(Contact.TABLENAME, Contact.UUID + "=?", args); - } - - public Contact getContact(String uuid) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { uuid }; - Cursor cursor = db.query(Contact.TABLENAME, null, Contact.UUID + "=?", args, null, null, null); - if (cursor.getCount() == 0) { - return null; - } - cursor.moveToFirst(); - return Contact.fromCursor(cursor); - } - public Conversation findConversationByUuid(String conversationUuid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = { conversationUuid }; - Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.UUID + "=?", selectionArgs, null, null, null); + Cursor cursor = db.query(Conversation.TABLENAME, null, + Conversation.UUID + "=?", selectionArgs, null, null, null); if (cursor.getCount() == 0) { return null; } @@ -326,18 +257,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { public Message findMessageByUuid(String messageUuid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = { messageUuid }; - Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?", selectionArgs, null, null, null); + Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?", + selectionArgs, null, null, null); if (cursor.getCount() == 0) { return null; } cursor.moveToFirst(); return Message.fromCursor(cursor); } - + public Account findAccountByUuid(String accountUuid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = { accountUuid }; - Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", selectionArgs, null, null, null); + Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", + selectionArgs, null, null, null); if (cursor.getCount() == 0) { return null; } diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index becb1ee3..9d64c45f 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/eu/siacs/conversations/persistance/FileBackend.java @@ -1,12 +1,14 @@ package eu.siacs.conversations.persistance; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.ref.WeakReference; + +import org.bouncycastle.crypto.engines.ISAACEngine; import android.content.Context; import android.graphics.Bitmap; @@ -14,14 +16,9 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Log; import android.util.LruCache; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.jingle.JingleFile; public class FileBackend { @@ -43,11 +40,11 @@ public class FileBackend { }; } - + public LruCache<String, Bitmap> getThumbnailCache() { return thumbnailCache; } - + public JingleFile getJingleFile(Message message) { return getJingleFile(message, true); } @@ -58,7 +55,7 @@ public class FileBackend { String path = prefix + "/" + conversation.getAccount().getJid() + "/" + conversation.getContactJid(); String filename; - if ((decrypted)||(message.getEncryption() == Message.ENCRYPTION_NONE)) { + if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { filename = message.getUuid() + ".webp"; } else { filename = message.getUuid() + ".webp.pgp"; @@ -86,37 +83,64 @@ public class FileBackend { return originalBitmap; } } + + public JingleFile copyImageToPrivateStorage(Message message, Uri image) throws ImageCopyException { + return this.copyImageToPrivateStorage(message, image,0); + } - public JingleFile copyImageToPrivateStorage(Message message, Uri image) { + private JingleFile copyImageToPrivateStorage(Message message, Uri image, int sampleSize) + throws ImageCopyException { try { - Log.d("xmppService","copying file: "+image.toString()+ " to internal storage"); - InputStream is = context.getContentResolver() - .openInputStream(image); + InputStream is; + if (image != null) { + is = context.getContentResolver().openInputStream(image); + } else { + is = new FileInputStream(getIncomingFile()); + } JingleFile file = getJingleFile(message); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream os = new FileOutputStream(file); - Bitmap originalBitmap = BitmapFactory.decodeStream(is); + Bitmap originalBitmap; + BitmapFactory.Options options = new BitmapFactory.Options(); + int inSampleSize = (int) Math.pow(2, sampleSize); + Log.d("xmppService","reading bitmap with sample size "+inSampleSize); + options.inSampleSize = inSampleSize; + originalBitmap = BitmapFactory.decodeStream(is, null, options); is.close(); + if (originalBitmap == null) { + throw new ImageCopyException(R.string.error_not_an_image_file); + } + if (image == null) { + getIncomingFile().delete(); + } Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE); + OutputStream os = new FileOutputStream(file); boolean success = scalledBitmap.compress( Bitmap.CompressFormat.WEBP, 75, os); if (!success) { - return null; + throw new ImageCopyException(R.string.error_compressing_image); } os.flush(); os.close(); long size = file.getSize(); int width = scalledBitmap.getWidth(); int height = scalledBitmap.getHeight(); - message.setBody(""+size+","+width+","+height); + message.setBody("" + size + "," + width + "," + height); return file; } catch (FileNotFoundException e) { - return null; + throw new ImageCopyException(R.string.error_file_not_found); } catch (IOException e) { - return null; + throw new ImageCopyException(R.string.error_io_exception); } catch (SecurityException e) { - return null; + throw new ImageCopyException( + R.string.error_security_exception_during_image_copy); + } catch (OutOfMemoryError e) { + ++sampleSize; + if (sampleSize<=3) { + return copyImageToPrivateStorage(message, image, sampleSize); + } else { + throw new ImageCopyException(R.string.error_out_of_memory); + } } } @@ -128,7 +152,7 @@ public class FileBackend { public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException { Bitmap thumbnail = thumbnailCache.get(message.getUuid()); - if ((thumbnail == null)&&(!cacheOnly)) { + if ((thumbnail == null) && (!cacheOnly)) { Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message) .getAbsolutePath()); if (fullsize == null) { @@ -160,4 +184,21 @@ public class FileBackend { } f.delete(); } + + public File getIncomingFile() { + return new File(context.getFilesDir().getAbsolutePath() + "/incoming"); + } + + public class ImageCopyException extends Exception { + private static final long serialVersionUID = -1010013599132881427L; + private int resId; + + public ImageCopyException(int resId) { + this.resId = resId; + } + + public int getResId() { + return resId; + } + } } diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java index c1bae661..798a4e25 100644 --- a/src/eu/siacs/conversations/services/ImageProvider.java +++ b/src/eu/siacs/conversations/services/ImageProvider.java @@ -2,13 +2,13 @@ package eu.siacs.conversations.services; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; - import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; @@ -21,46 +21,60 @@ public class ImageProvider extends ContentProvider { @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - DatabaseBackend databaseBackend = DatabaseBackend - .getInstance(getContext()); + ParcelFileDescriptor pfd; FileBackend fileBackend = new FileBackend(getContext()); - String uuids = uri.getPath(); - Log.d("xmppService", "uuids = " + uuids); - if (uuids == null) { - throw new FileNotFoundException(); - } - String[] uuidsSplited = uuids.split("/"); - if (uuidsSplited.length != 3) { + if ("r".equals(mode)) { + DatabaseBackend databaseBackend = DatabaseBackend + .getInstance(getContext()); + String uuids = uri.getPath(); + Log.d("xmppService", "uuids = " + uuids+" mode="+mode); + if (uuids == null) { + throw new FileNotFoundException(); + } + String[] uuidsSplited = uuids.split("/"); + if (uuidsSplited.length != 3) { + throw new FileNotFoundException(); + } + String conversationUuid = uuidsSplited[1]; + String messageUuid = uuidsSplited[2]; + + Conversation conversation = databaseBackend + .findConversationByUuid(conversationUuid); + if (conversation == null) { + throw new FileNotFoundException("conversation " + conversationUuid + + " could not be found"); + } + Message message = databaseBackend.findMessageByUuid(messageUuid); + if (message == null) { + throw new FileNotFoundException("message " + messageUuid + + " could not be found"); + } + + Account account = databaseBackend.findAccountByUuid(conversation + .getAccountUuid()); + if (account == null) { + throw new FileNotFoundException("account " + + conversation.getAccountUuid() + " cound not be found"); + } + message.setConversation(conversation); + conversation.setAccount(account); + + File file = fileBackend.getJingleFile(message); + pfd = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_READ_ONLY); + return pfd; + } else if ("w".equals(mode)){ + File file = fileBackend.getIncomingFile(); + try { + file.createNewFile(); + } catch (IOException e) { + throw new FileNotFoundException(); + } + pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + return pfd; + } else { throw new FileNotFoundException(); } - String conversationUuid = uuidsSplited[1]; - String messageUuid = uuidsSplited[2]; - - Conversation conversation = databaseBackend - .findConversationByUuid(conversationUuid); - if (conversation == null) { - throw new FileNotFoundException("conversation " + conversationUuid - + " could not be found"); - } - Message message = databaseBackend.findMessageByUuid(messageUuid); - if (message == null) { - throw new FileNotFoundException("message " + messageUuid - + " could not be found"); - } - - Account account = databaseBackend.findAccountByUuid(conversation - .getAccountUuid()); - if (account == null) { - throw new FileNotFoundException("account " - + conversation.getAccountUuid() + " cound not be found"); - } - message.setConversation(conversation); - conversation.setAccount(account); - - File file = fileBackend.getJingleFile(message); - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_READ_ONLY); - return pfd; } @Override @@ -93,5 +107,16 @@ public class ImageProvider extends ContentProvider { public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { return 0; } + + public static Uri getContentUri(Message message) { + return Uri + .parse("content://eu.siacs.conversations.images/" + + message.getConversationUuid() + + "/" + + message.getUuid()); + } -} + public static Uri getIncomingContentUri() { + return Uri.parse("content://eu.siacs.conversations.images/incoming"); + } +}
\ No newline at end of file diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index d2742997..e31d28e0 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -10,14 +10,12 @@ import java.util.List; import java.util.Locale; import java.util.Random; -import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; -import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -26,20 +24,19 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.parser.MessageParser; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.persistance.OnPhoneContactsMerged; import eu.siacs.conversations.ui.OnAccountListChangedListener; import eu.siacs.conversations.ui.OnConversationListChangedListener; -import eu.siacs.conversations.ui.OnRosterFetchedListener; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.ExceptionHelper; -import eu.siacs.conversations.utils.MessageParser; import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnBindListener; +import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; @@ -47,7 +44,6 @@ import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnTLSExceptionReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; -import eu.siacs.conversations.xmpp.jingle.JingleFile; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -60,7 +56,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.ContentObserver; -import android.database.DatabaseUtils; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -88,6 +83,10 @@ public class XmppConnectionService extends Service { private static final int CONNECT_TIMEOUT = 60; private static final long CARBON_GRACE_PERIOD = 60000L; + private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + + private MessageParser mMessageParser = new MessageParser(this); + private List<Account> accounts; private List<Conversation> conversations = null; private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( @@ -97,6 +96,16 @@ public class XmppConnectionService extends Service { private int convChangedListenerCount = 0; private OnAccountListChangedListener accountChangedListener = null; private OnTLSExceptionReceived tlsException = null; + private OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { + + @Override + public void onContactStatusChanged(Contact contact) { + Conversation conversation = findActiveConversation(contact); + if (conversation!=null) { + conversation.endOtrIfNeeded(); + } + } + }; public void setOnTLSExceptionReceivedListener( OnTLSExceptionReceived listener) { @@ -111,13 +120,13 @@ public class XmppConnectionService extends Service { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - Log.d(LOGTAG, "contact list has changed"); - mergePhoneContactsWithRoster(null); + Intent intent = new Intent(getApplicationContext(), + XmppConnectionService.class); + intent.setAction(ACTION_MERGE_PHONE_CONTACTS); + startService(intent); } }; - private XmppConnectionService service = this; - private final IBinder mBinder = new XmppConnectionBinder(); private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() { @@ -132,26 +141,25 @@ public class XmppConnectionService extends Service { } if ((packet.getType() == MessagePacket.TYPE_CHAT)) { - String pgpBody = MessageParser.getPgpBody(packet); + String pgpBody = mMessageParser.getPgpBody(packet); if (pgpBody != null) { - message = MessageParser.parsePgpChat(pgpBody, packet, - account, service); + message = mMessageParser.parsePgpChat(pgpBody, packet, + account); message.markUnread(); } else if ((packet.getBody() != null) && (packet.getBody().startsWith("?OTR"))) { - message = MessageParser.parseOtrChat(packet, account, - service); + message = mMessageParser.parseOtrChat(packet, account); if (message != null) { message.markUnread(); } } else if (packet.hasChild("body")) { - message = MessageParser.parsePlainTextChat(packet, account, - service); + message = mMessageParser + .parsePlainTextChat(packet, account); message.markUnread(); } else if (packet.hasChild("received") || (packet.hasChild("sent"))) { - message = MessageParser.parseCarbonMessage(packet, account, - service); + message = mMessageParser + .parseCarbonMessage(packet, account); if (message != null) { if (message.getStatus() == Message.STATUS_SEND) { lastCarbonMessageReceived = SystemClock @@ -165,8 +173,7 @@ public class XmppConnectionService extends Service { } } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) { - message = MessageParser - .parseGroupchat(packet, account, service); + message = mMessageParser.parseGroupchat(packet, account); if (message != null) { if (message.getStatus() == Message.STATUS_RECIEVED) { message.markUnread(); @@ -176,7 +183,7 @@ public class XmppConnectionService extends Service { } } } else if (packet.getType() == MessagePacket.TYPE_ERROR) { - MessageParser.parseError(packet, account, service); + mMessageParser.parseError(packet, account); return; } else if (packet.getType() == MessagePacket.TYPE_NORMAL) { if (packet.hasChild("x")) { @@ -237,6 +244,7 @@ public class XmppConnectionService extends Service { sendUnsendMessages(conversations.get(i)); } } + syncDirtyContacts(account); scheduleWakeupCall(PING_MAX_INTERVAL, true); } else if (account.getStatus() == Account.STATUS_OFFLINE) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { @@ -247,10 +255,18 @@ public class XmppConnectionService extends Service { } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) { databaseBackend.updateAccount(account); reconnectAccount(account, true); - } else { - UIHelper.showErrorNotification(getApplicationContext(), - getAccounts()); + } else if ((account.getStatus() != Account.STATUS_CONNECTING) + && (account.getStatus() != Account.STATUS_NO_INTERNET)) { + int next = account.getXmppConnection().getTimeToNextAttempt(); + Log.d(LOGTAG, account.getJid() + + ": error connecting account. try again in " + next + + "s for the " + + (account.getXmppConnection().getAttempt() + 1) + + " time"); + scheduleWakeupCall(next, false); } + UIHelper.showErrorNotification(getApplicationContext(), + getAccounts()); } }; @@ -298,17 +314,8 @@ public class XmppConnectionService extends Service { } } else { - Contact contact = findContact(account, fromParts[0]); - if (contact == null) { - if ("subscribe".equals(type)) { - account.getXmppConnection().addPendingSubscription( - fromParts[0]); - } else { - // Log.d(LOGTAG,packet.getFrom()+ - // " could not be found"); - } - return; - } + Contact contact = account.getRoster().getContact( + packet.getFrom()); if (type == null) { if (fromParts.length == 2) { contact.updatePresence(fromParts[1], Presences @@ -325,15 +332,11 @@ public class XmppConnectionService extends Service { } else { msg = ""; } - contact.setPgpKeyId(pgp.fetchKeyId(account,msg,x.getContent())); - Log.d("xmppService",account.getJid()+": fetched key id for "+contact.getJid()+" was:"+contact.getPgpKeyId()); + contact.setPgpKeyId(pgp.fetchKeyId(account, + msg, x.getContent())); } } - replaceContactInConversation(account, - contact.getJid(), contact); - databaseBackend.updateContact(contact, true); - } else { - // Log.d(LOGTAG,"presence without resource "+packet.toString()); + onContactStatusChanged.onContactStatusChanged(contact); } } else if (type.equals("unavailable")) { if (fromParts.length != 2) { @@ -341,30 +344,21 @@ public class XmppConnectionService extends Service { } else { contact.removePresence(fromParts[1]); } - replaceContactInConversation(account, contact.getJid(), - contact); - databaseBackend.updateContact(contact, true); + onContactStatusChanged.onContactStatusChanged(contact); } else if (type.equals("subscribe")) { Log.d(LOGTAG, "received subscribe packet from " + packet.getFrom()); - if (contact - .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) { + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { Log.d(LOGTAG, "preemptive grant; granting"); sendPresenceUpdatesTo(contact); - contact.setSubscriptionOption(Contact.Subscription.FROM); - contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); - replaceContactInConversation(account, - contact.getJid(), contact); - databaseBackend.updateContact(contact, false); - if ((contact - .getSubscriptionOption(Contact.Subscription.ASKING)) - && (!contact - .getSubscriptionOption(Contact.Subscription.TO))) { + contact.setOption(Contact.Options.FROM); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + if ((contact.getOption(Contact.Options.ASKING)) + && (!contact.getOption(Contact.Options.TO))) { requestPresenceUpdatesFrom(contact); } } else { - account.getXmppConnection().addPendingSubscription( - fromParts[0]); + contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); } } else { // Log.d(LOGTAG, packet.toString()); @@ -378,14 +372,13 @@ public class XmppConnectionService extends Service { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.hasChild("query","jabber:iq:roster")) { + if (packet.hasChild("query", "jabber:iq:roster")) { String from = packet.getFrom(); - if ((from==null)||(from.equals(account.getJid()))) { + if ((from == null) || (from.equals(account.getJid()))) { Element query = packet.findChild("query"); processRosterItems(account, query); - mergePhoneContactsWithRoster(null); } else { - Log.d(LOGTAG,"unauthorized roster push from: "+from); + Log.d(LOGTAG, "unauthorized roster push from: " + from); } } else if (packet .hasChild("open", "http://jabber.org/protocol/ibb") @@ -393,20 +386,30 @@ public class XmppConnectionService extends Service { .hasChild("data", "http://jabber.org/protocol/ibb")) { XmppConnectionService.this.mJingleConnectionManager .deliverIbbPacket(account, packet); - } else if (packet.hasChild("query","http://jabber.org/protocol/disco#info")) { - IqPacket iqResponse = packet.generateRespone(IqPacket.TYPE_RESULT); - Element query = iqResponse.addChild("query", "http://jabber.org/protocol/disco#info"); - query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:1"); - query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:apps:file-transfer:3"); - query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:transports:s5b:1"); - query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:transports:ibb:1"); + } else if (packet.hasChild("query", + "http://jabber.org/protocol/disco#info")) { + IqPacket iqResponse = packet + .generateRespone(IqPacket.TYPE_RESULT); + Element query = iqResponse.addChild("query", + "http://jabber.org/protocol/disco#info"); + query.addChild("feature").setAttribute("var", + "urn:xmpp:jingle:1"); + query.addChild("feature").setAttribute("var", + "urn:xmpp:jingle:apps:file-transfer:3"); + query.addChild("feature").setAttribute("var", + "urn:xmpp:jingle:transports:s5b:1"); + query.addChild("feature").setAttribute("var", + "urn:xmpp:jingle:transports:ibb:1"); account.getXmppConnection().sendIqPacket(iqResponse, null); } else { - if ((packet.getType() == IqPacket.TYPE_GET)||(packet.getType() == IqPacket.TYPE_SET)) { - IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR); + if ((packet.getType() == IqPacket.TYPE_GET) + || (packet.getType() == IqPacket.TYPE_SET)) { + IqPacket response = packet + .generateRespone(IqPacket.TYPE_ERROR); Element error = response.addChild("error"); - error.setAttribute("type","cancel"); - error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas"); + error.setAttribute("type", "cancel"); + error.addChild("feature-not-implemented", + "urn:ietf:params:xml:ns:xmpp-stanzas"); account.getXmppConnection().sendIqPacket(response, null); } } @@ -433,7 +436,7 @@ public class XmppConnectionService extends Service { if (this.mPgpEngine == null) { this.mPgpEngine = new PgpEngine(new OpenPgpApi( getApplicationContext(), - pgpServiceConnection.getService()),this); + pgpServiceConnection.getService()), this); } return mPgpEngine; } else { @@ -446,10 +449,12 @@ public class XmppConnectionService extends Service { return this.fileBackend; } - public Message attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { + public Message attachImageToConversation(final Conversation conversation, + final Uri uri, final UiCallback callback) { final Message message; if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "",Message.ENCRYPTION_DECRYPTED); + message = new Message(conversation, "", + Message.ENCRYPTION_DECRYPTED); } else { message = new Message(conversation, "", Message.ENCRYPTION_NONE); } @@ -460,21 +465,21 @@ public class XmppConnectionService extends Service { @Override public void run() { - JingleFile file = getFileBackend().copyImageToPrivateStorage(message, uri); - if (file==null) { - callback.error(R.string.error_copying_image_file); - } else { + try { + getFileBackend().copyImageToPrivateStorage(message, uri); if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { getPgpEngine().encrypt(message, callback); } else { callback.success(); } + } catch (FileBackend.ImageCopyException e) { + callback.error(e.getResId()); } } }).start(); return message; } - + protected Conversation findMuc(String name, Account account) { for (Conversation conversation : this.conversations) { if (conversation.getContactJid().split("/")[0].equals(name) @@ -488,51 +493,28 @@ public class XmppConnectionService extends Service { private void processRosterItems(Account account, Element elements) { String version = elements.getAttribute("ver"); if (version != null) { - account.setRosterVersion(version); - databaseBackend.updateAccount(account); + account.getRoster().setVersion(version); } for (Element item : elements.getChildren()) { if (item.getName().equals("item")) { String jid = item.getAttribute("jid"); + String name = item.getAttribute("name"); String subscription = item.getAttribute("subscription"); - Contact contact = databaseBackend.findContact(account, jid); - if (contact == null) { - if (!subscription.equals("remove")) { - String name = item.getAttribute("name"); - if (name == null) { - name = jid.split("@")[0]; - } - contact = new Contact(account, name, jid, null); - contact.parseSubscriptionFromElement(item); - databaseBackend.createContact(contact); - } + Contact contact = account.getRoster().getContact(jid); + if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { + contact.setServerName(name); + } + if (subscription.equals("remove")) { + contact.resetOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_DELETE); } else { - if (subscription.equals("remove")) { - databaseBackend.deleteContact(contact); - replaceContactInConversation(account, contact.getJid(), - null); - } else { - contact.parseSubscriptionFromElement(item); - databaseBackend.updateContact(contact, false); - replaceContactInConversation(account, contact.getJid(), - contact); - } + contact.setOption(Contact.Options.IN_ROSTER); + contact.parseSubscriptionFromElement(item); } } } } - private void replaceContactInConversation(Account account, String jid, - Contact contact) { - List<Conversation> conversations = getConversations(); - for (Conversation c : conversations) { - if (c.getContactJid().equals(jid) && (c.getAccount() == account)) { - c.setContact(contact); - break; - } - } - } - public class XmppConnectionBinder extends Binder { public XmppConnectionService getService() { return XmppConnectionService.this; @@ -542,7 +524,15 @@ public class XmppConnectionService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { this.wakeLock.acquire(); - // Log.d(LOGTAG,"calling start service. caller was:"+intent.getAction()); + if ((intent != null) + && (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) { + mergePhoneContactsWithRoster(); + return START_STICKY; + } else if ((intent != null) + && (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) { + logoutAndSave(); + return START_NOT_STICKY; + } ConnectivityManager cm = (ConnectivityManager) getApplicationContext() .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); @@ -563,8 +553,6 @@ public class XmppConnectionService extends Service { statusListener.onStatusChanged(account); } } - - // TODO 3 remaining cases if (account.getStatus() == Account.STATUS_ONLINE) { long lastReceived = account.getXmppConnection().lastPaketReceived; long lastSent = account.getXmppConnection().lastPingSent; @@ -592,15 +580,9 @@ public class XmppConnectionService extends Service { + ": time out during connect reconnecting"); reconnectAccount(account, true); } else { - Log.d(LOGTAG, - "seconds since last connect:" - + ((SystemClock.elapsedRealtime() - account - .getXmppConnection().lastConnect) / 1000)); - Log.d(LOGTAG, - account.getJid() + ": status=" - + account.getStatus()); - // TODO notify user of ssl cert problem or auth problem - // or what ever + if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { + reconnectAccount(account, true); + } } // in any case. reschedule wakup call this.scheduleWakeupCall(PING_MAX_INTERVAL, true); @@ -624,6 +606,10 @@ public class XmppConnectionService extends Service { this.fileBackend = new FileBackend(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); + for (Account account : this.accounts) { + this.databaseBackend.readRoster(account.getRoster()); + } + this.mergePhoneContactsWithRoster(); this.getConversations(); getContentResolver().registerContentObserver( @@ -639,13 +625,30 @@ public class XmppConnectionService extends Service { @Override public void onDestroy() { - Log.d(LOGTAG,"stopping service"); super.onDestroy(); + this.logoutAndSave(); + } + + @Override + public void onTaskRemoved(Intent rootIntent) { + super.onTaskRemoved(rootIntent); + this.logoutAndSave(); + } + + private void logoutAndSave() { for (Account account : accounts) { + databaseBackend.writeRoster(account.getRoster()); if (account.getXmppConnection() != null) { - disconnect(account, true); + disconnect(account, false); } } + Context context = getApplicationContext(); + AlarmManager alarmManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, EventReceiver.class); + alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0)); + Log.d(LOGTAG, "good bye"); + stopSelf(); } protected void scheduleWakeupCall(int seconds, boolean ping) { @@ -663,7 +666,6 @@ public class XmppConnectionService extends Service { this.pingIntent, 0); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingPingIntent); - // Log.d(LOGTAG,"schedule ping in "+seconds+" seconds"); } else { long scheduledTime = this.pingIntent.getLongExtra("time", 0); if (scheduledTime < SystemClock.elapsedRealtime() @@ -674,7 +676,6 @@ public class XmppConnectionService extends Service { context, 0, this.pingIntent, 0); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingPingIntent); - // Log.d(LOGTAG,"reschedule old ping to ping in "+seconds+" seconds"); } } } else { @@ -715,12 +716,11 @@ public class XmppConnectionService extends Service { connection.setOnBindListener(new OnBindListener() { @Override - public void onBind(Account account) { - databaseBackend.clearPresences(account); + public void onBind(final Account account) { + account.getRoster().clearPresences(); account.clearPresences(); // self presences - if (account.getXmppConnection().hasFeatureRosterManagment()) { - updateRoster(account, null); - } + fetchRosterFromServer(account); + sendPresence(account); connectMultiModeConversations(account); if (convChangedListener != null) { convChangedListener.onConversationListChanged(); @@ -758,11 +758,7 @@ public class XmppConnectionService extends Service { addToConversation = true; } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { message.getConversation().endOtrIfNeeded(); - packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); - packet.setFrom(message.getConversation().getAccount() - .getFullJid()); - packet.setTo(message.getCounterpart()); + packet = prepareMessagePacket(account, message, null); packet.setBody("This is an XEP-0027 encryted message"); packet.addChild("x", "jabber:x:encrypted").setContent( message.getEncryptedBody()); @@ -776,17 +772,26 @@ public class XmppConnectionService extends Service { // don't encrypt if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { message.setStatus(Message.STATUS_SEND); - saveInDb = true; - addToConversation = true; } packet = prepareMessagePacket(account, message, null); send = true; + saveInDb = true; + addToConversation = true; } } } else { - // account is offline - saveInDb = true; - addToConversation = true; + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + String pgpBody = message.getEncryptedBody(); + String decryptedBody = message.getBody(); + message.setBody(pgpBody); + databaseBackend.createMessage(message); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + message.setBody(decryptedBody); + addToConversation = true; + } else { + saveInDb = true; + addToConversation = true; + } } if (saveInDb) { @@ -806,22 +811,35 @@ public class XmppConnectionService extends Service { private void sendUnsendMessages(Conversation conversation) { for (int i = 0; i < conversation.getMessages().size(); ++i) { - if ((conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) - && (conversation.getMessages().get(i).getEncryption() == Message.ENCRYPTION_NONE)) { - Message message = conversation.getMessages().get(i); - MessagePacket packet = prepareMessagePacket( - conversation.getAccount(), message, null); - conversation.getAccount().getXmppConnection() - .sendMessagePacket(packet); - message.setStatus(Message.STATUS_SEND); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - databaseBackend.updateMessage(message); - } else { - databaseBackend.deleteMessage(message); - conversation.getMessages().remove(i); - i--; - } + if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) { + resendMessage(conversation.getMessages().get(i)); + } + } + } + + private void resendMessage(Message message) { + Account account = message.getConversation().getAccount(); + MessagePacket packet = null; + if (message.getEncryption() == Message.ENCRYPTION_NONE) { + packet = prepareMessagePacket(account, message, null); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + packet = prepareMessagePacket(account, message, null); + packet.setBody("This is an XEP-0027 encryted message"); + if (message.getEncryptedBody() == null) { + markMessage(message, Message.STATUS_SEND_FAILED); + return; } + packet.addChild("x", "jabber:x:encrypted").setContent( + message.getEncryptedBody()); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + packet = prepareMessagePacket(account, message, null); + packet.setBody("This is an XEP-0027 encryted message"); + packet.addChild("x", "jabber:x:encrypted").setContent( + message.getBody()); + } + if (packet != null) { + account.getXmppConnection().sendMessagePacket(packet); + markMessage(message, Message.STATUS_SEND); } } @@ -860,27 +878,7 @@ public class XmppConnectionService extends Service { return packet; } - private void getRoster(Account account, - final OnRosterFetchedListener listener) { - List<Contact> contacts = databaseBackend.getContactsByAccount(account); - for (int i = 0; i < contacts.size(); ++i) { - contacts.get(i).setAccount(account); - } - if (listener != null) { - listener.onRosterFetched(contacts); - } - } - - public List<Contact> getRoster(Account account) { - List<Contact> contacts = databaseBackend.getContactsByAccount(account); - for (int i = 0; i < contacts.size(); ++i) { - contacts.get(i).setAccount(account); - } - return contacts; - } - - public void updateRoster(final Account account, - final OnRosterFetchedListener listener) { + public void fetchRosterFromServer(Account account) { IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); if (!"".equals(account.getRosterVersion())) { Log.d(LOGTAG, account.getJid() + ": fetching roster version " @@ -898,62 +896,26 @@ public class XmppConnectionService extends Service { IqPacket packet) { Element roster = packet.findChild("query"); if (roster != null) { - Log.d(LOGTAG, account.getJid() - + ": processing roster"); + account.getRoster().markAllAsNotInRoster(); processRosterItems(account, roster); - StringBuilder mWhere = new StringBuilder(); - mWhere.append("jid NOT IN("); - List<Element> items = roster.getChildren(); - for (int i = 0; i < items.size(); ++i) { - mWhere.append(DatabaseUtils - .sqlEscapeString(items.get(i) - .getAttribute("jid"))); - if (i != items.size() - 1) { - mWhere.append(","); - } - } - mWhere.append(") and accountUuid = \""); - mWhere.append(account.getUuid()); - mWhere.append("\""); - List<Contact> contactsToDelete = databaseBackend - .getContacts(mWhere.toString()); - for (Contact contact : contactsToDelete) { - databaseBackend.deleteContact(contact); - replaceContactInConversation(account, - contact.getJid(), null); - } - - } else { - Log.d(LOGTAG, account.getJid() - + ": empty roster returend"); } - mergePhoneContactsWithRoster(new OnPhoneContactsMerged() { - - @Override - public void phoneContactsMerged() { - if (listener != null) { - getRoster(account, listener); - } - } - }); } }); } - public void mergePhoneContactsWithRoster( - final OnPhoneContactsMerged listener) { + private void mergePhoneContactsWithRoster() { PhoneHelper.loadPhoneContacts(getApplicationContext(), new OnPhoneContactsLoadedListener() { @Override - public void onPhoneContactsLoaded( - Hashtable<String, Bundle> phoneContacts) { - List<Contact> contacts = databaseBackend - .getContactsByAccount(null); - for (int i = 0; i < contacts.size(); ++i) { - Contact contact = contacts.get(i); - if (phoneContacts.containsKey(contact.getJid())) { - Bundle phoneContact = phoneContacts.get(contact - .getJid()); + public void onPhoneContactsLoaded(List<Bundle> phoneContacts) { + for(Account account : accounts) { + account.getRoster().clearSystemAccounts(); + } + for (Bundle phoneContact : phoneContacts) { + for (Account account : accounts) { + String jid = phoneContact.getString("jid"); + Contact contact = account.getRoster() + .getContact(jid); String systemAccount = phoneContact .getInt("phoneid") + "#" @@ -961,28 +923,10 @@ public class XmppConnectionService extends Service { contact.setSystemAccount(systemAccount); contact.setPhotoUri(phoneContact .getString("photouri")); - contact.setDisplayName(phoneContact + contact.setSystemName(phoneContact .getString("displayname")); - databaseBackend.updateContact(contact, false); - replaceContactInConversation( - contact.getAccount(), contact.getJid(), - contact); - } else { - if ((contact.getSystemAccount() != null) - || (contact.getProfilePhoto() != null)) { - contact.setSystemAccount(null); - contact.setPhotoUri(null); - databaseBackend.updateContact(contact, - false); - replaceContactInConversation( - contact.getAccount(), - contact.getJid(), contact); - } } } - if (listener != null) { - listener.phoneContactsMerged(); - } } }); } @@ -998,7 +942,6 @@ public class XmppConnectionService extends Service { for (Conversation conv : this.conversations) { Account account = accountLookupTable.get(conv.getAccountUuid()); conv.setAccount(account); - conv.setContact(findContact(account, conv.getContactJid())); conv.setMessages(databaseBackend.getMessages(conv, 50)); } } @@ -1015,13 +958,14 @@ public class XmppConnectionService extends Service { public List<Account> getAccounts() { return this.accounts; } - - public Contact findContact(Account account, String jid) { - Contact contact = databaseBackend.findContact(account, jid); - if (contact != null) { - contact.setAccount(account); + + public Conversation findActiveConversation(Contact contact) { + for (Conversation conversation : this.getConversations()) { + if (conversation.getContact() == contact) { + return conversation; + } } - return contact; + return null; } public Conversation findOrCreateConversation(Account account, String jid, @@ -1045,11 +989,9 @@ public class XmppConnectionService extends Service { conversation.setMessages(databaseBackend.getMessages(conversation, 50)); this.databaseBackend.updateConversation(conversation); - conversation.setContact(findContact(account, - conversation.getContactJid())); } else { String conversationName; - Contact contact = findContact(account, jid); + Contact contact = account.getRoster().getContact(jid); if (contact != null) { conversationName = contact.getDisplayName(); } else { @@ -1062,7 +1004,6 @@ public class XmppConnectionService extends Service { conversation = new Conversation(conversationName, account, jid, Conversation.MODE_SINGLE); } - conversation.setContact(contact); this.databaseBackend.createConversation(conversation); } this.conversations.add(conversation); @@ -1110,23 +1051,14 @@ public class XmppConnectionService extends Service { accountChangedListener.onAccountListChangedListener(); } - public void deleteContact(Contact contact) { - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - Element query = iq.query("jabber:iq:roster"); - query.addChild("item").setAttribute("jid", contact.getJid()) - .setAttribute("subscription", "remove"); - contact.getAccount().getXmppConnection().sendIqPacket(iq, null); - replaceContactInConversation(contact.getAccount(), contact.getJid(), - null); - databaseBackend.deleteContact(contact); - } - public void updateAccount(Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); reconnectAccount(account, false); - if (accountChangedListener != null) + if (accountChangedListener != null) { accountChangedListener.onAccountListChangedListener(); + } + UIHelper.showErrorNotification(getApplicationContext(), getAccounts()); } public void deleteAccount(Account account) { @@ -1135,8 +1067,10 @@ public class XmppConnectionService extends Service { } databaseBackend.deleteAccount(account); this.accounts.remove(account); - if (accountChangedListener != null) + if (accountChangedListener != null) { accountChangedListener.onAccountListChangedListener(); + } + UIHelper.showErrorNotification(getApplicationContext(), getAccounts()); } public void setOnConversationListChangedListener( @@ -1214,8 +1148,8 @@ public class XmppConnectionService extends Service { renameListener.onRename(success); } if (success) { - String jid = conversation.getContactJid().split("/")[0] + "/" - + nick; + String jid = conversation.getContactJid().split("/")[0] + + "/" + nick; conversation.setContactJid(jid); databaseBackend.updateConversation(conversation); } @@ -1277,96 +1211,107 @@ public class XmppConnectionService extends Service { return mBinder; } - public void updateContact(Contact contact) { - databaseBackend.updateContact(contact, false); - replaceContactInConversation(contact.getAccount(), contact.getJid(), - contact); - } - public void updateMessage(Message message) { databaseBackend.updateMessage(message); } + + protected void syncDirtyContacts(Account account) { + for(Contact contact : account.getRoster().getContacts()) { + if (contact.getOption(Contact.Options.DIRTY_PUSH)) { + pushContactToServer(contact); + } + if (contact.getOption(Contact.Options.DIRTY_DELETE)) { + Log.d(LOGTAG,"dirty delete"); + deleteContactOnServer(contact); + } + } + } public void createContact(Contact contact) { SharedPreferences sharedPref = getPreferences(); boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true); if (autoGrant) { - contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); - contact.setSubscriptionOption(Contact.Subscription.ASKING); + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + contact.setOption(Contact.Options.ASKING); } - databaseBackend.createContact(contact); - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - Element query = new Element("query"); - query.setAttribute("xmlns", "jabber:iq:roster"); - Element item = new Element("item"); - item.setAttribute("jid", contact.getJid()); - item.setAttribute("name", contact.getJid()); - query.addChild(item); - iq.addChild(query); + pushContactToServer(contact); + } + + public void pushContactToServer(Contact contact) { + contact.resetOption(Contact.Options.DIRTY_DELETE); Account account = contact.getAccount(); - account.getXmppConnection().sendIqPacket(iq, null); - if (autoGrant) { - requestPresenceUpdatesFrom(contact); - if (account.getXmppConnection().hasPendingSubscription( - contact.getJid())) { + if (account.getStatus() == Account.STATUS_ONLINE) { + IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + iq.query("jabber:iq:roster").addChild(contact.asElement()); + account.getXmppConnection().sendIqPacket(iq, null); + if (contact.getOption(Contact.Options.ASKING)) { + requestPresenceUpdatesFrom(contact); + } + if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { Log.d("xmppService", "contact had pending subscription"); sendPresenceUpdatesTo(contact); } + contact.resetOption(Contact.Options.DIRTY_PUSH); + } else { + contact.setOption(Contact.Options.DIRTY_PUSH); + } + } + + public void deleteContactOnServer(Contact contact) { + contact.resetOption(Contact.Options.DIRTY_PUSH); + Account account = contact.getAccount(); + if (account.getStatus() == Account.STATUS_ONLINE) { + IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + Element item = iq.query("jabber:iq:roster").addChild("item"); + item.setAttribute("jid", contact.getJid()); + item.setAttribute("subscription", "remove"); + account.getXmppConnection().sendIqPacket(iq, null); + contact.resetOption(Contact.Options.DIRTY_DELETE); + } else { + contact.setOption(Contact.Options.DIRTY_DELETE); } - replaceContactInConversation(contact.getAccount(), contact.getJid(), - contact); } public void requestPresenceUpdatesFrom(Contact contact) { - // Requesting a Subscription type=subscribe PresencePacket packet = new PresencePacket(); packet.setAttribute("type", "subscribe"); packet.setAttribute("to", contact.getJid()); packet.setAttribute("from", contact.getAccount().getJid()); - Log.d(LOGTAG, packet.toString()); contact.getAccount().getXmppConnection().sendPresencePacket(packet); } public void stopPresenceUpdatesFrom(Contact contact) { - // Unsubscribing type='unsubscribe' PresencePacket packet = new PresencePacket(); packet.setAttribute("type", "unsubscribe"); packet.setAttribute("to", contact.getJid()); packet.setAttribute("from", contact.getAccount().getJid()); - Log.d(LOGTAG, packet.toString()); contact.getAccount().getXmppConnection().sendPresencePacket(packet); } public void stopPresenceUpdatesTo(Contact contact) { - // Canceling a Subscription type=unsubscribed PresencePacket packet = new PresencePacket(); packet.setAttribute("type", "unsubscribed"); packet.setAttribute("to", contact.getJid()); packet.setAttribute("from", contact.getAccount().getJid()); - Log.d(LOGTAG, packet.toString()); contact.getAccount().getXmppConnection().sendPresencePacket(packet); } public void sendPresenceUpdatesTo(Contact contact) { - // type='subscribed' PresencePacket packet = new PresencePacket(); packet.setAttribute("type", "subscribed"); packet.setAttribute("to", contact.getJid()); packet.setAttribute("from", contact.getAccount().getJid()); - Log.d(LOGTAG, packet.toString()); contact.getAccount().getXmppConnection().sendPresencePacket(packet); } - public void sendPgpPresence(Account account, String signature) { + public void sendPresence(Account account) { PresencePacket packet = new PresencePacket(); packet.setAttribute("from", account.getFullJid()); - Element status = new Element("status"); - status.setContent("online"); - packet.addChild(status); - Element x = new Element("x"); - x.setAttribute("xmlns", "jabber:x:signed"); - x.setContent(signature); - packet.addChild(x); + String sig = account.getPgpSignature(); + if (sig != null) { + packet.addChild("status").setContent("online"); + packet.addChild("x", "jabber:x:signed").setContent(sig); + } account.getXmppConnection().sendPresencePacket(packet); } @@ -1374,23 +1319,10 @@ public class XmppConnectionService extends Service { this.databaseBackend.updateConversation(conversation); } - public Contact findContact(String uuid) { - Contact contact = this.databaseBackend.getContact(uuid); - if (contact != null) { - for (Account account : getAccounts()) { - if (contact.getAccountUuid().equals(account.getUuid())) { - contact.setAccount(account); - } - } - } - return contact; - } - public void removeOnTLSExceptionReceivedListener() { this.tlsException = null; } - // TODO dont let thread sleep but schedule wake up public void reconnectAccount(final Account account, final boolean force) { new Thread(new Runnable() { @@ -1447,21 +1379,24 @@ public class XmppConnectionService extends Service { public boolean markMessage(Account account, String recipient, String uuid, int status) { - boolean marked = false; for (Conversation conversation : getConversations()) { if (conversation.getContactJid().equals(recipient) && conversation.getAccount().equals(account)) { - for (Message message : conversation.getMessages()) { - if (message.getUuid().equals(uuid)) { - markMessage(message, status); - marked = true; - break; - } - } - break; + return markMessage(conversation, uuid, status); + } + } + return false; + } + + public boolean markMessage(Conversation conversation, String uuid, + int status) { + for (Message message : conversation.getMessages()) { + if (message.getUuid().equals(uuid)) { + markMessage(message, status); + return true; } } - return marked; + return false; } public void markMessage(Message message, int status) { @@ -1476,12 +1411,22 @@ public class XmppConnectionService extends Service { return PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); } - + public void updateUi(Conversation conversation, boolean notify) { if (convChangedListener != null) { convChangedListener.onConversationListChanged(); } else { - UIHelper.updateNotification(getApplicationContext(), getConversations(), conversation, notify); + UIHelper.updateNotification(getApplicationContext(), + getConversations(), conversation, notify); + } + } + + public Account findAccountByJid(String accountJid) { + for (Account account : this.accounts) { + if (account.getJid().equals(accountJid)) { + return account; + } } + return null; } } diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java index 06179bc6..154533db 100644 --- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -1,8 +1,6 @@ package eu.siacs.conversations.ui; -import java.math.BigInteger; import java.util.Iterator; -import java.util.Locale; import org.openintents.openpgp.util.OpenPgpUtils; @@ -17,7 +15,6 @@ import android.os.Bundle; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -31,6 +28,7 @@ import android.widget.TextView; import android.widget.Toast; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.utils.UIHelper; @@ -40,12 +38,14 @@ public class ContactDetailsActivity extends XmppActivity { protected ContactDetailsActivity activity = this; - private String uuid; private Contact contact; - + + private String accountJid; + private String contactJid; + private EditText name; - private TextView contactJid; - private TextView accountJid; + private TextView contactJidTv; + private TextView accountJidTv; private TextView status; private TextView askAgain; private CheckBox send; @@ -56,7 +56,7 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { - activity.xmppConnectionService.deleteContact(contact); + activity.xmppConnectionService.deleteContactOnServer(contact); activity.finish(); } }; @@ -65,8 +65,8 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { - contact.setDisplayName(name.getText().toString()); - activity.xmppConnectionService.updateContact(contact); + contact.setServerName(name.getText().toString()); + activity.xmppConnectionService.pushContactToServer(contact); populateView(); } }; @@ -89,11 +89,10 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle("Add to phone book"); - builder.setMessage("Do you want to add " + contact.getJid() - + " to your phones contact list?"); - builder.setNegativeButton("Cancel", null); - builder.setPositiveButton("Add", addToPhonebook); + builder.setTitle(getString(R.string.action_add_phone_book)); + builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid())); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.add), addToPhonebook); builder.create().show(); } }; @@ -104,12 +103,13 @@ public class ContactDetailsActivity extends XmppActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { - this.uuid = getIntent().getExtras().getString("uuid"); + this.accountJid = getIntent().getExtras().getString("account"); + this.contactJid = getIntent().getExtras().getString("contact"); } setContentView(R.layout.activity_contact_details); - contactJid = (TextView) findViewById(R.id.details_contactjid); - accountJid = (TextView) findViewById(R.id.details_account); + contactJidTv = (TextView) findViewById(R.id.details_contactjid); + accountJidTv = (TextView) findViewById(R.id.details_account); status = (TextView) findViewById(R.id.details_contactstatus); send = (CheckBox) findViewById(R.id.details_send_presence); receive = (CheckBox) findViewById(R.id.details_receive_presence); @@ -124,17 +124,17 @@ public class ContactDetailsActivity extends XmppActivity { @Override public boolean onOptionsItemSelected(MenuItem menuItem) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setNegativeButton("Cancel", null); + builder.setNegativeButton(getString(R.string.cancel), null); switch (menuItem.getItemId()) { case android.R.id.home: finish(); break; case R.id.action_delete_contact: - builder.setTitle("Delete from roster") + builder.setTitle(getString(R.string.action_delete_contact)) .setMessage( getString(R.string.remove_contact_text, contact.getJid())) - .setPositiveButton("Delete", removeFromRoster).create() + .setPositiveButton(getString(R.string.delete), removeFromRoster).create() .show(); break; case R.id.action_edit_contact: @@ -145,7 +145,7 @@ public class ContactDetailsActivity extends XmppActivity { name = (EditText) view.findViewById(R.id.editText1); name.setText(contact.getDisplayName()); builder.setView(view).setTitle(contact.getJid()) - .setPositiveButton("Edit", editContactNameListener) + .setPositiveButton(getString(R.string.edit), editContactNameListener) .create().show(); } else { @@ -170,18 +170,18 @@ public class ContactDetailsActivity extends XmppActivity { private void populateView() { setTitle(contact.getDisplayName()); - if (contact.getSubscriptionOption(Contact.Subscription.FROM)) { + if (contact.getOption(Contact.Options.FROM)) { send.setChecked(true); } else { send.setText(R.string.preemptively_grant); if (contact - .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) { + .getOption(Contact.Options.PREEMPTIVE_GRANT)) { send.setChecked(true); } else { send.setChecked(false); } } - if (contact.getSubscriptionOption(Contact.Subscription.TO)) { + if (contact.getOption(Contact.Options.TO)) { receive.setChecked(true); } else { receive.setText(R.string.ask_for_presence_updates); @@ -190,12 +190,13 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(View v) { - Toast.makeText(getApplicationContext(), "Asked for presence updates",Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), getString(R.string.asked_for_presence_updates), + Toast.LENGTH_SHORT).show(); xmppConnectionService.requestPresenceUpdatesFrom(contact); } }); - if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) { + if (contact.getOption(Contact.Options.ASKING)) { receive.setChecked(true); } else { receive.setChecked(false); @@ -204,40 +205,40 @@ public class ContactDetailsActivity extends XmppActivity { switch (contact.getMostAvailableStatus()) { case Presences.CHAT: - status.setText("free to chat"); + status.setText(R.string.contact_status_free_to_chat); status.setTextColor(0xFF83b600); break; case Presences.ONLINE: - status.setText("online"); + status.setText(R.string.contact_status_online); status.setTextColor(0xFF83b600); break; case Presences.AWAY: - status.setText("away"); + status.setText(R.string.contact_status_away); status.setTextColor(0xFFffa713); break; case Presences.XA: - status.setText("extended away"); + status.setText(R.string.contact_status_extended_away); status.setTextColor(0xFFffa713); break; case Presences.DND: - status.setText("do not disturb"); + status.setText(R.string.contact_status_do_not_disturb); status.setTextColor(0xFFe92727); break; case Presences.OFFLINE: - status.setText("offline"); + status.setText(R.string.contact_status_offline); status.setTextColor(0xFFe92727); break; default: - status.setText("offline"); + status.setText(R.string.contact_status_offline); status.setTextColor(0xFFe92727); break; } if (contact.getPresences().size() > 1) { - contactJid.setText(contact.getJid()+" ("+contact.getPresences().size()+")"); + contactJidTv.setText(contact.getJid()+" ("+contact.getPresences().size()+")"); } else { - contactJid.setText(contact.getJid()); + contactJidTv.setText(contact.getJid()); } - accountJid.setText(contact.getAccount().getJid()); + accountJidTv.setText(contact.getAccount().getJid()); UIHelper.prepareContactBadge(this, badge, contact, getApplicationContext()); @@ -286,65 +287,83 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onBackendConnected() { - if (uuid != null) { - this.contact = xmppConnectionService.findContact(uuid); - if (this.contact != null) { - populateView(); + if ((accountJid != null)&&(contactJid != null)) { + Account account = xmppConnectionService.findAccountByJid(accountJid); + if (account==null) { + return; } + this.contact = account.getRoster().getContact(contactJid); + populateView(); } } @Override protected void onStop() { super.onStop(); - boolean needsUpdating = false; - if (contact.getSubscriptionOption(Contact.Subscription.FROM)) { + boolean updated = false; + boolean online = contact.getAccount().getStatus() == Account.STATUS_ONLINE; + if (contact.getOption(Contact.Options.FROM)) { if (!send.isChecked()) { - contact.resetSubscriptionOption(Contact.Subscription.FROM); - contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); - activity.xmppConnectionService.stopPresenceUpdatesTo(contact); - needsUpdating = true; + if (online) { + contact.resetOption(Contact.Options.FROM); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + activity.xmppConnectionService.stopPresenceUpdatesTo(contact); + } + updated = true; } } else { if (contact - .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) { + .getOption(Contact.Options.PREEMPTIVE_GRANT)) { if (!send.isChecked()) { - contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); - needsUpdating = true; + if (online) { + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } + updated = true; } } else { if (send.isChecked()) { - contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); - needsUpdating = true; + if (online) { + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + } + updated = true; } } } - if (contact.getSubscriptionOption(Contact.Subscription.TO)) { + if (contact.getOption(Contact.Options.TO)) { if (!receive.isChecked()) { - contact.resetSubscriptionOption(Contact.Subscription.TO); - activity.xmppConnectionService.stopPresenceUpdatesFrom(contact); - needsUpdating = true; + if (online) { + contact.resetOption(Contact.Options.TO); + activity.xmppConnectionService.stopPresenceUpdatesFrom(contact); + } + updated = true; } } else { - if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) { + if (contact.getOption(Contact.Options.ASKING)) { if (!receive.isChecked()) { - contact.resetSubscriptionOption(Contact.Subscription.ASKING); - activity.xmppConnectionService + if (online) { + contact.resetOption(Contact.Options.ASKING); + activity.xmppConnectionService .stopPresenceUpdatesFrom(contact); - needsUpdating = true; + } + updated = true; } } else { if (receive.isChecked()) { - contact.setSubscriptionOption(Contact.Subscription.ASKING); - activity.xmppConnectionService + if (online) { + contact.setOption(Contact.Options.ASKING); + activity.xmppConnectionService .requestPresenceUpdatesFrom(contact); - needsUpdating = true; + } + updated = true; } } } - if (needsUpdating) { - Toast.makeText(getApplicationContext(), "Subscription updated", Toast.LENGTH_SHORT).show(); - activity.xmppConnectionService.updateContact(contact); + if (updated) { + if (online) { + Toast.makeText(getApplicationContext(), getString(R.string.subscription_updated), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getApplicationContext(), getString(R.string.subscription_not_updated_offline), Toast.LENGTH_SHORT).show(); + } } } diff --git a/src/eu/siacs/conversations/ui/ContactsActivity.java b/src/eu/siacs/conversations/ui/ContactsActivity.java index e403450a..f4c8ef0b 100644 --- a/src/eu/siacs/conversations/ui/ContactsActivity.java +++ b/src/eu/siacs/conversations/ui/ContactsActivity.java @@ -18,7 +18,6 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; -import android.util.Log; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.LayoutInflater; @@ -34,13 +33,11 @@ import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import android.widget.ImageView; import android.widget.Toast; import android.annotation.SuppressLint; import android.app.AlertDialog; -import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -130,8 +127,10 @@ public class ContactsActivity extends XmppActivity { Intent intent = new Intent(getApplicationContext(), ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra("uuid", selectedContacts.get(0).getUuid()); + intent.putExtra("account", selectedContacts.get(0).getAccount().getJid()); + intent.putExtra("contact",selectedContacts.get(0).getJid()); startActivity(intent); + finish(); break; case R.id.action_invite: invite(); @@ -187,7 +186,7 @@ public class ContactsActivity extends XmppActivity { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.account_offline)); builder.setMessage(getString(R.string.cant_invite_while_offline)); - builder.setNegativeButton("OK", null); + builder.setNegativeButton(getString(R.string.ok), null); builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.create().show(); return false; @@ -259,7 +258,7 @@ public class ContactsActivity extends XmppActivity { conversation, subject.toString()); xmppConnectionService.inviteToConference(conversation, selectedContacts); - switchToConversation(conversation, null); + switchToConversation(conversation, null,false); } }); builder.create().show(); @@ -270,7 +269,7 @@ public class ContactsActivity extends XmppActivity { aggregatedContacts.clear(); for (Contact contact : rosterContacts) { - if (contact.match(searchString)) + if (contact.match(searchString)&&(contact.showInRoster())) aggregatedContacts.add(contact); } @@ -287,16 +286,15 @@ public class ContactsActivity extends XmppActivity { if (aggregatedContacts.size() == 0) { if (Validator.isValidJid(searchString)) { - String name = searchString.split("@")[0]; - Contact newContact = new Contact(null, name, searchString, null); - newContact.flagAsNotInRoster(); + Contact newContact = new Contact(searchString); + newContact.resetOption(Contact.Options.IN_ROSTER); aggregatedContacts.add(newContact); - contactsHeader.setText("Create new contact"); + contactsHeader.setText(getString(R.string.new_contact)); } else { - contactsHeader.setText("Contacts"); + contactsHeader.setText(getString(R.string.contacts)); } } else { - contactsHeader.setText("Contacts"); + contactsHeader.setText(getString(R.string.contacts)); } contactsAdapter.notifyDataSetChanged(); @@ -429,7 +427,7 @@ public class ContactsActivity extends XmppActivity { } AlertDialog.Builder accountChooser = new AlertDialog.Builder(this); - accountChooser.setTitle("Choose account"); + accountChooser.setTitle(getString(R.string.choose_account)); accountChooser.setItems(accountList, listener); return accountChooser.create(); } @@ -437,9 +435,9 @@ public class ContactsActivity extends XmppActivity { public void showIsMucDialogIfNeeded(final Contact clickedContact) { if (clickedContact.couldBeMuc()) { AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Multi User Conference"); - dialog.setMessage("Are you trying to join a conference?"); - dialog.setPositiveButton("Yes", new OnClickListener() { + dialog.setTitle(getString(R.string.multi_user_conference)); + dialog.setMessage(getString(R.string.trying_join_conference)); + dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -447,7 +445,7 @@ public class ContactsActivity extends XmppActivity { clickedContact.getAccount(), true); } }); - dialog.setNegativeButton("No", new OnClickListener() { + dialog.setNegativeButton(getString(R.string.no), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -463,13 +461,13 @@ public class ContactsActivity extends XmppActivity { } public void startConversation(Contact contact, Account account, boolean muc) { - if (!contact.isInRoster()&&(!muc)) { + if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) { xmppConnectionService.createContact(contact); } Conversation conversation = xmppConnectionService .findOrCreateConversation(account, contact.getJid(), muc); - switchToConversation(conversation, null); + switchToConversation(conversation, null,false); } @Override @@ -496,7 +494,7 @@ public class ContactsActivity extends XmppActivity { .findOrCreateConversation( accounts.get(which), finalJid, false); - switchToConversation(conversation, null); + switchToConversation(conversation, null,false); finish(); } }).show(); @@ -504,7 +502,7 @@ public class ContactsActivity extends XmppActivity { Conversation conversation = xmppConnectionService .findOrCreateConversation(this.accounts.get(0), jid, false); - switchToConversation(conversation, null); + switchToConversation(conversation, null,false); finish(); } } @@ -515,9 +513,10 @@ public class ContactsActivity extends XmppActivity { getActionBar().setHomeButtonEnabled(false); } this.rosterContacts.clear(); - for (int i = 0; i < accounts.size(); ++i) { - rosterContacts.addAll(xmppConnectionService.getRoster(accounts - .get(i))); + for(Account account : accounts) { + if (account.getStatus() != Account.STATUS_DISABLED) { + rosterContacts.addAll(account.getRoster().getContacts()); + } } updateAggregatedContacts(); } @@ -532,52 +531,12 @@ public class ContactsActivity extends XmppActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.action_refresh_contacts: - refreshContacts(); - break; default: break; } return super.onOptionsItemSelected(item); } - private void refreshContacts() { - final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1); - final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search); - final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header); - final ListView contactList = (ListView) findViewById(R.id.contactList); - searchBar.setVisibility(View.GONE); - contactsHeader.setVisibility(View.GONE); - contactList.setVisibility(View.GONE); - progress.setVisibility(View.VISIBLE); - this.accounts = xmppConnectionService.getAccounts(); - this.rosterContacts.clear(); - for (int i = 0; i < accounts.size(); ++i) { - if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) { - xmppConnectionService.updateRoster(accounts.get(i), - new OnRosterFetchedListener() { - - @Override - public void onRosterFetched( - final List<Contact> roster) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - rosterContacts.addAll(roster); - progress.setVisibility(View.GONE); - searchBar.setVisibility(View.VISIBLE); - contactList.setVisibility(View.VISIBLE); - contactList.setVisibility(View.VISIBLE); - updateAggregatedContacts(); - } - }); - } - }); - } - } - } - @Override public void onActionModeStarted(ActionMode mode) { super.onActionModeStarted(mode); diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index 88728245..bd98e979 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -6,19 +6,19 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.List; -import org.openintents.openpgp.OpenPgpError; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.ImageProvider; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.UIHelper; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; +import android.provider.MediaStore; import android.app.AlertDialog; import android.app.FragmentTransaction; import android.app.PendingIntent; @@ -65,9 +65,14 @@ public class ConversationActivity extends XmppActivity { public static final int REQUEST_SEND_MESSAGE = 0x75441; public static final int REQUEST_DECRYPT_PGP = 0x76783; private static final int REQUEST_ATTACH_FILE_DIALOG = 0x48502; + private static final int REQUEST_IMAGE_CAPTURE = 0x33788; + private static final int REQUEST_RECORD_AUDIO = 0x46189; private static final int REQUEST_SEND_PGP_IMAGE = 0x53883; - private static final int REQUEST_ATTACH_FILE = 0x73824; public static final int REQUEST_ENCRYPT_MESSAGE = 0x378018; + + private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x92734; + private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x84123; + private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x75291; protected SlidingPaneLayout spl; @@ -120,6 +125,10 @@ public class ConversationActivity extends XmppActivity { public Conversation getSelectedConversation() { return this.selectedConversation; } + + public void setSelectedConversation(Conversation conversation) { + this.selectedConversation = conversation; + } public ListView getConversationListView() { return this.listView; @@ -214,7 +223,7 @@ public class ConversationActivity extends XmppActivity { } ((TextView) view.findViewById(R.id.conversation_lastupdate)) - .setText(UIHelper.readableTimeDifference(conv + .setText(UIHelper.readableTimeDifference(getContext(), conv .getLatestMessage().getTimeSent())); ImageView profilePicture = (ImageView) view @@ -235,8 +244,8 @@ public class ConversationActivity extends XmppActivity { public void onItemClick(AdapterView<?> arg0, View clickedView, int position, long arg3) { paneShouldBeOpen = false; - if (selectedConversation != conversationList.get(position)) { - selectedConversation = conversationList.get(position); + if (getSelectedConversation() != conversationList.get(position)) { + setSelectedConversation(conversationList.get(position)); swapConversationFragment(); // .onBackendConnected(conversationList.get(position)); } else { spl.closePane(); @@ -327,28 +336,40 @@ public class ConversationActivity extends XmppActivity { return true; } - private void attachFileDialog() { + private void selectPresenceToAttachFile(final int attachmentChoice) { selectPresence(getSelectedConversation(), new OnPresenceSelected() { @Override public void onPresenceSelected(boolean success, String presence) { if (success) { - Intent attachFileIntent = new Intent(); - attachFileIntent.setType("image/*"); - attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); - Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file)); - startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG); + if (attachmentChoice==ATTACHMENT_CHOICE_TAKE_PHOTO) { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, ImageProvider.getIncomingContentUri()); + if (takePictureIntent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); + } + } else if (attachmentChoice==ATTACHMENT_CHOICE_CHOOSE_IMAGE) { + Intent attachFileIntent = new Intent(); + attachFileIntent.setType("image/*"); + attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); + Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file)); + startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG); + } else if (attachmentChoice==ATTACHMENT_CHOICE_RECORD_VOICE) { + Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + startActivityForResult(intent, REQUEST_RECORD_AUDIO); + } } } @Override public void onSendPlainTextInstead() { + // TODO Auto-generated method stub } },"file"); } - private void attachFile() { + private void attachFile(final int attachmentChoice) { final Conversation conversation = getSelectedConversation(); if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { if (hasPgp()) { @@ -357,18 +378,17 @@ public class ConversationActivity extends XmppActivity { @Override public void userInputRequried(PendingIntent pi) { - ConversationActivity.this.runIntent(pi, REQUEST_ATTACH_FILE); + ConversationActivity.this.runIntent(pi, attachmentChoice); } @Override public void success() { - attachFileDialog(); + selectPresenceToAttachFile(attachmentChoice); } @Override public void error(int error) { - // TODO Auto-generated method stub - + displayErrorDialog(error); } }); } else { @@ -380,14 +400,14 @@ public class ConversationActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { conversation.setNextEncryption(Message.ENCRYPTION_NONE); - attachFileDialog(); + selectPresenceToAttachFile(attachmentChoice); } }); } } } } else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) { - attachFileDialog(); + selectPresenceToAttachFile(attachmentChoice); } else { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.otr_file_transfer)); @@ -399,7 +419,7 @@ public class ConversationActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { conversation.setNextEncryption(Message.ENCRYPTION_NONE); - attachFile(); + attachFile(attachmentChoice); } }); } else { @@ -408,7 +428,7 @@ public class ConversationActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); - attachFile(); + attachFile(attachmentChoice); } }); } @@ -423,7 +443,28 @@ public class ConversationActivity extends XmppActivity { spl.openPane(); break; case R.id.action_attach_file: - attachFile(); + View menuAttachFile = findViewById(R.id.action_attach_file); + PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); + attachFilePopup.inflate(R.menu.attachment_choices); + attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; + } + return false; + } + }); + attachFilePopup.show(); break; case R.id.action_add: startActivity(new Intent(this, ContactsActivity.class)); @@ -433,10 +474,11 @@ public class ConversationActivity extends XmppActivity { break; case R.id.action_contact_details: Contact contact = this.getSelectedConversation().getContact(); - if (contact != null) { + if (contact.showInRoster()) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra("uuid", contact.getUuid()); + intent.putExtra("account", this.getSelectedConversation().getAccount().getJid()); + intent.putExtra("contact",contact.getJid()); startActivity(intent); } else { showAddToRosterDialog(getSelectedConversation()); @@ -452,7 +494,7 @@ public class ConversationActivity extends XmppActivity { Intent inviteIntent = new Intent(getApplicationContext(), ContactsActivity.class); inviteIntent.setAction("invite"); - inviteIntent.putExtra("uuid", selectedConversation.getUuid()); + inviteIntent.putExtra("uuid", getSelectedConversation().getUuid()); startActivity(inviteIntent); break; case R.id.action_security: @@ -531,9 +573,9 @@ public class ConversationActivity extends XmppActivity { spl.openPane(); xmppConnectionService.archiveConversation(conversation); if (conversationList.size() > 0) { - selectedConversation = conversationList.get(0); + setSelectedConversation(conversationList.get(0)); } else { - selectedConversation = null; + setSelectedConversation(null); } } @@ -580,6 +622,23 @@ public class ConversationActivity extends XmppActivity { } @Override + protected void onNewIntent (Intent intent) { + if ((Intent.ACTION_VIEW.equals(intent.getAction())&&(VIEW_CONVERSATION.equals(intent.getType())))) { + String convToView = (String) intent.getExtras().get( + CONVERSATION); + + for (int i = 0; i < conversationList.size(); ++i) { + if (conversationList.get(i).getUuid().equals(convToView)) { + setSelectedConversation(conversationList.get(i)); + } + } + paneShouldBeOpen = false; + String text = intent.getExtras().getString(TEXT, null); + swapConversationFragment().setText(text); + } + } + + @Override public void onStart() { super.onStart(); SharedPreferences preferences = PreferenceManager @@ -619,7 +678,7 @@ public class ConversationActivity extends XmppActivity { for (int i = 0; i < conversationList.size(); ++i) { if (conversationList.get(i).getUuid().equals(convToView)) { - selectedConversation = conversationList.get(i); + setSelectedConversation(conversationList.get(i)); } } paneShouldBeOpen = false; @@ -642,7 +701,7 @@ public class ConversationActivity extends XmppActivity { if (selectedFragment != null) { selectedFragment.onBackendConnected(); } else { - selectedConversation = conversationList.get(0); + setSelectedConversation(conversationList.get(0)); swapConversationFragment(); } ExceptionHelper.checkForCrash(this, this.xmppConnectionService); @@ -668,67 +727,68 @@ public class ConversationActivity extends XmppActivity { selectedFragment.hidePgpPassphraseBox(); } } else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) { - prepareImageToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); - final Conversation conversation = getSelectedConversation(); - if (conversation.getNextEncryption() == Message.ENCRYPTION_NONE) { - prepareImageToast.show(); - this.pendingMessage = xmppConnectionService.attachImageToConversation(conversation, data.getData(),new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi) { - - } - - @Override - public void success() { - sendPendingImageMessage(); - } - - @Override - public void error(int error) { - pendingMessage = null; - displayErrorDialog(error); - } - }); - } else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - prepareImageToast.show(); - attachPgpFile(conversation,data.getData()); - } else { - Log.d(LOGTAG,"unknown next message encryption: "+conversation.getNextEncryption()); - } + attachImageToConversation(getSelectedConversation(),data.getData()); } else if (requestCode == REQUEST_SEND_PGP_IMAGE) { - } else if (requestCode == REQUEST_ATTACH_FILE) { - attachFile(); + } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); } else if (requestCode == REQUEST_ANNOUNCE_PGP) { announcePgp(getSelectedConversation().getAccount(),getSelectedConversation()); } else if (requestCode == REQUEST_ENCRYPT_MESSAGE) { encryptTextMessage(); + } else if (requestCode == REQUEST_IMAGE_CAPTURE) { + attachImageToConversation(getSelectedConversation(), null); + } else if (requestCode == REQUEST_RECORD_AUDIO) { + Log.d("xmppService",data.getData().toString()); + attachAudioToConversation(getSelectedConversation(),data.getData()); } else { Log.d(LOGTAG,"unknown result code:"+requestCode); } } } - private void attachPgpFile(Conversation conversation, Uri uri) { - pendingMessage = xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi) { - ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE); - } + private void attachAudioToConversation(Conversation conversation, Uri uri) { + + } + + private void attachImageToConversation(Conversation conversation, Uri uri) { + prepareImageToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareImageToast.show(); + pendingMessage = xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() { - @Override - public void success() { - sendPendingImageMessage(); - } + @Override + public void userInputRequried(PendingIntent pi) { + hidePrepareImageToast(); + ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE); + } + + @Override + public void success() { + sendPendingImageMessage(); + hidePrepareImageToast(); + } + + @Override + public void error(int error) { + hidePrepareImageToast(); + pendingMessage = null; + displayErrorDialog(error); + } + }); + } + + private void hidePrepareImageToast() { + if (prepareImageToast!=null) { + runOnUiThread(new Runnable() { @Override - public void error(int error) { - pendingMessage = null; - displayErrorDialog(error); + public void run() { + prepareImageToast.cancel(); } }); + } } private void sendPendingImageMessage() { @@ -832,8 +892,7 @@ public class ConversationActivity extends XmppActivity { public void onClick(DialogInterface dialog, int which) { String jid = conversation.getContactJid(); Account account = getSelectedConversation().getAccount(); - String name = jid.split("@")[0]; - Contact contact = new Contact(account, name, jid, null); + Contact contact = account.getRoster().getContact(jid); xmppConnectionService.createContact(contact); } }); diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index 91a39ecc..44d1848f 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -5,17 +5,15 @@ import java.util.HashMap; import java.util.List; import java.util.Set; -import org.openintents.openpgp.OpenPgpError; - import net.java.otr4j.session.SessionStatus; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; +import eu.siacs.conversations.services.ImageProvider; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jingle.JingleConnection; @@ -30,7 +28,6 @@ import android.content.SharedPreferences; import android.content.IntentSender.SendIntentException; import android.graphics.Bitmap; import android.graphics.Typeface; -import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Editable; @@ -236,7 +233,7 @@ public class ConversationFragment extends Fragment { viewHolder.indicator.setVisibility(View.VISIBLE); } - String formatedTime = UIHelper.readableTimeDifference(message + String formatedTime = UIHelper.readableTimeDifference(getContext(), message .getTimeSent()); if (message.getStatus() <= Message.STATUS_RECIEVED) { if ((filesize != null) && (info != null)) { @@ -266,7 +263,9 @@ public class ConversationFragment extends Fragment { } private void displayInfoMessage(ViewHolder viewHolder, int r) { - viewHolder.download_button.setVisibility(View.GONE); + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getString(r)); @@ -329,15 +328,9 @@ public class ConversationFragment extends Fragment { @Override public void onClick(View v) { - Uri uri = Uri - .parse("content://eu.siacs.conversations.images/" - + message.getConversationUuid() - + "/" - + message.getUuid()); - Log.d("xmppService", - "staring intent with uri:" + uri.toString()); Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(uri, "image/*"); + intent.setDataAndType( + ImageProvider.getContentUri(message), "image/*"); startActivity(intent); } }); @@ -399,20 +392,17 @@ public class ConversationFragment extends Fragment { if (type == RECIEVED) { if (item.getConversation().getMode() == Conversation.MODE_MULTI) { - if (item.getCounterpart() != null) { - viewHolder.contact_picture - .setImageBitmap(mBitmapCache.get(item - .getCounterpart(), null, - getActivity() - .getApplicationContext())); - } else { - viewHolder.contact_picture - .setImageBitmap(mBitmapCache.get( - item.getConversation().getName( - useSubject), null, - getActivity() - .getApplicationContext())); - } + viewHolder.contact_picture.setImageBitmap(mBitmapCache + .get(item.getCounterpart(), null, getActivity() + .getApplicationContext())); + viewHolder.contact_picture + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + highlightInConference(item.getCounterpart()); + } + }); } } @@ -468,6 +458,18 @@ public class ConversationFragment extends Fragment { return view; } + protected void highlightInConference(String nick) { + String oldString = chatMsg.getText().toString().trim(); + if (oldString.isEmpty()) { + chatMsg.setText(nick+": "); + } else { + chatMsg.setText(oldString+" "+nick+" "); + } + int position = chatMsg.length(); + Editable etext = chatMsg.getText(); + Selection.setSelection(etext, position); + } + protected Bitmap findSelfPicture() { SharedPreferences sharedPref = PreferenceManager .getDefaultSharedPreferences(getActivity() @@ -494,7 +496,7 @@ public class ConversationFragment extends Fragment { @Override public void onStop() { super.onStop(); - if (this.conversation!=null) { + if (this.conversation != null) { this.conversation.setNextMessage(chatMsg.getText().toString()); } } @@ -504,10 +506,16 @@ public class ConversationFragment extends Fragment { if (this.conversation == null) { return; } + String oldString = conversation.getNextMessage().trim(); if (this.pastedText == null) { - this.chatMsg.setText(conversation.getNextMessage()); + this.chatMsg.setText(oldString); } else { - chatMsg.setText(conversation.getNextMessage() + " " + pastedText); + + if (oldString.isEmpty()) { + chatMsg.setText(pastedText); + } else { + chatMsg.setText(oldString + " " + pastedText); + } pastedText = null; } int position = chatMsg.length(); @@ -585,14 +593,15 @@ public class ConversationFragment extends Fragment { } public void updateMessages() { - if (getView()==null) { + if (getView() == null) { return; } ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { for (Message message : this.conversation.getMessages()) { if ((message.getEncryption() == Message.ENCRYPTION_PGP) - && (message.getStatus() == Message.STATUS_RECIEVED)) { + && ((message.getStatus() == Message.STATUS_RECIEVED) || (message + .getStatus() == Message.STATUS_SEND))) { decryptMessage(message); break; } @@ -632,31 +641,26 @@ public class ConversationFragment extends Fragment { protected void makeFingerprintWarning(int latestEncryption) { final LinearLayout fingerprintWarning = (LinearLayout) getView() .findViewById(R.id.new_fingerprint); - if (conversation.getContact() != null) { - Set<String> knownFingerprints = conversation.getContact() - .getOtrFingerprints(); - if ((latestEncryption == Message.ENCRYPTION_OTR) - && (conversation.hasValidOtrSession() - && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints - .contains(conversation.getOtrFingerprint())))) { - fingerprintWarning.setVisibility(View.VISIBLE); - TextView fingerprint = (TextView) getView().findViewById( - R.id.otr_fingerprint); - fingerprint.setText(conversation.getOtrFingerprint()); - fingerprintWarning.setOnClickListener(new OnClickListener() { + Set<String> knownFingerprints = conversation.getContact() + .getOtrFingerprints(); + if ((latestEncryption == Message.ENCRYPTION_OTR) + && (conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints + .contains(conversation.getOtrFingerprint())))) { + fingerprintWarning.setVisibility(View.VISIBLE); + TextView fingerprint = (TextView) getView().findViewById( + R.id.otr_fingerprint); + fingerprint.setText(conversation.getOtrFingerprint()); + fingerprintWarning.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - AlertDialog dialog = UIHelper - .getVerifyFingerprintDialog( - (ConversationActivity) getActivity(), - conversation, fingerprintWarning); - dialog.show(); - } - }); - } else { - fingerprintWarning.setVisibility(View.GONE); - } + @Override + public void onClick(View v) { + AlertDialog dialog = UIHelper.getVerifyFingerprintDialog( + (ConversationActivity) getActivity(), conversation, + fingerprintWarning); + dialog.show(); + } + }); } else { fingerprintWarning.setVisibility(View.GONE); } @@ -675,35 +679,31 @@ public class ConversationFragment extends Fragment { final Contact contact = message.getConversation().getContact(); if (activity.hasPgp()) { if (contact.getPgpKeyId() != 0) { - xmppService.getPgpEngine().hasKey(contact, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi) { - activity.runIntent( - pi, - ConversationActivity.REQUEST_ENCRYPT_MESSAGE); - } + xmppService.getPgpEngine().hasKey(contact, new UiCallback() { - @Override - public void success() { - activity.encryptTextMessage(); - } + @Override + public void userInputRequried(PendingIntent pi) { + activity.runIntent(pi, + ConversationActivity.REQUEST_ENCRYPT_MESSAGE); + } - @Override - public void error(int error) { - - } - }); + @Override + public void success() { + activity.encryptTextMessage(); + } + + @Override + public void error(int error) { + + } + }); } else { showNoPGPKeyDialog(new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + public void onClick(DialogInterface dialog, int which) { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.sendMessage(message, null); chatMsg.setText(""); @@ -712,15 +712,15 @@ public class ConversationFragment extends Fragment { } } } - + public void showNoPGPKeyDialog(DialogInterface.OnClickListener listener) { - AlertDialog.Builder builder = new AlertDialog.Builder( - getActivity()); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getString(R.string.no_pgp_key)); builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setMessage(getText(R.string.contact_has_no_pgp_key)); builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.send_unencrypted),listener); + builder.setPositiveButton(getString(R.string.send_unencrypted), + listener); builder.create().show(); } @@ -766,7 +766,6 @@ public class ConversationFragment extends Fragment { private class BitmapCache { private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>(); - private Bitmap error = null; public Bitmap get(String name, Contact contact, Context context) { if (bitmaps.containsKey(name)) { diff --git a/src/eu/siacs/conversations/ui/EditAccount.java b/src/eu/siacs/conversations/ui/EditAccount.java index 8b1c0fa6..d1863f7e 100644 --- a/src/eu/siacs/conversations/ui/EditAccount.java +++ b/src/eu/siacs/conversations/ui/EditAccount.java @@ -51,27 +51,19 @@ public class EditAccount extends DialogFragment { final CheckBox registerAccount = (CheckBox) view .findViewById(R.id.edit_account_register_new); - final String okButtonDesc; - if (account != null) { jidText.setText(account.getJid()); password.setText(account.getPassword()); - Log.d("xmppService","mein debugger. account != null"); if (account.isOptionSet(Account.OPTION_REGISTER)) { registerAccount.setChecked(true); - builder.setTitle(getString(R.string.register_account)); - okButtonDesc = "Register"; passwordConfirm.setVisibility(View.VISIBLE); passwordConfirm.setText(account.getPassword()); } else { registerAccount.setVisibility(View.GONE); - builder.setTitle("Edit account"); - okButtonDesc = "Edit"; } - } else { - builder.setTitle("Add account"); - okButtonDesc = "Add"; } + builder.setTitle(R.string.account_settings); + registerAccount .setOnCheckedChangeListener(new OnCheckedChangeListener() { @@ -79,26 +71,19 @@ public class EditAccount extends DialogFragment { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - AlertDialog d = (AlertDialog) getDialog(); - Button positiveButton = (Button) d - .getButton(Dialog.BUTTON_POSITIVE); if (isChecked) { - d.setTitle(getString(R.string.register_account)); - positiveButton.setText("Register"); passwordConfirm.setVisibility(View.VISIBLE); confirmPwDesc.setVisibility(View.VISIBLE); } else { - d.setTitle("Add account"); passwordConfirm.setVisibility(View.GONE); - positiveButton.setText("Add"); confirmPwDesc.setVisibility(View.GONE); } } }); builder.setView(view); - builder.setNeutralButton("Cancel", null); - builder.setPositiveButton(okButtonDesc, null); + builder.setNeutralButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.save), null); return builder.create(); } @@ -114,7 +99,9 @@ public class EditAccount extends DialogFragment { String jid = jidEdit.getText().toString(); EditText passwordEdit = (EditText) d .findViewById(R.id.account_password); + EditText passwordConfirmEdit = (EditText) d.findViewById(R.id.account_password_confirm2); String password = passwordEdit.getText().toString(); + String passwordConfirm = passwordConfirmEdit.getText().toString(); CheckBox register = (CheckBox) d.findViewById(R.id.edit_account_register_new); String username; String server; @@ -123,9 +110,15 @@ public class EditAccount extends DialogFragment { username = parts[0]; server = parts[1]; } else { - jidEdit.setError("Invalid Jabber ID"); + jidEdit.setError(getString(R.string.invalid_jid)); return; } + if (register.isChecked()) { + if (!passwordConfirm.equals(password)) { + passwordConfirmEdit.setError(getString(R.string.passwords_do_not_match)); + return; + } + } if (account != null) { account.setPassword(password); account.setUsername(username); diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java index 0b7dac58..70af44e0 100644 --- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -73,7 +73,7 @@ public class ManageAccountActivity extends XmppActivity { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle("Untrusted Certificate"); + builder.setTitle(getString(R.string.account_status_error)); builder.setIconAttribute(android.R.attr.alertDialogIcon); View view = (View) getLayoutInflater().inflate(R.layout.cert_warning, null); TextView sha = (TextView) view.findViewById(R.id.sha); @@ -91,8 +91,8 @@ public class ManageAccountActivity extends XmppActivity { hint.setText(getString(R.string.untrusted_cert_hint,account.getServer())); sha.setText(humanReadableSha.toString()); builder.setView(view); - builder.setNegativeButton("Don't connect", null); - builder.setPositiveButton("Trust certificate", new OnClickListener() { + builder.setNegativeButton(getString(R.string.certif_no_trust), null); + builder.setPositiveButton(getString(R.string.certif_trust), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -130,55 +130,55 @@ public class ManageAccountActivity extends XmppActivity { .findViewById(R.id.account_status); switch (account.getStatus()) { case Account.STATUS_DISABLED: - statusView.setText("temporarily disabled"); + statusView.setText(getString(R.string.account_status_disabled)); statusView.setTextColor(0xFF1da9da); break; case Account.STATUS_ONLINE: - statusView.setText("online"); + statusView.setText(getString(R.string.account_status_online)); statusView.setTextColor(0xFF83b600); break; case Account.STATUS_CONNECTING: - statusView.setText("connecting\u2026"); + statusView.setText(getString(R.string.account_status_connecting)); statusView.setTextColor(0xFF1da9da); break; case Account.STATUS_OFFLINE: - statusView.setText("offline"); + statusView.setText(getString(R.string.account_status_offline)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_UNAUTHORIZED: - statusView.setText("unauthorized"); + statusView.setText(getString(R.string.account_status_unauthorized)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_SERVER_NOT_FOUND: - statusView.setText("server not found"); + statusView.setText(getString(R.string.account_status_not_found)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_NO_INTERNET: - statusView.setText("no internet"); + statusView.setText(getString(R.string.account_status_no_internet)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_SERVER_REQUIRES_TLS: - statusView.setText("server requires TLS"); + statusView.setText(getString(R.string.account_status_requires_tls)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_TLS_ERROR: - statusView.setText("untrusted cerficate"); + statusView.setText(getString(R.string.account_status_error)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_REGISTRATION_FAILED: - statusView.setText("registration failed"); + statusView.setText(getString(R.string.account_status_regis_fail)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_REGISTRATION_CONFLICT: - statusView.setText("username already in use"); + statusView.setText(getString(R.string.account_status_regis_conflict)); statusView.setTextColor(0xFFe92727); break; case Account.STATUS_REGISTRATION_SUCCESSFULL: - statusView.setText("registration completed"); + statusView.setText(getString(R.string.account_status_regis_success)); statusView.setTextColor(0xFF83b600); break; case Account.STATUS_REGISTRATION_NOT_SUPPORTED: - statusView.setText("server does not support registration"); + statusView.setText(getString(R.string.account_status_regis_not_sup)); statusView.setTextColor(0xFFe92727); break; default: @@ -261,10 +261,10 @@ public class ManageAccountActivity extends XmppActivity { mode.finish(); } else if (item.getItemId()==R.id.mgmt_account_delete) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle("Are you sure?"); + builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage("If you delete your account your entire conversation history will be lost"); - builder.setPositiveButton("Delete", new OnClickListener() { + builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text)); + builder.setPositiveButton(getString(R.string.delete), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -273,7 +273,7 @@ public class ManageAccountActivity extends XmppActivity { mode.finish(); } }); - builder.setNegativeButton("Cancel",null); + builder.setNegativeButton(getString(R.string.cancel),null); builder.create().show(); } else if (item.getItemId()==R.id.mgmt_account_announce_pgp) { if (activity.hasPgp()) { @@ -293,7 +293,7 @@ public class ManageAccountActivity extends XmppActivity { noFingerprintView.setVisibility(View.GONE); } builder.setView(view); - builder.setPositiveButton("Done", null); + builder.setPositiveButton(getString(R.string.done), null); builder.create().show(); } else if (item.getItemId() == R.id.mgmt_account_info) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); diff --git a/src/eu/siacs/conversations/ui/MucDetailsActivity.java b/src/eu/siacs/conversations/ui/MucDetailsActivity.java index c6807c61..d1725d41 100644 --- a/src/eu/siacs/conversations/ui/MucDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/MucDetailsActivity.java @@ -153,11 +153,11 @@ public class MucDetailsActivity extends XmppActivity { switch (self.getAffiliation()) { case User.AFFILIATION_ADMIN: mRoleAffiliaton.setText(getReadableRole(self.getRole()) - + " (Admin)"); + + " (" + getString(R.string.admin) + ")"); break; case User.AFFILIATION_OWNER: mRoleAffiliaton.setText(getReadableRole(self.getRole()) - + " (Owner)"); + + " (" + getString(R.string.owner) + ")"); break; default: mRoleAffiliaton diff --git a/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java b/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java deleted file mode 100644 index d69ce35b..00000000 --- a/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package eu.siacs.conversations.ui; - -import java.util.List; - -import eu.siacs.conversations.entities.Contact; - -public interface OnRosterFetchedListener { - public void onRosterFetched(List<Contact> roster); -} diff --git a/src/eu/siacs/conversations/ui/ShareWithActivity.java b/src/eu/siacs/conversations/ui/ShareWithActivity.java index 1bc9fc46..6dbb20c9 100644 --- a/src/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/eu/siacs/conversations/ui/ShareWithActivity.java @@ -17,7 +17,6 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.os.Bundle; import android.preference.PreferenceManager; -import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; @@ -29,15 +28,6 @@ public class ShareWithActivity extends XmppActivity { private LinearLayout conversations; private LinearLayout contacts; - private OnClickListener click = new OnClickListener() { - - @Override - public void onClick(View v) { - // TODO Auto-generated method stub - - } - }; - @Override protected void onCreate(Bundle savedInstanceState) { @@ -71,7 +61,7 @@ public class ShareWithActivity extends XmppActivity { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); boolean useSubject = preferences.getBoolean("use_subject_in_muc", true); - Set<String> displayedContacts = new HashSet<String>(); + Set<Contact> displayedContacts = new HashSet<Contact>(); conversations.removeAllViews(); List<Conversation> convList = xmppConnectionService.getConversations(); Collections.sort(convList, new Comparator<Conversation>() { @@ -90,20 +80,18 @@ public class ShareWithActivity extends XmppActivity { @Override public void onClick(View v) { String sharedText = getIntent().getStringExtra(Intent.EXTRA_TEXT); - switchToConversation(conversation, sharedText); + switchToConversation(conversation, sharedText,true); finish(); } }); conversations.addView(view); - if (conversation.getContact() != null) { - displayedContacts.add(conversation.getContact().getUuid()); - } + displayedContacts.add(conversation.getContact()); } contacts.removeAllViews(); - final List<Contact> contactsList = new ArrayList<Contact>(); + List<Contact> contactsList = new ArrayList<Contact>(); for(Account account : xmppConnectionService.getAccounts()) { - for(final Contact contact : xmppConnectionService.getRoster(account)) { - if (!displayedContacts.contains(contact.getUuid())) { + for(Contact contact : account.getRoster().getContacts()) { + if (!displayedContacts.contains(contact)&&(contact.showInRoster())) { contactsList.add(contact); } } @@ -126,7 +114,7 @@ public class ShareWithActivity extends XmppActivity { public void onClick(View v) { String sharedText = getIntent().getStringExtra(Intent.EXTRA_TEXT); Conversation conversation = xmppConnectionService.findOrCreateConversation(con.getAccount(), con.getJid(), false); - switchToConversation(conversation, sharedText); + switchToConversation(conversation, sharedText,true); finish(); } }); diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java index dc894ad5..7c8c46b2 100644 --- a/src/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -1,7 +1,5 @@ package eu.siacs.conversations.ui; -import java.nio.channels.AlreadyConnectedException; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; @@ -147,7 +145,7 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); } - public void switchToConversation(Conversation conversation, String text) { + public void switchToConversation(Conversation conversation, String text, boolean newTask) { Intent viewConversationIntent = new Intent(this, ConversationActivity.class); viewConversationIntent.setAction(Intent.ACTION_VIEW); @@ -157,8 +155,12 @@ public abstract class XmppActivity extends Activity { viewConversationIntent.putExtra(ConversationActivity.TEXT, text); } viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); - viewConversationIntent.setFlags(viewConversationIntent.getFlags() + if (newTask) { + viewConversationIntent.setFlags(viewConversationIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_TASK_ON_HOME ); + } else { + viewConversationIntent.setFlags(viewConversationIntent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP); + } startActivity(viewConversationIntent); } @@ -177,7 +179,7 @@ public abstract class XmppActivity extends Activity { @Override public void success() { xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPgpPresence(account, account.getPgpSignature()); + xmppConnectionService.sendPresence(account); if (conversation!=null) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); } diff --git a/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java b/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java index fa8cea04..9a689768 100644 --- a/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java +++ b/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java @@ -1,9 +1,9 @@ package eu.siacs.conversations.utils; -import java.util.Hashtable; +import java.util.List; import android.os.Bundle; public interface OnPhoneContactsLoadedListener { - public void onPhoneContactsLoaded(Hashtable<String, Bundle> phoneContacts); + public void onPhoneContactsLoaded(List<Bundle> phoneContacts); } diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java index 6355e378..773312bb 100644 --- a/src/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/eu/siacs/conversations/utils/PhoneHelper.java @@ -1,8 +1,8 @@ package eu.siacs.conversations.utils; -import java.util.Hashtable; +import java.util.ArrayList; +import java.util.List; -import android.app.Activity; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; @@ -10,21 +10,15 @@ import android.content.Loader.OnLoadCompleteListener; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Looper; import android.provider.ContactsContract; import android.provider.ContactsContract.Profile; -import android.provider.MediaStore; public class PhoneHelper { public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - final Looper mLooper = Looper.myLooper(); - final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>(); - + final List<Bundle> phoneContacts = new ArrayList<Bundle>(); + final String[] PROJECTION = new String[] { ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_THUMBNAIL_URI, @@ -58,15 +52,14 @@ public class PhoneHelper { .getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI))); contact.putString("lookup", cursor.getString(cursor .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - phoneContacts.put( - cursor.getString(cursor - .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)), - contact); + + contact.putString("jid",cursor.getString(cursor + .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + phoneContacts.add(contact); } if (listener != null) { listener.onPhoneContactsLoaded(phoneContacts); } - mLooper.quit(); } }); mCursorLoader.startLoading(); diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index 8baa3c25..e790d903 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -6,13 +6,14 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.regex.Pattern; +import java.util.regex.Matcher; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; @@ -27,7 +28,6 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -38,11 +38,9 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.provider.ContactsContract.Contacts; import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationCompat.InboxStyle; import android.support.v4.app.TaskStackBuilder; import android.text.Html; import android.util.DisplayMetrics; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; @@ -54,16 +52,16 @@ public class UIHelper { private static final int FG_COLOR = 0xFFE5E5E5; private static final int TRANSPARENT = 0x00000000; - public static String readableTimeDifference(long time) { + public static String readableTimeDifference(Context context, long time) { if (time == 0) { - return "just now"; + return context.getString(R.string.just_now); } Date date = new Date(time); long difference = (System.currentTimeMillis() - time) / 1000; if (difference < 60) { - return "just now"; + return context.getString(R.string.just_now); } else if (difference < 60 * 10) { - return difference / 60 + " min ago"; + return difference / 60 + " " + context.getString(R.string.minutes_ago); } else if (difference < 60 * 60 * 24) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm",Locale.US); return sdf.format(date); @@ -85,6 +83,24 @@ public class UIHelper { return color; } + private static void drawTile(Canvas canvas, String letter, int tileColor, int textColor, int left, int top, int right, int bottom) { + int size = canvas.getWidth(); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setColor(textColor); + textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + textPaint.setTextSize((float) ((right - left) * 0.8)); + Rect rect = new Rect(); + + canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); + textPaint.getTextBounds(letter, 0, 1, rect); + float width = textPaint.measureText(letter); + canvas.drawText(letter, + (right+left)/2 - width/2, + (top+bottom)/2 + rect.height()/2, + textPaint); + } + private static Bitmap getUnknownContactPicture(String[] names, int size, int bgColor, int fgColor) { int tiles = (names.length > 4)? 4 : (names.length < 1)? 1 : @@ -110,135 +126,46 @@ public class UIHelper { colors[3] = 0xFF444444; } } - Paint textPaint = new Paint(), tilePaint = new Paint(); - textPaint.setColor(fgColor); - textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); - Rect rect, left, right, topLeft, bottomLeft, topRight, bottomRight; - float width; + + bitmap.eraseColor(bgColor); switch(tiles) { case 1: - bitmap.eraseColor(colors[0]); - - textPaint.setTextSize((float) (size * 0.8)); - textPaint.setAntiAlias(true); - rect = new Rect(); - textPaint.getTextBounds(letters[0], 0, 1, rect); - width = textPaint.measureText(letters[0]); - canvas.drawText(letters[0], (size / 2) - (width / 2), (size / 2) - + (rect.height() / 2), textPaint); + drawTile(canvas, letters[0], colors[0], fgColor, + 0, 0, size, size); break; case 2: - bitmap.eraseColor(bgColor); - - tilePaint.setColor(colors[0]); - left = new Rect(0, 0, (size/2)-1, size); - canvas.drawRect(left, tilePaint); - - tilePaint.setColor(colors[1]); - right = new Rect((size/2)+1, 0, size, size); - canvas.drawRect(right, tilePaint); - - textPaint.setTextSize((float) (size * 0.8*0.5)); - textPaint.setAntiAlias(true); - rect = new Rect(); - textPaint.getTextBounds(letters[0], 0, 1, rect); - width = textPaint.measureText(letters[0]); - canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2) - + (rect.height() / 2), textPaint); - textPaint.getTextBounds(letters[1], 0, 1, rect); - width = textPaint.measureText(letters[1]); - canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 2) - + (rect.height() / 2), textPaint); + drawTile(canvas, letters[0], colors[0], fgColor, + 0, 0, size/2 - 1, size); + drawTile(canvas, letters[1], colors[1], fgColor, + size/2 + 1, 0, size, size); break; case 3: - bitmap.eraseColor(bgColor); - - tilePaint.setColor(colors[0]); - left = new Rect(0, 0, (size/2)-1, size); - canvas.drawRect(left, tilePaint); - - tilePaint.setColor(colors[1]); - topRight = new Rect((size/2)+1, 0, size, (size/2 - 1)); - canvas.drawRect(topRight, tilePaint); - - tilePaint.setColor(colors[2]); - bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size); - canvas.drawRect(bottomRight, tilePaint); - - textPaint.setTextSize((float) (size * 0.8*0.5)); - textPaint.setAntiAlias(true); - rect = new Rect(); - - textPaint.getTextBounds(letters[0], 0, 1, rect); - width = textPaint.measureText(letters[0]); - canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2) - + (rect.height() / 2), textPaint); - - textPaint.getTextBounds(letters[1], 0, 1, rect); - width = textPaint.measureText(letters[1]); - canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 4) - + (rect.height() / 2), textPaint); - - textPaint.getTextBounds(letters[2], 0, 1, rect); - width = textPaint.measureText(letters[2]); - canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (3* size / 4) - + (rect.height() / 2), textPaint); + drawTile(canvas, letters[0], colors[0], fgColor, + 0, 0, size/2 - 1, size); + drawTile(canvas, letters[1], colors[1], fgColor, + size/2 + 1, 0, size, size/2 - 1); + drawTile(canvas, letters[2], colors[2], fgColor, + size/2 + 1, size/2 + 1, size, size); break; case 4: - bitmap.eraseColor(bgColor); - - tilePaint.setColor(colors[0]); - topLeft = new Rect(0, 0, (size/2)-1, (size/2)-1); - canvas.drawRect(topLeft, tilePaint); - - tilePaint.setColor(colors[1]); - bottomLeft = new Rect(0, (size/2)+1, (size/2)-1, size); - canvas.drawRect(bottomLeft, tilePaint); - - tilePaint.setColor(colors[2]); - topRight = new Rect((size/2)+1, 0, size, (size/2 - 1)); - canvas.drawRect(topRight, tilePaint); - - tilePaint.setColor(colors[3]); - bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size); - canvas.drawRect(bottomRight, tilePaint); - - textPaint.setTextSize((float) (size * 0.8*0.5)); - textPaint.setAntiAlias(true); - rect = new Rect(); - - textPaint.getTextBounds(letters[0], 0, 1, rect); - width = textPaint.measureText(letters[0]); - canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 4) - + (rect.height() / 2), textPaint); - - textPaint.getTextBounds(letters[1], 0, 1, rect); - width = textPaint.measureText(letters[1]); - canvas.drawText(letters[1], (size / 4) - (width / 2), (3* size / 4) - + (rect.height() / 2), textPaint); - - textPaint.getTextBounds(letters[2], 0, 1, rect); - width = textPaint.measureText(letters[2]); - canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (size / 4) - + (rect.height() / 2), textPaint); - - textPaint.getTextBounds(letters[3], 0, 1, rect); - width = textPaint.measureText(letters[3]); - canvas.drawText(letters[3], (3 * size / 4) - (width / 2), (3* size / 4) - + (rect.height() / 2), textPaint); + drawTile(canvas, letters[0], colors[0], fgColor, + 0, 0, size/2 - 1, size/2 - 1); + drawTile(canvas, letters[0], colors[0], fgColor, + 0, size/2 + 1, size/2 - 1, size); + drawTile(canvas, letters[2], colors[2], fgColor, + size/2 + 1, 0, size, size/2 - 1); + drawTile(canvas, letters[3], colors[3], fgColor, + size/2 + 1, size/2 + 1, size, size); break; } - return bitmap; - } - private static Bitmap getUnknownContactPicture(String[] names, int size) { - return getUnknownContactPicture(names, size, UIHelper.BG_COLOR, UIHelper.FG_COLOR); + return bitmap; } - + private static Bitmap getMucContactPicture(Conversation conversation, int size, int bgColor, int fgColor) { List<User> members = conversation.getMucOptions().getUsers(); if (members.size() == 0) { @@ -255,13 +182,8 @@ public class UIHelper { public static Bitmap getContactPicture(Conversation conversation, int dpSize, Context context, boolean notification) { if(conversation.getMode() == Conversation.MODE_SINGLE) { - if (conversation.getContact() != null){ return getContactPicture(conversation.getContact(), dpSize, context, notification); - } else { - return getContactPicture(conversation.getName(false), dpSize, - context, notification); - } } else{ int fgColor = UIHelper.FG_COLOR, bgColor = (notification) ? @@ -273,10 +195,6 @@ public class UIHelper { } public static Bitmap getContactPicture(Contact contact, int dpSize, Context context, boolean notification) { - int fgColor = UIHelper.FG_COLOR, - bgColor = (notification) ? - UIHelper.BG_COLOR : UIHelper.TRANSPARENT; - String uri = contact.getProfilePhoto(); if (uri==null) { return getContactPicture(contact.getDisplayName(), dpSize, @@ -340,6 +258,15 @@ public class UIHelper { mNotificationManager.notify(1111, notification); } + private static Pattern generateNickHighlightPattern(String nick) { + // We expect a word boundary, i.e. space or start of string, followed by the + // nick (matched in case-insensitive manner), followed by optional + // punctuation (for example "bob: i disagree" or "how are you alice?"), + // followed by another word boundary. + return Pattern.compile("\\b"+nick+"\\p{Punct}?\\b", + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + } + public static void updateNotification(Context context, List<Conversation> conversations, Conversation currentCon, boolean notify) { NotificationManager mNotificationManager = (NotificationManager) context @@ -360,7 +287,9 @@ public class UIHelper { if ((currentCon != null) &&(currentCon.getMode() == Conversation.MODE_MULTI)&&(!alwaysNotify)) { String nick = currentCon.getMucOptions().getNick(); - notify = currentCon.getLatestMessage().getBody().contains(nick); + Pattern highlight = generateNickHighlightPattern(nick); + Matcher m = highlight.matcher(currentCon.getLatestMessage().getBody()); + notify = m.find(); } List<Conversation> unread = new ArrayList<Conversation>(); @@ -412,7 +341,7 @@ public class UIHelper { .bigText(bigText.toString())); } else { NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - style.setBigContentTitle(unread.size() + " unread Conversations"); + style.setBigContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations)); StringBuilder names = new StringBuilder(); for (int i = 0; i < unread.size(); ++i) { targetUuid = unread.get(i).getUuid(); @@ -424,7 +353,7 @@ public class UIHelper { style.addLine(Html.fromHtml("<b>" + unread.get(i).getName(useSubject) + "</b> " + unread.get(i).getLatestMessage().getReadableBody(context))); } - mBuilder.setContentTitle(unread.size() + " unread Conversations"); + mBuilder.setContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations)); mBuilder.setContentText(names.toString()); mBuilder.setStyle(style); } @@ -470,11 +399,13 @@ public class UIHelper { private static boolean wasHighlighted(Conversation conversation) { List<Message> messages = conversation.getMessages(); String nick = conversation.getMucOptions().getNick(); + Pattern highlight = generateNickHighlightPattern(nick); for(int i = messages.size() - 1; i >= 0; --i) { if (messages.get(i).isRead()) { break; } else { - if (messages.get(i).getBody().contains(nick)) { + Matcher m = highlight.matcher(messages.get(i).getBody()); + if (m.find()) { return true; } } @@ -518,7 +449,7 @@ public class UIHelper { public void onClick(DialogInterface dialog, int which) { contact.addOtrFingerprint(conversation.getOtrFingerprint()); msg.setVisibility(View.GONE); - activity.xmppConnectionService.updateContact(contact); + //activity.xmppConnectionService.updateContact(contact); } }); builder.setView(view); diff --git a/src/eu/siacs/conversations/utils/Validator.java b/src/eu/siacs/conversations/utils/Validator.java index fce953ae..51d8b25c 100644 --- a/src/eu/siacs/conversations/utils/Validator.java +++ b/src/eu/siacs/conversations/utils/Validator.java @@ -5,7 +5,7 @@ import java.util.regex.Pattern; public class Validator { public static final Pattern VALID_JID = - Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); + Pattern.compile("\\b^[A-Z0-9._%+-]+@([A-Z0-9.-]+\\.)?\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}\\b$|^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); public static boolean isValidJid(String jid) { Matcher matcher = VALID_JID.matcher(jid); diff --git a/src/eu/siacs/conversations/xml/XmlReader.java b/src/eu/siacs/conversations/xml/XmlReader.java index 0a82a5d8..b4b1647e 100644 --- a/src/eu/siacs/conversations/xml/XmlReader.java +++ b/src/eu/siacs/conversations/xml/XmlReader.java @@ -85,10 +85,8 @@ public class XmlReader { public Element readElement(Tag currentTag) throws XmlPullParserException, IOException { Element element = new Element(currentTag.getName()); - //Log.d(LOGTAG,"trying to read element "+element.getName()); element.setAttributes(currentTag.getAttributes()); Tag nextTag = this.readTag(); - //Log.d(LOGTAG,"next Tag is: "+nextTag.toString()); if(nextTag.isNo()) { element.setContent(nextTag.getName()); nextTag = this.readTag(); @@ -96,15 +94,16 @@ public class XmlReader { if (nextTag == null) { throw new IOException("unterupted mid tag"); } - //Log.d(LOGTAG,"reading till the end of "+element.getName()); while(!nextTag.isEnd(element.getName())) { if (!nextTag.isNo()) { Element child = this.readElement(nextTag); element.addChild(child); } nextTag = this.readTag(); + if (nextTag == null) { + throw new IOException("unterupted mid tag"); + } } - //Log.d(LOGTAG,"return with element"+element); return element; } } diff --git a/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java b/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java new file mode 100644 index 00000000..8597a753 --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.xmpp; + +import eu.siacs.conversations.entities.Contact; + +public interface OnContactStatusChanged { + public void onContactStatusChanged(Contact contact); +} diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index adb27ec8..a1aaf66a 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -21,7 +21,6 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.SSLContext; @@ -31,7 +30,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import org.json.JSONException; import org.xmlpull.v1.XmlPullParserException; import android.os.Bundle; @@ -76,20 +74,20 @@ public class XmppConnection implements Runnable { private boolean shouldAuthenticate = true; private Element streamFeatures; private HashMap<String, List<String>> disco = new HashMap<String, List<String>>(); - - private HashSet<String> pendingSubscriptions = new HashSet<String>(); - + private String streamId = null; private int smVersion = 3; - + private int stanzasReceived = 0; private int stanzasSent = 0; - + public long lastPaketReceived = 0; public long lastPingSent = 0; public long lastConnect = 0; public long lastSessionStarted = 0; + private int attempt = 0; + private static final int PACKET_IQ = 0; private static final int PACKET_MESSAGE = 1; private static final int PACKET_PRESENCE = 2; @@ -105,15 +103,22 @@ public class XmppConnection implements Runnable { public XmppConnection(Account account, PowerManager pm) { this.account = account; - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,account.getJid()); + this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + account.getJid()); tagWriter = new TagWriter(); } protected void changeStatus(int nextStatus) { if (account.getStatus() != nextStatus) { - if ((nextStatus == Account.STATUS_OFFLINE)&&(account.getStatus() != Account.STATUS_CONNECTING)&&(account.getStatus() != Account.STATUS_ONLINE)) { + if ((nextStatus == Account.STATUS_OFFLINE) + && (account.getStatus() != Account.STATUS_CONNECTING) + && (account.getStatus() != Account.STATUS_ONLINE) + && (account.getStatus() != Account.STATUS_DISABLED)) { return; } + if (nextStatus == Account.STATUS_ONLINE) { + this.attempt = 0; + } account.setStatus(nextStatus); if (statusListener != null) { statusListener.onStatusChanged(account); @@ -122,17 +127,19 @@ public class XmppConnection implements Runnable { } protected void connect() { - Log.d(LOGTAG,account.getJid()+ ": connecting"); + Log.d(LOGTAG, account.getJid() + ": connecting"); lastConnect = SystemClock.elapsedRealtime(); + this.attempt++; try { - shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER); + shouldAuthenticate = shouldBind = !account + .isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); packetCallbacks.clear(); this.changeStatus(Account.STATUS_CONNECTING); Bundle namePort = DNSHelper.getSRVRecord(account.getServer()); if ("timeout".equals(namePort.getString("error"))) { - Log.d(LOGTAG,account.getJid()+": dns timeout"); + Log.d(LOGTAG, account.getJid() + ": dns timeout"); this.changeStatus(Account.STATUS_OFFLINE); return; } @@ -142,12 +149,12 @@ public class XmppConnection implements Runnable { if (srvRecordServer != null) { if (srvIpServer != null) { Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + "[" + srvIpServer + "]:" - + srvRecordPort); + + srvRecordServer + "[" + srvIpServer + "]:" + + srvRecordPort); socket = new Socket(srvIpServer, srvRecordPort); } else { Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort); + + srvRecordServer + ":" + srvRecordPort); socket = new Socket(srvRecordServer, srvRecordPort); } } else { @@ -227,8 +234,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("compressed")) { switchOverToZLib(nextTag); } else if (nextTag.isStart("success")) { - Log.d(LOGTAG, account.getJid() - + ": logged in"); + Log.d(LOGTAG, account.getJid() + ": logged in"); tagReader.readTag(); tagReader.reset(); sendStartStream(); @@ -240,43 +246,48 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("challenge")) { String challange = tagReader.readElement(nextTag).getContent(); Element response = new Element("response"); - response.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); - response.setContent(CryptoHelper.saslDigestMd5(account, challange)); + response.setAttribute("xmlns", + "urn:ietf:params:xml:ns:xmpp-sasl"); + response.setContent(CryptoHelper.saslDigestMd5(account, + challange)); tagWriter.writeElement(response); } else if (nextTag.isStart("enabled")) { this.stanzasSent = 0; Element enabled = tagReader.readElement(nextTag); if ("true".equals(enabled.getAttribute("resume"))) { this.streamId = enabled.getAttribute("id"); - Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled (resumable)"); + Log.d(LOGTAG, account.getJid() + ": stream managment(" + + smVersion + ") enabled (resumable)"); } else { - Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled"); + Log.d(LOGTAG, account.getJid() + ": stream managment(" + + smVersion + ") enabled"); } this.lastSessionStarted = SystemClock.elapsedRealtime(); this.stanzasReceived = 0; RequestPacket r = new RequestPacket(smVersion); tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { + lastPaketReceived = SystemClock.elapsedRealtime(); + Log.d(LOGTAG, account.getJid() + ": session resumed"); tagReader.readElement(nextTag); sendPing(); changeStatus(Account.STATUS_ONLINE); - Log.d(LOGTAG,account.getJid()+": session resumed"); } else if (nextTag.isStart("r")) { tagReader.readElement(nextTag); - AckPacket ack = new AckPacket(this.stanzasReceived,smVersion); - //Log.d(LOGTAG,ack.toString()); + AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + // Log.d(LOGTAG,ack.toString()); tagWriter.writeStanzaAsync(ack); } else if (nextTag.isStart("a")) { Element ack = tagReader.readElement(nextTag); lastPaketReceived = SystemClock.elapsedRealtime(); int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (serverSequence>this.stanzasSent) { + if (serverSequence > this.stanzasSent) { this.stanzasSent = serverSequence; } - //Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")"); + // Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")"); } else if (nextTag.isStart("failed")) { tagReader.readElement(nextTag); - Log.d(LOGTAG,account.getJid()+": resumption failed"); + Log.d(LOGTAG, account.getJid() + ": resumption failed"); streamId = null; if (account.getStatus() != Account.STATUS_ONLINE) { sendBindRequest(); @@ -319,16 +330,23 @@ public class XmppConnection implements Runnable { } element.setAttributes(currentTag.getAttributes()); Tag nextTag = tagReader.readTag(); + if (nextTag==null) { + throw new IOException("interrupted mid tag"); + } while (!nextTag.isEnd(element.getName())) { if (!nextTag.isNo()) { Element child = tagReader.readElement(nextTag); - if ((packetType == PACKET_IQ)&&("jingle".equals(child.getName()))) { + if ((packetType == PACKET_IQ) + && ("jingle".equals(child.getName()))) { element = new JinglePacket(); element.setAttributes(currentTag.getAttributes()); } element.addChild(child); } nextTag = tagReader.readTag(); + if (nextTag==null) { + throw new IOException("interrupted mid tag"); + } } ++stanzasReceived; lastPaketReceived = SystemClock.elapsedRealtime(); @@ -338,14 +356,15 @@ public class XmppConnection implements Runnable { private void processIq(Tag currentTag) throws XmlPullParserException, IOException { IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - + if (packet.getId() == null) { - return; //an iq packet without id is definitely invalid + return; // an iq packet without id is definitely invalid } - + if (packet instanceof JinglePacket) { - if (this.jingleListener !=null) { - this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet); + if (this.jingleListener != null) { + this.jingleListener.onJinglePacketReceived(account, + (JinglePacket) packet); } } else { if (packetCallbacks.containsKey(packet.getId())) { @@ -353,7 +372,7 @@ public class XmppConnection implements Runnable { ((OnIqPacketReceived) packetCallbacks.get(packet.getId())) .onIqPacketReceived(account, packet); } - + packetCallbacks.remove(packet.getId()); } else if (this.unregisteredIqListener != null) { this.unregisteredIqListener.onIqPacketReceived(account, packet); @@ -400,15 +419,18 @@ public class XmppConnection implements Runnable { tagWriter.writeElement(compress); } - private void switchOverToZLib(Tag currentTag) throws XmlPullParserException, - IOException, NoSuchAlgorithmException { + private void switchOverToZLib(Tag currentTag) + throws XmlPullParserException, IOException, + NoSuchAlgorithmException { tagReader.readTag(); // read tag close - tagWriter.setOutputStream(new ZLibOutputStream(tagWriter.getOutputStream())); - tagReader.setInputStream(new ZLibInputStream(tagReader.getInputStream())); + tagWriter.setOutputStream(new ZLibOutputStream(tagWriter + .getOutputStream())); + tagReader + .setInputStream(new ZLibInputStream(tagReader.getInputStream())); sendStartStream(); - Log.d(LOGTAG,account.getJid()+": compression enabled"); + Log.d(LOGTAG, account.getJid() + ": compression enabled"); processStream(tagReader.readTag()); } @@ -454,13 +476,15 @@ public class XmppConnection implements Runnable { if (e.getCause() instanceof CertPathValidatorException) { String sha; try { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + MessageDigest sha1 = MessageDigest + .getInstance("SHA1"); sha1.update(chain[0].getEncoded()); sha = CryptoHelper.bytesToHex(sha1.digest()); if (!sha.equals(account.getSSLFingerprint())) { changeStatus(Account.STATUS_TLS_ERROR); - if (tlsListener!=null) { - tlsListener.onTLSExceptionReceived(sha,account); + if (tlsListener != null) { + tlsListener.onTLSExceptionReceived(sha, + account); } throw new CertificateException(); } @@ -483,12 +507,12 @@ public class XmppConnection implements Runnable { sc.init(null, wrappedTrustManagers, null); SSLSocketFactory factory = sc.getSocketFactory(); SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, - socket.getInetAddress().getHostAddress(), socket.getPort(), - true); + socket.getInetAddress().getHostAddress(), socket.getPort(), + true); tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); - Log.d(LOGTAG,account.getJid()+": TLS connection established"); + Log.d(LOGTAG, account.getJid() + ": TLS connection established"); processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { @@ -509,7 +533,7 @@ public class XmppConnection implements Runnable { auth.setContent(saslString); tagWriter.writeElement(auth); } - + private void sendSaslAuthDigestMd5() throws IOException { Element auth = new Element("auth"); auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); @@ -525,41 +549,47 @@ public class XmppConnection implements Runnable { sendStartTLS(); } else if (compressionAvailable()) { sendCompressionZlib(); - } else if (this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) { - sendRegistryRequest(); - } else if (!this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) { + } else if (this.streamFeatures.hasChild("register") + && (account.isOptionSet(Account.OPTION_REGISTER))) { + sendRegistryRequest(); + } else if (!this.streamFeatures.hasChild("register") + && (account.isOptionSet(Account.OPTION_REGISTER))) { changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") && shouldAuthenticate) { - List<String> mechanisms = extractMechanisms( streamFeatures.findChild("mechanisms")); + List<String> mechanisms = extractMechanisms(streamFeatures + .findChild("mechanisms")); if (mechanisms.contains("PLAIN")) { sendSaslAuthPlain(); } else if (mechanisms.contains("DIGEST-MD5")) { sendSaslAuthDigestMd5(); } - } else if (this.streamFeatures.hasChild("sm","urn:xmpp:sm:"+smVersion) && streamId != null) { - ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived,smVersion); + } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + + smVersion) + && streamId != null) { + ResumePacket resume = new ResumePacket(this.streamId, + stanzasReceived, smVersion); this.tagWriter.writeStanzaAsync(resume); } else if (this.streamFeatures.hasChild("bind") && shouldBind) { sendBindRequest(); - if (this.streamFeatures.hasChild("session")) { - Log.d(LOGTAG,account.getJid()+": sending deprecated session"); - IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); - startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); //setContent("") - this.sendIqPacket(startSession, null); - } } } private boolean compressionAvailable() { - if (!this.streamFeatures.hasChild("compression", "http://jabber.org/features/compress")) return false; - if (!ZLibOutputStream.SUPPORTED) return false; - if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) return false; + if (!this.streamFeatures.hasChild("compression", + "http://jabber.org/features/compress")) + return false; + if (!ZLibOutputStream.SUPPORTED) + return false; + if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) + return false; - Element compression = this.streamFeatures.findChild("compression", "http://jabber.org/features/compress"); + Element compression = this.streamFeatures.findChild("compression", + "http://jabber.org/features/compress"); for (Element child : compression.getChildren()) { - if (!"method".equals(child.getName())) continue; + if (!"method".equals(child.getName())) + continue; if ("zlib".equalsIgnoreCase(child.getContent())) { return true; @@ -569,8 +599,9 @@ public class XmppConnection implements Runnable { } private List<String> extractMechanisms(Element stream) { - ArrayList<String> mechanisms = new ArrayList<String>(stream.getChildren().size()); - for(Element child : stream.getChildren()) { + ArrayList<String> mechanisms = new ArrayList<String>(stream + .getChildren().size()); + for (Element child : stream.getChildren()) { mechanisms.add(child.getContent()); } return mechanisms; @@ -581,28 +612,35 @@ public class XmppConnection implements Runnable { register.query("jabber:iq:register"); register.setTo(account.getServer()); sendIqPacket(register, new OnIqPacketReceived() { - + @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element instructions = packet.query().findChild("instructions"); - if (packet.query().hasChild("username")&&(packet.query().hasChild("password"))) { + if (packet.query().hasChild("username") + && (packet.query().hasChild("password"))) { IqPacket register = new IqPacket(IqPacket.TYPE_SET); - Element username = new Element("username").setContent(account.getUsername()); - Element password = new Element("password").setContent(account.getPassword()); + Element username = new Element("username") + .setContent(account.getUsername()); + Element password = new Element("password") + .setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); sendIqPacket(register, new OnIqPacketReceived() { - + @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType()==IqPacket.TYPE_RESULT) { - account.setOption(Account.OPTION_REGISTER, false); + public void onIqPacketReceived(Account account, + IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.setOption(Account.OPTION_REGISTER, + false); changeStatus(Account.STATUS_REGISTRATION_SUCCESSFULL); - } else if (packet.hasChild("error")&&(packet.findChild("error").hasChild("conflict"))){ + } else if (packet.hasChild("error") + && (packet.findChild("error") + .hasChild("conflict"))) { changeStatus(Account.STATUS_REGISTRATION_CONFLICT); } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); - Log.d(LOGTAG,packet.toString()); + Log.d(LOGTAG, packet.toString()); } disconnect(true); } @@ -610,54 +648,49 @@ public class XmppConnection implements Runnable { } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); disconnect(true); - Log.d(LOGTAG,account.getJid()+": could not register. instructions are"+instructions.getContent()); + Log.d(LOGTAG, account.getJid() + + ": could not register. instructions are" + + instructions.getContent()); } } }); } - private void sendInitialPresence() { - PresencePacket packet = new PresencePacket(); - packet.setAttribute("from", account.getFullJid()); - if (account.getKeys().has("pgp_signature")) { - try { - String signature = account.getKeys().getString("pgp_signature"); - packet.addChild("status").setContent("online"); - packet.addChild("x","jabber:x:signed").setContent(signature); - } catch (JSONException e) { - // - } - } - this.sendPresencePacket(packet); - } - private void sendBindRequest() throws IOException { IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind").addChild("resource").setContent(account.getResource()); - this.sendIqPacket(iq, new OnIqPacketReceived() { + iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") + .addChild("resource").setContent(account.getResource()); + this.sendUnboundIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { String resource = packet.findChild("bind").findChild("jid") .getContent().split("/")[1]; account.setResource(resource); - if (streamFeatures.hasChild("sm","urn:xmpp:sm:3")) { + if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { smVersion = 3; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); - } else if (streamFeatures.hasChild("sm","urn:xmpp:sm:2")) { + } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { smVersion = 2; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); } - sendInitialPresence(); sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryItems(account.getServer()); - if (bindListener !=null) { + if (bindListener != null) { bindListener.onBind(account); } + changeStatus(Account.STATUS_ONLINE); } }); + if (this.streamFeatures.hasChild("session")) { + Log.d(LOGTAG, account.getJid() + ": sending deprecated session"); + IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); + startSession.addChild("session", + "urn:ietf:params:xml:ns:xmpp-session"); + this.sendUnboundIqPacket(startSession, null); + } } private void sendServiceDiscoveryInfo(final String server) { @@ -672,25 +705,24 @@ public class XmppConnection implements Runnable { List<String> features = new ArrayList<String>(); for (int i = 0; i < elements.size(); ++i) { if (elements.get(i).getName().equals("feature")) { - features.add(elements.get(i).getAttribute( - "var")); + features.add(elements.get(i).getAttribute("var")); } } disco.put(server, features); - + if (account.getServer().equals(server)) { enableAdvancedStreamFeatures(); } } }); } - + private void enableAdvancedStreamFeatures() { if (hasFeaturesCarbon()) { sendEnableCarbons(); } } - + private void sendServiceDiscoveryItems(final String server) { IqPacket iq = new IqPacket(IqPacket.TYPE_GET); iq.setTo(server); @@ -702,8 +734,7 @@ public class XmppConnection implements Runnable { List<Element> elements = packet.query().getChildren(); for (int i = 0; i < elements.size(); ++i) { if (elements.get(i).getName().equals("item")) { - String jid = elements.get(i).getAttribute( - "jid"); + String jid = elements.get(i).getAttribute("jid"); sendServiceDiscoveryInfo(jid); } } @@ -713,7 +744,7 @@ public class XmppConnection implements Runnable { private void sendEnableCarbons() { IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - iq.addChild("enable","urn:xmpp:carbons:2"); + iq.addChild("enable", "urn:xmpp:carbons:2"); this.sendIqPacket(iq, new OnIqPacketReceived() { @Override @@ -749,7 +780,7 @@ public class XmppConnection implements Runnable { } public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) { - if (packet.getId()==null) { + if (packet.getId() == null) { String id = nextRandomId(); packet.setAttribute("id", id); } @@ -757,6 +788,14 @@ public class XmppConnection implements Runnable { this.sendPacket(packet, callback); } + public void sendUnboundIqPacket(IqPacket packet, OnIqPacketReceived callback) { + if (packet.getId() == null) { + String id = nextRandomId(); + packet.setAttribute("id", id); + } + this.sendPacket(packet, callback); + } + public void sendMessagePacket(MessagePacket packet) { this.sendPacket(packet, null); } @@ -774,26 +813,27 @@ public class XmppConnection implements Runnable { OnPresencePacketReceived callback) { this.sendPacket(packet, callback); } - - private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) { + + private synchronized void sendPacket(final AbstractStanza packet, + PacketReceived callback) { // TODO dont increment stanza count if packet = request packet or ack; ++stanzasSent; tagWriter.writeStanzaAsync(packet); if (callback != null) { - if (packet.getId()==null) { + if (packet.getId() == null) { packet.setId(nextRandomId()); } packetCallbacks.put(packet.getId(), callback); } } - + public void sendPing() { if (streamFeatures.hasChild("sm")) { tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); } else { IqPacket iq = new IqPacket(IqPacket.TYPE_GET); iq.setFrom(account.getFullJid()); - iq.addChild("ping","urn:xmpp:ping"); + iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, null); } } @@ -812,82 +852,95 @@ public class XmppConnection implements Runnable { OnPresencePacketReceived listener) { this.presenceListener = listener; } - - public void setOnJinglePacketReceivedListener(OnJinglePacketReceived listener) { + + public void setOnJinglePacketReceivedListener( + OnJinglePacketReceived listener) { this.jingleListener = listener; } public void setOnStatusChangedListener(OnStatusChanged listener) { this.statusListener = listener; } - - public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) { + + public void setOnTLSExceptionReceivedListener( + OnTLSExceptionReceived listener) { this.tlsListener = listener; } - + public void setOnBindListener(OnBindListener listener) { this.bindListener = listener; } public void disconnect(boolean force) { changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG,"disconnecting"); + Log.d(LOGTAG, "disconnecting"); try { - if (force) { + if (force) { socket.close(); return; - } - if (tagWriter.isActive()) { - tagWriter.finish(); - while(!tagWriter.finished()) { - //Log.d(LOGTAG,"not yet finished"); - Thread.sleep(100); } - tagWriter.writeTag(Tag.end("stream:stream")); - } + new Thread(new Runnable() { + + @Override + public void run() { + if (tagWriter.isActive()) { + tagWriter.finish(); + try { + while (!tagWriter.finished()) { + Log.d(LOGTAG, "not yet finished"); + Thread.sleep(100); + } + tagWriter.writeTag(Tag.end("stream:stream")); + } catch (IOException e) { + Log.d(LOGTAG, "io exception during disconnect"); + } catch (InterruptedException e) { + Log.d(LOGTAG, "interrupted"); + } + } + } + }); } catch (IOException e) { - Log.d(LOGTAG,"io exception during disconnect"); - } catch (InterruptedException e) { - Log.d(LOGTAG,"interupted while waiting for disconnect"); + Log.d(LOGTAG, "io exception during disconnect"); } } - + public boolean hasFeatureRosterManagment() { - if (this.streamFeatures==null) { + if (this.streamFeatures == null) { return false; } else { return this.streamFeatures.hasChild("ver"); } } - + public boolean hasFeatureStreamManagment() { - if (this.streamFeatures==null) { + if (this.streamFeatures == null) { return false; } else { return this.streamFeatures.hasChild("sm"); } } - + public boolean hasFeaturesCarbon() { return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); } - + public boolean hasDiscoFeature(String server, String feature) { if (!disco.containsKey(server)) { return false; } return disco.get(server).contains(feature); } - + public String findDiscoItemByFeature(String feature) { - Iterator<Entry<String, List<String>>> it = this.disco.entrySet().iterator(); - while (it.hasNext()) { - Entry<String, List<String>> pairs = it.next(); - if (pairs.getValue().contains(feature)) { - return pairs.getKey(); - } - it.remove(); - } + Iterator<Entry<String, List<String>>> it = this.disco.entrySet() + .iterator(); + while (it.hasNext()) { + Entry<String, List<String>> pairs = it.next(); + if (pairs.getValue().contains(feature)) { + return pairs.getKey(); + } + it.remove(); + } return null; } @@ -898,7 +951,7 @@ public class XmppConnection implements Runnable { public int getReceivedStanzas() { return this.stanzasReceived; } - + public int getSentStanzas() { return this.stanzasSent; } @@ -906,13 +959,14 @@ public class XmppConnection implements Runnable { public String getMucServer() { return findDiscoItemByFeature("http://jabber.org/protocol/muc"); } - - public boolean hasPendingSubscription(String jid) { - return this.pendingSubscriptions.contains(jid); + + public int getTimeToNextAttempt() { + int interval = (int) (25 * Math.pow(1.5, attempt)); + int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); + return interval - secondsSinceLast; } - - public void addPendingSubscription(String jid) { - Log.d(LOGTAG,"adding "+jid+" to pending subscriptions"); - this.pendingSubscriptions.add(jid); + + public int getAttempt() { + return this.attempt; } } |