diff options
67 files changed, 3789 insertions, 1458 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 44bfd18d..a10a9cc5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,13 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.siacs.conversations" - android:versionCode="20" - android:versionName="0.5-beta" > + android:versionCode="24" + android:versionName="0.6-alpha" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.INTERNET" /> @@ -52,8 +54,7 @@ android:name="eu.siacs.conversations.ui.StartConversationActivity" android:configChanges="orientation|screenSize" android:label="@string/title_activity_start_conversation" - android:logo="@drawable/ic_activity" - android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity" > + android:logo="@drawable/ic_activity" > <intent-filter> <action android:name="android.intent.action.SENDTO" /> @@ -65,17 +66,20 @@ </activity> <activity android:name="eu.siacs.conversations.ui.SettingsActivity" - android:label="@string/title_activity_settings" - android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity" > + android:label="@string/title_activity_settings" > </activity> - <activity android:name="eu.siacs.conversations.ui.ChooseContactActivity" - android:label="@string/title_activity_choose_contact"> + <activity + android:name="eu.siacs.conversations.ui.ChooseContactActivity" + android:label="@string/title_activity_choose_contact" > </activity> <activity android:name="eu.siacs.conversations.ui.ManageAccountActivity" android:configChanges="orientation|screenSize" - android:label="@string/title_activity_manage_accounts" - android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity" > + android:label="@string/title_activity_manage_accounts" > + </activity> + <activity + android:name="eu.siacs.conversations.ui.EditAccountActivity" + android:windowSoftInputMode="stateHidden|adjustResize" > </activity> <activity android:name="eu.siacs.conversations.ui.ConferenceDetailsActivity" @@ -88,6 +92,11 @@ android:windowSoftInputMode="stateHidden" > </activity> <activity + android:name="eu.siacs.conversations.ui.PublishProfilePictureActivity" + android:label="@string/publish_avatar" + android:windowSoftInputMode="stateHidden" > + </activity> + <activity android:name="eu.siacs.conversations.ui.ShareWithActivity" android:label="@string/title_activity_conversations" > <intent-filter> @@ -108,4 +117,4 @@ <activity android:name="de.duenndns.ssl.MemorizingActivity" /> </application> -</manifest> +</manifest>
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 433b580f..37e3c920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ###Changelog +####Version 0.5.2 +* minor bug fixes + +####Version 0.5.1 +* couple of small bug fixes that have been missed in 0.5 +* complete translations for Swedish, Dutch, German, Spanish, French, Russian + ####Version 0.5 * UI overhaul * MUC / Conference bookmarks @@ -1,6 +1,6 @@ #Conversations -Conversations is an open source XMPP (formerly known as Jabber) client for -Android 4.0+ smart phones. +Conversations - the very last word in instant messaging + [![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations) ![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png) @@ -8,18 +8,20 @@ Android 4.0+ smart phones. ##Design principles * Be as beautiful and easy to use as possible without sacrificing security or privacy -* Rely on existing, well established protocols +* Rely on existing, well established protocols (XMPP) * Do not require a Google Account or specifically Google Cloud Messaging (GCM) * Require as little permissons as possible ##Features * End-to-end encryption with either OTR or openPGP * Sending and receiving images -* Holo UI -* Syncs with your desktop client -* Group Chats +* Intuitive UI that follows Android Design guidelines +* Syncs with desktop client +* Conferences (with support for bookmarks) * Address book integration * Multiple Accounts / unified inbox +* Very low impact on battery life + ###XMPP Features Conversations works with every XMPP server out there. However XMPP is an extensible @@ -56,6 +58,7 @@ These XEPs are - as of now: * [Aitor Beriain](https://github.com/beriain) (Basque) * [Ilia Rostovtsev](https://github.com/rostovtsev) (Russian) * [Jelmer Vernooij](https://github.com/jelmer) (Dutch) +* [Anders Sandblad](https://github.com/andersruneson) (Swedish) ##FAQ ###General @@ -190,9 +193,21 @@ git submodule update --init --recursive ant clean ant debug ``` +####How do I debug Conversations +If something goes wrong Conversations usually exposes very little information in +the UI. (Other than the fact that something didn't work) +However with adb (android debug bridge) you squeeze some more information out of +Conversations. These information are especially useful if you are experiencing +troubles with your connection or with file transfer. +```` +adb -d logcat -v time -s xmppService +```` ####I found a bug Please report it to our issue tracker. If your app crashes please provide a stack trace. If you are experiencing missbehaviour please provide detailed steps to reproduce. Always mention whether you are running the latest Play Store version or the current HEAD. +If you are having problems connecting to your XMPP server your file tranfer +doesn’t work as expected please always include a logcat debug output with your +issue. (See above) diff --git a/res/layout/account_row.xml b/res/layout/account_row.xml index 0c18d9b2..5494e436 100644 --- a/res/layout/account_row.xml +++ b/res/layout/account_row.xml @@ -2,55 +2,42 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="8dp" - android:background="?android:attr/activatedBackgroundIndicator"> + android:background="?android:attr/activatedBackgroundIndicator" + android:padding="8dp" > - + <ImageView + android:id="@+id/account_image" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentLeft="true" + android:src="@drawable/ic_profile" > + </ImageView> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:orientation="vertical"> + android:layout_toRightOf="@+id/account_image" + android:layout_centerVertical="true" + android:orientation="vertical" + android:paddingLeft="8dp" > <TextView android:id="@+id/account_jid" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="18sp" - android:textColor="@color/primarytext" + android:scrollHorizontally="false" android:singleLine="true" - android:scrollHorizontally="false"/> + android:textColor="@color/primarytext" + android:textSize="18sp" /> - <LinearLayout - android:layout_width="match_parent" + <TextView + android:id="@+id/account_status" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingTop="3dp"> - - <TextView - android:id="@+id/textView2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/account_status" - android:textStyle="bold" - android:textSize="14sp" - android:textColor="@color/primarytext"/> - - <TextView - android:id="@+id/account_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="4dp" - android:text="@string/account_status_unknown" - android:textStyle="bold" - android:textSize="14sp"/> - - </LinearLayout> - + android:text="@string/account_status_unknown" + android:textSize="14sp" + android:textColor="@color/secondarytext" + android:textStyle="bold"/> </LinearLayout> - -</RelativeLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/activity_contact_details.xml b/res/layout/activity_contact_details.xml index 8f0b42c1..a00b2340 100644 --- a/res/layout/activity_contact_details.xml +++ b/res/layout/activity_contact_details.xml @@ -27,7 +27,7 @@ android:layout_width="72dp" android:layout_height="72dp" android:layout_centerVertical="true" - android:scaleType="fitXY"/> + android:scaleType="centerCrop"/> <LinearLayout android:id="@+id/details_jidbox" diff --git a/res/layout/activity_edit_account.xml b/res/layout/activity_edit_account.xml new file mode 100644 index 00000000..620a1f70 --- /dev/null +++ b/res/layout/activity_edit_account.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primarybackground" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="8dp" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_jabber_id" + android:textSize="14sp" + android:textColor="@color/primarytext"/> + + <AutoCompleteTextView + android:id="@+id/account_jid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/account_settings_example_jabber_id" + android:inputType="textEmailAddress"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/account_settings_password" + android:textSize="14sp" + android:textColor="@color/primarytext"/> + + <EditText + android:id="@+id/account_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password" + android:inputType="textPassword" /> + + <CheckBox + android:id="@+id/account_register_new" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/register_account" + android:textSize="14sp" + android:textColor="@color/primarytext"/> + + <TextView + android:id="@+id/account_confirm_password_desc" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_confirm_password" + android:textSize="14sp" + android:textColor="@color/primarytext" + android:visibility="gone" /> + + <EditText + android:id="@+id/account_password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:hint="@string/confirm_password" + android:inputType="textPassword" + android:visibility="gone" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" > + + <Button + android:id="@+id/cancel_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/cancel" + android:textColor="@color/primarytext" /> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/divider" /> + + <Button + android:id="@+id/save_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:enabled="false" + android:text="@string/save" + android:textColor="@color/secondarytext" /> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/activity_publish_profile_picture.xml b/res/layout/activity_publish_profile_picture.xml new file mode 100644 index 00000000..b3c6c427 --- /dev/null +++ b/res/layout/activity_publish_profile_picture.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primarybackground" > + + <LinearLayout + android:id="@+id/account_image_wrapper" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:layout_marginBottom="8dp" + android:layout_marginTop="24dp" + android:background="@drawable/message_border" > + + <ImageView + android:id="@+id/account_image" + android:layout_width="194dp" + android:layout_height="194dp" /> + </LinearLayout> + + <TextView + android:id="@+id/hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/account_image_wrapper" + android:layout_centerHorizontal="true" + android:text="@string/touch_to_choose_picture" + android:textColor="@color/secondarytext" /> + + <TextView + android:id="@+id/secondary_hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/hint" + android:layout_centerHorizontal="true" + android:text="@string/or_long_press_for_default" + android:textColor="@color/secondarytext" /> + + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" > + + <Button + android:id="@+id/cancel_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/cancel" + android:textColor="@color/primarytext" /> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/divider" /> + + <Button + android:id="@+id/publish_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:enabled="false" + android:text="@string/publish_avatar" + android:textColor="@color/secondarytext" /> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_above="@+id/button_bar" + android:layout_below="@+id/secondary_hint" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:gravity="center_vertical" + android:orientation="vertical" + android:paddingLeft="8dp" + android:paddingRight="8dp" > + + <TextView + android:id="@+id/account" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/primarytext" + android:textSize="18sp"/> + + <TextView + android:id="@+id/hint_or_warning" + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/publish_avatar_explanation" + android:textColor="@color/primarytext" + android:textSize="14sp" + android:minLines="3" /> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/contact.xml b/res/layout/contact.xml index 8432c7a3..f16ad061 100644 --- a/res/layout/contact.xml +++ b/res/layout/contact.xml @@ -11,6 +11,7 @@ android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" + android:scaleType="centerCrop" android:src="@drawable/ic_profile"> </ImageView> <LinearLayout diff --git a/res/layout/conversation_list_row.xml b/res/layout/conversation_list_row.xml index 97985737..a6001e5f 100644 --- a/res/layout/conversation_list_row.xml +++ b/res/layout/conversation_list_row.xml @@ -9,7 +9,8 @@ android:id="@+id/conversation_image" android:layout_width="56dp" android:layout_height="56dp" - android:layout_alignParentLeft="true"/> + android:layout_alignParentLeft="true" + android:scaleType="centerCrop"/> <RelativeLayout android:layout_toRightOf="@+id/conversation_image" diff --git a/res/layout/fragment_conversation.xml b/res/layout/fragment_conversation.xml index 2d612984..3f7f27e5 100644 --- a/res/layout/fragment_conversation.xml +++ b/res/layout/fragment_conversation.xml @@ -31,7 +31,7 @@ android:layout_alignParentLeft="true" android:background="@color/primarybackground" > - <EditText + <eu.siacs.conversations.ui.EditMessage android:id="@+id/textinput" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -46,10 +46,10 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:paddingTop="12dp" - android:textColor="@color/primarytext"> - + android:textColor="@color/primarytext" + android:imeOptions="flagNoExtractUi"> <requestFocus /> - </EditText> + </eu.siacs.conversations.ui.EditMessage> <ImageButton android:id="@+id/textSendButton" diff --git a/res/layout/manage_accounts.xml b/res/layout/manage_accounts.xml index a2a01bf1..71eb7572 100644 --- a/res/layout/manage_accounts.xml +++ b/res/layout/manage_accounts.xml @@ -2,7 +2,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" - android:layout_height="fill_parent" > + android:layout_height="fill_parent" + android:background="@color/primarybackground"> <ListView android:id="@+id/account_list" diff --git a/res/layout/message_recieved.xml b/res/layout/message_recieved.xml index 563d730d..ec003920 100644 --- a/res/layout/message_recieved.xml +++ b/res/layout/message_recieved.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:adjustViewBounds="true" android:paddingBottom="2dp" - android:scaleType="fitXY" + android:scaleType="centerCrop" android:background="@color/primarytext" /> diff --git a/res/layout/message_sent.xml b/res/layout/message_sent.xml index d4970e6f..21563eb4 100644 --- a/res/layout/message_sent.xml +++ b/res/layout/message_sent.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:adjustViewBounds="true" android:paddingBottom="2dp" - android:scaleType="fitXY" + android:scaleType="centerCrop" android:background="@color/primarytext" /> diff --git a/res/menu/manageaccounts_context.xml b/res/menu/manageaccounts_context.xml index 04ecc25f..103f95cc 100644 --- a/res/menu/manageaccounts_context.xml +++ b/res/menu/manageaccounts_context.xml @@ -25,6 +25,10 @@ android:showAsAction="never" android:title="@string/announce_pgp"/> <item + android:id="@+id/mgmt_account_publish_avatar" + android:showAsAction="never" + android:title="@string/mgmt_account_publish_avatar"/> + <item android:id="@+id/mgmt_otr_key" android:showAsAction="never" android:title="@string/show_otr_key"/> diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index ef500600..e095ed14 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <array name="resources"> + <string-array name="resources"> <item>Mobile</item> <item>Phone</item> <item>Tablet</item> <item>Conversations</item> <item>Android</item> - </array> + </string-array> <string-array name="filesizes"> <item>nie</item> <item>256 KB</item> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 46aa99c3..9f9d63cc 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Einstellungen</string> <string name="action_add">Neue Unterhaltung</string> @@ -21,13 +20,15 @@ <string name="title_activity_contact_details">Kontaktdetails</string> <string name="title_activity_conversations">Conversations</string> <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> + <string name="title_activity_start_conversation">Beginne Unterhaltung</string> + <string name="title_activity_choose_contact">Kontakt auswählen</string> <string name="just_now">gerade</string> <string name="minute_ago">vor einer Minute</string> <string name="minutes_ago">vor %d Minuten</string> <string name="unread_conversations">ungelesene Unterhaltungen</string> <string name="sending">senden…</string> - <string name="announce_pgp">PGP Ankündigung erneuern</string> - <string name="encrypted_message">Entschlüssle Nachricht. Bitte warten…</string> + <string name="announce_pgp">PGP-Ankündigung erneuern</string> + <string name="encrypted_message">Entschlüssele Nachricht. Bitte warten…</string> <string name="conference_details">Konferenzdetails</string> <string name="nick_in_use">Nickname wird bereits verwendet</string> <string name="admin">Administrator</string> @@ -36,17 +37,19 @@ <string name="participant">Teilnehmer</string> <string name="visitor">Besucher</string> <string name="enter_new_name">Gib einen neuen Namen ein:</string> - <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="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> + <string name="remove_bookmark_text">Möchtest du das Lesezeichen %s entfernen? Die Unterhaltung mit diesem Lesezeichen wird dabei nicht entfernt.</string> + <string name="untrusted_cert_hint">Der Server %s hat Dir ein unbekanntes, möglicherweise selbst signiertes Zertifikat geschickt.</string> + <string name="account_info">Server-Informationen</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> - <string name="no_otr_fingerprint">Es wurde noch kein OTR-Fingerabdruck erzeugt. Beginne einfach eine verschlüsselte Unterhaltung um einen Fingerabdruck zu erzeugen.</string> + <string name="ask_again"><u>Klicke, um noch einmal zu fragen</u></string> + <string name="show_otr_key">OTR-Fingerabdruck</string> + <string name="no_otr_fingerprint">Es wurde noch kein OTR-Fingerabdruck erzeugt. Beginne einfach eine verschlüsselte Unterhaltung, um einen Fingerabdruck zu erzeugen.</string> <string name="start_conversation">Beginne Unterhaltung</string> + <string name="invite_contact">Kontakt einladen</string> <string name="contacts">Kontakte</string> - <string name="search_jabber_id">Jabber ID eingeben oder suchen</string> + <string name="search_jabber_id">Jabber-ID eingeben oder suchen</string> <string name="choose_account">Account auswählen</string> <string name="multi_user_conference">Mehrbenutzerkonferenz</string> <string name="trying_join_conference">Möchtest du einer Konferenz beitreten?</string> @@ -62,14 +65,14 @@ <string name="hide">Verstecken</string> <string name="invitation_sent">Einladung wurde versandt</string> <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="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">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="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du bei der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird eines deiner 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 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="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> @@ -79,7 +82,7 @@ <string name="preparing_image">Bereite Bild für die Übertragung vor</string> <string name="action_clear_history">Verlauf löschen</string> <string name="clear_conversation_history">Unterhaltungsverlauf löschen</string> - <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Das beeinflust nicht Nachrichten die eventuell auf anderen Geräten gespeichert wurden.</string> + <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string> <string name="delete_messages">Nachrichten löschen</string> <string name="also_end_conversation">Diese Unterhaltung danach beenden</string> <string name="choose_presence">Choose presence to contact</string> @@ -87,68 +90,73 @@ <string name="send_otr_message">OTR-verschlüsselt schreiben</string> <string name="send_pgp_message">OpenPGP-verschlüsselt schreiben</string> <string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string> - <string name="download_image">Bild herunter laden</string> - <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="download_image">Bild herunterladen</string> + <string name="error_loading_image">Fehler beim Laden des Bildes (Datei wurde nicht gefunden)</string> + <string name="image_offered_for_download"><i>Bilddatei zum Download angeboten</i></string> <string name="not_connected">Nicht verbunden</string> <string name="otr_messages">OTR-verschlüsselte Nachrichten</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> + <string name="contact_offline_otr">Dein Kontakt muss online sein, um OTR-verschlüsselte Nachrichten zu empfangen. Möchtest Du die Nachricht im Klartext übertragen?</string> + <string name="contact_offline_file">Der Kontakt muss online sein, um Dateien zu empfangen.</string> <string name="send_unencrypted">Unverschlüsselt verschicken</string> - <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen Private Key.</string> + <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations benutzt eine Third-party-app names <b>OpenKeychain</b> um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3 lizenziert und bei F-Droid oder Google Play zu bekommen.\n\n<small>(Bitte starte Conversations danach neu)</small></string> + <string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string> <string name="restart">Neustarten</string> <string name="install">Installieren</string> <string name="offering">angeboten…</string> <string name="waiting">warten…</string> - <string name="no_pgp_key">Kein OpenPGP Schlüssel gefunden</string> - <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage deine Nachrichten zu verschlüsseln weil dein Kontakt sein oder ihren Schlüssel nicht preis gibt.\n\n<small>Bitte sag deinem Kontakt er oder sie möge bitte OpenPGP einrichten.</small></string> - <string name="contact_has_no_pgp_keys">Conversations ist nicht in der Lage deine Nachrichten zu verschlüsseln weil dein Kontakt sein oder ihren Schlüssel nicht preis gibt.\n\n<small>Bitte sag deinem Kontakt er oder sie möge bitte OpenPGP einrichten.</small></string> - <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier um sie anzuzeigen und zu entschlüsseln.</i></string> - <string name="encrypted_image_received"><i>Verschlüsseltes Bild erhalten. Drücke hier um es anzuzeigen und zu entschlüsseln.</i></string> - <string name="image_file"><i>Bild erhalten. Drücke hier um es anzuzeigen.</i></string> + <string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string> + <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge bitte OpenPGP einrichten.</small></string> + <string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string> + <string name="contact_has_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt sein oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge bitte OpenPGP einrichten.</small></string> + <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie anzuzeigen und zu entschlüsseln.</i></string> + <string name="encrypted_image_received"><i>Verschlüsseltes Bild erhalten. Drücke hier, um es anzuzeigen und zu entschlüsseln.</i></string> + <string name="image_file"><i>Bild erhalten. Drücke hier, um es anzuzeigen.</i></string> <string name="otr_file_transfer">OTR-Verschlüsselung nicht verfügbar</string> - <string name="otr_file_transfer_msg">Es ist nicht möglich Datein mitels OTR zu verschlüsseln. Du kannst entweder OpenPGP wählen oder die Datei nicht verschlüsseln.</string> + <string name="otr_file_transfer_msg">Es ist nicht möglich, Dateien mittels OTR zu verschlüsseln. Du kannst entweder OpenPGP wählen oder die Datei unverschlüsselt senden.</string> <string name="use_pgp_encryption">OpenPGP verwenden</string> - <string name="pref_xmpp_resource">XMPP resource</string> - <string name="pref_xmpp_resource_summary">Der Name mit dem sich der Client selber identifiziert</string> - <string name="pref_accept_files">Dateiannahme</string> - <string name="pref_accept_files_summary">Datein die kleiner sind als … automatisch annehmen</string> - <string name="pref_notification_settings">Benachrichtigungseinstellung</string> + <string name="pref_xmpp_resource">XMPP-Ressource</string> + <string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string> + <string name="pref_accept_files">Dateiannahme</string> + <string name="pref_accept_files_summary">Dateien, die kleiner sind als …, automatisch annehmen</string> + <string name="pref_notification_settings">Benachrichtigungseinstellungen</string> <string name="pref_notifications">Benachrichtigungen</string> - <string name="pref_notifications_summary">Benachrichtige mich wenn eine neu Nachricht ankommt</string> + <string name="pref_notifications_summary">Benachrichtige mich, wenn eine neue Nachricht ankommt</string> <string name="pref_vibrate">Vibrieren</string> - <string name="pref_vibrate_summary">Vibriere wenn eine neue Nachricht ankommt</string> + <string name="pref_vibrate_summary">Vibriere, wenn eine neue Nachricht ankommt</string> <string name="pref_sound">Klingelton</string> - <string name="pref_sound_summary">Spiel Klingelton wenn eine Nachricht ankommt</string> - <string name="pref_conference_notifications">Konferenz Benachrichtigungen</string> - <string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenznachricht und nicht nur wenn ich angesprochen werde.</string> + <string name="pref_sound_summary">Spiele Klingelton, wenn eine neue Nachricht ankommt</string> + <string name="pref_conference_notifications">Konferenz-Benachrichtigungen</string> + <string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenznachricht und nicht nur, wenn ich angesprochen werde.</string> <string name="pref_notification_grace_period">Gnadenfrist</string> - <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach erhalt einer Nachricht die von einem anderen Client von Dir kommt.</string> - <string name="pref_ui_options">Aussehen</string> - <string name="pref_use_phone_self_picture">Benutze Dein Kontaktbild</string> - <string name="pref_use_phone_self_picture_summary">Wenn du mehrere Accounts hast bist du eventuell nicht mehr in der Lage diese auseinander zu halten.</string> - <string name="pref_conference_name">Konferenznamen</string> - <string name="pref_conference_name_summary">Benutze das Thema der Konferenz als Name in der Überschicht</string> + <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string> + <string name="pref_ui_options">Benutzeroberfläche</string> + <string name="pref_use_phone_self_picture">Benutze dein Kontaktbild</string> + <string name="pref_use_phone_self_picture_summary">Wenn du mehrere Accounts hast, bist du eventuell nicht mehr in der Lage, diese auseinander zu halten</string> + <string name="pref_conference_name">Konferenzname</string> + <string name="pref_conference_name_summary">Benutze das Thema der Konferenz als Name in der Übersicht</string> <string name="pref_advanced_options">Erweiterte Optionen</string> <string name="pref_never_send_crash">Sende niemals Absturzberichte</string> - <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst hilfst du Conversations stetig zu verbessern.</string> + <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string> + <string name="pref_confirm_messages">Lesebestätigung senden</string> + <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfängst oder liest</string> + <string name="pref_show_last_seen">Letzte Nutzung anzeigen</string> + <string name="pref_show_last_seen_summary">Zeige die Zeit an, zu welcher ein Kontakt zuletzt online war</string> <string name="openpgp_error">Fehler mit OpenKeychain</string> - <string name="error_decrypting_file">Fehler beim entschlüsseln der Datei</string> + <string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string> <string name="error_copying_image_file">Fehler beim Kopieren des Bildes</string> <string name="accept">Annehmen</string> <string name="error">Ein unbekannter Fehler ist aufgetreten</string> - <string name="pref_grant_presence_updates">Online Status</string> - <string name="pref_grant_presence_updates_summary">Erlaube Kontakten die von Dir erstellt wurden deinen Status zu sehen und frage um Erlaubnis ihren zu sehen.</string> + <string name="pref_grant_presence_updates">Online-Status</string> + <string name="pref_grant_presence_updates_summary">Erlaube Kontakten, die von dir erstellt wurden, deinen Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string> <string name="subscriptions">Abonnements</string> <string name="your_account">Dein Account</string> <string name="keys">Schlüssel</string> <string name="send_presence_updates">Anwesenheitsbenachrichtigungen senden</string> <string name="receive_presence_updates">Empfange Anwesenheitsbenachrichtigungen</string> - <string name="ask_for_presence_updates">Frage um Erlaubnis Anwesenheitsbenachrichtigungen sehen zu dürfen</string> + <string name="ask_for_presence_updates">Frage um Erlaubnis, Anwesenheitsbenachrichtigungen sehen zu dürfen</string> <string name="asked_for_presence_updates">Es wurde um Anwesenheitsbenachrichtigungen gefragt</string> <string name="attach_choose_picture">Foto auswählen</string> <string name="attach_take_picture">Foto aufnehmen</string> @@ -157,7 +165,7 @@ <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="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> @@ -179,23 +187,20 @@ <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="passwords_do_not_match">Passwörter stimmen nicht überein</string> - <string name="invalid_jid">Ungültige Jabber ID</string> - <string name="pref_confirm_messages">Lesebestätigung senden</string> - <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfängst oder liest</string> - <string name="pref_show_last_seen">Letzte Nutzung anzeigen</string> - <string name="pref_show_last_seen_summary">Zeige die Zeit an, zu welcher ein Kontakt zuletzt online war</string> + <string name="mgmt_account_publish_avatar">Avatar veröffentlichen</string> <string name="mgmt_account_are_you_sure">Bist du dir sicher?</string> <string name="mgmt_account_delete_confirm_text">Wenn du dein Konto löscht, gehen alle Gesprächsverläufe verloren</string> <string name="mgmt_account_account_offline">Das Konto ist offline</string> - <string name="account_settings_jabber_id">Jabber ID:</string> + <string name="attach_record_voice">Sprache aufzeichnen</string> + <string name="account_settings">Kontoeinstellungen</string> + <string name="account_settings_jabber_id">Jabber-ID:</string> <string name="account_settings_password">Passwort:</string> <string name="account_settings_example_jabber_id">benutzer@domain.de</string> - <string name="account_settings_confirm_password">Passwort bestätigen:</string> + <string name="account_settings_confirm_password">Passwort bestätigen</string> <string name="password">Passwort</string> <string name="confirm_password">Passwort bestätigen</string> + <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> + <string name="invalid_jid">Ungültige Jabber-ID</string> <string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string> <string name="add_phone_book_text">Möchtest du %s zum Telefonbuch hinzufügen?</string> <string name="contact_status_online">Online</string> @@ -208,11 +213,11 @@ <string name="muc_details_conference_subject">Konferenzthema</string> <string name="muc_details_your_nickname">Dein Name</string> <string name="muc_details_other_members">Andere Mitglieder</string> - <string name="subscription_not_updated_offline">Der Account ist offline. Die Abonemments konnten nicht aktualisiert werden</string> + <string name="subscription_not_updated_offline">Der Account ist offline. Die Abonnements konnten nicht aktualisiert werden</string> <string name="share_with_active_conversations">Aktive Gespräche</string> <string name="server_info_statistics">Statistiken</string> - <string name="server_info_connection_age">Verbindungsalter</string> - <string name="server_info_session_age">Sitzungsalter</string> + <string name="server_info_connection_age">Verbindungsdauer</string> + <string name="server_info_session_age">Sitzungsdauer</string> <string name="server_info_packets_sent">Gesendete Pakete</string> <string name="server_info_packets_received">Empfangene Pakete</string> <string name="server_info_connected_accounts">Verbundene Konten</string> @@ -238,4 +243,41 @@ <string name="openpgp_click_to_decrypt">Hier klicken, um das Passwort einzugeben und die Nachricht zu entschlüsseln</string> <string name="reception_failed">Empfang ist fehlgeschlagen</string> <string name="no_muc_server_found">Es wurde kein Konferenzserver gefunden</string> + <string name="your_fingerprint">Dein Fingerabdruck</string> + <string name="otr_fingerprint">OTR-Fingerabdruck</string> + <string name="verify">Verifizieren</string> + <string name="decrypt">Entschlüsseln</string> + <string name="conferences">Konferenzen</string> + <string name="search">Suche</string> + <string name="create_contact">Kontakt erstellen</string> + <string name="join_conference">Konferenz beitreten</string> + <string name="delete_contact">Kontakt löschen</string> + <string name="view_contact_details">Kontaktdetails anzeigen</string> + <string name="create">Erstellen</string> + <string name="contact_already_exists">Der Kontakt existiert bereits</string> + <string name="join">Beitreten</string> + <string name="conference_address">Konferenzadresse</string> + <string name="conference_address_example">raum@conference.domain.de</string> + <string name="save_as_bookmark">Als Lesezeichen speichern</string> + <string name="delete_bookmark">Lesezeichen löschen</string> + <string name="bookmark_already_exists">Das Lesezeichen existiert bereits</string> + <string name="you">Du</string> + <string name="action_edit_subject">Konferenzthema anpassen</string> + <string name="conference_not_found">Konferenz nicht gefunden</string> + <string name="leave">Verlassen</string> + <string name="contact_added_you">Der Kontakt hat dich zur Kontaktliste hinzugefügt</string> + <string name="add_back">Auch hinzufügen</string> + <string name="contact_has_read_up_to_this_point">%s hat bis zu diesem Punkt gelesen</string> + <string name="publish_avatar">Avatar veröffentlichen</string> + <string name="touch_to_choose_picture">Klicke hier, um ein Avatar auszuwählen</string> + <string name="publish_avatar_explanation">Achtung: Jeder, der deinen Status sehen darf, sieht auch dein Avatar.</string> + <string name="publishing">Veröffentliche…</string> + <string name="error_publish_avatar_server_reject">Der Server hat die Veröffentlichung des Avatars abgelehnt.</string> + <string name="error_publish_avatar_converting">Bei der Konvertierung des Avatars lief etwas schief.</string> + <string name="error_saving_avatar">Kann Avatar nicht speichern.</string> + <string name="or_long_press_for_default">(Oder klicke lange, um Standard herzustellen)</string> + <string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avatars nicht.</string> + <string name="private_message">private Nachricht</string> + <string name="private_message_to">private Nachricht an %s</string> + <string name="send_private_message_to">Sende private Nachricht an %s</string> </resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 9141e726..afeb6c8b 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -187,6 +187,7 @@ <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_publish_avatar">Imagen de perfil</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> @@ -268,4 +269,16 @@ <string name="contact_added_you">El contacto te ha añadido a su lista de contactos</string> <string name="add_back">Añadir contacto</string> <string name="contact_has_read_up_to_this_point">%s ha leído hasta aquí</string> + <string name="publish_avatar">Publicar imagen</string> + <string name="touch_to_choose_picture">Pulsa para seleccionar una imagen de la galería</string> + <string name="publish_avatar_explanation">Nota: Todos tus contactos podrán ver esta imagen.</string> + <string name="publishing">Publicando…</string> + <string name="error_publish_avatar_server_reject">El servidor rechazó la publicación</string> + <string name="error_publish_avatar_converting">Se ha producido un error mientras se convertía la imagen</string> + <string name="error_saving_avatar">No se ha podido guardar la imagen de perfil en disco</string> + <string name="or_long_press_for_default">(O pulsación prolongada para volver a tu imagen de la agenda)</string> + <string name="error_publish_avatar_no_server_support">Tu servidor no soporta la publicación de imágenes de perfil</string> + <string name="private_message">en privado</string> + <string name="private_message_to">en privado para %s</string> + <string name="send_private_message_to">Enviar mensaje privado a %s</string> </resources> diff --git a/res/values-he/arrays.xml b/res/values-he/arrays.xml new file mode 100644 index 00000000..1895dee5 --- /dev/null +++ b/res/values-he/arrays.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="resources"> + <item>נייד</item> + <item>טלפון</item> + <item>טאבלט</item> + <item>Conversations</item> + <item>Android</item> + </string-array> + <string-array name="filesizes"> + <item>אף פעם</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-he/strings.xml b/res/values-he/strings.xml new file mode 100644 index 00000000..e9d1d50e --- /dev/null +++ b/res/values-he/strings.xml @@ -0,0 +1,284 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Conversations</string> + <string name="action_settings">הגדרות</string> + <string name="action_add">דיון חדש</string> + <string name="action_accounts">נהל חשבונות</string> + <string name="action_refresh">רענן רשימת קשר</string> + <string name="action_end_conversation">סיים את דיון זה</string> + <string name="action_contact_details">פרטי איש קשר</string> + <string name="action_muc_details">פרטי ועידה</string> + <string name="action_secure">דיון מאובטח</string> + <string name="action_add_account">הוסף חשבון</string> + <string name="action_edit_contact">ערוך שם</string> + <string name="action_add_phone_book">הוסף אל פנקס טלפונים</string> + <string name="action_delete_contact">מחק מתוך רשימה</string> + <string name="title_activity_contacts">אנשי קשר</string> + <string name="title_activity_manage_accounts">נהל חשבונות</string> + <string name="title_activity_settings">הגדרות</string> + <string name="title_activity_conference_details">פרטי ועידה</string> + <string name="title_activity_contact_details">פרטי איש קשר</string> + <string name="title_activity_conversations">דיונים</string> + <string name="title_activity_sharewith">שתף בעזרת Conversations</string> + <string name="title_activity_start_conversation">התחל דיון</string> + <string name="title_activity_choose_contact">בחר איש קשר</string> + <string name="just_now">רק כעת</string> + <string name="minute_ago">לפני דקה 1</string> + <string name="minutes_ago">לפני %d דקות</string> + <string name="unread_conversations">דיונים שלא נקראו</string> + <string name="sending">כעת שולח…</string> + <string name="announce_pgp">חדש הכרזת PGP</string> + <string name="encrypted_message">כעת מפענח הודעה. אנא המתן…</string> + <string name="conference_details">פרטי ועידה</string> + <string name="nick_in_use">שם כינוי כבר מצוי בשימוש</string> + <string name="admin">מנהל</string> + <string name="owner">בעלים</string> + <string name="moderator">אחראי</string> + <string name="participant">משתתף</string> + <string name="visitor">מבקר</string> + <string name="enter_new_name">הזן שם חדש:</string> + <string name="remove_contact_text">האם ברצונך להסיר את %s מתןך הרשימה שלך? הדיונים אשר משוייכים עם חשבון זה לא יוסרו.</string> + <string name="remove_bookmark_text">האם ברצונך להסיר את %s בתוור סימנייה? הדיונים אשר משוייכים עם סימנייה זו לא יוסרו.</string> + <string name="untrusted_cert_hint">השרת %s הגיש לך תעודה לא מהימנה, אפשרי כי זו חתומה באופן עצמי.</string> + <string name="account_info">מידע שרת</string> + <string name="register_account">רשום חשבון חדש על שרת</string> + <string name="share_with">שתף בעזרת</string> + <string name="ask_again"><u>לחץ כדי לשאול שוב</u></string> + <string name="show_otr_key">טביעת אצבע OTR</string> + <string name="no_otr_fingerprint">לא הופקה טביעת אצבע OTR. פשוט המשך והתחל דיון מוצפן</string> + <string name="start_conversation">התחל דיון</string> + <string name="invite_contact">הזמן איש קשר</string> + <string name="contacts">אנשי קשר</string> + <string name="search_jabber_id">חפש או הזן מזהה Jabber</string> + <string name="choose_account">בחר חשבון</string> + <string name="multi_user_conference">ועידה מרובת משתתפים</string> + <string name="trying_join_conference">האם הינך מנסה להצטרף אל ועידה?</string> + <string name="cancel">ביטול</string> + <string name="add">הוסף</string> + <string name="edit">ערוך</string> + <string name="delete">מחק</string> + <string name="save">שמור</string> + <string name="yes">כן</string> + <string name="no">לא</string> + <string name="ok">אישור</string> + <string name="done">סיים</string> + <string name="hide">הסתר</string> + <string name="invitation_sent">הזמנה נשלחה</string> + <string name="account_offline">חשבון לא מקוון</string> + <string name="cant_invite_while_offline">עליך להיות מקוון כדי להזמין אנשים אל ועידות</string> + <string name="crash_report_title">Conversations קרסה</string> + <string name="crash_report_message">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations\n<b>אזהרה:</b> זו תעשה שימוש בחשבון XMPP שלך כדי לשלוח עקבות מחסנית אל המפתח.</string> + <string name="send_now">שלח עכשיו</string> + <string name="send_never">לעולם אל תשאל שוב</string> + <string name="problem_connecting_to_account">לא מסוגל להתחבר אל חשבון</string> + <string name="problem_connecting_to_accounts">לא מסוגל להתחבר אל חשבונות מרובים</string> + <string name="touch_to_fix">לחץ כאן כדי לנהל את החשבונות שלך</string> + <string name="attach_file">צרף קובץ</string> + <string name="not_in_roster">איש קשר אינו מצוי בתוך הרשימה שלך. האם ברצונך להוסיפו?</string> + <string name="add_contact">הוסף איש קשר</string> + <string name="send_failed">מסירה נכשלה</string> + <string name="send_rejected">סורב</string> + <string name="receiving_image">כעת מקבל קובץ תצלום. אנא המתן…</string> + <string name="preparing_image">כעת מכין תצלום לשם תמסורת</string> + <string name="action_clear_history">טהר היסטוריה</string> + <string name="clear_conversation_history">טהר היסטוריית דיונים</string> + <string name="clear_histor_msg">האם ברצונך למחוק את כל ההודעות בתוך דיון זה?\n\n<b>אזהרה:</b> זו לא תשפיע על הודעות מאוחסנות על מכשירים או שרתים אחרים.</string> + <string name="delete_messages">מחק הודעות</string> + <string name="also_end_conversation">סיים את דיון זה לאחר מכן</string> + <string name="choose_presence">בחר נוכחות לאיש קשר</string> + <string name="send_plain_text_message">שלח הודעת טקסט גלוי</string> + <string name="send_otr_message">שלח הודעה מוצפנת OTR</string> + <string name="send_pgp_message">שלח הודעה מוצפנת OpenPGP</string> + <string name="your_nick_has_been_changed">שם כינוי שלך השתנה</string> + <string name="download_image">הורד תצלום</string> + <string name="error_loading_image">שגיאה בטעינת תצלום (קובץ לא נמצא)</string> + <string name="image_offered_for_download"><i>קובץ תצלום מוצע להורדה</i></string> + <string name="not_connected">לא מחובר</string> + <string name="otr_messages">הודעות מוצפנות OTR</string> + <string name="manage_account">נהל חשבון</string> + <string name="contact_offline">איש הקשר שלך אינו מקוון</string> + <string name="contact_offline_otr">שליחת הודעות מוצפנות OTR אל איש קשר לא מקוון אינה נתמכת למרבה הצער.\nהאם ברצונך לשלוח את ההודעה בטקסט גלוי?</string> + <string name="contact_offline_file">שליחת קבצים אל איש קשר לא מקוון אינה נתמכת למרבה הצער.</string> + <string name="send_unencrypted">שלח לא מוצפנת</string> + <string name="decryption_failed">פענוח נכשל. אולי אין לך את המפתח הפרטי המתאים.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations מפיקה תועלת מן אפליקציית צד-שלישי הקרויה <b>OpenKeychain</b> כדי להצפין ולפענח הודעות וגם כדי לנהל את המפתחות הפומביים שלך.\n\nOpenKeychain הינה רשויה תחת GPLv3 וזמינה אצל F-Droid וגם Google Play.\n\n<small>(אנא התחל מחדש את Conversations לאחר מכן.)</small></string> + <string name="restart">התחל מחדש</string> + <string name="install">התקן</string> + <string name="offering">כעת מציע…</string> + <string name="waiting">כעת ממתין…</string> + <string name="no_pgp_key">לא נמצא מפתח OpenPGP</string> + <string name="contact_has_no_pgp_key">Conversations אינה מסוגלת להצפין את הודעותיך משום שאיש הקשר שלך אינו מכריז על המפתח הפומבי שלו או שלה.\n\n<small>אנא בקש מאיש הקשר שלך לארגן OpenPGP.</small></string> + <string name="no_pgp_keys">לא נמצאו מפתחות OpenPGP</string> + <string name="contacts_have_no_pgp_keys">Conversations אינה מסוגלת להצפין את הודעותיך משום שאנשי הקשר שלך אינם מכריזים על המפתח הפומבי שלהם.\n\n<small>אנא בקש מאנשי הקשר שלך לארגן OpenPGP.</small></string> + <string name="encrypted_message_received"><i>הודעה מוצפנת התקבלה. לחץ כדי לצפות ולפענח.</i></string> + <string name="encrypted_image_received"><i>תצלום מוצפן התקבל. לחץ כדי לצפות ולפענח.</i></string> + <string name="image_file"><i>תצלום התקבל. לחץ כדי לצפות</i></string> + <string name="otr_file_transfer">הצפנת OTR אינה זמינה</string> + <string name="otr_file_transfer_msg">למרבה הצער הצפנת OTR אינה זמינה עבור העברת קובץ. באפשרותך לבחור OpenPGP או שום הצפנה.</string> + <string name="use_pgp_encryption">השתמש בהצפנת OpenPGP</string> + <string name="pref_xmpp_resource">משאב XMPP</string> + <string name="pref_xmpp_resource_summary">השם שלקוח זה מזהה את עצמו עם</string> + <string name="pref_accept_files">קבל קבצים</string> + <string name="pref_accept_files_summary">קבל אוטומטית קבצים קטנים יותר מאשר…</string> + <string name="pref_notification_settings">הגדרות התראה</string> + <string name="pref_notifications">התראות</string> + <string name="pref_notifications_summary">תודיע כאשר הודעה חדשה מגיעה</string> + <string name="pref_vibrate">הרטט</string> + <string name="pref_vibrate_summary">הרטט גם כאשר הודעה חדשה מגיעה</string> + <string name="pref_sound">צליל</string> + <string name="pref_sound_summary">נגן צלצול עם התראה</string> + <string name="pref_conference_notifications">התראות ועידה</string> + <string name="pref_conference_notifications_summary">תמיד תודיע כאשר הודעת ועידה חדשה מגיעה במקום רק כאשר מודגשת</string> + <string name="pref_notification_grace_period">משך ארכת התראה</string> + <string name="pref_notification_grace_period_summary">נטרל התראות לזמן קצר לאחר שהודעת פחם התקבלה</string> + <string name="pref_ui_options">אפשרויות ממשק משתמש</string> + <string name="pref_use_phone_self_picture">השתמש בתמונת איש קשר עצמית של טלפון</string> + <string name="pref_use_phone_self_picture_summary">אתה עשוי שלא להבחין באיזה חשבון אתה משתמש בעת דיון</string> + <string name="pref_conference_name">שם ועידה</string> + <string name="pref_conference_name_summary">השתמש בנושא חדר כדי לזהות ועידות</string> + <string name="pref_advanced_options">אפשרויות מתקדמות</string> + <string name="pref_never_send_crash">לעולם אל תשלח דיווחי קריסה</string> + <string name="pref_never_send_crash_summary">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations</string> + <string name="pref_confirm_messages">אשר הודעות</string> + <string name="pref_confirm_messages_summary">אפשר לאיש קשר שלך לדעת מתי קיבלת וקראת הודעה</string> + <string name="pref_show_last_seen">הצג נראה לאחרונה</string> + <string name="pref_show_last_seen_summary">הצג את הפעם האחרונה בה איש קשר נראה מקוון</string> + <string name="openpgp_error">OpenKeychain דיווח שגיאה</string> + <string name="error_decrypting_file">שגיאת I/O פענוח קובץ</string> + <string name="error_copying_image_file">שגיאה בהעתקת קובץ תצלום.</string> + <string name="accept">קבל</string> + <string name="error">אירעה שגיאה</string> + <string name="pref_grant_presence_updates">הענק עדכוני נוכחות</string> + <string name="pref_grant_presence_updates_summary">הענק ובקש הרשמות נוכחות מראש עבור אנשי קשר שיצרת</string> + <string name="subscriptions">הרשמות</string> + <string name="your_account">החשבון שלך</string> + <string name="keys">מפתחות</string> + <string name="send_presence_updates">שלח עדכוני נוכחות</string> + <string name="receive_presence_updates">קבל עדכוני נוכחות</string> + <string name="ask_for_presence_updates">בקש עדכוני נוכחות</string> + <string name="asked_for_presence_updates">התבקש לשם עדכוני נוכחות</string> + <string name="attach_choose_picture">בחר תמונה</string> + <string name="attach_take_picture">קח תמונה</string> + <string name="preemptively_grant">הענק בקשת הרשמה מראש</string> + <string name="error_not_an_image_file">הקובץ שבחרת אינו תצלום</string> + <string name="error_compressing_image">שגיאה במהלך המרת קובץ תצלום</string> + <string name="error_file_not_found">קובץ לא נמצא</string> + <string name="error_io_exception">שגיאת I/O כללית. אולי אזל לך נפח אחסון?</string> + <string name="error_security_exception_during_image_copy">האפליקציה בה השתמשת כדי לבחור את תצלום זה לא סיפקה לנו מספיק הרשאות כדי לקרוא את הקובץ.\n\n<small>השתמש במנהל קבצים אחר כדי לבחור תצלום</small></string> + <string name="account_status">מצב:</string> + <string name="account_status_unknown">לא ידוע</string> + <string name="account_status_disabled">מנוטרל זמנית</string> + <string name="account_status_online">מקוון</string> + <string name="account_status_connecting">כעת מתחבר\u2026</string> + <string name="account_status_offline">לא מקוון</string> + <string name="account_status_unauthorized">לא מורשה</string> + <string name="account_status_not_found">שרת לא נמצא</string> + <string name="account_status_no_internet">אין חיבוריות</string> + <string name="account_status_requires_tls">שרת מצריך TLS</string> + <string name="account_status_regis_fail">הרשמה נכשלה</string> + <string name="account_status_regis_conflict">שם משתמש כבר מצוי בשימוש</string> + <string name="account_status_regis_success">הרשמה הושלמה</string> + <string name="account_status_regis_not_sup">שרת לא תומך הרשמה</string> + <string name="encryption_choice_none">טקסט גלוי</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="mgmt_account_edit">ערוך חשבון</string> + <string name="mgmt_account_delete">מחק</string> + <string name="mgmt_account_disable">נטרל זמנית</string> + <string name="mgmt_account_publish_avatar">פרסם אווטאר</string> + <string name="mgmt_account_enable">אפשר</string> + <string name="mgmt_account_are_you_sure">האם אתה בטוח?</string> + <string name="mgmt_account_delete_confirm_text">אם אתה מוחק את חשבונך כל היסטוריית הדיון שלך תאבד</string> + <string name="mgmt_account_account_offline">חשבון אינו מקוון</string> + <string name="attach_record_voice">הקלט קול</string> + <string name="account_settings">הגדרות חשבון</string> + <string name="account_settings_jabber_id">מזהה Jabber</string> + <string name="account_settings_password">סיסמה</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">אמת סיסמה</string> + <string name="password">סיסמה</string> + <string name="confirm_password">אמת סיסמה</string> + <string name="passwords_do_not_match">סיסמאות לא תואמות</string> + <string name="invalid_jid">זה אינו מזהה Jabber תקף</string> + <string name="error_out_of_memory">חסר זיכרון. תצלום גדול מדי</string> + <string name="add_phone_book_text">האם ברצונך להוסיף את %s אל רשימת קשר טלפונית?</string> + <string name="contact_status_online">מקוון</string> + <string name="contact_status_free_to_chat">חופשי לשיחה</string> + <string name="contact_status_away">נעדר</string> + <string name="contact_status_extended_away">נעדר לזמן מה</string> + <string name="contact_status_do_not_disturb">אל תפריעו</string> + <string name="contact_status_offline">לא מקוון</string> + <string name="muc_details_conference">ועידה</string> + <string name="muc_details_conference_subject">נושא ועידה</string> + <string name="muc_details_your_nickname">שם כינוי שלך</string> + <string name="muc_details_other_members">חברים אחרים</string> + <string name="subscription_not_updated_offline">חשבון לא מקוון. לא היה מסוגל לעדכן הרשמה</string> + <string name="share_with_active_conversations">דיונים פעילים</string> + <string name="server_info_statistics">סטטיסטיקה</string> + <string name="server_info_connection_age">תקופת התחברות</string> + <string name="server_info_session_age">תקופת הפעלה</string> + <string name="server_info_packets_sent">חבילות מידע נשלחו</string> + <string name="server_info_packets_received">חבילות מידע נתקבלו</string> + <string name="server_info_connected_accounts">חשבונות מחוברים</string> + <string name="server_info_server_features">תכונות שרת</string> + <string name="server_info_roster_versioning">נסחיות רשימה</string> + <string name="server_info_carbon_messages">הודעות פחם</string> + <string name="server_info_stream_management">ניהול זרם</string> + <string name="hours">שעות</string> + <string name="mins">דקות</string> + <string name="missing_public_keys">הכרזות מפתח פומבי חסרות</string> + <string name="last_seen_now">נראה לאחרונה ממש עכשיו</string> + <string name="last_seen_min">נראה לאחרונה לפני דקה 1</string> + <string name="last_seen_mins">נראה לאחרונה לפני %d דקות</string> + <string name="last_seen_hour">נראה לאחרונה לפני שעה 1</string> + <string name="last_seen_hours">נראה לאחרונה לפני %d שעות ago</string> + <string name="last_seen_day">נראה לאחרונה לפני יום 1</string> + <string name="last_seen_days">נראה לאחרונה לפני %d ימים</string> + <string name="never_seen">לא נראה מעולם</string> + <string name="install_openkeychain">הודעה מוצפנת. אנא התקן OpenKeychain כדי לפענח.</string> + <string name="unknown_otr_fingerprint">טביעת אצבע OTR לא מוכרת</string> + <string name="edit_conference_details">לחץ כדי לערוך פרטי ועידה</string> + <string name="openpgp_messages_found">הודעות מוצפנות OpenPGP נמצאו</string> + <string name="openpgp_click_to_decrypt">לחץ כאן כדי להקליד מימרת סיסמה ולהצפין הודעות</string> + <string name="reception_failed">קבלה נכשלה</string> + <string name="no_muc_server_found">לא נמצא שרת ועידה הולם</string> + <string name="your_fingerprint">טביעת אצבע שלך</string> + <string name="otr_fingerprint">טביעת אצבע OTR</string> + <string name="verify">אמת</string> + <string name="decrypt">פענח</string> + <string name="conferences">ועידות</string> + <string name="search">חפש</string> + <string name="create_contact">צור איש קשר</string> + <string name="join_conference">הצטרף לועידה</string> + <string name="delete_contact">מחק איש קשר</string> + <string name="view_contact_details">צפה בפרטי איש קשר</string> + <string name="create">צור</string> + <string name="contact_already_exists">איש קשר כבר קיים</string> + <string name="join">הצטרף</string> + <string name="conference_address">כתובת ועידה</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">שמור בתור סימנייה</string> + <string name="delete_bookmark">מחק סימנייה</string> + <string name="bookmark_already_exists">סימנייה זו כבר קיימת</string> + <string name="you">אני</string> + <string name="action_edit_subject">ערוך נושא ועידה</string> + <string name="conference_not_found">ועידה לא נמצאה</string> + <string name="leave">עזוב</string> + <string name="contact_added_you">איש קשר הוסיף אותך אל רשימת קשר</string> + <string name="add_back">הוסף בחזרה</string> + <string name="contact_has_read_up_to_this_point">%s קרא עד לנקודה זו</string> + <string name="publish_avatar">פרסם אווטאר</string> + <string name="touch_to_choose_picture">לחץ על אווטאר כדי לבחור תמונה מתוך גלריה</string> + <string name="publish_avatar_explanation">לתשומת לבך: כל מי אשר רשום לעדכוני נוכחות שלך יורשה לראות את תמונה זו.</string> + <string name="publishing">כעת מפרסם…</string> + <string name="error_publish_avatar_server_reject">השרת פסל פרסום</string> + <string name="error_publish_avatar_converting">משהו השתבש במהלך המרת תמונה</string> + <string name="error_saving_avatar">לא היה מסוגל לשמור אווטאר אל כונן</string> + <string name="or_long_press_for_default">(או לחיצה ארוכה כדי להחזיר לשגרה)</string> + <string name="error_publish_avatar_no_server_support">שרתך לא תומך בפרסום של אווטארים</string> + <string name="private_message">בפרטי</string> + <string name="private_message_to">בפרטי אל %s</string> + <string name="send_private_message_to">שלח הודעה פרטית אל %s</string> +</resources> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index cffccbf7..ee56e537 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -116,9 +116,9 @@ <string name="use_pgp_encryption">Gebruik OpenPGP versleuteling</string> <string name="pref_xmpp_resource">XMPP resource</string> <string name="pref_xmpp_resource_summary">De naam waarmee deze client zich identificeert</string> - <string name="pref_accept_files">Accepteer bestanden</string> + <string name="pref_accept_files">Accepteer bestanden</string> <string name="pref_accept_files_summary">Accepteer automatisch bestanden kleiner dan…</string> - <string name="pref_notification_settings">Notificatie Instellingen</string> + <string name="pref_notification_settings">Notificatie Instellingen</string> <string name="pref_notifications">Notificaties</string> <string name="pref_notifications_summary">Notificatie als een nieuw bericht arriveert</string> <string name="pref_vibrate">Trillen</string> @@ -239,4 +239,34 @@ <string name="openpgp_click_to_decrypt">Raak hier aan om het wachtwoord in te voeren het bericht te ontsleutelen</string> <string name="reception_failed">Ontvangen mislukt</string> <string name="no_muc_server_found">Geen geschikte Groepsconversatie Server gevonden</string> + + <string name="join_conference">Aan groepsconversatie deelnemen</string> + <string name="invite_contact">Contact uitnodigen</string> + <string name="your_fingerprint">Uw vingerafdruk</string> + <string name="delete_bookmark">Bladwijzer verwijderen</string> + <string name="join">Deelnemen</string> + <string name="otr_fingerprint">OTR vingerafdruk</string> + <string name="you">U</string> + <string name="conference_not_found">Groepsconversatie niet gevonden</string> + <string name="search">Zoeken</string> + <string name="contact_already_exists">Het contact bestaat al</string> + <string name="title_activity_start_conversation">Start Groepsconversatie</string> + <string name="title_activity_choose_contact">Kies contact</string> + <string name="contact_added_you">Contact added you to contact list</string> + <string name="view_contact_details">Contactdetails bekijken</string> + <string name="conferences">Groepsconversaties</string> + <string name="verify">Controleren</string> + <string name="create_contact">Contact Aanmaken</string> + <string name="remove_bookmark_text">Wilt u %s als bladwijzer verwijderen? De groepsconversatie die verbonden is met deze bladwijzer zal niet verwijderd worden.</string> + <string name="action_edit_subject">Onderwerp van groepsconversatie veranderen</string> + <string name="delete_contact">Contact Verwijderen</string> + <string name="create">Aanmaken</string> + <string name="leave">Verlaten</string> + <string name="conference_address">Groepsconversatie adres</string> + <string name="save_as_bookmark">Bladwijzer toevoegen</string> + <string name="conference_address_example">kamer@groepsconversatie.voorbeeld.nl</string> + <string name="add_back">Terug toevoegen</string> + <string name="bookmark_already_exists">Deze bladwijzer bestaat al</string> + <string name="decrypt">Ontsleutelen</string> + <string name="contact_has_read_up_to_this_point">%s heeft tot hier gelezen</string> </resources> diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml new file mode 100644 index 00000000..c7cc9f2d --- /dev/null +++ b/res/values-sv/arrays.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="resources"> + <item>Mobile</item> + <item>Phone</item> + <item>Tablet</item> + <item>Conversations</item> + <item>Android</item> + </string-array> + <string-array name="filesizes"> + <item>aldrig</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-sv/strings.xml b/res/values-sv/strings.xml new file mode 100644 index 00000000..242d710a --- /dev/null +++ b/res/values-sv/strings.xml @@ -0,0 +1,271 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Conversations</string> + <string name="action_settings">Inställningar</string> + <string name="action_add">Ny konversation</string> + <string name="action_accounts">Kontoinställningar</string> + <string name="action_refresh">Uppdatera konstaktlistan</string> + <string name="action_end_conversation">Avsluta denna konversation</string> + <string name="action_contact_details">Kontaktdetaljer</string> + <string name="action_muc_details">Konferensdetaljer</string> + <string name="action_secure">Skyddad konversation</string> + <string name="action_add_account">Lägg till konto</string> + <string name="action_edit_contact">Ändra namn</string> + <string name="action_add_phone_book">Lägg till i telefonbok</string> + <string name="action_delete_contact">Ta bort kontakt</string> + <string name="title_activity_contacts">Kontakter</string> + <string name="title_activity_manage_accounts">Hantera konton</string> + <string name="title_activity_settings">Inställningar</string> + <string name="title_activity_conference_details">Konferensdetaljer</string> + <string name="title_activity_contact_details">Kontaktdetaljer</string> + <string name="title_activity_conversations">Konversationer</string> + <string name="title_activity_sharewith">Dela med konversation</string> + <string name="title_activity_start_conversation">Starta konversation</string> + <string name="title_activity_choose_contact">Välj kontakt</string> + <string name="just_now">just nu</string> + <string name="minute_ago">1 min sedan</string> + <string name="minutes_ago">%d min sedan</string> + <string name="unread_conversations">olästa konversationer</string> + <string name="sending">skickar…</string> + <string name="announce_pgp">Förnya PGP annonsering</string> + <string name="encrypted_message">Avkrypterar meddelande. Vänta…</string> + <string name="conference_details">Konferensdetaljer</string> + <string name="nick_in_use">Nick används redan</string> + <string name="admin">Admin</string> + <string name="owner">Ägare</string> + <string name="moderator">Moderator</string> + <string name="participant">Deltagare</string> + <string name="visitor">Besökare</string> + <string name="enter_new_name">Skriv in nytt namn:</string> + <string name="remove_contact_text">Vill du ta bort %s från din kontaktlista? Konversationer associerade med denna kontakt kommer inte tas bort.</string> + <string name="remove_bookmark_text">Vill du ta bort %s som bokmärke? Konversationer associerade med detta bokmärke kommer inte tas bort.</string> + <string name="untrusted_cert_hint">Servern %s har ett ej pålitligt, möjligtvis självsignerat, certifikat.</string> + <string name="account_info">Server info</string> + <string name="register_account">Registrera nytt konto på servern</string> + <string name="share_with">Dela med</string> + <string name="ask_again"><u>Tryck för att fråga igen</u></string> + <string name="show_otr_key">OTR-fingeravtryck</string> + <string name="no_otr_fingerprint">Inget OTR-fingeravtryck genererat. Fortsätt och starta en krypterad konversation</string> + <string name="start_conversation">Starta konversation</string> + <string name="invite_contact">Bjud in kontakt</string> + <string name="contacts">Kontakter</string> + <string name="search_jabber_id">Sök eller skriv in Jabber ID</string> + <string name="choose_account">Välj konto</string> + <string name="multi_user_conference">Multianvändarkonferens</string> + <string name="trying_join_conference">Försöker du gå in i en konferens?</string> + <string name="cancel">Avbryt</string> + <string name="add">Lägg till</string> + <string name="edit">Ändra</string> + <string name="delete">Ta bort</string> + <string name="save">Spara</string> + <string name="yes">Ja</string> + <string name="no">Nej</string> + <string name="ok">Ok</string> + <string name="done">Klar</string> + <string name="hide">Göm</string> + <string name="invitation_sent">Inbjudan skickad</string> + <string name="account_offline">Konto offline</string> + <string name="cant_invite_while_offline">Du måste vara online för att bjuda in personer till konferenser</string> + <string name="crash_report_title">Conversations har kraschat</string> + <string name="crash_report_message">Genom att skicka in stack traces hjälper du utvecklarna av Conversations\n<b>Varning:</b> Detta använder ditt XMPP konto för att skicka informationen till utvecklarna.</string> + <string name="send_now">Skicka nu</string> + <string name="send_never">Fråga aldrig igen</string> + <string name="problem_connecting_to_account">Kan inte ansluta till konto</string> + <string name="problem_connecting_to_accounts">Kan inte ansluta till flera konton</string> + <string name="touch_to_fix">Tryck här för att hantera dina konton</string> + <string name="attach_file">Bifoga fil</string> + <string name="not_in_roster">Kontakten är inte i din kontaktlista. Vill du lägga till den?</string> + <string name="add_contact">Lägg till kontakt</string> + <string name="send_failed">sändning misslyckades</string> + <string name="send_rejected">avvisad</string> + <string name="receiving_image">Tar emot bildfil. Vänta…</string> + <string name="preparing_image">Förbereder bild för sändning</string> + <string name="action_clear_history">Rensa historik</string> + <string name="clear_conversation_history">Rensa konversationshistorik</string> + <string name="clear_histor_msg">Vill du ta bort alla meddelanden i denna konversation?\n\n<b>Varning:</b> Detta kommer inte påverka meddelanden lagrade på andra enheter eller servrar.</string> + <string name="delete_messages">Ta bort meddelanden</string> + <string name="also_end_conversation">Avsluta denna konversation efter</string> + <string name="choose_presence">Välj närvaro till kontakt</string> + <string name="send_plain_text_message">Skicka meddelande i klartext</string> + <string name="send_otr_message">Skicka OTR-krypterat meddelande</string> + <string name="send_pgp_message">Skicka OpenPGP-krypterat meddelande</string> + <string name="your_nick_has_been_changed">Ditt nick har ändrats</string> + <string name="download_image">Ladda ner bild</string> + <string name="error_loading_image">Fel vid öppnande av bild (Fil hittas ej)</string> + <string name="image_offered_for_download"><i>Bildfil erbjuds för nedladdning</i></string> + <string name="not_connected">Ej ansluten</string> + <string name="otr_messages">OTR-krypterade meddelanden</string> + <string name="manage_account">Hantera konto</string> + <string name="contact_offline">Kontakten är offline</string> + <string name="contact_offline_otr">Skicka OTR-krypterade meddelanden till en kontakt som är offline stöds tyvärr inte.\nVill du skicka meddelandet i klartext?</string> + <string name="contact_offline_file">Skicka filer till en kontakt som är offline stöds tyvärr inte.</string> + <string name="send_unencrypted">Skicka okrypterat</string> + <string name="decryption_failed">Avkryptering gick fel. Du kanske inte har rätt privat nyckel.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations använder en tredjeparts applikation som heter <b>OpenKeychain</b> för att kryptera och avkryptera meddelanden och hantera dina publika nycklar.\n\nOpenKeychain är licensierat under GPLv3 och tillgängligt på F-Droid och Google Play.\n\n<small>(Starta om Conversations efter.)</small></string> + <string name="restart">Starta om</string> + <string name="install">Installera</string> + <string name="offering">erbjuder…</string> + <string name="waiting">väntar…</string> + <string name="no_pgp_key">Ingen OpenPGP-nyckel funnen</string> + <string name="contact_has_no_pgp_key">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="no_pgp_keys">Inga OpenPGP-nycklar funna</string> + <string name="contacts_have_no_pgp_keys">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Krypterat meddelande mottaget. Tryck för att se och avkryptera.</i></string> + <string name="encrypted_image_received"><i>Krypterad bild mottagen. Tryck för att se och avkryptera.</i></string> + <string name="image_file"><i>Bild mottagen. Tryck för att se</i></string> + <string name="otr_file_transfer">OTR-kryptering ej tillgänglig</string> + <string name="otr_file_transfer_msg">Tyvärr är OTR-kryptering inte tillgänglig för filöverföringar. Du kan välja antingen OpenPGP eller ingen kryptering.</string> + <string name="use_pgp_encryption">Använd OpenPGP-kryptering</string> + <string name="pref_xmpp_resource">XMPP resurs</string> + <string name="pref_xmpp_resource_summary">Namnet som klienten identifierar sig med</string> + <string name="pref_accept_files">Acceptera filer</string> + <string name="pref_accept_files_summary">Acceptera automatistk filer som är mindre än…</string> + <string name="pref_notification_settings">Notifieringsinställningar</string> + <string name="pref_notifications">Notifieringar</string> + <string name="pref_notifications_summary">Notifiera när meddelande tagits emot</string> + <string name="pref_vibrate">Vibrera</string> + <string name="pref_vibrate_summary">Vibrera när meddelande tagits emot</string> + <string name="pref_sound">Ljud</string> + <string name="pref_sound_summary">Spela ljud med notifiering</string> + <string name="pref_conference_notifications">Konferensnotifieringar</string> + <string name="pref_conference_notifications_summary">Notifiera alltid när nytt konferensmeddelande tagits emot istället för endast vid highlight</string> + <string name="pref_notification_grace_period">Notifieringsfrist</string> + <string name="pref_notification_grace_period_summary">Deaktivera notifieringar en kort stund efter att en carbon copy tagits emot</string> + <string name="pref_ui_options">UI inställningar</string> + <string name="pref_use_phone_self_picture">Används telefonens kontaktbild på dig</string> + <string name="pref_use_phone_self_picture_summary">Du kommer inte längre kunna avgöra vilket konto du använder i en konversation</string> + <string name="pref_conference_name">Konferensnamn</string> + <string name="pref_conference_name_summary">Använd konferensens ämne för att identifera konferensen</string> + <string name="pref_advanced_options">Avancerade inställningar</string> + <string name="pref_never_send_crash">Skicka aldrig krasch-rapporter</string> + <string name="pref_never_send_crash_summary">Genom att skicka in stack traces hjälper du utvecklarna av Conversations</string> + <string name="pref_confirm_messages">Bekräfta meddelanden</string> + <string name="pref_confirm_messages_summary">Låter dina kontakter veta när du har tagit emot och läst ett meddelande</string> + <string name="pref_show_last_seen">Visa senast sedd</string> + <string name="pref_show_last_seen_summary">Visa senaste tid som din kontakt har setts online</string> + <string name="openpgp_error">OpenKeychain rapporterade ett fel</string> + <string name="error_decrypting_file">I/O-fel vid avkryptering av fil</string> + <string name="error_copying_image_file">Fel vid kopiering av bildfil.</string> + <string name="accept">Acceptera</string> + <string name="error">Ett fel har inträffat</string> + <string name="pref_grant_presence_updates">Tillåt tillänglighetsuppdateringar</string> + <string name="pref_grant_presence_updates_summary">Tillåt i förväg och be om tillgänglighetsuppdateringar för kontakter du skapat</string> + <string name="subscriptions">Abonnemang</string> + <string name="your_account">Ditt konto</string> + <string name="keys">Nycklar</string> + <string name="send_presence_updates">Skicka tillgänglighetsuppdatering</string> + <string name="receive_presence_updates">Ta emot tillgänglighetsuppdateringar</string> + <string name="ask_for_presence_updates">Be om tillgänglighetsuppdateringar</string> + <string name="asked_for_presence_updates">Bad om tillgänglighetsuppdateringar</string> + <string name="attach_choose_picture">Välj bild</string> + <string name="attach_take_picture">Ta ny bild</string> + <string name="preemptively_grant">Tillåt abonnemangsbegäran i förväg</string> + <string name="error_not_an_image_file">Filen du valt är inte en bild</string> + <string name="error_compressing_image">Fel vid konvertering av bildfilen</string> + <string name="error_file_not_found">Filen hittas ej</string> + <string name="error_io_exception">Generellt I/O-fel. Du kanske fick slut på plats?</string> + <string name="error_security_exception_during_image_copy">Applikationen du använde för att välja bilden gav inte tillräckliga rättigheter för att läsa filen.\n\n<small>Använd en annan filhanterare för att välja bild</small></string> + <string name="account_status">Status:</string> + <string name="account_status_unknown">Okänd</string> + <string name="account_status_disabled">Tillfälligt deaktiverad</string> + <string name="account_status_online">Online</string> + <string name="account_status_connecting">Ansluter\u2026</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Otillåten</string> + <string name="account_status_not_found">Server ej funnen</string> + <string name="account_status_no_internet">Ingen anslutning</string> + <string name="account_status_requires_tls">Servern kräver TLS</string> + <string name="account_status_regis_fail">Registreringsfel</string> + <string name="account_status_regis_conflict">Användarnamn används redan</string> + <string name="account_status_regis_success">Registrering klar</string> + <string name="account_status_regis_not_sup">Servern stödjer inte registrering</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">Ändra konto</string> + <string name="mgmt_account_delete">Ta bort</string> + <string name="mgmt_account_disable">Deaktivera tillfälligt</string> + <string name="mgmt_account_enable">Aktivera</string> + <string name="mgmt_account_are_you_sure">Är du säker?</string> + <string name="mgmt_account_delete_confirm_text">Om du tar bort kontot kommer all konversationshistorik att försvinna</string> + <string name="mgmt_account_account_offline">Kontot är offline</string> + <string name="attach_record_voice">Spela in röst</string> + <string name="account_settings">Kontoinställningar</string> + <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_password">Lösenord</string> + <string name="account_settings_example_jabber_id">användarnamn@exempel.se</string> + <string name="account_settings_confirm_password">Bekräfta lösenord</string> + <string name="password">Lösenord</string> + <string name="confirm_password">Bekräfta lösenord</string> + <string name="passwords_do_not_match">Lösenorden är inte lika</string> + <string name="invalid_jid">Detta är inte ett korrekt Jabber ID</string> + <string name="error_out_of_memory">Slut på minne. Bilden är för stor</string> + <string name="add_phone_book_text">Vill du lägga till %s i din telefons kontaktlista?</string> + <string name="contact_status_online">online</string> + <string name="contact_status_free_to_chat">tillgänglig</string> + <string name="contact_status_away">borta</string> + <string name="contact_status_extended_away">borta (förlängt)</string> + <string name="contact_status_do_not_disturb">stör ej</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Konferens</string> + <string name="muc_details_conference_subject">Konferensämne</string> + <string name="muc_details_your_nickname">Ditt nick</string> + <string name="muc_details_other_members">Andra medlemmar</string> + <string name="subscription_not_updated_offline">Konto offline. Kunde inte uppdatera abonnemang</string> + <string name="share_with_active_conversations">Aktiva konversationer</string> + <string name="server_info_statistics">Statistik</string> + <string name="server_info_connection_age">Uppkopplingstid</string> + <string name="server_info_session_age">Sessionstid</string> + <string name="server_info_packets_sent">Skickade paket</string> + <string name="server_info_packets_received">Mottagna paket</string> + <string name="server_info_connected_accounts">Uppkopplade konton</string> + <string name="server_info_server_features">Serverfunktioner</string> + <string name="server_info_roster_versioning">Roster Versioning</string> + <string name="server_info_carbon_messages">Carbon Messages</string> + <string name="server_info_stream_management">Stream Management</string> + <string name="hours">timmar</string> + <string name="mins">minuter</string> + <string name="missing_public_keys">Annonsering om publik nyckel saknas</string> + <string name="last_seen_now">senast sedd just nu</string> + <string name="last_seen_min">senast sedd 1 minut sedan</string> + <string name="last_seen_mins">senast sedd %d minuter sedan</string> + <string name="last_seen_hour">senast sedd 1 timme sedan</string> + <string name="last_seen_hours">senast sedd %d timmar sedan</string> + <string name="last_seen_day">senast sedd 1 dag sedan</string> + <string name="last_seen_days">senast sedd %d dagar sedan</string> + <string name="never_seen">aldrig sedd</string> + <string name="install_openkeychain">Krypterat meddelande. Installera OpenKeychain för att avkryptera.</string> + <string name="unknown_otr_fingerprint">Okänt OTR-fingeravtryck</string> + <string name="edit_conference_details">Tryck för att ändra konferensdetaljer</string> + <string name="openpgp_messages_found">OpenPGP-krypterat meddelande funnet</string> + <string name="openpgp_click_to_decrypt">Tryck här för att ange passfras och dektryptera meddelande</string> + <string name="reception_failed">Mottagning misslyckades</string> + <string name="no_muc_server_found">Ingen passande konferensserver hittades</string> + <string name="your_fingerprint">Ditt fingeravtryck</string> + <string name="otr_fingerprint">OTR-fingeravtryck</string> + <string name="verify">Verifiera</string> + <string name="decrypt">Avkryptera</string> + <string name="conferences">Konferenser</string> + <string name="search">Sök</string> + <string name="create_contact">Skapa kontakt</string> + <string name="join_conference">Gå med i konferens</string> + <string name="delete_contact">Ta bort kontakt</string> + <string name="view_contact_details">Se kontaktdetaljer</string> + <string name="create">Skapa</string> + <string name="contact_already_exists">Kontakten finns redan</string> + <string name="join">Gå med</string> + <string name="conference_address">Konferensadress</string> + <string name="conference_address_example">rum@conference.exempel.se</string> + <string name="save_as_bookmark">Spara som bokmärke</string> + <string name="delete_bookmark">Ta bort bokmärke</string> + <string name="bookmark_already_exists">Detta bokmärke finns redan</string> + <string name="you">Du</string> + <string name="action_edit_subject">Ändra konferensämne</string> + <string name="conference_not_found">Konferens hittades inte</string> + <string name="leave">Lämna</string> + <string name="contact_added_you">Kontakten lade till dig i sin kontaktlista</string> + <string name="add_back">Addera tillbaks</string> + <string name="contact_has_read_up_to_this_point">%s har läst fram hit</string> +</resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index b6477939..ed0a0ffb 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -9,5 +9,5 @@ <color name="secondarybackground" type="color">#ffeeeeee</color> <color name="darkbackground" type="color">#ff323232</color> <color name="divider">#1f000000</color> - <color name="red">#ffe51c23</color> + <color name="warningtext">#ffe51c23</color> </resources>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index c636be79..00137f1c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -187,6 +187,7 @@ <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_publish_avatar">Publish avatar</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> @@ -268,4 +269,19 @@ <string name="contact_added_you">Contact added you to contact list</string> <string name="add_back">Add back</string> <string name="contact_has_read_up_to_this_point">%s has read up to this point</string> -</resources> + <string name="publish_avatar">Publish avatar</string> + <string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string> + <string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string> + <string name="publishing">Publishing…</string> + <string name="error_publish_avatar_server_reject">The server rejected your publication</string> + <string name="error_publish_avatar_converting">Something went wrong while converting your picture</string> + <string name="error_saving_avatar">Could not save avatar to disk</string> + <string name="or_long_press_for_default">(Or long press to bring back default)</string> + <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string> + <string name="private_message">in private</string> + <string name="private_message_to">in private to %s</string> + <string name="send_private_message_to">Send private message to %s</string> + <string name="connect">Connect</string> + <string name="account_already_exists">This account does already exist</string> + <string name="next">Next</string> +</resources>
\ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 40039cd5..b64a477d 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -63,11 +63,6 @@ <PreferenceCategory android:title="@string/pref_ui_options"> <CheckBoxPreference - android:key="show_phone_selfcontact_picture" - android:title="@string/pref_use_phone_self_picture" - android:summary="@string/pref_use_phone_self_picture_summary" - android:defaultValue="true"/> - <CheckBoxPreference android:key="use_subject_in_muc" android:title="@string/pref_conference_name" android:summary="@string/pref_conference_name_summary" diff --git a/src/eu/siacs/conversations/crypto/OtrEngine.java b/src/eu/siacs/conversations/crypto/OtrEngine.java index 01ba5e49..7960aa2b 100644 --- a/src/eu/siacs/conversations/crypto/OtrEngine.java +++ b/src/eu/siacs/conversations/crypto/OtrEngine.java @@ -154,13 +154,16 @@ public class OtrEngine implements OtrEngineHost { @Override public void injectMessage(SessionID session, String body) throws OtrException { MessagePacket packet = new MessagePacket(); - packet.setFrom(account.getFullJid()); //sender - packet.setTo(session.getAccountID()+"/"+session.getUserID()); //reciepient + packet.setFrom(account.getFullJid()); + if (session.getUserID().isEmpty()) { + packet.setTo(session.getAccountID()); + } else { + packet.setTo(session.getAccountID()+"/"+session.getUserID()); + } packet.setBody(body); packet.addChild("private","urn:xmpp:carbons:2"); packet.addChild("no-copy","urn:xmpp:hints"); packet.setType(MessagePacket.TYPE_CHAT); - //Log.d(LOGTAG,packet.toString()); account.getXmppConnection().sendMessagePacket(packet); } diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java index 2d0c56e1..65b7ccc7 100644 --- a/src/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/eu/siacs/conversations/crypto/PgpEngine.java @@ -54,9 +54,18 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - message.setBody(os.toString()); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - callback.success(message); + try { + os.flush(); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + message.setBody(os.toString()); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + callback.success(message); + } + } catch (IOException e) { + callback.error(R.string.openpgp_error, message); + return; + } + return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: callback.userInputRequried((PendingIntent) result @@ -64,6 +73,8 @@ public class PgpEngine { message); return; case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + Log.d("xmppService",error.getMessage()); callback.error(R.string.openpgp_error, message); return; default: @@ -153,14 +164,20 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - StringBuilder encryptedMessageBody = new StringBuilder(); - String[] lines = os.toString().split("\n"); - for (int i = 3; i < lines.length - 1; ++i) { - encryptedMessageBody.append(lines[i].trim()); + try { + os.flush(); + StringBuilder encryptedMessageBody = new StringBuilder(); + String[] lines = os.toString().split("\n"); + for (int i = 3; i < lines.length - 1; ++i) { + encryptedMessageBody.append(lines[i].trim()); + } + message.setEncryptedBody(encryptedMessageBody + .toString()); + callback.success(message); + } catch (IOException e) { + callback.error(R.string.openpgp_error, message); } - message.setEncryptedBody(encryptedMessageBody - .toString()); - callback.success(message); + break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: callback.userInputRequried((PendingIntent) result diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index b19889bf..5de2c532 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.entities; import java.security.interfaces.DSAPublicKey; -import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; @@ -13,10 +12,14 @@ import org.json.JSONException; import org.json.JSONObject; import eu.siacs.conversations.crypto.OtrEngine; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.XmppConnection; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; public class Account extends AbstractEntity{ @@ -42,8 +45,6 @@ public class Account extends AbstractEntity{ public static final int STATUS_UNAUTHORIZED = 3; public static final int STATUS_SERVER_NOT_FOUND = 5; - public static final int STATUS_SERVER_REQUIRES_TLS = 6; - public static final int STATUS_REGISTRATION_FAILED = 7; public static final int STATUS_REGISTRATION_CONFLICT = 8; public static final int STATUS_REGISTRATION_SUCCESSFULL = 9; @@ -57,6 +58,7 @@ public class Account extends AbstractEntity{ protected String resource = "mobile"; protected int status = -1; protected JSONObject keys = new JSONObject(); + protected String avatar; protected boolean online = false; @@ -68,7 +70,7 @@ public class Account extends AbstractEntity{ private Roster roster = null; - private List<Bookmark> bookmarks = new ArrayList<Bookmark>(); + private List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>(); public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>(); public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>(); @@ -142,6 +144,11 @@ public class Account extends AbstractEntity{ } } + public boolean errorStatus() { + int s = getStatus(); + return (s == STATUS_OFFLINE || s == STATUS_SERVER_NOT_FOUND || s == STATUS_UNAUTHORIZED); + } + public boolean hasErrorStatus() { return getStatus() > STATUS_NO_INTERNET && (getXmppConnection().getAttempt() >= 2); } @@ -319,4 +326,25 @@ public class Account extends AbstractEntity{ } return false; } + + public Bitmap getImage(Context context, int size) { + if (this.avatar!=null) { + Bitmap bm = FileBackend.getAvatar(this.avatar, size, context); + if (bm==null) { + return UIHelper.getContactPicture(getJid(), size, context, false); + } else { + return bm; + } + } else { + return UIHelper.getContactPicture(getJid(), size, context, false); + } + } + + public void setAvatar(String filename) { + this.avatar = filename; + } + + public String getAvatar() { + return this.avatar; + } } diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java index 8f8e38a5..47a3a0d7 100644 --- a/src/eu/siacs/conversations/entities/Contact.java +++ b/src/eu/siacs/conversations/entities/Contact.java @@ -8,12 +8,14 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; public class Contact implements ListItem { public static final String TABLENAME = "contacts"; @@ -34,6 +36,7 @@ public class Contact implements ListItem { protected int subscription = 0; protected String systemAccount; protected String photoUri; + protected String avatar; protected JSONObject keys = new JSONObject(); protected Presences presences = new Presences(); @@ -316,7 +319,20 @@ public class Contact implements ListItem { } @Override - public Bitmap getImage(int dpSize, Context context) { - return UIHelper.getContactPicture(this, dpSize, context, false); + public Bitmap getImage(int size, Context context) { + if (this.avatar!=null) { + Bitmap bm = FileBackend.getAvatar(avatar, size, context); + if (bm==null) { + return UIHelper.getContactPicture(this, size, context, false); + } else { + return bm; + } + } else { + return UIHelper.getContactPicture(this, size, context, false); + } + } + + public void setAvatar(String filename) { + this.avatar = filename; } } diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index 76fe84cf..be641ea4 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -1,8 +1,10 @@ package eu.siacs.conversations.entities; import java.security.interfaces.DSAPublicKey; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import eu.siacs.conversations.utils.UIHelper; import net.java.otr4j.OtrException; import net.java.otr4j.crypto.OtrCryptoEngineImpl; @@ -13,7 +15,7 @@ import net.java.otr4j.session.SessionStatus; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.net.Uri; +import android.graphics.Bitmap; public class Conversation extends AbstractEntity { public static final String TABLENAME = "conversations"; @@ -43,7 +45,7 @@ public class Conversation extends AbstractEntity { private String nextPresence; - private transient List<Message> messages = null; + private transient CopyOnWriteArrayList<Message> messages = null; private transient Account account = null; private transient SessionImpl otrSession; @@ -85,8 +87,9 @@ public class Conversation extends AbstractEntity { } public List<Message> getMessages() { - if (messages == null) - this.messages = new ArrayList<Message>(); // prevent null pointer + if (messages == null) { + this.messages = new CopyOnWriteArrayList<Message>(); // prevent null pointer + } // populate with Conversation (this) @@ -133,7 +136,7 @@ public class Conversation extends AbstractEntity { } } - public void setMessages(List<Message> msgs) { + public void setMessages(CopyOnWriteArrayList<Message> msgs) { this.messages = msgs; } @@ -173,13 +176,6 @@ public class Conversation extends AbstractEntity { return this.contactJid; } - public Uri getProfilePhotoUri() { - if (this.getProfilePhotoString() != null) { - return Uri.parse(this.getProfilePhotoString()); - } - return null; - } - public int getStatus() { return this.status; } @@ -395,4 +391,12 @@ public class Conversation extends AbstractEntity { public Bookmark getBookmark() { return this.bookmark; } + + public Bitmap getImage(Context context, int size) { + if (mode==MODE_SINGLE) { + return getContact().getImage(size, context); + } else { + return UIHelper.getContactPicture(this, size, context, false); + } + } } diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java index 49c5ce58..17b4e5b8 100644 --- a/src/eu/siacs/conversations/entities/Message.java +++ b/src/eu/siacs/conversations/entities/Message.java @@ -33,9 +33,11 @@ public class Message extends AbstractEntity { public static final int TYPE_IMAGE = 1; public static final int TYPE_AUDIO = 2; public static final int TYPE_STATUS = 3; + public static final int TYPE_PRIVATE = 4; public static String CONVERSATION = "conversationUuid"; public static String COUNTERPART = "counterpart"; + public static String TRUE_COUNTERPART = "trueCounterpart"; public static String BODY = "body"; public static String TIME_SENT = "timeSent"; public static String ENCRYPTION = "encryption"; @@ -44,6 +46,7 @@ public class Message extends AbstractEntity { protected String conversationUuid; protected String counterpart; + protected String trueCounterpart; protected String body; protected String encryptedBody; protected long timeSent; @@ -62,21 +65,22 @@ public class Message extends AbstractEntity { public Message(Conversation conversation, String body, int encryption) { this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), - conversation.getContactJid(), body, System.currentTimeMillis(), encryption, + conversation.getContactJid(), null, body, System.currentTimeMillis(), encryption, Message.STATUS_UNSEND,TYPE_TEXT); this.conversation = conversation; } public Message(Conversation conversation, String counterpart, String body, int encryption, int status) { - this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),counterpart, body, System.currentTimeMillis(), encryption,status,TYPE_TEXT); + this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),counterpart, null, body, System.currentTimeMillis(), encryption,status,TYPE_TEXT); this.conversation = conversation; } - public Message(String uuid, String conversationUUid, String counterpart, + public Message(String uuid, String conversationUUid, String counterpart, String trueCounterpart, String body, long timeSent, int encryption, int status, int type) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; + this.trueCounterpart = trueCounterpart; this.body = body; this.timeSent = timeSent; this.encryption = encryption; @@ -90,6 +94,7 @@ public class Message extends AbstractEntity { values.put(UUID, uuid); values.put(CONVERSATION, conversationUuid); values.put(COUNTERPART, counterpart); + values.put(TRUE_COUNTERPART,trueCounterpart); values.put(BODY, body); values.put(TIME_SENT, timeSent); values.put(ENCRYPTION, encryption); @@ -109,6 +114,24 @@ public class Message extends AbstractEntity { public String getCounterpart() { return counterpart; } + + public Contact getContact() { + if (this.conversation.getMode() == Conversation.MODE_SINGLE) { + return this.conversation.getContact(); + } else { + if (this.trueCounterpart == null) { + return null; + } else { + Account account = this.conversation.getAccount(); + Contact contact = account.getRoster().getContact(this.trueCounterpart); + if (contact.showInRoster()) { + return contact; + } else { + return null; + } + } + } + } public String getBody() { return body; @@ -144,6 +167,7 @@ public class Message extends AbstractEntity { return new Message(cursor.getString(cursor.getColumnIndex(UUID)), cursor.getString(cursor.getColumnIndex(CONVERSATION)), cursor.getString(cursor.getColumnIndex(COUNTERPART)), + cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)), cursor.getString(cursor.getColumnIndex(BODY)), cursor.getLong(cursor.getColumnIndex(TIME_SENT)), cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), @@ -200,13 +224,17 @@ public class Message extends AbstractEntity { } public void setPresence(String presence) { - if (presence == null) { + if (presence == null || presence.isEmpty()) { this.counterpart = this.counterpart.split("/")[0]; } else { this.counterpart = this.counterpart.split("/")[0] + "/" + presence; } } + public void setTrueCounterpart(String trueCounterpart) { + this.trueCounterpart = trueCounterpart; + } + public String getPresence() { String[] counterparts = this.counterpart.split("/"); if (counterparts.length == 2) { @@ -230,4 +258,8 @@ public class Message extends AbstractEntity { message.setConversation(conversation); return message; } + + public void setCounterpart(String counterpart) { + this.counterpart = counterpart; + } } diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java index 0bb9b295..61b2732d 100644 --- a/src/eu/siacs/conversations/entities/MucOptions.java +++ b/src/eu/siacs/conversations/entities/MucOptions.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.entities; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.xml.Element; @@ -13,11 +14,11 @@ public class MucOptions { public static final int ERROR_NO_ERROR = 0; public static final int ERROR_NICK_IN_USE = 1; public static final int ERROR_ROOM_NOT_FOUND = 2; - + public interface OnRenameListener { public void onRename(boolean success); } - + public class User { public static final int ROLE_MODERATOR = 3; public static final int ROLE_NONE = 0; @@ -28,22 +29,33 @@ public class MucOptions { public static final int AFFILIATION_MEMBER = 2; public static final int AFFILIATION_OUTCAST = 1; public static final int AFFILIATION_NONE = 0; - + private int role; private int affiliation; private String name; + private String jid; private long pgpKeyId = 0; - + public String getName() { return name; } + public void setName(String user) { this.name = user; } - + + public void setJid(String jid) { + this.jid = jid; + } + + public String getJid() { + return this.jid; + } + public int getRole() { return this.role; } + public void setRole(String role) { role = role.toLowerCase(); if (role.equals("moderator")) { @@ -56,9 +68,11 @@ public class MucOptions { this.role = ROLE_NONE; } } + public int getAffiliation() { return this.affiliation; } + public void setAffiliation(String affiliation) { if (affiliation.equalsIgnoreCase("admin")) { this.affiliation = AFFILIATION_ADMIN; @@ -72,16 +86,18 @@ public class MucOptions { this.affiliation = AFFILIATION_NONE; } } + public void setPgpKeyId(long id) { this.pgpKeyId = id; } - + public long getPgpKeyId() { return this.pgpKeyId; } } + private Account account; - private ArrayList<User> users = new ArrayList<User>(); + private List<User> users = new CopyOnWriteArrayList<User>(); private Conversation conversation; private boolean isOnline = false; private int error = ERROR_ROOM_NOT_FOUND; @@ -94,44 +110,47 @@ public class MucOptions { public MucOptions(Account account) { this.account = account; } - + public void deleteUser(String name) { - for(int i = 0; i < users.size(); ++i) { + for (int i = 0; i < users.size(); ++i) { if (users.get(i).getName().equals(name)) { users.remove(i); return; } } } - + public void addUser(User user) { - for(int i = 0; i < users.size(); ++i) { + for (int i = 0; i < users.size(); ++i) { if (users.get(i).getName().equals(user.getName())) { users.set(i, user); return; } } users.add(user); - } - + } + public void processPacket(PresencePacket packet, PgpEngine pgp) { String[] fromParts = packet.getFrom().split("/"); - if (fromParts.length>=2) { + if (fromParts.length >= 2) { String name = fromParts[1]; String type = packet.getAttribute("type"); - if (type==null) { + if (type == null) { User user = new User(); - Element item = packet.findChild("x","http://jabber.org/protocol/muc#user").findChild("item"); + Element item = packet.findChild("x", + "http://jabber.org/protocol/muc#user") + .findChild("item"); user.setName(name); user.setAffiliation(item.getAttribute("affiliation")); user.setRole(item.getAttribute("role")); + user.setJid(item.getAttribute("jid")); user.setName(name); if (name.equals(this.joinnick)) { this.isOnline = true; this.error = ERROR_NO_ERROR; self = user; if (aboutToRename) { - if (renameListener!=null) { + if (renameListener != null) { renameListener.onRename(true); } aboutToRename = false; @@ -140,8 +159,7 @@ public class MucOptions { addUser(user); } if (pgp != null) { - Element x = packet.findChild("x", - "jabber:x:signed"); + Element x = packet.findChild("x", "jabber:x:signed"); if (x != null) { Element status = packet.findChild("status"); String msg; @@ -150,7 +168,8 @@ public class MucOptions { } else { msg = ""; } - user.setPgpKeyId(pgp.fetchKeyId(account,msg, x.getContent())); + user.setPgpKeyId(pgp.fetchKeyId(account, msg, + x.getContent())); } } } else if (type.equals("unavailable")) { @@ -159,26 +178,27 @@ public class MucOptions { Element error = packet.findChild("error"); if (error.hasChild("conflict")) { if (aboutToRename) { - if (renameListener!=null) { + if (renameListener != null) { renameListener.onRename(false); } aboutToRename = false; this.setJoinNick(getActualNick()); } else { - this.error = ERROR_NICK_IN_USE; + this.error = ERROR_NICK_IN_USE; } } } } } - + public List<User> getUsers() { return this.users; } - + public String getProposedNick() { String[] mucParts = conversation.getContactJid().split("/"); - if (conversation.getBookmark() != null && conversation.getBookmark().getNick() != null) { + if (conversation.getBookmark() != null + && conversation.getBookmark().getNick() != null) { return conversation.getBookmark().getNick(); } else { if (mucParts.length == 2) { @@ -188,27 +208,27 @@ public class MucOptions { } } } - + public String getActualNick() { - if (this.self.getName()!=null) { + if (this.self.getName() != null) { return this.self.getName(); } else { return this.getProposedNick(); } } - + public void setJoinNick(String nick) { this.joinnick = nick; } - + public void setConversation(Conversation conversation) { this.conversation = conversation; } - + public boolean online() { return this.isOnline; } - + public int getError() { return this.error; } @@ -216,7 +236,7 @@ public class MucOptions { public void setOnRenameListener(OnRenameListener listener) { this.renameListener = listener; } - + public OnRenameListener getOnRenameListener() { return this.renameListener; } @@ -234,7 +254,7 @@ public class MucOptions { public void setSubject(String content) { this.subject = content; } - + public String getSubject() { return this.subject; } @@ -242,33 +262,33 @@ public class MucOptions { public void flagAboutToRename() { this.aboutToRename = true; } - + public long[] getPgpKeyIds() { List<Long> ids = new ArrayList<Long>(); - for(User user : getUsers()) { - if(user.getPgpKeyId()!=0) { + for (User user : getUsers()) { + if (user.getPgpKeyId() != 0) { ids.add(user.getPgpKeyId()); } } long[] primitivLongArray = new long[ids.size()]; - for(int i = 0; i < ids.size(); ++i) { + for (int i = 0; i < ids.size(); ++i) { primitivLongArray[i] = ids.get(i); } return primitivLongArray; } - + public boolean pgpKeysInUse() { - for(User user : getUsers()) { - if (user.getPgpKeyId()!=0) { + for (User user : getUsers()) { + if (user.getPgpKeyId() != 0) { return true; } } return false; } - + public boolean everybodyHasKeys() { - for(User user : getUsers()) { - if (user.getPgpKeyId()==0) { + for (User user : getUsers()) { + if (user.getPgpKeyId() == 0) { return false; } } @@ -276,6 +296,16 @@ public class MucOptions { } public String getJoinJid() { - return this.conversation.getContactJid().split("/")[0]+"/"+this.joinnick; + return this.conversation.getContactJid().split("/")[0] + "/" + + this.joinnick; + } + + public String getTrueCounterpart(String counterpart) { + for(User user : this.getUsers()) { + if (user.getName().equals(counterpart)) { + return user.getJid(); + } + } + return null; } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/generator/AbstractGenerator.java b/src/eu/siacs/conversations/generator/AbstractGenerator.java index 05d5799c..d9839572 100644 --- a/src/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/eu/siacs/conversations/generator/AbstractGenerator.java @@ -18,7 +18,8 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/muc", "jabber:x:conference", "http://jabber.org/protocol/caps", - "http://jabber.org/protocol/disco#info"}; + "http://jabber.org/protocol/disco#info", + "urn:xmpp:avatar:metadata+notify"}; public final String IDENTITY_NAME = "Conversations 0.5"; public final String IDENTITY_TYPE = "phone"; /*public final String[] FEATURES = { "http://jabber.org/protocol/muc","http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/disco#items", "http://jabber.org/protocol/caps" }; @@ -45,6 +46,6 @@ public abstract class AbstractGenerator { s.append(feature+"<"); } byte[] sha1 = md.digest(s.toString().getBytes()); - return new String(Base64.encode(sha1, Base64.DEFAULT)); + return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } } diff --git a/src/eu/siacs/conversations/generator/IqGenerator.java b/src/eu/siacs/conversations/generator/IqGenerator.java index 7b3350d4..259538c2 100644 --- a/src/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/eu/siacs/conversations/generator/IqGenerator.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { @@ -28,4 +29,61 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + protected IqPacket publish(String node, Element item) { + IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); + Element publish = pubsub.addChild("publish"); + publish.setAttribute("node", node); + publish.addChild(item); + return packet; + } + + protected IqPacket retrieve(String node, Element item) { + IqPacket packet = new IqPacket(IqPacket.TYPE_GET); + Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); + Element items = pubsub.addChild("items"); + items.setAttribute("node", node); + if (item!=null) { + items.addChild(item); + } + return packet; + } + + public IqPacket publishAvatar(Avatar avatar) { + Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + Element data = item.addChild("data","urn:xmpp:avatar:data"); + data.setContent(avatar.image); + return publish("urn:xmpp:avatar:data", item); + } + + public IqPacket publishAvatarMetadata(Avatar avatar) { + Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + Element metadata = item.addChild("metadata","urn:xmpp:avatar:metadata"); + Element info = metadata.addChild("info"); + info.setAttribute("bytes",avatar.size); + info.setAttribute("id",avatar.sha1sum); + info.setAttribute("height",avatar.height); + info.setAttribute("width",avatar.height); + info.setAttribute("type", avatar.type); + return publish("urn:xmpp:avatar:metadata",item); + } + + public IqPacket retrieveAvatar(Avatar avatar) { + Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + IqPacket packet = retrieve("urn:xmpp:avatar:data", item); + packet.setTo(avatar.owner); + return packet; + } + + public IqPacket retrieveAvatarMetaData(String to) { + IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); + if (to!=null) { + packet.setTo(to); + } + return packet; + } } diff --git a/src/eu/siacs/conversations/generator/MessageGenerator.java b/src/eu/siacs/conversations/generator/MessageGenerator.java index 4449a7ec..26182aad 100644 --- a/src/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/eu/siacs/conversations/generator/MessageGenerator.java @@ -22,6 +22,9 @@ public class MessageGenerator { packet.setTo(message.getCounterpart()); packet.setType(MessagePacket.TYPE_CHAT); packet.addChild("markable", "urn:xmpp:chat-markers:0"); + } else if (message.getType() == Message.TYPE_PRIVATE) { + packet.setTo(message.getCounterpart()); + packet.setType(MessagePacket.TYPE_CHAT); } else { packet.setTo(message.getCounterpart().split("/")[0]); packet.setType(MessagePacket.TYPE_GROUPCHAT); diff --git a/src/eu/siacs/conversations/parser/AbstractParser.java b/src/eu/siacs/conversations/parser/AbstractParser.java index c4c6720a..96d11508 100644 --- a/src/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/eu/siacs/conversations/parser/AbstractParser.java @@ -63,6 +63,8 @@ public abstract class AbstractParser { String presence = null; if (fromParts.length >= 2) { presence = fromParts[1]; + } else { + presence = ""; } Contact contact = account.getRoster().getContact(from); long timestamp = getTimestamp(packet); @@ -73,4 +75,16 @@ public abstract class AbstractParser { } } } + + protected String avatarData(Element items) { + Element item = items.findChild("item"); + if (item==null) { + return null; + } + Element data = item.findChild("data","urn:xmpp:avatar:data"); + if (data==null) { + return null; + } + return data.getContent(); + } } diff --git a/src/eu/siacs/conversations/parser/IqParser.java b/src/eu/siacs/conversations/parser/IqParser.java index 023fb4df..a22ff6a5 100644 --- a/src/eu/siacs/conversations/parser/IqParser.java +++ b/src/eu/siacs/conversations/parser/IqParser.java @@ -27,19 +27,33 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { 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); - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - } else { - contact.setOption(Contact.Options.IN_ROSTER); - contact.resetOption(Contact.Options.DIRTY_PUSH); - contact.parseSubscriptionFromElement(item); + if (subscription!=null) { + if (subscription.equals("remove")) { + contact.resetOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_DELETE); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } else { + contact.setOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_PUSH); + contact.parseSubscriptionFromElement(item); + } } } } mXmppConnectionService.updateRosterUi(); } + + public String avatarData(IqPacket packet) { + Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub==null) { + return null; + } + Element items = pubsub.findChild("items"); + if (items==null) { + return null; + } + return super.avatarData(items); + } @Override public void onIqPacketReceived(Account account, IqPacket packet) { diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index a4fcc810..7de7e9e1 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -1,15 +1,18 @@ package eu.siacs.conversations.parser; import android.os.SystemClock; +import android.util.Log; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; 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.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageParser extends AbstractParser implements @@ -37,6 +40,14 @@ public class MessageParser extends AbstractParser implements packet.getBody(), Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED); } + if (conversation.getMode() == Conversation.MODE_MULTI + && fromParts.length >= 2) { + finishedMessage.setType(Message.TYPE_PRIVATE); + finishedMessage.setPresence(fromParts[1]); + finishedMessage.setTrueCounterpart(conversation.getMucOptions() + .getTrueCounterpart(fromParts[1])); + + } finishedMessage.setTime(getTimestamp(packet)); return finishedMessage; } @@ -47,25 +58,31 @@ public class MessageParser extends AbstractParser implements String[] fromParts = packet.getFrom().split("/"); Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, fromParts[0], false); + String presence; + if (fromParts.length >= 2) { + presence = fromParts[1]; + } else { + presence = ""; + } updateLastseen(packet, account, true); String body = packet.getBody(); if (!conversation.hasValidOtrSession()) { if (properlyAddressed) { conversation.startOtrSession( mXmppConnectionService.getApplicationContext(), - fromParts[1], false); + presence, false); } else { return null; } } else { String foreignPresence = conversation.getOtrSession() .getSessionID().getUserID(); - if (!foreignPresence.equals(fromParts[1])) { + if (!foreignPresence.equals(presence)) { conversation.endOtrIfNeeded(); if (properlyAddressed) { conversation.startOtrSession( mXmppConnectionService.getApplicationContext(), - fromParts[1], false); + presence, false); } else { return null; } @@ -110,7 +127,8 @@ public class MessageParser extends AbstractParser implements private Message parseGroupchat(MessagePacket packet, Account account) { int status; String[] fromParts = packet.getFrom().split("/"); - if (mXmppConnectionService.find(account.pendingConferenceLeaves,account,fromParts[0]) != null) { + if (mXmppConnectionService.find(account.pendingConferenceLeaves, + account, fromParts[0]) != null) { return null; } Conversation conversation = mXmppConnectionService @@ -146,6 +164,10 @@ public class MessageParser extends AbstractParser implements Message.ENCRYPTION_PGP, status); } finishedMessage.setTime(getTimestamp(packet)); + if (status == Message.STATUS_RECIEVED) { + finishedMessage.setTrueCounterpart(conversation.getMucOptions() + .getTrueCounterpart(counterPart)); + } return finishedMessage; } @@ -167,24 +189,29 @@ public class MessageParser extends AbstractParser implements } Element message = forwarded.findChild("message"); if ((message == null) || (!message.hasChild("body"))) { - if (status == Message.STATUS_RECIEVED) { + if (status == Message.STATUS_RECIEVED && message.getAttribute("from")!=null) { parseNormal(message, account); } return null; } if (status == Message.STATUS_RECIEVED) { fullJid = message.getAttribute("from"); - updateLastseen(message, account, true); + if (fullJid == null) { + return null; + } else { + updateLastseen(message, account, true); + } } else { fullJid = message.getAttribute("to"); - } - if (fullJid==null) { - return null; + if (fullJid == null) { + return null; + } } String[] parts = fullJid.split("/"); Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, parts[0], false); conversation.setLatestMarkableMessageId(getMarkableMessageId(packet)); + String pgpBody = getPgpBody(message); Message finishedMessage; if (pgpBody != null) { @@ -196,6 +223,16 @@ public class MessageParser extends AbstractParser implements Message.ENCRYPTION_NONE, status); } finishedMessage.setTime(getTimestamp(message)); + + if (conversation.getMode() == Conversation.MODE_MULTI + && parts.length >= 2) { + finishedMessage.setType(Message.TYPE_PRIVATE); + finishedMessage.setPresence(parts[1]); + finishedMessage.setTrueCounterpart(conversation.getMucOptions() + .getTrueCounterpart(parts[1])); + + } + return finishedMessage; } @@ -206,6 +243,11 @@ public class MessageParser extends AbstractParser implements } private void parseNormal(Element packet, Account account) { + if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) { + Element event = packet.findChild("event", + "http://jabber.org/protocol/pubsub#event"); + parseEvent(event, packet.getAttribute("from"), account); + } if (packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) { String id = packet .findChild("displayed", "urn:xmpp:chat-markers:0") @@ -221,8 +263,9 @@ public class MessageParser extends AbstractParser implements updateLastseen(packet, account, false); mXmppConnectionService.markMessage(account, fromParts[0], id, Message.STATUS_SEND_RECEIVED); - } else if (packet.hasChild("x","http://jabber.org/protocol/muc#user")) { - Element x = packet.findChild("x","http://jabber.org/protocol/muc#user"); + } else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { + Element x = packet.findChild("x", + "http://jabber.org/protocol/muc#user"); if (x.hasChild("invite")) { Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, @@ -230,15 +273,15 @@ public class MessageParser extends AbstractParser implements if (!conversation.getMucOptions().online()) { mXmppConnectionService.joinMuc(conversation); mXmppConnectionService.updateConversationUi(); - } + } } } else if (packet.hasChild("x", "jabber:x:conference")) { Element x = packet.findChild("x", "jabber:x:conference"); String jid = x.getAttribute("jid"); - if (jid!=null) { + if (jid != null) { Conversation conversation = mXmppConnectionService - .findOrCreateConversation(account,jid, true); + .findOrCreateConversation(account, jid, true); if (!conversation.getMucOptions().online()) { mXmppConnectionService.joinMuc(conversation); mXmppConnectionService.updateConversationUi(); @@ -247,6 +290,35 @@ public class MessageParser extends AbstractParser implements } } + private void parseEvent(Element event, String from, Account account) { + Element items = event.findChild("items"); + String node = items.getAttribute("node"); + if (node != null) { + if (node.equals("urn:xmpp:avatar:metadata")) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar!=null) { + avatar.owner = from; + if (mXmppConnectionService.getFileBackend().isAvatarCached( + avatar)) { + if (account.getJid().equals(from)) { + account.setAvatar(avatar.getFilename()); + } else { + Contact contact = account.getRoster().getContact(from); + contact.setAvatar(avatar.getFilename()); + } + } else { + mXmppConnectionService.fetchAvatar(account, avatar); + } + } + } else { + Log.d("xmppService", account.getJid() + ": " + node + " from " + + from); + } + } else { + Log.d("xmppService", event.toString()); + } + } + private String getPgpBody(Element message) { Element child = message.findChild("x", "jabber:x:encrypted"); if (child == null) { @@ -306,8 +378,7 @@ public class MessageParser extends AbstractParser implements message.markUnread(); } else { message.getConversation().markRead(); - lastCarbonMessageReceived = SystemClock - .elapsedRealtime(); + lastCarbonMessageReceived = SystemClock.elapsedRealtime(); notify = false; } } @@ -317,6 +388,9 @@ public class MessageParser extends AbstractParser implements } else if (packet.getType() == MessagePacket.TYPE_NORMAL) { this.parseNormal(packet, account); return; + } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) { + this.parseHeadline(packet, account); + return; } if ((message == null) || (message.getBody() == null)) { return; @@ -324,11 +398,15 @@ public class MessageParser extends AbstractParser implements if ((mXmppConnectionService.confirmMessages()) && ((packet.getId() != null))) { if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0"); + MessagePacket receipt = mXmppConnectionService + .getMessageGenerator().received(account, packet, + "urn:xmpp:chat-markers:0"); mXmppConnectionService.sendMessagePacket(account, receipt); } if (packet.hasChild("request", "urn:xmpp:receipts")) { - MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:receipts"); + MessagePacket receipt = mXmppConnectionService + .getMessageGenerator().received(account, packet, + "urn:xmpp:receipts"); mXmppConnectionService.sendMessagePacket(account, receipt); } } @@ -339,4 +417,12 @@ public class MessageParser extends AbstractParser implements } mXmppConnectionService.notifyUi(conversation, notify); } + + private void parseHeadline(MessagePacket packet, Account account) { + if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) { + Element event = packet.findChild("event", + "http://jabber.org/protocol/pubsub#event"); + parseEvent(event, packet.getFrom(), account); + } + } } diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java index 33f4185f..ea19df6f 100644 --- a/src/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/eu/siacs/conversations/parser/PresenceParser.java @@ -13,7 +13,7 @@ import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceParser extends AbstractParser implements OnPresencePacketReceived { - + public PresenceParser(XmppConnectionService service) { super(service); } @@ -21,23 +21,31 @@ public class PresenceParser extends AbstractParser implements public void parseConferencePresence(PresencePacket packet, Account account) { PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { - Conversation muc = mXmppConnectionService.find(account,packet + Conversation muc = mXmppConnectionService.find(account, packet .getAttribute("from").split("/")[0]); if (muc != null) { + boolean before = muc.getMucOptions().online(); muc.getMucOptions().processPacket(packet, mPgpEngine); + if (before!=muc.getMucOptions().online()) { + mXmppConnectionService.updateConversationUi(); + } } } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { - Conversation muc = mXmppConnectionService.find(account,packet + Conversation muc = mXmppConnectionService.find(account, packet .getAttribute("from").split("/")[0]); if (muc != null) { + boolean before = muc.getMucOptions().online(); muc.getMucOptions().processPacket(packet, mPgpEngine); + if (before!=muc.getMucOptions().online()) { + mXmppConnectionService.updateConversationUi(); + } } } - mXmppConnectionService.updateConversationUi(); } public void parseContactPresence(PresencePacket packet, Account account) { - PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); + PresenceGenerator mPresenceGenerator = mXmppConnectionService + .getPresenceGenerator(); if (packet.getFrom() == null) { return; } @@ -56,30 +64,34 @@ public class PresenceParser extends AbstractParser implements } else { Contact contact = account.getRoster().getContact(packet.getFrom()); if (type == null) { - if (fromParts.length == 2) { - int sizeBefore = contact.getPresences().size(); - contact.updatePresence(fromParts[1], - Presences.parseShow(packet.findChild("show"))); - PgpEngine pgp = mXmppConnectionService.getPgpEngine(); - if (pgp != null) { - Element x = packet.findChild("x", "jabber:x:signed"); - if (x != null) { - Element status = packet.findChild("status"); - String msg; - if (status != null) { - msg = status.getContent(); - } else { - msg = ""; - } - contact.setPgpKeyId(pgp.fetchKeyId(account, msg, - x.getContent())); + String presence; + if (fromParts.length >= 2) { + presence = fromParts[1]; + } else { + presence = ""; + } + int sizeBefore = contact.getPresences().size(); + contact.updatePresence(presence, + Presences.parseShow(packet.findChild("show"))); + PgpEngine pgp = mXmppConnectionService.getPgpEngine(); + if (pgp != null) { + Element x = packet.findChild("x", "jabber:x:signed"); + if (x != null) { + Element status = packet.findChild("status"); + String msg; + if (status != null) { + msg = status.getContent(); + } else { + msg = ""; } + contact.setPgpKeyId(pgp.fetchKeyId(account, msg, + x.getContent())); } - boolean online = sizeBefore < contact.getPresences().size(); - updateLastseen(packet, account, true); - mXmppConnectionService.onContactStatusChanged - .onContactStatusChanged(contact, online); } + boolean online = sizeBefore < contact.getPresences().size(); + updateLastseen(packet, account, true); + mXmppConnectionService.onContactStatusChanged + .onContactStatusChanged(contact, online); } else if (type.equals("unavailable")) { if (fromParts.length != 2) { contact.clearPresences(); @@ -90,7 +102,8 @@ public class PresenceParser extends AbstractParser implements .onContactStatusChanged(contact, false); } else if (type.equals("subscribe")) { if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - mXmppConnectionService.sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact)); + mXmppConnectionService.sendPresencePacket(account, + mPresenceGenerator.sendPresenceUpdatesTo(contact)); } else { contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); } diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index 7643076a..c3f0d9ee 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -20,15 +20,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 5; + private static final int DATABASE_VERSION = 6; 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);"; + + 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); @@ -54,8 +56,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, " + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART - + " TEXT, " + Message.BODY + " TEXT, " + Message.ENCRYPTION - + " NUMBER, " + Message.STATUS + " NUMBER," + Message.TYPE + + " TEXT, " + Message.TRUE_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);"); @@ -74,9 +77,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.TYPE + " NUMBER"); } if (oldVersion < 5 && newVersion >= 5) { - db.execSQL("DROP TABLE "+Contact.TABLENAME); + db.execSQL("DROP TABLE " + Contact.TABLENAME); db.execSQL(CREATE_CONTATCS_STATEMENT); - db.execSQL("UPDATE "+Account.TABLENAME+ " SET "+Account.ROSTERVERSION+" = NULL"); + db.execSQL("UPDATE " + Account.TABLENAME + " SET " + + Account.ROSTERVERSION + " = NULL"); + } + if (oldVersion < 6 && newVersion >= 6) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.TRUE_COUNTERPART + " TEXT"); } } @@ -128,24 +136,27 @@ public class DatabaseBackend extends SQLiteOpenHelper { } return list; } - - public List<Message> getMessages(Conversation conversations, int limit) { - return getMessages(conversations, limit,-1); + + public CopyOnWriteArrayList<Message> getMessages( + Conversation conversations, int limit) { + return getMessages(conversations, limit, -1); } - public List<Message> getMessages(Conversation conversation, int limit, long timestamp) { - List<Message> list = new CopyOnWriteArrayList<Message>(); + public CopyOnWriteArrayList<Message> getMessages(Conversation conversation, + int limit, long timestamp) { + CopyOnWriteArrayList<Message> list = new CopyOnWriteArrayList<Message>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; - if (timestamp==-1) { + if (timestamp == -1) { String[] selectionArgs = { conversation.getUuid() }; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=?", selectionArgs, null, null, Message.TIME_SENT + " DESC", - String.valueOf(limit)); + + "=?", selectionArgs, null, null, Message.TIME_SENT + + " DESC", String.valueOf(limit)); } else { - String[] selectionArgs = { conversation.getUuid() , ""+timestamp}; + String[] selectionArgs = { conversation.getUuid(), "" + timestamp }; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? and "+Message.TIME_SENT+"<?", selectionArgs, null, null, Message.TIME_SENT + " DESC", + + "=? and " + Message.TIME_SENT + "<?", selectionArgs, + null, null, Message.TIME_SENT + " DESC", String.valueOf(limit)); } if (cursor.getCount() > 0) { @@ -225,16 +236,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { roster.initContact(Contact.fromCursor(cursor)); } } - + public void writeRoster(Roster roster) { Account account = roster.getAccount(); SQLiteDatabase db = this.getWritableDatabase(); - for(Contact contact : roster.getContacts()) { + 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()}; + String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; + String[] whereArgs = { account.getUuid(), contact.getJid() }; db.delete(Contact.TABLENAME, where, whereArgs); } } diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index 1ee68a27..f4067f87 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/eu/siacs/conversations/persistance/FileBackend.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.persistance; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -7,19 +8,33 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import android.content.Context; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.RectF; import android.media.ExifInterface; import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Base64; +import android.util.Base64OutputStream; import android.util.Log; import android.util.LruCache; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.ImageProvider; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jingle.JingleFile; +import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { @@ -45,11 +60,11 @@ public class FileBackend { return thumbnailCache; } - public JingleFile getJingleFile(Message message) { - return getJingleFile(message, true); + public JingleFile getJingleFileLegacy(Message message) { + return getJingleFileLegacy(message, true); } - public JingleFile getJingleFile(Message message, boolean decrypted) { + public JingleFile getJingleFileLegacy(Message message, boolean decrypted) { Conversation conversation = message.getConversation(); String prefix = context.getFilesDir().getAbsolutePath(); String path = prefix + "/" + conversation.getAccount().getJid() + "/" @@ -66,7 +81,28 @@ public class FileBackend { } return new JingleFile(path + "/" + filename); } + + public JingleFile getJingleFile(Message message) { + return getJingleFile(message, true); + } + public JingleFile getJingleFile(Message message, boolean decrypted) { + StringBuilder filename = new StringBuilder(); + filename.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); + filename.append("/Conversations/"); + filename.append(message.getUuid()); + if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { + filename.append(".webp"); + } else { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + filename.append(".webp"); + } else { + filename.append(".webp.pgp"); + } + } + return new JingleFile(filename.toString()); + } + public Bitmap resize(Bitmap originalBitmap, int size) { int w = originalBitmap.getWidth(); int h = originalBitmap.getHeight(); @@ -125,21 +161,11 @@ public class FileBackend { if (originalBitmap == null) { throw new ImageCopyException(R.string.error_not_an_image_file); } - if (image == null) { - getIncomingFile().delete(); - } Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE); originalBitmap = null; - ExifInterface exif = new ExifInterface(image.toString()); - if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("6")) { - scalledBitmap = rotate(scalledBitmap, 90); - } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("8")) { - scalledBitmap = rotate(scalledBitmap, 270); - } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("3")) { - scalledBitmap = rotate(scalledBitmap, 180); + int rotation = getRotation(image); + if (rotation > 0) { + scalledBitmap = rotate(scalledBitmap, rotation); } OutputStream os = new FileOutputStream(file); boolean success = scalledBitmap.compress( @@ -170,6 +196,38 @@ public class FileBackend { } } } + + private int getRotation(Uri image) { + if ("content".equals(image.getScheme())) { + Cursor cursor = context.getContentResolver().query(image, + new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); + + if (cursor.getCount() != 1) { + return -1; + } + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + ExifInterface exif; + try { + exif = new ExifInterface(image.toString()); + if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) + .equalsIgnoreCase("6")) { + return 90; + } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) + .equalsIgnoreCase("8")) { + return 270; + } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) + .equalsIgnoreCase("3")) { + return 180; + } else { + return 0; + } + } catch (IOException e) { + return -1; + } + } + } public Bitmap getImageFromMessage(Message message) { return BitmapFactory.decodeFile(getJingleFile(message) @@ -180,8 +238,11 @@ public class FileBackend { throws FileNotFoundException { Bitmap thumbnail = thumbnailCache.get(message.getUuid()); if ((thumbnail == null) && (!cacheOnly)) { - Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message) - .getAbsolutePath()); + File file = getJingleFile(message); + if (!file.exists()) { + file = getJingleFileLegacy(message); + } + Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath()); if (fullsize == null) { throw new FileNotFoundException(); } @@ -215,10 +276,148 @@ public class FileBackend { public File getIncomingFile() { return new File(context.getFilesDir().getAbsolutePath() + "/incoming"); } - + public Uri getIncomingUri() { return Uri.parse(context.getFilesDir().getAbsolutePath() + "/incoming"); } + + public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { + try { + Avatar avatar = new Avatar(); + Bitmap bm = cropCenterSquare(image, size); + if (bm==null) { + return null; + } + ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); + Base64OutputStream mBase64OutputSttream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputSttream, digest); + if (!bm.compress(format, 75, mDigestOutputStream)) { + return null; + } + mDigestOutputStream.flush(); + mDigestOutputStream.close(); + avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); + avatar.image = new String(mByteArrayOutputStream.toByteArray()); + return avatar; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (IOException e) { + return null; + } + } + + public boolean isAvatarCached(Avatar avatar) { + File file = new File(getAvatarPath(context, avatar.getFilename())); + return file.exists(); + } + + public boolean save(Avatar avatar) { + if (isAvatarCached(avatar)) { + return true; + } + String filename = getAvatarPath(context, avatar.getFilename()); + File file = new File(filename+".tmp"); + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + FileOutputStream mFileOutputStream = new FileOutputStream(file); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.reset(); + DigestOutputStream mDigestOutputStream = new DigestOutputStream(mFileOutputStream, digest); + mDigestOutputStream.write(avatar.getImageAsBytes()); + mDigestOutputStream.flush(); + mDigestOutputStream.close(); + avatar.size = file.length(); + String sha1sum = CryptoHelper.bytesToHex(digest.digest()); + if (sha1sum.equals(avatar.sha1sum)) { + file.renameTo(new File(filename)); + return true; + } else { + Log.d("xmppService","sha1sum mismatch for "+avatar.owner); + file.delete(); + return false; + } + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } catch (NoSuchAlgorithmException e) { + return false; + } + } + + public static String getAvatarPath(Context context, String avatar) { + return context.getFilesDir().getAbsolutePath() + "/avatars/"+avatar; + } + + public Bitmap cropCenterSquare(Uri image, int size) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(image, size); + InputStream is = context.getContentResolver() + .openInputStream(image); + Bitmap input = BitmapFactory.decodeStream(is, null, options); + if (input==null) { + return null; + } else { + return cropCenterSquare(input, size); + } + } catch (FileNotFoundException e) { + return null; + } + } + + public static Bitmap cropCenterSquare(Bitmap input, int size) { + int w = input.getWidth(); + int h = input.getHeight(); + + float scale = Math.max((float) size / h, (float) size / w); + + float outWidth = scale * w; + float outHeight = scale * h; + float left = (size - outWidth) / 2; + float top = (size - outHeight) / 2; + RectF target = new RectF(left, top, left + outWidth, top + + outHeight); + + Bitmap output = Bitmap.createBitmap(size, size, input.getConfig()); + Canvas canvas = new Canvas(output); + canvas.drawBitmap(input, null, target, null); + return output; + } + + private int calcSampleSize(Uri image, int size) + throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(context.getContentResolver() + .openInputStream(image), null, options); + int height = options.outHeight; + int width = options.outWidth; + int inSampleSize = 1; + + if (height > size || width > size) { + int halfHeight = height / 2; + int halfWidth = width / 2; + + while ((halfHeight / inSampleSize) > size + && (halfWidth / inSampleSize) > size) { + inSampleSize *= 2; + } + } + return inSampleSize; + + } + + public Uri getJingleFileUri(Message message) { + File file = getJingleFile(message); + if (file.exists()) { + return Uri.parse("file://"+file.getAbsolutePath()); + } else { + return ImageProvider.getProviderUri(message); + } + } public class ImageCopyException extends Exception { private static final long serialVersionUID = -1010013599132881427L; @@ -232,4 +431,12 @@ public class FileBackend { return resId; } } + + public static Bitmap getAvatar(String avatar, int size, Context context) { + Bitmap bm = BitmapFactory.decodeFile(FileBackend.getAvatarPath(context, avatar)); + if (bm==null) { + return null; + } + return cropCenterSquare(bm, UIHelper.getRealPx(size, context)); + } } diff --git a/src/eu/siacs/conversations/services/Defaults.java b/src/eu/siacs/conversations/services/Defaults.java new file mode 100644 index 00000000..c942dd48 --- /dev/null +++ b/src/eu/siacs/conversations/services/Defaults.java @@ -0,0 +1,11 @@ +package eu.siacs.conversations.services; + +import android.graphics.Bitmap; + +public final class Defaults { + public static final int AVATAR_SIZE = 192; + public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; + private Defaults() { + + } +} diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java index 80cfbd95..ff8eec0b 100644 --- a/src/eu/siacs/conversations/services/ImageProvider.java +++ b/src/eu/siacs/conversations/services/ImageProvider.java @@ -61,7 +61,7 @@ public class ImageProvider extends ContentProvider { message.setConversation(conversation); conversation.setAccount(account); - File file = fileBackend.getJingleFile(message); + File file = fileBackend.getJingleFileLegacy(message); pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return pfd; @@ -110,7 +110,7 @@ public class ImageProvider extends ContentProvider { return 0; } - public static Uri getContentUri(Message message) { + public static Uri getProviderUri(Message message) { return Uri .parse("content://eu.siacs.conversations.images/" + message.getConversationUuid() diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index c9813d82..2c27315e 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -20,6 +20,7 @@ import de.duenndns.ssl.MemorizingTrustManager; 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.Bookmark; @@ -53,6 +54,7 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; @@ -64,6 +66,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.ContentObserver; +import android.graphics.Bitmap; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -109,6 +112,7 @@ public class XmppConnectionService extends Service { private OnConversationUpdate mOnConversationUpdate = null; private int convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; + private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @@ -525,7 +529,7 @@ public class XmppConnectionService extends Service { } else { message.getConversation().endOtrIfNeeded(); failWaitingOtrMessages(message.getConversation()); - if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { + if (message.getConversation().getMode() == Conversation.MODE_SINGLE || message.getType() == Message.TYPE_PRIVATE) { message.setStatus(Message.STATUS_SEND); } packet = mMessageGenerator.generateChat(message); @@ -668,7 +672,7 @@ public class XmppConnectionService extends Service { @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element query = packet.query(); - List<Bookmark> bookmarks = new ArrayList<Bookmark>(); + List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>(); Element storage = query.findChild("storage", "storage:bookmarks"); if (storage!=null) { for(Element item : storage.getChildren()) { @@ -921,10 +925,14 @@ public class XmppConnectionService extends Service { public void setOnAccountListChangedListener(OnAccountUpdate listener) { this.mOnAccountUpdate = listener; + this.accountChangedListenerCount++; } public void removeOnAccountListChangedListener() { - this.mOnAccountUpdate = null; + this.accountChangedListenerCount--; + if (this.accountChangedListenerCount == 0) { + this.mOnAccountUpdate = null; + } } public void setOnRosterUpdateListener(OnRosterUpdate listener) { @@ -1183,6 +1191,117 @@ public class XmppConnectionService extends Service { } } + + public void publishAvatar(Account account, Uri image, final UiCallback<Avatar> callback) { + final Bitmap.CompressFormat format = Defaults.AVATAR_FORMAT; + final int size = Defaults.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); + if (avatar!=null) { + avatar.height = size; + avatar.width = size; + if (format.equals(Bitmap.CompressFormat.WEBP)) { + avatar.type = "image/webp"; + } else if (format.equals(Bitmap.CompressFormat.JPEG)) { + avatar.type = "image/jpeg"; + } else if (format.equals(Bitmap.CompressFormat.PNG)) { + avatar.type = "image/png"; + } + if (!getFileBackend().save(avatar)) { + callback.error(R.string.error_saving_avatar, avatar); + return; + } + IqPacket packet = this.mIqGenerator.publishAvatar(avatar); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE_RESULT) { + IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE_RESULT) { + account.setAvatar(avatar.getFilename()); + callback.success(avatar); + } else { + callback.error(R.string.error_publish_avatar_server_reject, avatar); + } + } + }); + } else { + callback.error(R.string.error_publish_avatar_server_reject, avatar); + } + } + }); + } else { + callback.error(R.string.error_publish_avatar_converting, null); + } + } + + public void fetchAvatar(Account account, Avatar avatar) { + fetchAvatar(account, avatar, null); + } + + public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + Log.d(LOGTAG,account.getJid()+": retrieving avatar for "+avatar.owner); + IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar); + sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + avatar.image = mIqParser.avatarData(result); + if (avatar.image!=null) { + if (getFileBackend().save(avatar)) { + if (account.getJid().equals(avatar.owner)) { + account.setAvatar(avatar.getFilename()); + } else { + Contact contact = account.getRoster().getContact(avatar.owner); + contact.setAvatar(avatar.getFilename()); + } + if (callback!=null) { + callback.success(avatar); + } + return; + } + } + if (callback!=null) { + callback.error(0, null); + } + } + }); + } + + public void checkForAvatar(Account account, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub!=null) { + Element items = pubsub.findChild("items"); + if (items!=null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar!=null) { + avatar.owner = account.getJid(); + if (fileBackend.isAvatarCached(avatar)) { + account.setAvatar(avatar.getFilename()); + callback.success(avatar); + } else { + fetchAvatar(account, avatar,callback); + } + return; + } + } + } + } + callback.error(0, null); + } + }); + } + public void deleteContactOnServer(Contact contact) { contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); contact.resetOption(Contact.Options.DIRTY_PUSH); diff --git a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 56903da8..c33277f9 100644 --- a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -7,6 +7,8 @@ import org.openintents.openpgp.util.OpenPgpUtils; 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.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; @@ -54,7 +56,7 @@ public class ConferenceDetailsActivity extends XmppActivity { private List<User> users = new ArrayList<MucOptions.User>(); private OnConversationUpdate onConvChanged = new OnConversationUpdate() { - + @Override public void onConversationUpdate() { runOnUiThread(new Runnable() { @@ -150,13 +152,14 @@ public class ConferenceDetailsActivity extends XmppActivity { this.uuid = getIntent().getExtras().getString("uuid"); } if (uuid != null) { - this.conversation = xmppConnectionService.findConversationByUuid(uuid); + this.conversation = xmppConnectionService + .findConversationByUuid(uuid); if (this.conversation != null) { populateView(); } } } - + @Override protected void onStop() { if (xmppConnectionServiceBound) { @@ -164,39 +167,39 @@ public class ConferenceDetailsActivity extends XmppActivity { } super.onStop(); } - + protected void registerListener() { if (xmppConnectionServiceBound) { xmppConnectionService .setOnConversationListChangedListener(this.onConvChanged); - xmppConnectionService.setOnRenameListener(new OnRenameListener() { + xmppConnectionService.setOnRenameListener(new OnRenameListener() { - @Override - public void onRename(final boolean success) { - runOnUiThread(new Runnable() { + @Override + public void onRename(final boolean success) { + runOnUiThread(new Runnable() { - @Override - public void run() { - populateView(); - if (success) { - Toast.makeText(ConferenceDetailsActivity.this, - getString(R.string.your_nick_has_been_changed), - Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(ConferenceDetailsActivity.this, - getString(R.string.nick_in_use), - Toast.LENGTH_SHORT).show(); + @Override + public void run() { + populateView(); + if (success) { + Toast.makeText( + ConferenceDetailsActivity.this, + getString(R.string.your_nick_has_been_changed), + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ConferenceDetailsActivity.this, + getString(R.string.nick_in_use), + Toast.LENGTH_SHORT).show(); + } } - } - }); - } - }); + }); + } + }); } } private void populateView() { - mYourPhoto.setImageBitmap(UIHelper.getContactPicture(conversation - .getMucOptions().getActualNick(), 48, this, false)); + mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48)); setTitle(conversation.getName(true)); mFullJid.setText(conversation.getContactJid().split("/")[0]); mYourNick.setText(conversation.getMucOptions().getActualNick()); @@ -222,28 +225,44 @@ public class ConferenceDetailsActivity extends XmppActivity { this.users.addAll(conversation.getMucOptions().getUsers()); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); - for (final User contact : conversation.getMucOptions().getUsers()) { - View view = (View) inflater.inflate(R.layout.contact, null); - TextView displayName = (TextView) view + Account account = conversation.getAccount(); + for (final User user : conversation.getMucOptions().getUsers()) { + View view = (View) inflater.inflate(R.layout.contact, membersView, + false); + TextView name = (TextView) view .findViewById(R.id.contact_display_name); TextView key = (TextView) view.findViewById(R.id.key); - displayName.setText(contact.getName()); TextView role = (TextView) view.findViewById(R.id.contact_jid); - role.setText(getReadableRole(contact.getRole())); - if (contact.getPgpKeyId() != 0) { + if (user.getPgpKeyId() != 0) { key.setVisibility(View.VISIBLE); key.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - viewPgpKey(contact); + viewPgpKey(user); } }); - key.setText(OpenPgpUtils.convertKeyIdToHex(contact - .getPgpKeyId())); + key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); + } + Bitmap bm; + if (user.getJid() != null) { + Contact contact = account.getRoster().getContact(user.getJid()); + if (contact.showInRoster()) { + bm = contact.getImage(48, this); + name.setText(contact.getDisplayName()); + role.setText(user.getName() + " \u2022 " + getReadableRole(user.getRole())); + } else { + bm = UIHelper.getContactPicture(user.getName(), 48, this, + false); + name.setText(user.getName()); + role.setText(getReadableRole(user.getRole())); + } + } else { + bm = UIHelper + .getContactPicture(user.getName(), 48, this, false); + name.setText(user.getName()); + role.setText(getReadableRole(user.getRole())); } - Bitmap bm = UIHelper.getContactPicture(contact.getName(), 48, this, - false); ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); iv.setImageBitmap(bm); membersView.addView(view); diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index aa4fda4e..1e1acb53 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -4,6 +4,7 @@ import java.io.FileNotFoundException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; @@ -11,6 +12,7 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.ImageProvider; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; +import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.UIHelper; import android.net.Uri; @@ -18,10 +20,10 @@ import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.MediaStore; +import android.app.ActionBar; import android.app.AlertDialog; import android.app.FragmentTransaction; import android.app.PendingIntent; -import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.IntentSender.SendIntentException; @@ -29,20 +31,15 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; import android.util.DisplayMetrics; -import android.util.Log; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; @@ -50,7 +47,6 @@ import android.widget.CheckBox; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; -import android.widget.TextView; import android.widget.ImageView; import android.widget.Toast; @@ -61,17 +57,17 @@ public class ConversationActivity extends XmppActivity { public static final String TEXT = "text"; public static final String PRESENCE = "eu.siacs.conversations.presence"; - 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; - public static final int REQUEST_ENCRYPT_MESSAGE = 0x378018; + public static final int REQUEST_SEND_MESSAGE = 0x0201; + public static final int REQUEST_DECRYPT_PGP = 0x0202; + private static final int REQUEST_ATTACH_FILE_DIALOG = 0x0203; + private static final int REQUEST_IMAGE_CAPTURE = 0x0204; + private static final int REQUEST_RECORD_AUDIO = 0x0205; + private static final int REQUEST_SEND_PGP_IMAGE = 0x0206; + public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; - 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; + private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; + private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; + private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0303; protected SlidingPaneLayout spl; @@ -150,93 +146,11 @@ public class ConversationActivity extends XmppActivity { setContentView(R.layout.fragment_conversations_overview); listView = (ListView) findViewById(R.id.list); + + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); - this.listAdapter = new ArrayAdapter<Conversation>(this, - R.layout.conversation_list_row, conversationList) { - @Override - public View getView(int position, View view, ViewGroup parent) { - if (view == null) { - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = (View) inflater.inflate( - R.layout.conversation_list_row, null); - } - Conversation conv; - if (conversationList.size() > position) { - conv = getItem(position); - } else { - return view; - } - if (!spl.isSlideable()) { - if (conv == getSelectedConversation()) { - view.setBackgroundColor(0xffdddddd); - } else { - view.setBackgroundColor(Color.TRANSPARENT); - } - } else { - view.setBackgroundColor(Color.TRANSPARENT); - } - TextView convName = (TextView) view - .findViewById(R.id.conversation_name); - convName.setText(conv.getName(useSubject)); - TextView convLastMsg = (TextView) view - .findViewById(R.id.conversation_lastmsg); - ImageView imagePreview = (ImageView) view - .findViewById(R.id.conversation_lastimage); - - Message latestMessage = conv.getLatestMessage(); - - if (latestMessage.getType() == Message.TYPE_TEXT) { - if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP) - && (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { - convLastMsg.setText(conv.getLatestMessage().getBody()); - } else { - convLastMsg - .setText(getText(R.string.encrypted_message_received)); - } - convLastMsg.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - } else if (latestMessage.getType() == Message.TYPE_IMAGE) { - if (latestMessage.getStatus() >= Message.STATUS_RECIEVED) { - convLastMsg.setVisibility(View.GONE); - imagePreview.setVisibility(View.VISIBLE); - loadBitmap(latestMessage, imagePreview); - } else { - convLastMsg.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) { - convLastMsg - .setText(getText(R.string.image_offered_for_download)); - } else if (latestMessage.getStatus() == Message.STATUS_RECIEVING) { - convLastMsg - .setText(getText(R.string.receiving_image)); - } else { - convLastMsg.setText(""); - } - } - } - - if (!conv.isRead()) { - convName.setTypeface(null, Typeface.BOLD); - convLastMsg.setTypeface(null, Typeface.BOLD); - } else { - convName.setTypeface(null, Typeface.NORMAL); - convLastMsg.setTypeface(null, Typeface.NORMAL); - } - - ((TextView) view.findViewById(R.id.conversation_lastupdate)) - .setText(UIHelper.readableTimeDifference(getContext(), - conv.getLatestMessage().getTimeSent())); - - ImageView profilePicture = (ImageView) view - .findViewById(R.id.conversation_image); - profilePicture.setImageBitmap(UIHelper.getContactPicture(conv, - 56, activity.getApplicationContext(), false)); - - return view; - } - - }; - + this.listAdapter = new ConversationAdapter(this, conversationList); listView.setAdapter(this.listAdapter); listView.setOnItemClickListener(new OnItemClickListener() { @@ -247,7 +161,7 @@ public class ConversationActivity extends XmppActivity { paneShouldBeOpen = false; if (getSelectedConversation() != conversationList.get(position)) { setSelectedConversation(conversationList.get(position)); - swapConversationFragment(); // .onBackendConnected(conversationList.get(position)); + swapConversationFragment(); } else { spl.closePane(); } @@ -262,9 +176,12 @@ public class ConversationActivity extends XmppActivity { @Override public void onPanelOpened(View arg0) { paneShouldBeOpen = true; - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); - getActionBar().setTitle(R.string.app_name); + ActionBar ab = getActionBar(); + if (ab!=null) { + ab.setDisplayHomeAsUpEnabled(false); + ab.setHomeButtonEnabled(false); + ab.setTitle(R.string.app_name); + } invalidateOptionsMenu(); hideKeyboard(); } @@ -274,10 +191,13 @@ public class ConversationActivity extends XmppActivity { paneShouldBeOpen = false; if ((conversationList.size() > 0) && (getSelectedConversation() != null)) { - getActionBar().setDisplayHomeAsUpEnabled(true); - getActionBar().setHomeButtonEnabled(true); - getActionBar().setTitle( + ActionBar ab = getActionBar(); + if (ab!=null) { + ab.setDisplayHomeAsUpEnabled(true); + ab.setHomeButtonEnabled(true); + ab.setTitle( getSelectedConversation().getName(useSubject)); + } invalidateOptionsMenu(); if (!getSelectedConversation().isRead()) { xmppConnectionService @@ -427,7 +347,7 @@ public class ConversationActivity extends XmppActivity { switch (item.getItemId()) { case android.R.id.home: spl.openPane(); - break; + return true; case R.id.action_attach_file: View menuAttachFile = findViewById(R.id.action_attach_file); if (menuAttachFile==null) { @@ -602,12 +522,15 @@ public class ConversationActivity extends XmppActivity { protected ConversationFragment swapConversationFragment() { ConversationFragment selectedFragment = new ConversationFragment(); + if (!isFinishing()) { FragmentTransaction transaction = getFragmentManager() .beginTransaction(); transaction.replace(R.id.selected_conversation, selectedFragment, "conversation"); - transaction.commitAllowingStateLoss(); + + transaction.commitAllowingStateLoss(); + } return selectedFragment; } @@ -624,19 +547,24 @@ 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); - updateConversationList(); - for (int i = 0; i < conversationList.size(); ++i) { - if (conversationList.get(i).getUuid().equals(convToView)) { - setSelectedConversation(conversationList.get(i)); - break; + if (xmppConnectionServiceBound) { + if ((Intent.ACTION_VIEW.equals(intent.getAction()) && (VIEW_CONVERSATION + .equals(intent.getType())))) { + String convToView = (String) intent.getExtras().get(CONVERSATION); + updateConversationList(); + for (int i = 0; i < conversationList.size(); ++i) { + if (conversationList.get(i).getUuid().equals(convToView)) { + setSelectedConversation(conversationList.get(i)); + break; + } } + paneShouldBeOpen = false; + String text = intent.getExtras().getString(TEXT, null); + swapConversationFragment().setText(text); } - paneShouldBeOpen = false; - String text = intent.getExtras().getString(TEXT, null); - swapConversationFragment().setText(text); + } else { + handledViewIntent = false; + setIntent(intent); } } @@ -747,17 +675,14 @@ public class ConversationActivity extends XmppActivity { } 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 attachAudioToConversation(Conversation conversation, Uri uri) { - + } private void attachImageToConversation(Conversation conversation, Uri uri) { @@ -818,9 +743,7 @@ public class ConversationActivity extends XmppActivity { try { this.startIntentSenderForResult(pi.getIntentSender(), requestCode, null, 0, 0, 0); - } catch (SendIntentException e1) { - Log.d("xmppService", "failed to start intent to send message"); - } + } catch (SendIntentException e1) {} } class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { @@ -838,7 +761,6 @@ public class ConversationActivity extends XmppActivity { return xmppConnectionService.getFileBackend().getThumbnail( message, (int) (metrics.density * 288), false); } catch (FileNotFoundException e) { - Log.d("xmppService", "file not found!"); return null; } } @@ -873,7 +795,11 @@ public class ConversationActivity extends XmppActivity { final AsyncDrawable asyncDrawable = new AsyncDrawable( getResources(), null, task); imageView.setImageDrawable(asyncDrawable); - task.execute(message); + try { + task.execute(message); + } catch (RejectedExecutionException e) { + return; + } } } } diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index 1df59843..1270d23a 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -13,13 +13,16 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.EditMessage.OnEnterPressed; import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; +import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; import eu.siacs.conversations.utils.UIHelper; import android.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; @@ -30,14 +33,17 @@ import android.preference.PreferenceManager; import android.text.Editable; import android.text.Selection; import android.view.Gravity; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView.OnScrollListener; +import android.widget.TextView.OnEditorActionListener; import android.widget.AbsListView; -import android.widget.EditText; import android.widget.ListView; import android.widget.ImageButton; import android.widget.RelativeLayout; @@ -55,7 +61,7 @@ public class ConversationFragment extends Fragment { protected String queuedPqpMessage = null; - private EditText chatMsg; + private EditMessage mEditMessage; private String pastedText = null; private RelativeLayout snackbar; private TextView snackbarMessage; @@ -65,22 +71,27 @@ public class ConversationFragment extends Fragment { private boolean messagesLoaded = false; private IntentSender askForPassphraseIntent = null; + + private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) v.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + return true; + } else { + return false; + } + } + }; - private OnClickListener sendMsgListener = new OnClickListener() { + private OnClickListener mSendButtonListener = new OnClickListener() { @Override public void onClick(View v) { - if (chatMsg.getText().length() < 1) - return; - Message message = new Message(conversation, chatMsg.getText() - .toString(), conversation.getNextEncryption()); - if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { - sendOtrMessage(message); - } else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - sendPgpMessage(message); - } else { - sendPlainTextMessage(message); - } + sendMessage(); } }; protected OnClickListener clickToDecryptListener = new OnClickListener() { @@ -147,17 +158,44 @@ public class ConversationFragment extends Fragment { }; private ConversationActivity activity; + + + private void sendMessage() { + if (mEditMessage.getText().length() < 1) { + if (this.conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextPresence(null); + updateChatMsgHint(); + } + return; + } + Message message = new Message(conversation, mEditMessage.getText() + .toString(), conversation.getNextEncryption()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (conversation.getNextPresence() != null) { + message.setPresence(conversation.getNextPresence()); + message.setType(Message.TYPE_PRIVATE); + conversation.setNextPresence(null); + } + } + if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { + sendOtrMessage(message); + } else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + sendPgpMessage(message); + } else { + sendPlainTextMessage(message); + } + } public void updateChatMsgHint() { switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: - chatMsg.setHint(getString(R.string.send_plain_text_message)); + mEditMessage.setHint(getString(R.string.send_plain_text_message)); break; case Message.ENCRYPTION_OTR: - chatMsg.setHint(getString(R.string.send_otr_message)); + mEditMessage.setHint(getString(R.string.send_otr_message)); break; case Message.ENCRYPTION_PGP: - chatMsg.setHint(getString(R.string.send_pgp_message)); + mEditMessage.setHint(getString(R.string.send_pgp_message)); break; default: break; @@ -169,8 +207,8 @@ public class ConversationFragment extends Fragment { ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); - chatMsg = (EditText) view.findViewById(R.id.textinput); - chatMsg.setOnClickListener(new OnClickListener() { + mEditMessage = (EditMessage) view.findViewById(R.id.textinput); + mEditMessage.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -179,10 +217,18 @@ public class ConversationFragment extends Fragment { } } }); - + mEditMessage.setOnEditorActionListener(mEditorActionListener); + mEditMessage.setOnEnterPressedListener(new OnEnterPressed() { + + @Override + public void onEnterPressed() { + sendMessage(); + } + }); + ImageButton sendButton = (ImageButton) view .findViewById(R.id.textSendButton); - sendButton.setOnClickListener(this.sendMsgListener); + sendButton.setOnClickListener(this.mSendButtonListener); snackbar = (RelativeLayout) view.findViewById(R.id.snackbar); snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message); @@ -197,7 +243,24 @@ public class ConversationFragment extends Fragment { @Override public void onContactPictureClicked(Message message) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - highlightInConference(message.getCounterpart()); + if (message.getPresence() != null) { + highlightInConference(message.getPresence()); + } else { + highlightInConference(message.getCounterpart()); + } + } + } + }); + messageListAdapter.setOnContactPictureLongClicked(new OnContactPictureLongClicked() { + + @Override + public void onContactPictureLongClicked(Message message) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getPresence() != null) { + privateMessageWith(message.getPresence()); + } else { + privateMessageWith(message.getCounterpart()); + } } } }); @@ -205,16 +268,22 @@ public class ConversationFragment extends Fragment { return view; } + + protected void privateMessageWith(String counterpart) { + this.mEditMessage.setHint(getString(R.string.send_private_message_to,counterpart)); + this.mEditMessage.setText(""); + this.conversation.setNextPresence(counterpart); + } protected void highlightInConference(String nick) { - String oldString = chatMsg.getText().toString().trim(); + String oldString = mEditMessage.getText().toString().trim(); if (oldString.isEmpty()) { - chatMsg.setText(nick + ": "); + mEditMessage.setText(nick + ": "); } else { - chatMsg.setText(oldString + " " + nick + " "); + mEditMessage.setText(oldString + " " + nick + " "); } - int position = chatMsg.length(); - Editable etext = chatMsg.getText(); + int position = mEditMessage.length(); + Editable etext = mEditMessage.getText(); Selection.setSelection(etext, position); } @@ -234,29 +303,30 @@ public class ConversationFragment extends Fragment { public void onStop() { super.onStop(); if (this.conversation != null) { - this.conversation.setNextMessage(chatMsg.getText().toString()); + this.conversation.setNextMessage(mEditMessage.getText().toString()); } } public void onBackendConnected() { + this.activity = (ConversationActivity) getActivity(); this.conversation = activity.getSelectedConversation(); if (this.conversation == null) { return; } String oldString = conversation.getNextMessage().trim(); if (this.pastedText == null) { - this.chatMsg.setText(oldString); + this.mEditMessage.setText(oldString); } else { if (oldString.isEmpty()) { - chatMsg.setText(pastedText); + mEditMessage.setText(pastedText); } else { - chatMsg.setText(oldString + " " + pastedText); + mEditMessage.setText(oldString + " " + pastedText); } pastedText = null; } - int position = chatMsg.length(); - Editable etext = chatMsg.getText(); + int position = mEditMessage.length(); + Editable etext = mEditMessage.getText(); Selection.setSelection(etext, position); updateMessages(); if (activity.getSlidingPaneLayout().isSlideable()) { @@ -269,6 +339,9 @@ public class ConversationFragment extends Fragment { activity.invalidateOptionsMenu(); } } + if (this.conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextPresence(null); + } } private void decryptMessage(Message message) { @@ -371,7 +444,8 @@ public class ConversationFragment extends Fragment { if (size >= 1) { messagesView.setSelection(size - 1); } - chatMsg.setText(""); + mEditMessage.setText(""); + updateChatMsgHint(); } protected void updateStatusMessages() { @@ -425,7 +499,9 @@ public class ConversationFragment extends Fragment { protected void showSnackbar(int message, int action, OnClickListener clickListener) { snackbar.setVisibility(View.VISIBLE); + snackbar.setOnClickListener(null); snackbarMessage.setText(message); + snackbarMessage.setOnClickListener(null); snackbarAction.setText(action); snackbarAction.setOnClickListener(clickListener); } @@ -560,6 +636,6 @@ public class ConversationFragment extends Fragment { } public void clearInputField() { - this.chatMsg.setText(""); + this.mEditMessage.setText(""); } } diff --git a/src/eu/siacs/conversations/ui/EditAccountActivity.java b/src/eu/siacs/conversations/ui/EditAccountActivity.java new file mode 100644 index 00000000..e5920cdf --- /dev/null +++ b/src/eu/siacs/conversations/ui/EditAccountActivity.java @@ -0,0 +1,294 @@ +package eu.siacs.conversations.ui; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.CompoundButton.OnCheckedChangeListener; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; +import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; +import eu.siacs.conversations.utils.Validator; +import eu.siacs.conversations.xmpp.pep.Avatar; + +public class EditAccountActivity extends XmppActivity { + + private AutoCompleteTextView mAccountJid; + private EditText mPassword; + private EditText mPasswordConfirm; + private CheckBox mRegisterNew; + private Button mCancelButton; + private Button mSaveButton; + + private String jidToEdit; + private Account mAccount; + + private boolean mUserInputIsValid = false; + private boolean mFetchingAvatar = false; + + private OnClickListener mSaveButtonClickListener = new OnClickListener() { + + @Override + public void onClick(View v) { + if (mAccount != null && mAccount.errorStatus() + && !mUserInputIsValid) { + xmppConnectionService.reconnectAccount(mAccount, true); + return; + } + boolean registerNewAccount = mRegisterNew.isChecked(); + String[] jidParts = mAccountJid.getText().toString().split("@"); + String username = jidParts[0]; + String server; + if (jidParts.length >= 2) { + server = jidParts[1]; + } else { + server = ""; + } + String password = mPassword.getText().toString(); + String passwordConfirm = mPasswordConfirm.getText().toString(); + if (registerNewAccount) { + if (!password.equals(passwordConfirm)) { + mPasswordConfirm + .setError(getString(R.string.passwords_do_not_match)); + return; + } + } + if (mAccount != null) { + mAccount.setPassword(password); + mAccount.setUsername(username); + mAccount.setServer(server); + mAccount.setOption(Account.OPTION_REGISTER, mRegisterNew.isChecked()); + xmppConnectionService.updateAccount(mAccount); + } else { + if (xmppConnectionService.findAccountByJid(mAccountJid.getText().toString())!=null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + return; + } + mAccount = new Account(username, server, password); + mAccount.setOption(Account.OPTION_USETLS, true); + mAccount.setOption(Account.OPTION_USECOMPRESSION, true); + if (registerNewAccount) { + mAccount.setOption(Account.OPTION_REGISTER, true); + } + xmppConnectionService.createAccount(mAccount); + } + if (jidToEdit != null) { + finish(); + } else { + mUserInputIsValid = false; + updateSaveButton(); + updateAccountInformation(); + } + + } + }; + private OnClickListener mCancelButtonClickListener = new OnClickListener() { + + @Override + public void onClick(View v) { + finish(); + } + }; + private TextWatcher mTextWatcher = new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + mUserInputIsValid = inputDataDiffersFromAccount() && Validator.isValidJid(mAccountJid.getText().toString()); + updateSaveButton(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + private OnAccountUpdate mOnAccountUpdateListener = new OnAccountUpdate() { + + @Override + public void onAccountUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (jidToEdit==null && mAccount!=null && mAccount.getStatus() == Account.STATUS_ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback); + } + } else { + updateSaveButton(); + } + } + }); + } + }; + private UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() { + + @Override + public void userInputRequried(PendingIntent pi, Avatar avatar) { + finishInitialSetup(avatar); + } + + @Override + public void success(Avatar avatar) { + finishInitialSetup(avatar); + } + + @Override + public void error(int errorCode, Avatar avatar) { + finishInitialSetup(avatar); + } + }; + + protected void finishInitialSetup(final Avatar avatar) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + Intent intent; + if (avatar!=null) { + intent = new Intent(getApplicationContext(), StartConversationActivity.class); + } else { + intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra("account", mAccount.getJid()); + } + startActivity(intent); + finish(); + } + }); + } + + protected boolean inputDataDiffersFromAccount() { + if (mAccount == null) { + return true; + } else { + return (!mAccount.getJid().equals(mAccountJid.getText().toString())) + || (!mAccount.getPassword().equals( + mPassword.getText().toString()) || mAccount + .isOptionSet(Account.OPTION_REGISTER) != mRegisterNew + .isChecked()); + } + } + + protected void updateSaveButton() { + if (mAccount != null + && mAccount.getStatus() == Account.STATUS_CONNECTING + && !mUserInputIsValid) { + this.mSaveButton.setEnabled(false); + this.mSaveButton.setTextColor(getSecondaryTextColor()); + this.mSaveButton.setText(R.string.account_status_connecting); + } else if (mAccount != null && mAccount.errorStatus() + && !mUserInputIsValid) { + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + this.mSaveButton.setText(R.string.connect); + } else if (mUserInputIsValid) { + this.mSaveButton.setEnabled(true); + this.mSaveButton.setTextColor(getPrimaryTextColor()); + if (jidToEdit!=null) { + this.mSaveButton.setText(R.string.save); + } else { + this.mSaveButton.setText(R.string.next); + } + } else { + this.mSaveButton.setEnabled(false); + this.mSaveButton.setTextColor(getSecondaryTextColor()); + if (jidToEdit!=null) { + this.mSaveButton.setText(R.string.save); + } else { + this.mSaveButton.setText(R.string.next); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_account); + this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); + this.mPassword = (EditText) findViewById(R.id.account_password); + this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); + this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); + this.mSaveButton = (Button) findViewById(R.id.save_button); + this.mCancelButton = (Button) findViewById(R.id.cancel_button); + this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); + this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); + this.mRegisterNew + .setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + mPasswordConfirm.setVisibility(View.VISIBLE); + } else { + mPasswordConfirm.setVisibility(View.GONE); + } + mUserInputIsValid = inputDataDiffersFromAccount() && Validator.isValidJid(mAccountJid.getText().toString()); + updateSaveButton(); + } + }); + this.mAccountJid.addTextChangedListener(this.mTextWatcher); + this.mPassword.addTextChangedListener(this.mTextWatcher); + } + + @Override + protected void onStart() { + super.onStart(); + if (getIntent() != null) { + this.jidToEdit = getIntent().getStringExtra("jid"); + if (this.jidToEdit != null) { + this.mRegisterNew.setVisibility(View.GONE); + getActionBar().setTitle(R.string.mgmt_account_edit); + } else { + getActionBar().setTitle(R.string.action_add_account); + } + } + } + + @Override + protected void onBackendConnected() { + this.xmppConnectionService + .setOnAccountListChangedListener(this.mOnAccountUpdateListener); + this.mAccountJid.setAdapter(null); + if (this.jidToEdit != null) { + this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); + updateAccountInformation(); + } else if (this.xmppConnectionService.getAccounts().size() == 0) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setDisplayShowHomeEnabled(false); + this.mCancelButton.setEnabled(false); + } + this.mAccountJid.setAdapter(new KnownHostsAdapter(this, + android.R.layout.simple_list_item_1, xmppConnectionService + .getKnownHosts())); + updateSaveButton(); + } + + private void updateAccountInformation() { + this.mAccountJid.setText(this.mAccount.getJid()); + this.mPassword.setText(this.mAccount.getPassword()); + if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { + this.mRegisterNew.setVisibility(View.VISIBLE); + this.mRegisterNew.setChecked(true); + this.mPasswordConfirm.setText(this.mAccount.getPassword()); + } else { + this.mRegisterNew.setVisibility(View.GONE); + this.mRegisterNew.setChecked(false); + } + } +} diff --git a/src/eu/siacs/conversations/ui/EditAccountDialog.java b/src/eu/siacs/conversations/ui/EditAccountDialog.java deleted file mode 100644 index 7c135fc1..00000000 --- a/src/eu/siacs/conversations/ui/EditAccountDialog.java +++ /dev/null @@ -1,157 +0,0 @@ -package eu.siacs.conversations.ui; - -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; -import eu.siacs.conversations.utils.Validator; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.TextView; - -public class EditAccountDialog extends DialogFragment { - - protected Account account; - - protected AutoCompleteTextView mAccountJid; - - public void setAccount(Account account) { - this.account = account; - } - - public interface EditAccountListener { - public void onAccountEdited(Account account); - } - - protected EditAccountListener listener = null; - - private KnownHostsAdapter mKnownHostsAdapter; - - public void setEditAccountListener(EditAccountListener listener) { - this.listener = listener; - } - - public void setKnownHosts(List<String> hosts, Context context) { - this.mKnownHostsAdapter = new KnownHostsAdapter(context, android.R.layout.simple_list_item_1, hosts); - if (this.mAccountJid != null) { - this.mAccountJid.setAdapter(this.mKnownHostsAdapter); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - LayoutInflater inflater = getActivity().getLayoutInflater(); - View view = inflater.inflate(R.layout.edit_account_dialog, null); - mAccountJid = (AutoCompleteTextView) view.findViewById(R.id.account_jid); - if (this.mKnownHostsAdapter!=null) { - mAccountJid.setAdapter(this.mKnownHostsAdapter); - } - final TextView confirmPwDesc = (TextView) view - .findViewById(R.id.account_confirm_password_desc); - - final EditText password = (EditText) view - .findViewById(R.id.account_password); - final EditText passwordConfirm = (EditText) view - .findViewById(R.id.account_password_confirm2); - final CheckBox registerAccount = (CheckBox) view - .findViewById(R.id.edit_account_register_new); - - if (account != null) { - mAccountJid.setText(account.getJid()); - password.setText(account.getPassword()); - if (account.isOptionSet(Account.OPTION_REGISTER)) { - registerAccount.setChecked(true); - passwordConfirm.setVisibility(View.VISIBLE); - passwordConfirm.setText(account.getPassword()); - } else { - registerAccount.setVisibility(View.GONE); - } - } - builder.setTitle(R.string.account_settings); - - - registerAccount - .setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - passwordConfirm.setVisibility(View.VISIBLE); - confirmPwDesc.setVisibility(View.VISIBLE); - } else { - passwordConfirm.setVisibility(View.GONE); - confirmPwDesc.setVisibility(View.GONE); - } - } - }); - - builder.setView(view); - builder.setNeutralButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.save), null); - return builder.create(); - } - - @Override - public void onStart() { - super.onStart(); - final AlertDialog d = (AlertDialog) getDialog(); - Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE); - positiveButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - EditText jidEdit = (EditText) d.findViewById(R.id.account_jid); - 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; - if (Validator.isValidJid(jid)) { - String[] parts = jid.split("@"); - username = parts[0]; - server = parts[1]; - } else { - 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); - account.setServer(server); - } else { - account = new Account(username, server, password); - account.setOption(Account.OPTION_USETLS, true); - account.setOption(Account.OPTION_USECOMPRESSION, true); - } - account.setOption(Account.OPTION_REGISTER, register.isChecked()); - if (listener != null) { - listener.onAccountEdited(account); - d.dismiss(); - } - } - }); - } -} diff --git a/src/eu/siacs/conversations/ui/EditMessage.java b/src/eu/siacs/conversations/ui/EditMessage.java new file mode 100644 index 00000000..f8302050 --- /dev/null +++ b/src/eu/siacs/conversations/ui/EditMessage.java @@ -0,0 +1,39 @@ +package eu.siacs.conversations.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.EditText; + +public class EditMessage extends EditText { + + public EditMessage(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public EditMessage(Context context) { + super(context); + } + + protected OnEnterPressed mOnEnterPressed; + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER) { + if (mOnEnterPressed != null) { + mOnEnterPressed.onEnterPressed(); + } + return true; + } + return super.onKeyDown(keyCode, event); + } + + public void setOnEnterPressedListener(OnEnterPressed listener) { + this.mOnEnterPressed = listener; + } + + public interface OnEnterPressed { + public void onEnterPressed(); + } + +} diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java index e56e2db9..803917b9 100644 --- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -6,27 +6,22 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; -import eu.siacs.conversations.ui.EditAccountDialog.EditAccountListener; +import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.xmpp.XmppConnection; -import android.app.Activity; import android.app.AlertDialog; -import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.os.SystemClock; import android.view.ActionMode; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; @@ -41,7 +36,7 @@ public class ManageAccountActivity extends XmppActivity { protected List<Account> accountList = new ArrayList<Account>(); protected ListView accountListView; - protected ArrayAdapter<Account> accountListViewAdapter; + protected AccountAdapter mAccountAdapter; protected OnAccountUpdate accountChanged = new OnAccountUpdate() { @Override @@ -52,12 +47,176 @@ public class ManageAccountActivity extends XmppActivity { @Override public void run() { - accountListViewAdapter.notifyDataSetChanged(); + mAccountAdapter.notifyDataSetChanged(); } }); } }; + protected ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (selectedAccountForActionMode + .isOptionSet(Account.OPTION_DISABLED)) { + menu.findItem(R.id.mgmt_account_enable).setVisible(true); + menu.findItem(R.id.mgmt_account_disable).setVisible(false); + } else { + menu.findItem(R.id.mgmt_account_disable).setVisible(true); + menu.findItem(R.id.mgmt_account_enable).setVisible(false); + } + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.manageaccounts_context, menu); + return true; + } + + @Override + public boolean onActionItemClicked(final ActionMode mode, MenuItem item) { + if (item.getItemId() == R.id.mgmt_account_edit) { + editAccount(selectedAccountForActionMode); + } else if (item.getItemId() == R.id.mgmt_account_disable) { + selectedAccountForActionMode.setOption(Account.OPTION_DISABLED, + true); + xmppConnectionService + .updateAccount(selectedAccountForActionMode); + mode.finish(); + } else if (item.getItemId() == R.id.mgmt_account_enable) { + selectedAccountForActionMode.setOption(Account.OPTION_DISABLED, + false); + xmppConnectionService + .updateAccount(selectedAccountForActionMode); + mode.finish(); + } else if (item.getItemId() == R.id.mgmt_account_publish_avatar) { + Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra("account", selectedAccountForActionMode.getJid()); + startActivity(intent); + } else if (item.getItemId() == R.id.mgmt_account_delete) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + 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) { + xmppConnectionService + .deleteAccount(selectedAccountForActionMode); + selectedAccountForActionMode = null; + mode.finish(); + } + }); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.create().show(); + } else if (item.getItemId() == R.id.mgmt_account_announce_pgp) { + if (activity.hasPgp()) { + mode.finish(); + announcePgp(selectedAccountForActionMode, null); + } else { + activity.showInstallPgpDialog(); + } + } else if (item.getItemId() == R.id.mgmt_otr_key) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle("OTR Fingerprint"); + String fingerprintTxt = selectedAccountForActionMode + .getOtrFingerprint(getApplicationContext()); + View view = (View) getLayoutInflater().inflate( + R.layout.otr_fingerprint, null); + if (fingerprintTxt != null) { + TextView fingerprint = (TextView) view + .findViewById(R.id.otr_fingerprint); + TextView noFingerprintView = (TextView) view + .findViewById(R.id.otr_no_fingerprint); + fingerprint.setText(fingerprintTxt); + fingerprint.setVisibility(View.VISIBLE); + noFingerprintView.setVisibility(View.GONE); + } + builder.setView(view); + 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); + builder.setTitle(getString(R.string.account_info)); + if (selectedAccountForActionMode.getStatus() == Account.STATUS_ONLINE) { + XmppConnection xmpp = selectedAccountForActionMode + .getXmppConnection(); + long connectionAge = (SystemClock.elapsedRealtime() - xmpp.lastConnect) / 60000; + long sessionAge = (SystemClock.elapsedRealtime() - xmpp.lastSessionStarted) / 60000; + long connectionAgeHours = connectionAge / 60; + long sessionAgeHours = sessionAge / 60; + View view = (View) getLayoutInflater().inflate( + R.layout.server_info, null); + TextView connection = (TextView) view + .findViewById(R.id.connection); + TextView session = (TextView) view + .findViewById(R.id.session); + TextView pcks_sent = (TextView) view + .findViewById(R.id.pcks_sent); + TextView pcks_received = (TextView) view + .findViewById(R.id.pcks_received); + TextView carbon = (TextView) view.findViewById(R.id.carbon); + TextView stream = (TextView) view.findViewById(R.id.stream); + TextView roster = (TextView) view.findViewById(R.id.roster); + TextView presences = (TextView) view + .findViewById(R.id.number_presences); + presences.setText(selectedAccountForActionMode + .countPresences() + ""); + pcks_received.setText("" + xmpp.getReceivedStanzas()); + pcks_sent.setText("" + xmpp.getSentStanzas()); + if (connectionAgeHours >= 2) { + connection.setText(connectionAgeHours + " " + + getString(R.string.hours)); + } else { + connection.setText(connectionAge + " " + + getString(R.string.mins)); + } + if (xmpp.getFeatures().sm()) { + if (sessionAgeHours >= 2) { + session.setText(sessionAgeHours + " " + + getString(R.string.hours)); + } else { + session.setText(sessionAge + " " + + getString(R.string.mins)); + } + stream.setText(getString(R.string.yes)); + } else { + stream.setText(getString(R.string.no)); + session.setText(connection.getText()); + } + if (xmpp.getFeatures().carbons()) { + carbon.setText(getString(R.string.yes)); + } else { + carbon.setText(getString(R.string.no)); + } + if (xmpp.getFeatures().rosterVersioning()) { + roster.setText(getString(R.string.yes)); + } else { + roster.setText(getString(R.string.no)); + } + builder.setView(view); + } else { + builder.setMessage(getString(R.string.mgmt_account_account_offline)); + } + builder.setPositiveButton(getString(R.string.hide), null); + builder.create().show(); + } + return true; + } + + }; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -66,107 +225,16 @@ public class ManageAccountActivity extends XmppActivity { setContentView(R.layout.manage_accounts); accountListView = (ListView) findViewById(R.id.account_list); - accountListViewAdapter = new ArrayAdapter<Account>( - getApplicationContext(), R.layout.account_row, this.accountList) { - @Override - public View getView(int position, View view, ViewGroup parent) { - Account account = getItem(position); - if (view == null) { - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = (View) inflater.inflate(R.layout.account_row, null); - } - ((TextView) view.findViewById(R.id.account_jid)) - .setText(account.getJid()); - TextView statusView = (TextView) view - .findViewById(R.id.account_status); - switch (account.getStatus()) { - case Account.STATUS_DISABLED: - statusView - .setText(getString(R.string.account_status_disabled)); - statusView.setTextColor(0xFF1da9da); - break; - case Account.STATUS_ONLINE: - statusView - .setText(getString(R.string.account_status_online)); - statusView.setTextColor(0xFF83b600); - break; - case Account.STATUS_CONNECTING: - statusView - .setText(getString(R.string.account_status_connecting)); - statusView.setTextColor(0xFF1da9da); - break; - case Account.STATUS_OFFLINE: - statusView - .setText(getString(R.string.account_status_offline)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_UNAUTHORIZED: - statusView - .setText(getString(R.string.account_status_unauthorized)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_SERVER_NOT_FOUND: - statusView - .setText(getString(R.string.account_status_not_found)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_NO_INTERNET: - statusView - .setText(getString(R.string.account_status_no_internet)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_SERVER_REQUIRES_TLS: - statusView - .setText(getString(R.string.account_status_requires_tls)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_REGISTRATION_FAILED: - statusView - .setText(getString(R.string.account_status_regis_fail)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_REGISTRATION_CONFLICT: - statusView - .setText(getString(R.string.account_status_regis_conflict)); - statusView.setTextColor(0xFFe92727); - break; - case Account.STATUS_REGISTRATION_SUCCESSFULL: - statusView - .setText(getString(R.string.account_status_regis_success)); - statusView.setTextColor(0xFF83b600); - break; - case Account.STATUS_REGISTRATION_NOT_SUPPORTED: - statusView - .setText(getString(R.string.account_status_regis_not_sup)); - statusView.setTextColor(0xFFe92727); - break; - default: - statusView.setText(""); - break; - } - - return view; - } - }; final XmppActivity activity = this; - accountListView.setAdapter(this.accountListViewAdapter); + this.mAccountAdapter = new AccountAdapter(this, accountList); + accountListView.setAdapter(this.mAccountAdapter); accountListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) { if (!isActionMode) { - Account account = accountList.get(position); - if (account.getStatus() == Account.STATUS_OFFLINE) { - activity.xmppConnectionService.reconnectAccount( - accountList.get(position), true); - } else if (account.getStatus() == Account.STATUS_ONLINE) { - activity.startActivity(new Intent(activity - .getApplicationContext(), - StartConversationActivity.class)); - } else if (account.getStatus() != Account.STATUS_DISABLED) { - editAccount(account); - } + editAccount(accountList.get(position)); } else { selectedAccountForActionMode = accountList.get(position); actionMode.invalidate(); @@ -186,221 +254,7 @@ public class ManageAccountActivity extends XmppActivity { selectedAccountForActionMode = accountList .get(position); actionMode = activity - .startActionMode((new ActionMode.Callback() { - - @Override - public boolean onPrepareActionMode( - ActionMode mode, Menu menu) { - if (selectedAccountForActionMode - .isOptionSet(Account.OPTION_DISABLED)) { - menu.findItem( - R.id.mgmt_account_enable) - .setVisible(true); - menu.findItem( - R.id.mgmt_account_disable) - .setVisible(false); - } else { - menu.findItem( - R.id.mgmt_account_disable) - .setVisible(true); - menu.findItem( - R.id.mgmt_account_enable) - .setVisible(false); - } - return true; - } - - @Override - public void onDestroyActionMode( - ActionMode mode) { - // TODO Auto-generated method stub - - } - - @Override - public boolean onCreateActionMode( - ActionMode mode, Menu menu) { - MenuInflater inflater = mode - .getMenuInflater(); - inflater.inflate( - R.menu.manageaccounts_context, - menu); - return true; - } - - @Override - public boolean onActionItemClicked( - final ActionMode mode, - MenuItem item) { - if (item.getItemId() == R.id.mgmt_account_edit) { - editAccount(selectedAccountForActionMode); - } else if (item.getItemId() == R.id.mgmt_account_disable) { - selectedAccountForActionMode - .setOption( - Account.OPTION_DISABLED, - true); - xmppConnectionService - .updateAccount(selectedAccountForActionMode); - mode.finish(); - } else if (item.getItemId() == R.id.mgmt_account_enable) { - selectedAccountForActionMode - .setOption( - Account.OPTION_DISABLED, - false); - xmppConnectionService - .updateAccount(selectedAccountForActionMode); - mode.finish(); - } else if (item.getItemId() == R.id.mgmt_account_delete) { - AlertDialog.Builder builder = new AlertDialog.Builder( - activity); - builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - 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) { - xmppConnectionService - .deleteAccount(selectedAccountForActionMode); - selectedAccountForActionMode = null; - mode.finish(); - } - }); - builder.setNegativeButton( - getString(R.string.cancel), - null); - builder.create().show(); - } else if (item.getItemId() == R.id.mgmt_account_announce_pgp) { - if (activity.hasPgp()) { - mode.finish(); - announcePgp( - selectedAccountForActionMode, - null); - } else { - activity.showInstallPgpDialog(); - } - } else if (item.getItemId() == R.id.mgmt_otr_key) { - AlertDialog.Builder builder = new AlertDialog.Builder( - activity); - builder.setTitle("OTR Fingerprint"); - String fingerprintTxt = selectedAccountForActionMode - .getOtrFingerprint(getApplicationContext()); - View view = (View) getLayoutInflater() - .inflate( - R.layout.otr_fingerprint, - null); - if (fingerprintTxt != null) { - TextView fingerprint = (TextView) view - .findViewById(R.id.otr_fingerprint); - TextView noFingerprintView = (TextView) view - .findViewById(R.id.otr_no_fingerprint); - fingerprint - .setText(fingerprintTxt); - fingerprint - .setVisibility(View.VISIBLE); - noFingerprintView - .setVisibility(View.GONE); - } - builder.setView(view); - 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); - builder.setTitle(getString(R.string.account_info)); - if (selectedAccountForActionMode - .getStatus() == Account.STATUS_ONLINE) { - XmppConnection xmpp = selectedAccountForActionMode - .getXmppConnection(); - long connectionAge = (SystemClock - .elapsedRealtime() - xmpp.lastConnect) / 60000; - long sessionAge = (SystemClock - .elapsedRealtime() - xmpp.lastSessionStarted) / 60000; - long connectionAgeHours = connectionAge / 60; - long sessionAgeHours = sessionAge / 60; - View view = (View) getLayoutInflater() - .inflate( - R.layout.server_info, - null); - TextView connection = (TextView) view - .findViewById(R.id.connection); - TextView session = (TextView) view - .findViewById(R.id.session); - TextView pcks_sent = (TextView) view - .findViewById(R.id.pcks_sent); - TextView pcks_received = (TextView) view - .findViewById(R.id.pcks_received); - TextView carbon = (TextView) view - .findViewById(R.id.carbon); - TextView stream = (TextView) view - .findViewById(R.id.stream); - TextView roster = (TextView) view - .findViewById(R.id.roster); - TextView presences = (TextView) view - .findViewById(R.id.number_presences); - presences.setText(selectedAccountForActionMode - .countPresences() - + ""); - pcks_received.setText("" - + xmpp.getReceivedStanzas()); - pcks_sent.setText("" - + xmpp.getSentStanzas()); - if (connectionAgeHours >= 2) { - connection - .setText(connectionAgeHours - + " " - + getString(R.string.hours)); - } else { - connection - .setText(connectionAge - + " " - + getString(R.string.mins)); - } - if (xmpp.hasFeatureStreamManagment()) { - if (sessionAgeHours >= 2) { - session.setText(sessionAgeHours - + " " - + getString(R.string.hours)); - } else { - session.setText(sessionAge - + " " - + getString(R.string.mins)); - } - stream.setText(getString(R.string.yes)); - } else { - stream.setText(getString(R.string.no)); - session.setText(connection - .getText()); - } - if (xmpp.hasFeaturesCarbon()) { - carbon.setText(getString(R.string.yes)); - } else { - carbon.setText(getString(R.string.no)); - } - if (xmpp.hasFeatureRosterManagment()) { - roster.setText(getString(R.string.yes)); - } else { - roster.setText(getString(R.string.no)); - } - builder.setView(view); - } else { - builder.setMessage(getString(R.string.mgmt_account_account_offline)); - } - builder.setPositiveButton( - getString(R.string.hide), - null); - builder.create().show(); - } - return true; - } - - })); + .startActionMode(mActionModeCallback); return true; } else { return false; @@ -422,11 +276,10 @@ public class ManageAccountActivity extends XmppActivity { xmppConnectionService.setOnAccountListChangedListener(accountChanged); this.accountList.clear(); this.accountList.addAll(xmppConnectionService.getAccounts()); - accountListViewAdapter.notifyDataSetChanged(); + mAccountAdapter.notifyDataSetChanged(); if ((this.accountList.size() == 0) && (this.firstrun)) { getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setHomeButtonEnabled(false); - addAccount(); this.firstrun = false; } } @@ -441,7 +294,7 @@ public class ManageAccountActivity extends XmppActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_add_account: - addAccount(); + startActivity(new Intent(getApplicationContext(), EditAccountActivity.class)); break; default: break; @@ -452,7 +305,8 @@ public class ManageAccountActivity extends XmppActivity { @Override public boolean onNavigateUp() { if (xmppConnectionService.getConversations().size() == 0) { - Intent contactsIntent = new Intent(this, StartConversationActivity.class); + Intent contactsIntent = new Intent(this, + StartConversationActivity.class); contactsIntent.setFlags( // if activity exists in stack, pop the stack and go back to it Intent.FLAG_ACTIVITY_CLEAR_TOP | @@ -470,37 +324,9 @@ public class ManageAccountActivity extends XmppActivity { } private void editAccount(Account account) { - EditAccountDialog dialog = new EditAccountDialog(); - dialog.setAccount(account); - dialog.setEditAccountListener(new EditAccountListener() { - - @Override - public void onAccountEdited(Account account) { - xmppConnectionService.updateAccount(account); - if (actionMode != null) { - actionMode.finish(); - } - } - }); - dialog.show(getFragmentManager(), "edit_account"); - dialog.setKnownHosts(xmppConnectionService.getKnownHosts(), this); - - } - - protected void addAccount() { - final Activity activity = this; - EditAccountDialog dialog = new EditAccountDialog(); - dialog.setEditAccountListener(new EditAccountListener() { - - @Override - public void onAccountEdited(Account account) { - xmppConnectionService.createAccount(account); - activity.getActionBar().setDisplayHomeAsUpEnabled(true); - activity.getActionBar().setHomeButtonEnabled(true); - } - }); - dialog.show(getFragmentManager(), "add_account"); - dialog.setKnownHosts(xmppConnectionService.getKnownHosts(), this); + Intent intent = new Intent(this, EditAccountActivity.class); + intent.putExtra("jid", account.getJid()); + startActivity(intent); } @Override diff --git a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java new file mode 100644 index 00000000..e874eeb7 --- /dev/null +++ b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -0,0 +1,215 @@ +package eu.siacs.conversations.ui; + +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.utils.PhoneHelper; +import eu.siacs.conversations.xmpp.pep.Avatar; + +public class PublishProfilePictureActivity extends XmppActivity { + + private static final int REQUEST_CHOOSE_FILE = 0xac23; + + private ImageView avatar; + private TextView accountTextView; + private TextView hintOrWarning; + private TextView secondaryHint; + private Button cancelButton; + private Button publishButton; + + private Uri avatarUri; + private Uri defaultUri; + + private Account account; + + private boolean support = false; + + private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() { + + @Override + public void success(Avatar object) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + finish(); + } + }); + } + + @Override + public void error(final int errorCode, Avatar object) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + hintOrWarning.setText(errorCode); + hintOrWarning.setTextColor(getWarningTextColor()); + publishButton.setText(R.string.publish_avatar); + enablePublishButton(); + } + }); + + } + + @Override + public void userInputRequried(PendingIntent pi, Avatar object) { + } + }; + + private OnLongClickListener backToDefaultListener = new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + avatarUri = defaultUri; + loadImageIntoPreview(defaultUri); + return true; + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_publish_profile_picture); + this.avatar = (ImageView) findViewById(R.id.account_image); + this.cancelButton = (Button) findViewById(R.id.cancel_button); + this.publishButton = (Button) findViewById(R.id.publish_button); + this.accountTextView = (TextView) findViewById(R.id.account); + this.hintOrWarning = (TextView) findViewById(R.id.hint_or_warning); + this.secondaryHint = (TextView) findViewById(R.id.secondary_hint); + this.publishButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (avatarUri != null) { + publishButton.setText(R.string.publishing); + disablePublishButton(); + xmppConnectionService.publishAvatar(account, avatarUri, + avatarPublication); + } + } + }); + this.cancelButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + finish(); + } + }); + this.avatar.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + 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_CHOOSE_FILE); + } + }); + this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext()); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CHOOSE_FILE) { + this.avatarUri = data.getData(); + if (xmppConnectionServiceBound) { + loadImageIntoPreview(this.avatarUri); + } + } + } + } + + @Override + protected void onBackendConnected() { + if (getIntent() != null) { + String jid = getIntent().getStringExtra("account"); + if (jid != null) { + this.account = xmppConnectionService.findAccountByJid(jid); + if (this.account.getXmppConnection() != null) { + this.support = this.account.getXmppConnection().getFeatures().pubsub(); + } + if (this.avatarUri == null) { + if (this.account.getAvatar() != null || this.defaultUri == null) { + this.avatar.setImageBitmap(this.account.getImage( + getApplicationContext(), 384)); + if (this.defaultUri != null) { + this.avatar + .setOnLongClickListener(this.backToDefaultListener); + } else { + this.secondaryHint.setVisibility(View.INVISIBLE); + } + if (!support) { + this.hintOrWarning.setTextColor(getWarningTextColor()); + this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); + } + } else { + this.avatarUri = this.defaultUri; + loadImageIntoPreview(this.defaultUri); + this.secondaryHint.setVisibility(View.INVISIBLE); + } + } else { + loadImageIntoPreview(avatarUri); + } + this.accountTextView.setText(this.account.getJid()); + } + } + + } + + protected void loadImageIntoPreview(Uri uri) { + Bitmap bm = xmppConnectionService.getFileBackend().cropCenterSquare( + uri, 384); + if (bm==null) { + disablePublishButton(); + this.hintOrWarning.setTextColor(getWarningTextColor()); + this.hintOrWarning.setText(R.string.error_publish_avatar_converting); + return; + } + this.avatar.setImageBitmap(bm); + if (support) { + enablePublishButton(); + this.publishButton.setText(R.string.publish_avatar); + this.hintOrWarning.setText(R.string.publish_avatar_explanation); + this.hintOrWarning.setTextColor(getPrimaryTextColor()); + } else { + disablePublishButton(); + this.hintOrWarning.setTextColor(getWarningTextColor()); + this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); + } + if (this.defaultUri != null && uri.equals(this.defaultUri)) { + this.secondaryHint.setVisibility(View.INVISIBLE); + this.avatar.setOnLongClickListener(null); + } else if (this.defaultUri != null ) { + this.secondaryHint.setVisibility(View.VISIBLE); + this.avatar.setOnLongClickListener(this.backToDefaultListener); + } + } + + protected void enablePublishButton() { + this.publishButton.setEnabled(true); + this.publishButton.setTextColor(getPrimaryTextColor()); + } + + protected void disablePublishButton() { + this.publishButton.setEnabled(false); + this.publishButton.setTextColor(getSecondaryTextColor()); + } + +} diff --git a/src/eu/siacs/conversations/ui/StartConversationActivity.java b/src/eu/siacs/conversations/ui/StartConversationActivity.java index d12d2878..9dad5c16 100644 --- a/src/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/eu/siacs/conversations/ui/StartConversationActivity.java @@ -21,6 +21,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -63,6 +64,7 @@ public class StartConversationActivity extends XmppActivity { private List<String> mKnownHosts; private List<String> mKnownConferenceHosts; + private Menu mOptionsMenu; private EditText mSearchEditText; public int conference_context_id; @@ -148,7 +150,9 @@ public class StartConversationActivity extends XmppActivity { @Override public void run() { - filter(mSearchEditText.getText().toString()); + if (mSearchEditText != null) { + filter(mSearchEditText.getText().toString()); + } } }); } @@ -430,6 +434,7 @@ public class StartConversationActivity extends XmppActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { + this.mOptionsMenu = menu; getMenuInflater().inflate(R.menu.start_conversation, menu); MenuItem menuCreateContact = (MenuItem) menu .findItem(R.id.action_create_contact); @@ -461,6 +466,17 @@ public class StartConversationActivity extends XmppActivity { } return super.onOptionsItemSelected(item); } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if(keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) + { + mOptionsMenu.findItem(R.id.action_search).expandActionView(); + return true; + } + return super.onKeyUp(keyCode, event); + } @Override void onBackendConnected() { @@ -482,8 +498,10 @@ public class StartConversationActivity extends XmppActivity { } protected void filter(String needle) { - this.filterContacts(needle); - this.filterConferences(needle); + if (xmppConnectionServiceBound) { + this.filterContacts(needle); + this.filterConferences(needle); + } } protected void filterContacts(String needle) { diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java index fad4d026..44043a79 100644 --- a/src/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -31,22 +31,24 @@ import android.widget.EditText; public abstract class XmppActivity extends Activity { - public static final int REQUEST_ANNOUNCE_PGP = 0x73731; - protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x341830; + protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; + protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; protected final static String LOGTAG = "xmppService"; public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; protected boolean handledViewIntent = false; - + protected int mPrimaryTextColor; protected int mSecondaryTextColor; - + protected int mWarningTextColor; + protected int mPrimaryColor; + protected interface OnValueEdited { public void onValueEdited(String value); } - + public interface OnPresenceSelected { public void onPresenceSelected(); } @@ -152,6 +154,9 @@ public abstract class XmppActivity extends Activity { case R.id.action_accounts: startActivity(new Intent(this, ManageAccountActivity.class)); break; + case android.R.id.home: + finish(); + break; } return super.onOptionsItemSelected(item); } @@ -162,6 +167,8 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); mPrimaryTextColor = getResources().getColor(R.color.primarytext); mSecondaryTextColor = getResources().getColor(R.color.secondarytext); + mWarningTextColor = getResources().getColor(R.color.warningtext); + mPrimaryColor = getResources().getColor(R.color.primary); } public void switchToConversation(Conversation conversation) { @@ -199,11 +206,12 @@ public abstract class XmppActivity extends Activity { } protected void inviteToConversation(Conversation conversation) { - Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); - intent.putExtra("conversation",conversation.getUuid()); + Intent intent = new Intent(getApplicationContext(), + ChooseContactActivity.class); + intent.putExtra("conversation", conversation.getUuid()); startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION); } - + protected void announcePgp(Account account, final Conversation conversation) { xmppConnectionService.getPgpEngine().generateSignature(account, "online", new UiCallback<Account>() { @@ -214,7 +222,8 @@ public abstract class XmppActivity extends Activity { try { startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); - } catch (SendIntentException e) {} + } catch (SendIntentException e) { + } } @Override @@ -275,15 +284,17 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - protected void quickEdit(final String previousValue, final OnValueEdited callback) { + protected void quickEdit(final String previousValue, + final OnValueEdited callback) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - View view = (View) getLayoutInflater().inflate(R.layout.quickedit, null); + View view = (View) getLayoutInflater() + .inflate(R.layout.quickedit, null); final EditText editor = (EditText) view.findViewById(R.id.editor); editor.setText(previousValue); builder.setView(view); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.edit, new OnClickListener() { - + @Override public void onClick(DialogInterface dialog, int which) { String value = editor.getText().toString(); @@ -294,11 +305,11 @@ public abstract class XmppActivity extends Activity { }); builder.create().show(); } - + public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) { Contact contact = conversation.getContact(); - if (contact == null) { + if (!contact.showInRoster()) { showAddToRosterDialog(conversation); } else { Presences presences = contact.getPresences(); @@ -346,26 +357,37 @@ public abstract class XmppActivity extends Activity { } } } - + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { + if (requestCode == REQUEST_INVITE_TO_CONVERSATION + && resultCode == RESULT_OK) { String contactJid = data.getStringExtra("contact"); String conversationUuid = data.getStringExtra("conversation"); - Conversation conversation = xmppConnectionService.findConversationByUuid(conversationUuid); + Conversation conversation = xmppConnectionService + .findConversationByUuid(conversationUuid); if (conversation.getMode() == Conversation.MODE_MULTI) { xmppConnectionService.invite(conversation, contactJid); } - Log.d("xmppService","inviting "+contactJid+" to "+conversation.getName(true)); + Log.d("xmppService", "inviting " + contactJid + " to " + + conversation.getName(true)); } } - + public int getSecondaryTextColor() { return this.mSecondaryTextColor; } - + public int getPrimaryTextColor() { return this.mPrimaryTextColor; } + + public int getWarningTextColor() { + return this.mWarningTextColor; + } + + public int getPrimaryColor() { + return this.mPrimaryColor; + } } diff --git a/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java new file mode 100644 index 00000000..cd8b376f --- /dev/null +++ b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -0,0 +1,101 @@ +package eu.siacs.conversations.ui.adapter; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.ui.XmppActivity; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class AccountAdapter extends ArrayAdapter<Account> { + + private XmppActivity activity; + + public AccountAdapter(XmppActivity activity, List<Account> objects) { + super(activity, 0, objects); + this.activity = activity; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + Account account = getItem(position); + if (view == null) { + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = (View) inflater.inflate(R.layout.account_row, parent, false); + } + TextView jid = (TextView) view.findViewById(R.id.account_jid); + jid.setText(account.getJid()); + TextView statusView = (TextView) view.findViewById(R.id.account_status); + ImageView imageView = (ImageView) view.findViewById(R.id.account_image); + imageView.setImageBitmap(account.getImage(activity,48)); + switch (account.getStatus()) { + case Account.STATUS_DISABLED: + statusView.setText(getContext().getString( + R.string.account_status_disabled)); + statusView.setTextColor(activity.getSecondaryTextColor()); + break; + case Account.STATUS_ONLINE: + statusView.setText(getContext().getString( + R.string.account_status_online)); + statusView.setTextColor(activity.getPrimaryColor()); + break; + case Account.STATUS_CONNECTING: + statusView.setText(getContext().getString( + R.string.account_status_connecting)); + statusView.setTextColor(activity.getSecondaryTextColor()); + break; + case Account.STATUS_OFFLINE: + statusView.setText(getContext().getString( + R.string.account_status_offline)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_UNAUTHORIZED: + statusView.setText(getContext().getString( + R.string.account_status_unauthorized)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_SERVER_NOT_FOUND: + statusView.setText(getContext().getString( + R.string.account_status_not_found)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_NO_INTERNET: + statusView.setText(getContext().getString( + R.string.account_status_no_internet)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_REGISTRATION_FAILED: + statusView.setText(getContext().getString( + R.string.account_status_regis_fail)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_REGISTRATION_CONFLICT: + statusView.setText(getContext().getString( + R.string.account_status_regis_conflict)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + case Account.STATUS_REGISTRATION_SUCCESSFULL: + statusView.setText(getContext().getString( + R.string.account_status_regis_success)); + statusView.setTextColor(activity.getSecondaryTextColor()); + break; + case Account.STATUS_REGISTRATION_NOT_SUPPORTED: + statusView.setText(getContext().getString( + R.string.account_status_regis_not_sup)); + statusView.setTextColor(activity.getWarningTextColor()); + break; + default: + statusView.setText(""); + break; + } + + return view; + } +} diff --git a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java new file mode 100644 index 00000000..66403804 --- /dev/null +++ b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -0,0 +1,107 @@ +package eu.siacs.conversations.ui.adapter; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.utils.UIHelper; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class ConversationAdapter extends ArrayAdapter<Conversation> { + + ConversationActivity activity; + + public ConversationAdapter(ConversationActivity activity, + List<Conversation> conversations) { + super(activity, 0, conversations); + this.activity = activity; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + LayoutInflater inflater = (LayoutInflater) activity + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = (View) inflater.inflate(R.layout.conversation_list_row, + parent, false); + } + Conversation conv = getItem(position); + if (!activity.getSlidingPaneLayout().isSlideable()) { + if (conv == activity.getSelectedConversation()) { + view.setBackgroundColor(0xffdddddd); + } else { + view.setBackgroundColor(Color.TRANSPARENT); + } + } else { + view.setBackgroundColor(Color.TRANSPARENT); + } + TextView convName = (TextView) view + .findViewById(R.id.conversation_name); + convName.setText(conv.getName(true)); + TextView convLastMsg = (TextView) view + .findViewById(R.id.conversation_lastmsg); + ImageView imagePreview = (ImageView) view + .findViewById(R.id.conversation_lastimage); + + Message latestMessage = conv.getLatestMessage(); + + if (latestMessage.getType() == Message.TYPE_TEXT + || latestMessage.getType() == Message.TYPE_PRIVATE) { + if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP) + && (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { + convLastMsg.setText(conv.getLatestMessage().getBody()); + } else { + convLastMsg.setText(activity + .getText(R.string.encrypted_message_received)); + } + convLastMsg.setVisibility(View.VISIBLE); + imagePreview.setVisibility(View.GONE); + } else if (latestMessage.getType() == Message.TYPE_IMAGE) { + if (latestMessage.getStatus() >= Message.STATUS_RECIEVED) { + convLastMsg.setVisibility(View.GONE); + imagePreview.setVisibility(View.VISIBLE); + activity.loadBitmap(latestMessage, imagePreview); + } else { + convLastMsg.setVisibility(View.VISIBLE); + imagePreview.setVisibility(View.GONE); + if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) { + convLastMsg.setText(activity + .getText(R.string.image_offered_for_download)); + } else if (latestMessage.getStatus() == Message.STATUS_RECIEVING) { + convLastMsg.setText(activity + .getText(R.string.receiving_image)); + } else { + convLastMsg.setText(""); + } + } + } + + if (!conv.isRead()) { + convName.setTypeface(null, Typeface.BOLD); + convLastMsg.setTypeface(null, Typeface.BOLD); + } else { + convName.setTypeface(null, Typeface.NORMAL); + convLastMsg.setTypeface(null, Typeface.NORMAL); + } + + ((TextView) view.findViewById(R.id.conversation_lastupdate)) + .setText(UIHelper.readableTimeDifference(getContext(), conv + .getLatestMessage().getTimeSent())); + + ImageView profilePicture = (ImageView) view + .findViewById(R.id.conversation_image); + profilePicture.setImageBitmap(conv.getImage(activity, 56)); + + return view; + } +} diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 0a2857d2..34fea41f 100644 --- a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -7,16 +7,14 @@ import eu.siacs.conversations.R; 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.ui.ConversationActivity; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jingle.JingleConnection; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.Typeface; -import android.preference.PreferenceManager; +import android.text.Html; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; @@ -37,44 +35,38 @@ public class MessageAdapter extends ArrayAdapter<Message> { private ConversationActivity activity; - private Bitmap selfBitmap2; + private Bitmap accountBitmap; private BitmapCache mBitmapCache = new BitmapCache(); private DisplayMetrics metrics; - private boolean useSubject = true; - private OnContactPictureClicked mOnContactPictureClickedListener; + private OnContactPictureLongClicked mOnContactPictureLongClickedListener; public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; metrics = getContext().getResources().getDisplayMetrics(); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(getContext()); - useSubject = preferences.getBoolean("use_subject_in_muc", true); } private Bitmap getSelfBitmap() { - if (this.selfBitmap2 == null) { + if (this.accountBitmap == null) { if (getCount() > 0) { - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(getContext()); - boolean showPhoneSelfContactPicture = preferences.getBoolean( - "show_phone_selfcontact_picture", true); - - this.selfBitmap2 = UIHelper.getSelfContactPicture(getItem(0) - .getConversation().getAccount(), 48, - showPhoneSelfContactPicture, getContext()); + this.accountBitmap = getItem(0).getConversation().getAccount() + .getImage(getContext(), 48); } } - return this.selfBitmap2; + return this.accountBitmap; } public void setOnContactPictureClicked(OnContactPictureClicked listener) { this.mOnContactPictureClickedListener = listener; } + + public void setOnContactPictureLongClicked(OnContactPictureLongClicked listener) { + this.mOnContactPictureLongClickedListener = listener; + } @Override public int getViewTypeCount() { @@ -130,7 +122,16 @@ public class MessageAdapter extends ArrayAdapter<Message> { error = true; default: if (multiReceived) { - info = message.getCounterpart(); + Contact contact = message.getContact(); + if (contact != null) { + info = contact.getDisplayName(); + } else { + if (message.getPresence() != null) { + info = message.getPresence(); + } else { + info = message.getCounterpart(); + } + } } break; } @@ -199,14 +200,33 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setTextIsSelectable(false); } - private void displayTextMessage(ViewHolder viewHolder, String text) { + private void displayTextMessage(ViewHolder viewHolder, Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); - if (text != null) { - viewHolder.messageBody.setText(text.trim()); + if (message.getBody() != null) { + if (message.getType() != Message.TYPE_PRIVATE) { + viewHolder.messageBody.setText(message.getBody().trim()); + } else { + StringBuilder builder = new StringBuilder(); + builder.append(message.getBody().trim()); + builder.append(" <b><i>("); + if (message.getStatus() <= Message.STATUS_RECIEVED) { + builder.append(activity.getString(R.string.private_message)); + } else { + String to; + if (message.getPresence() != null) { + to = message.getPresence(); + } else { + to = message.getCounterpart(); + } + builder.append(activity.getString(R.string.private_message_to, to)); + } + builder.append(")</i></b>"); + viewHolder.messageBody.setText(Html.fromHtml(builder.toString())); + } } else { viewHolder.messageBody.setText(""); } @@ -245,8 +265,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(ImageProvider.getContentUri(message), - "image/*"); + intent.setDataAndType(activity.xmppConnectionService + .getFileBackend().getJingleFileUri(message), "image/*"); getContext().startActivity(intent); } }); @@ -257,7 +277,9 @@ public class MessageAdapter extends ArrayAdapter<Message> { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, - ImageProvider.getContentUri(message)); + activity.xmppConnectionService.getFileBackend() + .getJingleFileUri(message)); + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); shareIntent.setType("image/webp"); getContext().startActivity( Intent.createChooser(shareIntent, @@ -277,7 +299,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { switch (type) { case SENT: view = (View) activity.getLayoutInflater().inflate( - R.layout.message_sent, null); + R.layout.message_sent, parent, false); viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); viewHolder.contact_picture = (ImageView) view @@ -295,7 +317,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { break; case RECIEVED: view = (View) activity.getLayoutInflater().inflate( - R.layout.message_recieved, null); + R.layout.message_recieved, parent, false); viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); viewHolder.contact_picture = (ImageView) view @@ -307,9 +329,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - item.getConversation().getName(useSubject), item - .getConversation().getContact(), - getContext())); + item.getConversation().getContact(), getContext())); } viewHolder.indicator = (ImageView) view @@ -324,15 +344,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { break; case STATUS: view = (View) activity.getLayoutInflater().inflate( - R.layout.message_status, null); + R.layout.message_status, parent, false); viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - item.getConversation().getName(useSubject), item - .getConversation().getContact(), - getContext())); + item.getConversation().getContact(), getContext())); viewHolder.contact_picture.setAlpha(128); viewHolder.contact_picture .setOnClickListener(new OnClickListener() { @@ -366,8 +384,17 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (type == RECIEVED) { if (item.getConversation().getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - item.getCounterpart(), null, getContext())); + Contact contact = item.getContact(); + if (contact != null) { + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( + contact, getContext())); + } else { + String name = item.getPresence(); + if (name==null) { + name = item.getCounterpart(); + } + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(name, getContext())); + } viewHolder.contact_picture .setOnClickListener(new OnClickListener() { @@ -381,6 +408,18 @@ public class MessageAdapter extends ArrayAdapter<Message> { } }); + viewHolder.contact_picture.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener.onContactPictureLongClicked(item); + return true; + } else { + return false; + } + } + }); } } @@ -431,7 +470,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { displayDecryptionFailed(viewHolder); } else { - displayTextMessage(viewHolder, item.getBody()); + displayTextMessage(viewHolder, item); } } @@ -453,20 +492,24 @@ public class MessageAdapter extends ArrayAdapter<Message> { } private class BitmapCache { - private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>(); + private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>(); + private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>(); + + public Bitmap get(Contact contact, Context context) { + if (!contactBitmaps.containsKey(contact.getJid())) { + contactBitmaps.put(contact.getJid(), + contact.getImage(48, context)); + } + return contactBitmaps.get(contact.getJid()); + } - public Bitmap get(String name, Contact contact, Context context) { - if (bitmaps.containsKey(name)) { - return bitmaps.get(name); + public Bitmap get(String name, Context context) { + if (unknownBitmaps.containsKey(name)) { + return unknownBitmaps.get(name); } else { - Bitmap bm; - if (contact != null) { - bm = UIHelper - .getContactPicture(contact, 48, context, false); - } else { - bm = UIHelper.getContactPicture(name, 48, context, false); - } - bitmaps.put(name, bm); + Bitmap bm = UIHelper + .getContactPicture(name, 48, context, false); + unknownBitmaps.put(name, bm); return bm; } } @@ -475,4 +518,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { public interface OnContactPictureClicked { public void onContactPictureClicked(Message message); } + + public interface OnContactPictureLongClicked { + public void onContactPictureLongClicked(Message message); + } } diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java index bccd7210..63c17761 100644 --- a/src/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/eu/siacs/conversations/utils/PhoneHelper.java @@ -70,11 +70,11 @@ public class PhoneHelper { public static Uri getSefliUri(Context context) { String[] mProjection = new String[] { Profile._ID, - Profile.PHOTO_THUMBNAIL_URI }; + Profile.PHOTO_URI }; Cursor mProfileCursor = context.getContentResolver().query( Profile.CONTENT_URI, mProjection, null, null, null); - if (mProfileCursor.getCount() == 0) { + if (mProfileCursor == null || mProfileCursor.getCount() == 0) { return null; } else { mProfileCursor.moveToFirst(); diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index 1cd3403c..335b471a 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -15,6 +15,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions.User; +import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; import android.app.Activity; @@ -214,13 +215,17 @@ public class UIHelper { new String[] { conversation.getName(false) }, size, bgColor, fgColor); } - String[] names = new String[members.size() + 1]; - names[0] = conversation.getMucOptions().getActualNick(); - for (int i = 0; i < members.size(); ++i) { - names[i + 1] = members.get(i).getName(); + ArrayList<String> names = new ArrayList<String>(); + names.add(conversation.getMucOptions().getActualNick()); + for(User user : members) { + names.add(user.getName()); + if (names.size() > 4 ) { + break; + } } - - return getUnknownContactPicture(names, size, bgColor, fgColor); + String[] mArrayNames = new String[names.size()]; + names.toArray(mArrayNames); + return getUnknownContactPicture(mArrayNames, size, bgColor, fgColor); } public static Bitmap getContactPicture(Conversation conversation, @@ -341,7 +346,7 @@ public class UIHelper { if ((currentCon != null) && (currentCon.getMode() == Conversation.MODE_MULTI) - && (!alwaysNotify)) { + && (!alwaysNotify) && notify) { String nick = currentCon.getMucOptions().getActualNick(); Pattern highlight = generateNickHighlightPattern(nick); Matcher m = highlight.matcher(currentCon.getLatestMessage() @@ -372,8 +377,7 @@ public class UIHelper { } else if (unread.size() == 1) { Conversation conversation = unread.get(0); targetUuid = conversation.getUuid(); - mBuilder.setLargeIcon(UIHelper.getContactPicture(conversation, 64, - context, true)); + mBuilder.setLargeIcon(conversation.getImage(context, 64)); mBuilder.setContentTitle(conversation.getName(useSubject)); if (notify) { mBuilder.setTicker(conversation.getLatestMessage() @@ -484,8 +488,7 @@ public class UIHelper { long id = Long.parseLong(systemAccount[0]); badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1])); } - badge.setImageBitmap(UIHelper.getContactPicture(contact, 72, context, - false)); + badge.setImageBitmap(contact.getImage(72, context)); } public static AlertDialog getVerifyFingerprintDialog( diff --git a/src/eu/siacs/conversations/utils/Validator.java b/src/eu/siacs/conversations/utils/Validator.java index 9ca1e34f..a1a119e7 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("\\b^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); + Pattern.compile("^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); public static boolean isValidJid(String jid) { Matcher matcher = VALID_JID.matcher(jid); diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java index f8e070f7..3d22ba6a 100644 --- a/src/eu/siacs/conversations/xml/Element.java +++ b/src/eu/siacs/conversations/xml/Element.java @@ -144,4 +144,12 @@ public class Element { public void clearChildren() { this.children.clear(); } + + public void setAttribute(String name, long value) { + this.setAttribute(name, ""+value); + } + + public void setAttribute(String name, int value) { + this.setAttribute(name, ""+value); + } } diff --git a/src/eu/siacs/conversations/xml/TagWriter.java b/src/eu/siacs/conversations/xml/TagWriter.java index 23a260f2..4828d5d9 100644 --- a/src/eu/siacs/conversations/xml/TagWriter.java +++ b/src/eu/siacs/conversations/xml/TagWriter.java @@ -41,12 +41,18 @@ public class TagWriter { public TagWriter() { } - public void setOutputStream(OutputStream out) { + public void setOutputStream(OutputStream out) throws IOException { + if (out==null) { + throw new IOException(); + } this.plainOutputStream = out; this.outputStream = new OutputStreamWriter(out); } - public OutputStream getOutputStream() { + public OutputStream getOutputStream() throws IOException { + if (this.plainOutputStream==null) { + throw new IOException(); + } return this.plainOutputStream; } diff --git a/src/eu/siacs/conversations/xml/XmlReader.java b/src/eu/siacs/conversations/xml/XmlReader.java index 10b2fb06..1c7e94e6 100644 --- a/src/eu/siacs/conversations/xml/XmlReader.java +++ b/src/eu/siacs/conversations/xml/XmlReader.java @@ -28,24 +28,33 @@ public class XmlReader { this.wakeLock = wakeLock; } - public void setInputStream(InputStream inputStream) { + public void setInputStream(InputStream inputStream) throws IOException { + if (inputStream==null) { + throw new IOException(); + } this.is = inputStream; try { parser.setInput(new InputStreamReader(this.is)); } catch (XmlPullParserException e) { - Log.d(LOGTAG,"error setting input stream"); + throw new IOException("error resetting parser"); } } - public InputStream getInputStream() { + public InputStream getInputStream() throws IOException { + if (this.is==null) { + throw new IOException(); + } return is; } - public void reset() { + public void reset() throws IOException { + if (this.is==null) { + throw new IOException(); + } try { parser.setInput(new InputStreamReader(this.is)); } catch (XmlPullParserException e) { - Log.d(LOGTAG,"error resetting input stream"); + throw new IOException("error resetting parser"); } } @@ -54,7 +63,7 @@ public class XmlReader { try { wakeLock.release();} catch (RuntimeException re) {} } try { - while(parser.next() != XmlPullParser.END_DOCUMENT) { + while(this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { wakeLock.acquire(); if (parser.getEventType() == XmlPullParser.START_TAG) { Tag tag = Tag.start(parser.getName()); @@ -81,8 +90,6 @@ public class XmlReader { throw new IOException("xml parser mishandled ArrayIndexOufOfBounds", e); } catch (StringIndexOutOfBoundsException e) { throw new IOException("xml parser mishandled StringIndexOufOfBounds", e); - } catch (NullPointerException e) { - throw new IOException("null pointer in xml parser"); } return null; } diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index 45bac2f6..8e93a91d 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -7,14 +7,8 @@ import java.math.BigInteger; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; @@ -25,16 +19,13 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; + import javax.net.ssl.X509TrustManager; -import org.bouncycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe; import org.xmlpull.v1.XmlPullParserException; import de.duenndns.ssl.MemorizingTrustManager; -import android.content.Context; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -73,6 +64,8 @@ public class XmppConnection implements Runnable { private Socket socket; private XmlReader tagReader; private TagWriter tagWriter; + + private Features features = new Features(this); private boolean shouldBind = true; private boolean shouldAuthenticate = true; @@ -228,11 +221,6 @@ public class XmppConnection implements Runnable { processStreamError(nextTag); } else if (nextTag.isStart("features")) { processStreamFeatures(nextTag); - if ((streamFeatures.getChildren().size() == 1) - && (streamFeatures.hasChild("starttls")) - && (!account.isOptionSet(Account.OPTION_USETLS))) { - changeStatus(Account.STATUS_SERVER_REQUIRES_TLS); - } } else if (nextTag.isStart("proceed")) { switchOverToTls(nextTag); } else if (nextTag.isStart("compressed")) { @@ -422,7 +410,6 @@ public class XmppConnection implements Runnable { throws XmlPullParserException, IOException, NoSuchAlgorithmException { tagReader.readTag(); // read tag close - tagWriter.setOutputStream(new ZLibOutputStream(tagWriter .getOutputStream())); tagReader @@ -609,25 +596,32 @@ public class XmppConnection implements Runnable { 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")) { - smVersion = 3; - EnablePacket enable = new EnablePacket(smVersion); - tagWriter.writeStanzaAsync(enable); - } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { - smVersion = 2; - EnablePacket enable = new EnablePacket(smVersion); - tagWriter.writeStanzaAsync(enable); - } - sendServiceDiscoveryInfo(account.getServer()); - sendServiceDiscoveryItems(account.getServer()); - if (bindListener != null) { - bindListener.onBind(account); + Element bind = packet.findChild("bind"); + if (bind!=null) { + Element jid = bind.findChild("jid"); + if (jid!=null) { + account.setResource(jid.getContent().split("/")[1]); + 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")) { + smVersion = 2; + EnablePacket enable = new EnablePacket(smVersion); + tagWriter.writeStanzaAsync(enable); + } + sendServiceDiscoveryInfo(account.getServer()); + sendServiceDiscoveryItems(account.getServer()); + if (bindListener != null) { + bindListener.onBind(account); + } + changeStatus(Account.STATUS_ONLINE); + } else { + disconnect(true); + } + } else { + disconnect(true); } - - changeStatus(Account.STATUS_ONLINE); } }); if (this.streamFeatures.hasChild("session")) { @@ -664,7 +658,7 @@ public class XmppConnection implements Runnable { } private void enableAdvancedStreamFeatures() { - if (hasFeaturesCarbon()) { + if (getFeatures().carbons()) { sendEnableCarbons(); } } @@ -835,33 +829,6 @@ public class XmppConnection implements Runnable { } } - public boolean hasFeatureRosterManagment() { - if (this.streamFeatures == null) { - return false; - } else { - return this.streamFeatures.hasChild("ver"); - } - } - - public boolean hasFeatureStreamManagment() { - 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 List<String> findDiscoItemsByFeature(String feature) { List<String> items = new ArrayList<String>(); for (Entry<String, List<String>> cursor : disco.entrySet()) { @@ -905,4 +872,46 @@ public class XmppConnection implements Runnable { public int getAttempt() { return this.attempt; } + + public Features getFeatures() { + return this.features; + } + + public class Features { + XmppConnection connection; + public Features(XmppConnection connection) { + this.connection = connection; + } + + private boolean hasDiscoFeature(String server, String feature) { + if (!connection.disco.containsKey(server)) { + return false; + } + return connection.disco.get(server).contains(feature); + } + + public boolean carbons() { + return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); + } + + public boolean sm() { + if (connection.streamFeatures == null) { + return false; + } else { + return connection.streamFeatures.hasChild("sm"); + } + } + + public boolean pubsub() { + return hasDiscoFeature(account.getServer(), "http://jabber.org/protocol/pubsub#publish"); + } + + public boolean rosterVersioning() { + if (connection.streamFeatures == null) { + return false; + } else { + return connection.streamFeatures.hasChild("ver"); + } + } + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index f1a0373c..dafb2639 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -8,7 +8,9 @@ import java.util.List; import java.util.Locale; import java.util.Map.Entry; +import android.content.Intent; import android.graphics.BitmapFactory; +import android.net.Uri; import android.util.Log; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; @@ -23,12 +25,12 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class JingleConnection { - private final String[] extensions = {"webp","jpeg","jpg","png"}; - private final String[] cryptoExtensions = {"pgp","gpg","otr"}; - + private final String[] extensions = { "webp", "jpeg", "jpg", "png" }; + private final String[] cryptoExtensions = { "pgp", "gpg", "otr" }; + private JingleConnectionManager mJingleConnectionManager; private XmppConnectionService mXmppConnectionService; - + public static final int STATUS_INITIATED = 0; public static final int STATUS_ACCEPTED = 1; public static final int STATUS_TERMINATED = 2; @@ -36,9 +38,9 @@ public class JingleConnection { public static final int STATUS_FINISHED = 4; public static final int STATUS_TRANSMITTING = 5; public static final int STATUS_FAILED = 99; - + private int ibbBlockSize = 4096; - + private int status = -1; private Message message; private String sessionId; @@ -47,54 +49,64 @@ public class JingleConnection { private String responder; private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>(); private HashMap<String, JingleSocks5Transport> connections = new HashMap<String, JingleSocks5Transport>(); - + private String transportId; private Element fileOffer; private JingleFile file = null; - + private String contentName; private String contentCreator; - + private boolean receivedCandidate = false; private boolean sentCandidate = false; - + private boolean acceptedAutomatically = false; - + private JingleTransport transport = null; - + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { - + @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE_ERROR) { if (initiator.equals(account.getFullJid())) { - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, + Message.STATUS_SEND_FAILED); } status = STATUS_FAILED; } } }; - + final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() { - + @Override public void onFileTransmitted(JingleFile file) { if (responder.equals(account.getFullJid())) { sendSuccess(); if (acceptedAutomatically) { message.markUnread(); - JingleConnection.this.mXmppConnectionService.notifyUi(message.getConversation(), true); + JingleConnection.this.mXmppConnectionService.notifyUi( + message.getConversation(), true); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(),options); + BitmapFactory.decodeFile(file.getAbsolutePath(), options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; - message.setBody(""+file.getSize()+","+imageWidth+","+imageHeight); + message.setBody("" + file.getSize() + "," + imageWidth + "," + + imageHeight); mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVED); + mXmppConnectionService.markMessage(message, + Message.STATUS_RECIEVED); + } + Log.d("xmppService", + "sucessfully transmitted file:" + file.getAbsolutePath()); + if (message.getEncryption()!=Message.ENCRYPTION_PGP) { + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); } - Log.d("xmppService","sucessfully transmitted file:"+file.getAbsolutePath()); } @Override @@ -103,48 +115,49 @@ public class JingleConnection { JingleConnection.this.cancel(); } }; - + private OnProxyActivated onProxyActivated = new OnProxyActivated() { - + @Override public void success() { if (initiator.equals(account.getFullJid())) { - Log.d("xmppService","we were initiating. sending file"); - transport.send(file,onFileTransmissionSatusChanged); + Log.d("xmppService", "we were initiating. sending file"); + transport.send(file, onFileTransmissionSatusChanged); } else { - transport.receive(file,onFileTransmissionSatusChanged); - Log.d("xmppService","we were responding. receiving file"); + transport.receive(file, onFileTransmissionSatusChanged); + Log.d("xmppService", "we were responding. receiving file"); } } - + @Override public void failed() { - Log.d("xmppService","proxy activation failed"); + Log.d("xmppService", "proxy activation failed"); } }; - + public JingleConnection(JingleConnectionManager mJingleConnectionManager) { this.mJingleConnectionManager = mJingleConnectionManager; - this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService(); + this.mXmppConnectionService = mJingleConnectionManager + .getXmppConnectionService(); } - + public String getSessionId() { return this.sessionId; } - + public String getAccountJid() { return this.account.getFullJid(); } - + public String getCounterPart() { return this.message.getCounterpart(); } - + public void deliverPacket(JinglePacket packet) { boolean returnResult = true; if (packet.isAction("session-terminate")) { Reason reason = packet.getReason(); - if (reason!=null) { + if (reason != null) { if (reason.hasChild("cancel")) { this.cancel(); } else if (reason.hasChild("success")) { @@ -164,24 +177,26 @@ public class JingleConnection { returnResult = this.receiveFallbackToIbb(packet); } else { returnResult = false; - Log.d("xmppService","trying to fallback to something unknown"+packet.toString()); + Log.d("xmppService", "trying to fallback to something unknown" + + packet.toString()); } } else if (packet.isAction("transport-accept")) { returnResult = this.receiveTransportAccept(packet); } else { - Log.d("xmppService","packet arrived in connection. action was "+packet.getAction()); + Log.d("xmppService", "packet arrived in connection. action was " + + packet.getAction()); returnResult = false; } IqPacket response; if (returnResult) { response = packet.generateRespone(IqPacket.TYPE_RESULT); - + } else { response = packet.generateRespone(IqPacket.TYPE_ERROR); } account.getXmppConnection().sendIqPacket(response, null); } - + public void init(Message message) { this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); @@ -193,42 +208,52 @@ public class JingleConnection { if (this.candidates.size() > 0) { this.sendInitRequest(); } else { - this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() { - - @Override - public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) { - if (success) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate); - connections.put(candidate.getCid(), socksConnection); - socksConnection.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d("xmppService","connection to our own primary candidete failed"); - sendInitRequest(); - } - - @Override - public void established() { - Log.d("xmppService","succesfully connected to our own primary candidate"); + this.mJingleConnectionManager.getPrimaryCandidate(account, + new OnPrimaryCandidateFound() { + + @Override + public void onPrimaryCandidateFound(boolean success, + final JingleCandidate candidate) { + if (success) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + JingleConnection.this, candidate); + connections.put(candidate.getCid(), + socksConnection); + socksConnection + .connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d("xmppService", + "connection to our own primary candidete failed"); + sendInitRequest(); + } + + @Override + public void established() { + Log.d("xmppService", + "succesfully connected to our own primary candidate"); + mergeCandidate(candidate); + sendInitRequest(); + } + }); mergeCandidate(candidate); + } else { + Log.d("xmppService", + "no primary candidate of our own was found"); sendInitRequest(); } - }); - mergeCandidate(candidate); - } else { - Log.d("xmppService","no primary candidate of our own was found"); - sendInitRequest(); - } - } - }); + } + }); } - + } - + public void init(Account account, JinglePacket packet) { this.status = STATUS_INITIATED; - Conversation conversation = this.mXmppConnectionService.findOrCreateConversation(account, packet.getFrom().split("/")[0], false); + Conversation conversation = this.mXmppConnectionService + .findOrCreateConversation(account, + packet.getFrom().split("/")[0], false); this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); this.message.setType(Message.TYPE_IMAGE); this.message.setStatus(Message.STATUS_RECEIVED_OFFER); @@ -243,46 +268,62 @@ public class JingleConnection { this.contentCreator = content.getAttribute("creator"); this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport() + .getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); - if (fileOffer!=null) { + if (fileOffer != null) { Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); - if (fileNameElement!=null) { + if (fileNameElement != null) { boolean supportedFile = false; - String[] filename = fileNameElement.getContent().toLowerCase(Locale.US).split("\\."); - if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) { + String[] filename = fileNameElement.getContent() + .toLowerCase(Locale.US).split("\\."); + if (Arrays.asList(this.extensions).contains( + filename[filename.length - 1])) { supportedFile = true; - } else if (Arrays.asList(this.cryptoExtensions).contains(filename[filename.length - 1])) { + } else if (Arrays.asList(this.cryptoExtensions).contains( + filename[filename.length - 1])) { if (filename.length == 3) { - if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) { + if (Arrays.asList(this.extensions).contains( + filename[filename.length - 2])) { supportedFile = true; if (filename[filename.length - 1].equals("otr")) { - Log.d("xmppService","receiving otr file"); - this.message.setEncryption(Message.ENCRYPTION_OTR); + Log.d("xmppService", "receiving otr file"); + this.message + .setEncryption(Message.ENCRYPTION_OTR); } else { - this.message.setEncryption(Message.ENCRYPTION_PGP); + this.message + .setEncryption(Message.ENCRYPTION_PGP); } } } } if (supportedFile) { long size = Long.parseLong(fileSize.getContent()); - message.setBody(""+size); + message.setBody("" + size); conversation.getMessages().add(message); - if (size<=this.mJingleConnectionManager.getAutoAcceptFileSize()) { - Log.d("xmppService","auto accepting file from "+packet.getFrom()); + if (size <= this.mJingleConnectionManager + .getAutoAcceptFileSize()) { + Log.d("xmppService", "auto accepting file from " + + packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { message.markUnread(); - Log.d("xmppService","not auto accepting new file offer with size: "+size+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize()); - this.mXmppConnectionService.notifyUi(conversation, true); + Log.d("xmppService", + "not auto accepting new file offer with size: " + + size + + " allowed size:" + + this.mJingleConnectionManager + .getAutoAcceptFileSize()); + this.mXmppConnectionService + .notifyUi(conversation, true); } - this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false); + this.file = this.mXmppConnectionService.getFileBackend() + .getJingleFile(message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); - if (key==null) { + if (key == null) { this.sendCancel(); this.cancel(); return; @@ -304,20 +345,21 @@ public class JingleConnection { this.cancel(); } } - + private void sendInitRequest() { JinglePacket packet = this.bootstrapPacket("session-initiate"); - Content content = new Content(this.contentCreator,this.contentName); + Content content = new Content(this.contentCreator, this.contentName); if (message.getType() == Message.TYPE_IMAGE) { content.setTransportId(this.transportId); - this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false); + this.file = this.mXmppConnectionService.getFileBackend() + .getJingleFile(message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { Conversation conversation = this.message.getConversation(); this.mXmppConnectionService.renewSymmetricKey(conversation); content.setFileOffer(this.file, true); this.file.setKey(conversation.getSymmetricKey()); } else { - content.setFileOffer(this.file,false); + content.setFileOffer(this.file, false); } this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); @@ -327,62 +369,72 @@ public class JingleConnection { this.status = STATUS_INITIATED; } } - + private List<Element> getCandidatesAsElements() { List<Element> elements = new ArrayList<Element>(); - for(JingleCandidate c : this.candidates) { + for (JingleCandidate c : this.candidates) { elements.add(c.toElement()); } return elements; } - + private void sendAccept() { status = STATUS_ACCEPTED; mXmppConnectionService.markMessage(message, Message.STATUS_RECIEVING); - this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { - - @Override - public void onPrimaryCandidateFound(boolean success,final JingleCandidate candidate) { - final JinglePacket packet = bootstrapPacket("session-accept"); - final Content content = new Content(contentCreator,contentName); - content.setFileOffer(fileOffer); - content.setTransportId(transportId); - if ((success)&&(!equalCandidateExists(candidate))) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport(JingleConnection.this, candidate); - connections.put(candidate.getCid(), socksConnection); - socksConnection.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d("xmppService","connection to our own primary candidate failed"); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - - @Override - public void established() { - Log.d("xmppService","connected to primary candidate"); - mergeCandidate(candidate); - content.socks5transport().setChildren(getCandidatesAsElements()); + this.mJingleConnectionManager.getPrimaryCandidate(this.account, + new OnPrimaryCandidateFound() { + + @Override + public void onPrimaryCandidateFound(boolean success, + final JingleCandidate candidate) { + final JinglePacket packet = bootstrapPacket("session-accept"); + final Content content = new Content(contentCreator, + contentName); + content.setFileOffer(fileOffer); + content.setTransportId(transportId); + if ((success) && (!equalCandidateExists(candidate))) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + JingleConnection.this, candidate); + connections.put(candidate.getCid(), socksConnection); + socksConnection.connect(new OnTransportConnected() { + + @Override + public void failed() { + Log.d("xmppService", + "connection to our own primary candidate failed"); + content.socks5transport().setChildren( + getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + + @Override + public void established() { + Log.d("xmppService", + "connected to primary candidate"); + mergeCandidate(candidate); + content.socks5transport().setChildren( + getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + }); + } else { + Log.d("xmppService", + "did not find a primary candidate for ourself"); + content.socks5transport().setChildren( + getCandidatesAsElements()); packet.setContent(content); sendJinglePacket(packet); connectNextCandidate(); } - }); - } else { - Log.d("xmppService","did not find a primary candidate for ourself"); - content.socks5transport().setChildren(getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - } - }); - + } + }); + } - + private JinglePacket bootstrapPacket(String action) { JinglePacket packet = new JinglePacket(); packet.setAction(action); @@ -392,15 +444,16 @@ public class JingleConnection { packet.setInitiator(this.initiator); return packet; } - + private void sendJinglePacket(JinglePacket packet) { - //Log.d("xmppService",packet.toString()); - account.getXmppConnection().sendIqPacket(packet,responseListener); + // Log.d("xmppService",packet.toString()); + account.getXmppConnection().sendIqPacket(packet, responseListener); } - + private boolean receiveAccept(JinglePacket packet) { Content content = packet.getJingleContent(); - mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); + mergeCandidates(JingleCandidate.parse(content.socks5transport() + .getChildren())); this.status = STATUS_ACCEPTED; mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.connectNextCandidate(); @@ -411,16 +464,20 @@ public class JingleConnection { Content content = packet.getJingleContent(); if (content.hasSocks5Transport()) { if (content.socks5transport().hasChild("activated")) { - if ((this.transport!=null)&&(this.transport instanceof JingleSocks5Transport)) { + if ((this.transport != null) + && (this.transport instanceof JingleSocks5Transport)) { onProxyActivated.success(); } else { - String cid = content.socks5transport().findChild("activated").getAttribute("cid"); - Log.d("xmppService","received proxy activated ("+cid+")prior to choosing our own transport"); - JingleSocks5Transport connection = this.connections.get(cid); - if (connection!=null) { + String cid = content.socks5transport() + .findChild("activated").getAttribute("cid"); + Log.d("xmppService", "received proxy activated (" + cid + + ")prior to choosing our own transport"); + JingleSocks5Transport connection = this.connections + .get(cid); + if (connection != null) { connection.setActivated(true); } else { - Log.d("xmppService","activated connection not found"); + Log.d("xmppService", "activated connection not found"); this.sendCancel(); this.cancel(); } @@ -430,23 +487,25 @@ public class JingleConnection { onProxyActivated.failed(); return true; } else if (content.socks5transport().hasChild("candidate-error")) { - Log.d("xmppService","received candidate error"); + Log.d("xmppService", "received candidate error"); this.receivedCandidate = true; - if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) { + if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { this.connect(); } return true; - } else if (content.socks5transport().hasChild("candidate-used")){ - String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid"); - if (cid!=null) { - Log.d("xmppService","candidate used by counterpart:"+cid); + } else if (content.socks5transport().hasChild("candidate-used")) { + String cid = content.socks5transport() + .findChild("candidate-used").getAttribute("cid"); + if (cid != null) { + Log.d("xmppService", "candidate used by counterpart:" + cid); JingleCandidate candidate = getCandidate(cid); candidate.flagAsUsedByCounterpart(); this.receivedCandidate = true; - if ((status == STATUS_ACCEPTED)&&(this.sentCandidate)) { + if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { this.connect(); } else { - Log.d("xmppService","ignoring because file is already in transmission or we havent sent our candidate yet"); + Log.d("xmppService", + "ignoring because file is already in transmission or we havent sent our candidate yet"); } return true; } else { @@ -463,8 +522,8 @@ public class JingleConnection { private void connect() { final JingleSocks5Transport connection = chooseConnection(); this.transport = connection; - if (connection==null) { - Log.d("xmppService","could not find suitable candidate"); + if (connection == null) { + Log.d("xmppService", "could not find suitable candidate"); this.disconnect(); if (this.initiator.equals(account.getFullJid())) { this.sendFallbackToIbb(); @@ -473,65 +532,80 @@ public class JingleConnection { this.status = STATUS_TRANSMITTING; if (connection.needsActivation()) { if (connection.getCandidate().isOurs()) { - Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was our proxy. going to activate"); + Log.d("xmppService", "candidate " + + connection.getCandidate().getCid() + + " was our proxy. going to activate"); IqPacket activation = new IqPacket(IqPacket.TYPE_SET); activation.setTo(connection.getCandidate().getJid()); - activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId()); - activation.query().addChild("activate").setContent(this.getCounterPart()); - this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType()==IqPacket.TYPE_ERROR) { - onProxyActivated.failed(); - } else { - onProxyActivated.success(); - sendProxyActivated(connection.getCandidate().getCid()); - } - } - }); + activation.query("http://jabber.org/protocol/bytestreams") + .setAttribute("sid", this.getSessionId()); + activation.query().addChild("activate") + .setContent(this.getCounterPart()); + this.account.getXmppConnection().sendIqPacket(activation, + new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, + IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_ERROR) { + onProxyActivated.failed(); + } else { + onProxyActivated.success(); + sendProxyActivated(connection + .getCandidate().getCid()); + } + } + }); } else { - Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was a proxy. waiting for other party to activate"); + Log.d("xmppService", + "candidate " + + connection.getCandidate().getCid() + + " was a proxy. waiting for other party to activate"); } } else { if (initiator.equals(account.getFullJid())) { - Log.d("xmppService","we were initiating. sending file"); - connection.send(file,onFileTransmissionSatusChanged); + Log.d("xmppService", "we were initiating. sending file"); + connection.send(file, onFileTransmissionSatusChanged); } else { - Log.d("xmppService","we were responding. receiving file"); - connection.receive(file,onFileTransmissionSatusChanged); + Log.d("xmppService", "we were responding. receiving file"); + connection.receive(file, onFileTransmissionSatusChanged); } } } } - + private JingleSocks5Transport chooseConnection() { JingleSocks5Transport connection = null; - for (Entry<String, JingleSocks5Transport> cursor : connections.entrySet()) { - JingleSocks5Transport currentConnection = cursor.getValue(); - //Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString()); - if (currentConnection.isEstablished()&&(currentConnection.getCandidate().isUsedByCounterpart()||(!currentConnection.getCandidate().isOurs()))) { - //Log.d("xmppService","is usable"); - if (connection==null) { - connection = currentConnection; - } else { - if (connection.getCandidate().getPriority()<currentConnection.getCandidate().getPriority()) { - connection = currentConnection; - } else if (connection.getCandidate().getPriority()==currentConnection.getCandidate().getPriority()) { - //Log.d("xmppService","found two candidates with same priority"); - if (initiator.equals(account.getFullJid())) { - if (currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } - } else { - if (!currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } - } - } - } - } - } + for (Entry<String, JingleSocks5Transport> cursor : connections + .entrySet()) { + JingleSocks5Transport currentConnection = cursor.getValue(); + // Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString()); + if (currentConnection.isEstablished() + && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection + .getCandidate().isOurs()))) { + // Log.d("xmppService","is usable"); + if (connection == null) { + connection = currentConnection; + } else { + if (connection.getCandidate().getPriority() < currentConnection + .getCandidate().getPriority()) { + connection = currentConnection; + } else if (connection.getCandidate().getPriority() == currentConnection + .getCandidate().getPriority()) { + // Log.d("xmppService","found two candidates with same priority"); + if (initiator.equals(account.getFullJid())) { + if (currentConnection.getCandidate().isOurs()) { + connection = currentConnection; + } + } else { + if (!currentConnection.getCandidate().isOurs()) { + connection = currentConnection; + } + } + } + } + } + } return connection; } @@ -543,60 +617,68 @@ public class JingleConnection { this.sendJinglePacket(packet); this.disconnect(); this.status = STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECIEVED); + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_RECIEVED); this.mJingleConnectionManager.finishConnection(this); } - + private void sendFallbackToIbb() { JinglePacket packet = this.bootstrapPacket("transport-replace"); - Content content = new Content(this.contentCreator,this.contentName); + Content content = new Content(this.contentCreator, this.contentName); this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); - content.ibbTransport().setAttribute("block-size",""+this.ibbBlockSize); + content.ibbTransport().setAttribute("block-size", + "" + this.ibbBlockSize); packet.setContent(content); this.sendJinglePacket(packet); } - + private boolean receiveFallbackToIbb(JinglePacket packet) { - String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); - if (receivedBlockSize!=null) { + String receivedBlockSize = packet.getJingleContent().ibbTransport() + .getAttribute("block-size"); + if (receivedBlockSize != null) { int bs = Integer.parseInt(receivedBlockSize); - if (bs>this.ibbBlockSize) { + if (bs > this.ibbBlockSize) { this.ibbBlockSize = bs; } } this.transportId = packet.getJingleContent().getTransportId(); - this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize); + this.transport = new JingleInbandTransport(this.account, + this.responder, this.transportId, this.ibbBlockSize); this.transport.receive(file, onFileTransmissionSatusChanged); JinglePacket answer = bootstrapPacket("transport-accept"); Content content = new Content("initiator", "a-file-offer"); content.setTransportId(this.transportId); - content.ibbTransport().setAttribute("block-size", ""+this.ibbBlockSize); + content.ibbTransport().setAttribute("block-size", + "" + this.ibbBlockSize); answer.setContent(content); this.sendJinglePacket(answer); return true; } - + private boolean receiveTransportAccept(JinglePacket packet) { if (packet.getJingleContent().hasIbbTransport()) { - String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); - if (receivedBlockSize!=null) { + String receivedBlockSize = packet.getJingleContent().ibbTransport() + .getAttribute("block-size"); + if (receivedBlockSize != null) { int bs = Integer.parseInt(receivedBlockSize); - if (bs>this.ibbBlockSize) { + if (bs > this.ibbBlockSize) { this.ibbBlockSize = bs; } } - this.transport = new JingleInbandTransport(this.account,this.responder,this.transportId,this.ibbBlockSize); + this.transport = new JingleInbandTransport(this.account, + this.responder, this.transportId, this.ibbBlockSize); this.transport.connect(new OnTransportConnected() { - + @Override public void failed() { - Log.d("xmppService","ibb open failed"); + Log.d("xmppService", "ibb open failed"); } - + @Override public void established() { - JingleConnection.this.transport.send(file, onFileTransmissionSatusChanged); + JingleConnection.this.transport.send(file, + onFileTransmissionSatusChanged); } }); return true; @@ -604,31 +686,35 @@ public class JingleConnection { return false; } } - + private void receiveSuccess() { this.status = STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND); + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND); this.disconnect(); this.mJingleConnectionManager.finishConnection(this); } - + public void cancel() { this.disconnect(); - if (this.message!=null) { + if (this.message != null) { if (this.responder.equals(account.getFullJid())) { - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECEPTION_FAILED); + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_RECEPTION_FAILED); } else { if (this.status == STATUS_INITIATED) { - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED); + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND_REJECTED); } else { - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED); + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND_FAILED); } } } this.status = STATUS_CANCELED; this.mJingleConnectionManager.finishConnection(this); } - + private void sendCancel() { JinglePacket packet = bootstrapPacket("session-terminate"); Reason reason = new Reason(); @@ -638,73 +724,82 @@ public class JingleConnection { } private void connectNextCandidate() { - for(JingleCandidate candidate : this.candidates) { - if ((!connections.containsKey(candidate.getCid())&&(!candidate.isOurs()))) { + for (JingleCandidate candidate : this.candidates) { + if ((!connections.containsKey(candidate.getCid()) && (!candidate + .isOurs()))) { this.connectWithCandidate(candidate); return; } } this.sendCandidateError(); } - + private void connectWithCandidate(final JingleCandidate candidate) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this,candidate); + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + this, candidate); connections.put(candidate.getCid(), socksConnection); socksConnection.connect(new OnTransportConnected() { - + @Override public void failed() { - Log.d("xmppService", "connection failed with "+candidate.getHost()+":"+candidate.getPort()); + Log.d("xmppService", + "connection failed with " + candidate.getHost() + ":" + + candidate.getPort()); connectNextCandidate(); } - + @Override public void established() { - Log.d("xmppService", "established connection with "+candidate.getHost()+":"+candidate.getPort()); + Log.d("xmppService", + "established connection with " + candidate.getHost() + + ":" + candidate.getPort()); sendCandidateUsed(candidate.getCid()); } }); } private void disconnect() { - Iterator<Entry<String, JingleSocks5Transport>> it = this.connections.entrySet().iterator(); - while (it.hasNext()) { - Entry<String, JingleSocks5Transport> pairs = it.next(); - pairs.getValue().disconnect(); - it.remove(); - } - } - + Iterator<Entry<String, JingleSocks5Transport>> it = this.connections + .entrySet().iterator(); + while (it.hasNext()) { + Entry<String, JingleSocks5Transport> pairs = it.next(); + pairs.getValue().disconnect(); + it.remove(); + } + } + private void sendProxyActivated(String cid) { JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator,this.contentName); + Content content = new Content(this.contentCreator, this.contentName); content.setTransportId(this.transportId); - content.socks5transport().addChild("activated").setAttribute("cid", cid); + content.socks5transport().addChild("activated") + .setAttribute("cid", cid); packet.setContent(content); this.sendJinglePacket(packet); } - + private void sendCandidateUsed(final String cid) { JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator,this.contentName); + Content content = new Content(this.contentCreator, this.contentName); content.setTransportId(this.transportId); - content.socks5transport().addChild("candidate-used").setAttribute("cid", cid); + content.socks5transport().addChild("candidate-used") + .setAttribute("cid", cid); packet.setContent(content); this.sentCandidate = true; - if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) { + if ((receivedCandidate) && (status == STATUS_ACCEPTED)) { connect(); } this.sendJinglePacket(packet); } - + private void sendCandidateError() { JinglePacket packet = bootstrapPacket("transport-info"); - Content content = new Content(this.contentCreator,this.contentName); + Content content = new Content(this.contentCreator, this.contentName); content.setTransportId(this.transportId); content.socks5transport().addChild("candidate-error"); packet.setContent(content); this.sentCandidate = true; - if ((receivedCandidate)&&(status == STATUS_ACCEPTED)) { + if ((receivedCandidate) && (status == STATUS_ACCEPTED)) { connect(); } this.sendJinglePacket(packet); @@ -713,72 +808,73 @@ public class JingleConnection { public String getInitiator() { return this.initiator; } - + public String getResponder() { return this.responder; } - + public int getStatus() { return this.status; } - + private boolean equalCandidateExists(JingleCandidate candidate) { - for(JingleCandidate c : this.candidates) { + for (JingleCandidate c : this.candidates) { if (c.equalValues(candidate)) { return true; } } return false; } - + private void mergeCandidate(JingleCandidate candidate) { - for(JingleCandidate c : this.candidates) { + for (JingleCandidate c : this.candidates) { if (c.equals(candidate)) { return; } } this.candidates.add(candidate); } - + private void mergeCandidates(List<JingleCandidate> candidates) { - for(JingleCandidate c : candidates) { + for (JingleCandidate c : candidates) { mergeCandidate(c); } } - + private JingleCandidate getCandidate(String cid) { - for(JingleCandidate c : this.candidates) { + for (JingleCandidate c : this.candidates) { if (c.getCid().equals(cid)) { return c; } } return null; } - + interface OnProxyActivated { public void success(); + public void failed(); } public boolean hasTransportId(String sid) { return sid.equals(this.transportId); } - + public JingleTransport getTransport() { return this.transport; } public void accept() { - if (status==STATUS_INITIATED) { + if (status == STATUS_INITIATED) { new Thread(new Runnable() { - + @Override public void run() { sendAccept(); } }).start(); } else { - Log.d("xmppService","status ("+status+") was not ok"); + Log.d("xmppService", "status (" + status + ") was not ok"); } } } diff --git a/src/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/eu/siacs/conversations/xmpp/pep/Avatar.java new file mode 100644 index 00000000..6d5c1431 --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -0,0 +1,68 @@ +package eu.siacs.conversations.xmpp.pep; + +import eu.siacs.conversations.xml.Element; +import android.util.Base64; + +public class Avatar { + public String type; + public String sha1sum; + public String image; + public int height; + public int width; + public long size; + public String owner; + public byte[] getImageAsBytes() { + return Base64.decode(image, Base64.DEFAULT); + } + public String getFilename() { + if (type==null) { + return sha1sum; + } else if (type.equalsIgnoreCase("image/webp")) { + return sha1sum+".webp"; + } else if (type.equalsIgnoreCase("image/png")) { + return sha1sum+".png"; + } else { + return sha1sum; + } + } + + public static Avatar parseMetadata(Element items) { + Element item = items.findChild("item"); + if (item==null) { + return null; + } + Element metadata = item.findChild("metadata"); + if (metadata==null) { + return null; + } + String primaryId = item.getAttribute("id"); + if (primaryId==null) { + return null; + } + for(Element child : metadata.getChildren()) { + if (child.getName().equals("info") && primaryId.equals(child.getAttribute("id"))) { + Avatar avatar = new Avatar(); + String height = child.getAttribute("height"); + String width = child.getAttribute("width"); + String size = child.getAttribute("bytes"); + try { + if (height!=null) { + avatar.height = Integer.parseInt(height); + } + if (width!=null) { + avatar.width = Integer.parseInt(width); + } + if (size!=null) { + avatar.size = Long.parseLong(size); + } + } catch (NumberFormatException e) { + return null; + } + avatar.type = child.getAttribute("type"); + avatar.sha1sum = child.getAttribute("id"); + return avatar; + } + } + return null; + } +} diff --git a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index 64a9edc3..433b08c9 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -8,6 +8,7 @@ public class MessagePacket extends AbstractStanza { public static final int TYPE_NORMAL = 2; public static final int TYPE_GROUPCHAT = 3; public static final int TYPE_ERROR = 4; + public static final int TYPE_HEADLINE = 5; public MessagePacket() { super("message"); @@ -59,6 +60,8 @@ public class MessagePacket extends AbstractStanza { return TYPE_GROUPCHAT; } else if (type.equals("error")) { return TYPE_ERROR; + } else if (type.equals("headline")) { + return TYPE_HEADLINE; } else { return TYPE_UNKNOWN; } |