aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml31
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.md27
-rw-r--r--res/layout/account_row.xml61
-rw-r--r--res/layout/activity_contact_details.xml2
-rw-r--r--res/layout/activity_edit_account.xml105
-rw-r--r--res/layout/activity_publish_profile_picture.xml106
-rw-r--r--res/layout/contact.xml1
-rw-r--r--res/layout/conversation_list_row.xml3
-rw-r--r--res/layout/fragment_conversation.xml8
-rw-r--r--res/layout/manage_accounts.xml3
-rw-r--r--res/layout/message_recieved.xml2
-rw-r--r--res/layout/message_sent.xml2
-rw-r--r--res/menu/manageaccounts_context.xml4
-rw-r--r--res/values-de/arrays.xml4
-rw-r--r--res/values-de/strings.xml168
-rw-r--r--res/values-es/strings.xml13
-rw-r--r--res/values-he/arrays.xml22
-rw-r--r--res/values-he/strings.xml284
-rw-r--r--res/values-nl/strings.xml34
-rw-r--r--res/values-sv/arrays.xml22
-rw-r--r--res/values-sv/strings.xml271
-rw-r--r--res/values/colors.xml2
-rw-r--r--res/values/strings.xml18
-rw-r--r--res/xml/preferences.xml5
-rw-r--r--src/eu/siacs/conversations/crypto/OtrEngine.java9
-rw-r--r--src/eu/siacs/conversations/crypto/PgpEngine.java37
-rw-r--r--src/eu/siacs/conversations/entities/Account.java36
-rw-r--r--src/eu/siacs/conversations/entities/Contact.java20
-rw-r--r--src/eu/siacs/conversations/entities/Conversation.java30
-rw-r--r--src/eu/siacs/conversations/entities/Message.java40
-rw-r--r--src/eu/siacs/conversations/entities/MucOptions.java118
-rw-r--r--src/eu/siacs/conversations/generator/AbstractGenerator.java5
-rw-r--r--src/eu/siacs/conversations/generator/IqGenerator.java58
-rw-r--r--src/eu/siacs/conversations/generator/MessageGenerator.java3
-rw-r--r--src/eu/siacs/conversations/parser/AbstractParser.java14
-rw-r--r--src/eu/siacs/conversations/parser/IqParser.java30
-rw-r--r--src/eu/siacs/conversations/parser/MessageParser.java122
-rw-r--r--src/eu/siacs/conversations/parser/PresenceParser.java67
-rw-r--r--src/eu/siacs/conversations/persistance/DatabaseBackend.java61
-rw-r--r--src/eu/siacs/conversations/persistance/FileBackend.java245
-rw-r--r--src/eu/siacs/conversations/services/Defaults.java11
-rw-r--r--src/eu/siacs/conversations/services/ImageProvider.java4
-rw-r--r--src/eu/siacs/conversations/services/XmppConnectionService.java125
-rw-r--r--src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java91
-rw-r--r--src/eu/siacs/conversations/ui/ConversationActivity.java190
-rw-r--r--src/eu/siacs/conversations/ui/ConversationFragment.java146
-rw-r--r--src/eu/siacs/conversations/ui/EditAccountActivity.java294
-rw-r--r--src/eu/siacs/conversations/ui/EditAccountDialog.java157
-rw-r--r--src/eu/siacs/conversations/ui/EditMessage.java39
-rw-r--r--src/eu/siacs/conversations/ui/ManageAccountActivity.java530
-rw-r--r--src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java215
-rw-r--r--src/eu/siacs/conversations/ui/StartConversationActivity.java24
-rw-r--r--src/eu/siacs/conversations/ui/XmppActivity.java62
-rw-r--r--src/eu/siacs/conversations/ui/adapter/AccountAdapter.java101
-rw-r--r--src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java107
-rw-r--r--src/eu/siacs/conversations/ui/adapter/MessageAdapter.java147
-rw-r--r--src/eu/siacs/conversations/utils/PhoneHelper.java4
-rw-r--r--src/eu/siacs/conversations/utils/UIHelper.java25
-rw-r--r--src/eu/siacs/conversations/utils/Validator.java2
-rw-r--r--src/eu/siacs/conversations/xml/Element.java8
-rw-r--r--src/eu/siacs/conversations/xml/TagWriter.java10
-rw-r--r--src/eu/siacs/conversations/xml/XmlReader.java23
-rw-r--r--src/eu/siacs/conversations/xmpp/XmppConnection.java133
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java628
-rw-r--r--src/eu/siacs/conversations/xmpp/pep/Avatar.java68
-rw-r--r--src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java3
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
diff --git a/README.md b/README.md
index 6692974d..008d1d79 100644
--- a/README.md
+++ b/README.md
@@ -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&#8230;</string>
- <string name="announce_pgp">PGP Ankündigung erneuern</string>
- <string name="encrypted_message">Entschlüssle Nachricht. Bitte warten&#8230;</string>
+ <string name="announce_pgp">PGP-Ankündigung erneuern</string>
+ <string name="encrypted_message">Entschlüssele Nachricht. Bitte warten&#8230;</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&#8230;</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&#8230;</string>
<string name="waiting">warten&#8230;</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 &#8230; 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 &#8230;, 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&#8230;</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&#8230;</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">כעת שולח&#8230;</string>
+ <string name="announce_pgp">חדש הכרזת PGP</string>
+ <string name="encrypted_message">כעת מפענח הודעה. אנא המתן&#8230;</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">כעת מקבל קובץ תצלום. אנא המתן&#8230;</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">כעת מציע&#8230;</string>
+ <string name="waiting">כעת ממתין&#8230;</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">קבל אוטומטית קבצים קטנים יותר מאשר&#8230;</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">כעת מפרסם&#8230;</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&#8230;</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&#8230;</string>
+ <string name="announce_pgp">Förnya PGP annonsering</string>
+ <string name="encrypted_message">Avkrypterar meddelande. Vänta&#8230;</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&#8230;</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&#8230;</string>
+ <string name="waiting">väntar&#8230;</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&#8230;</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&#8230;</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("&nbsp;<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;
}