aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml7
-rw-r--r--CHANGELOG.md5
-rw-r--r--README.md2
-rw-r--r--art/logo.pngbin0 -> 53734 bytes
-rwxr-xr-xart/render.rb2
m---------libs/MemorizingTrustManager0
-rw-r--r--res/drawable-hdpi/ic_action_copy.pngbin0 -> 381 bytes
-rw-r--r--res/drawable-mdpi/ic_action_copy.pngbin0 -> 288 bytes
-rw-r--r--res/drawable-xhdpi/ic_action_copy.pngbin0 -> 353 bytes
-rw-r--r--res/drawable-xxhdpi/ic_action_copy.pngbin0 -> 470 bytes
-rw-r--r--res/drawable/infocard_border.xml19
-rw-r--r--res/drawable/section_header.xml11
-rw-r--r--res/layout-w360dp/fragment_conversations_overview.xml (renamed from res/layout-sw720dp/fragment_conversations_overview.xml)4
-rw-r--r--res/layout-w384dp/fragment_conversations_overview.xml (renamed from res/layout-sw384dp/fragment_conversations_overview.xml)2
-rw-r--r--res/layout-w600dp/fragment_conversations_overview.xml (renamed from res/layout-sw360dp/fragment_conversations_overview.xml)4
-rw-r--r--res/layout-w960dp/fragment_conversations_overview.xml (renamed from res/layout-sw600dp/fragment_conversations_overview.xml)18
-rw-r--r--res/layout/activity_contact_details.xml144
-rw-r--r--res/layout/activity_edit_account.xml125
-rw-r--r--res/layout/activity_muc_details.xml77
-rw-r--r--res/layout/contact_key.xml5
-rw-r--r--res/layout/fragment_conversations_overview.xml2
-rw-r--r--res/layout/message_null.xml3
-rw-r--r--res/values-de/strings.xml4
-rw-r--r--res/values-es/strings.xml4
-rw-r--r--res/values-nl/styles.xml19
-rw-r--r--res/values-zh-rCN/arrays.xml39
-rw-r--r--res/values-zh-rCN/strings.xml (renamed from res/values-zh/strings.xml)0
-rw-r--r--res/values-zh-rTW/arrays.xml39
-rw-r--r--res/values-zh-rTW/strings.xml263
-rw-r--r--res/values/strings.xml6
-rw-r--r--res/values/styles.xml11
-rw-r--r--src/eu/siacs/conversations/Config.java2
-rw-r--r--src/eu/siacs/conversations/crypto/PgpEngine.java24
-rw-r--r--src/eu/siacs/conversations/entities/Account.java8
-rw-r--r--src/eu/siacs/conversations/entities/Bookmark.java115
-rw-r--r--src/eu/siacs/conversations/entities/Contact.java5
-rw-r--r--src/eu/siacs/conversations/entities/Conversation.java49
-rw-r--r--src/eu/siacs/conversations/entities/Downloadable.java4
-rw-r--r--src/eu/siacs/conversations/entities/DownloadableFile.java149
-rw-r--r--src/eu/siacs/conversations/entities/Message.java110
-rw-r--r--src/eu/siacs/conversations/entities/MucOptions.java49
-rw-r--r--src/eu/siacs/conversations/entities/Roster.java2
-rw-r--r--src/eu/siacs/conversations/http/HttpConnection.java165
-rw-r--r--src/eu/siacs/conversations/http/HttpConnectionManager.java37
-rw-r--r--src/eu/siacs/conversations/parser/IqParser.java3
-rw-r--r--src/eu/siacs/conversations/parser/MessageParser.java63
-rw-r--r--src/eu/siacs/conversations/parser/PresenceParser.java2
-rw-r--r--src/eu/siacs/conversations/persistance/DatabaseBackend.java14
-rw-r--r--src/eu/siacs/conversations/persistance/FileBackend.java39
-rw-r--r--src/eu/siacs/conversations/services/AbstractConnectionManager.java24
-rw-r--r--src/eu/siacs/conversations/services/NotificationService.java55
-rw-r--r--src/eu/siacs/conversations/services/XmppConnectionService.java141
-rw-r--r--src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java49
-rw-r--r--src/eu/siacs/conversations/ui/ContactDetailsActivity.java24
-rw-r--r--src/eu/siacs/conversations/ui/ConversationActivity.java267
-rw-r--r--src/eu/siacs/conversations/ui/ConversationFragment.java112
-rw-r--r--src/eu/siacs/conversations/ui/EditAccountActivity.java93
-rw-r--r--src/eu/siacs/conversations/ui/ManageAccountActivity.java8
-rw-r--r--src/eu/siacs/conversations/ui/StartConversationActivity.java8
-rw-r--r--src/eu/siacs/conversations/ui/XmppActivity.java7
-rw-r--r--src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java5
-rw-r--r--src/eu/siacs/conversations/ui/adapter/MessageAdapter.java105
-rw-r--r--src/eu/siacs/conversations/utils/DNSHelper.java8
-rw-r--r--src/eu/siacs/conversations/xmpp/XmppConnection.java44
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java9
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java22
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleFile.java68
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java14
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java9
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java81
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java4
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java4
72 files changed, 1949 insertions, 867 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d86d5bb1..ce4ea5df 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.siacs.conversations"
- android:versionCode="28"
- android:versionName="0.7.2" >
+ android:versionCode="31"
+ android:versionName="0.7.3" >
<uses-sdk
android:minSdkVersion="14"
@@ -40,7 +40,6 @@
<activity
android:name="eu.siacs.conversations.ui.ConversationActivity"
- android:configChanges="orientation|screenSize"
android:label="@string/title_activity_conversations"
android:launchMode="singleTask"
android:windowSoftInputMode="stateHidden" >
@@ -125,4 +124,4 @@
<activity android:name="de.duenndns.ssl.MemorizingActivity" />
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c14f3316..29277eb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
###Changelog
+####Version 0.7.3
+* revised tablet ui
+* internal rewrites
+* bug fixes
+
####Version 0.7.2
* show full timestamp in messages
* brought back option to use JID to identify conferences
diff --git a/README.md b/README.md
index da376cf6..9fc74fdd 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ transfer (SEPA).
My Bitcoin Address is: 1NxSU1YxYzJVDpX1rcESAA3NJki7kRgeeu
-[![Flattr this!](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=inputmice&url=https%3A%2F%2Fgithub.com%2Fsiacs%2FConversations)
+[![Flattr this!](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=inputmice&url=http%3A%2F%2Fconversations.siacs.eu&title=Conversations&tags=github&category=software)
####How do I create an account?
XMPP like email for example is a federated protocol which means that there is
diff --git a/art/logo.png b/art/logo.png
new file mode 100644
index 00000000..a8ab6176
--- /dev/null
+++ b/art/logo.png
Binary files differ
diff --git a/art/render.rb b/art/render.rb
index 5464b9f5..f2d66b9a 100755
--- a/art/render.rb
+++ b/art/render.rb
@@ -1,6 +1,6 @@
#!/bin/env ruby
resolutions={'mdpi'=> 1, 'hdpi' => 1.5, 'xhdpi' => 2, 'xxhdpi' => 3}
-images = { 'conversations.svg' => ['ic_launcher',48], 'conversations_baloon.svg' => ['ic_activity', 32], 'conversations_mono.svg' => ['ic_notification',24], 'ic_received_indicator.svg' => ['ic_received_indicator',12] }
+images = { 'conversations.svg' => ['ic_launcher', 48], 'conversations_baloon.svg' => ['ic_activity', 32], 'conversations_mono.svg' => ['ic_notification', 24], 'ic_received_indicator.svg' => ['ic_received_indicator', 12] }
images.each do |source, result|
resolutions.each do |name, factor|
size = factor * result[1]
diff --git a/libs/MemorizingTrustManager b/libs/MemorizingTrustManager
-Subproject 452f70208f0dd5f9e56376944e96f5c10704245
+Subproject 3f67eba2e4663841dd0024908f8ffb261307814
diff --git a/res/drawable-hdpi/ic_action_copy.png b/res/drawable-hdpi/ic_action_copy.png
new file mode 100644
index 00000000..22327391
--- /dev/null
+++ b/res/drawable-hdpi/ic_action_copy.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_action_copy.png b/res/drawable-mdpi/ic_action_copy.png
new file mode 100644
index 00000000..71348202
--- /dev/null
+++ b/res/drawable-mdpi/ic_action_copy.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_action_copy.png b/res/drawable-xhdpi/ic_action_copy.png
new file mode 100644
index 00000000..5ddf1513
--- /dev/null
+++ b/res/drawable-xhdpi/ic_action_copy.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_action_copy.png b/res/drawable-xxhdpi/ic_action_copy.png
new file mode 100644
index 00000000..a0508df8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_action_copy.png
Binary files differ
diff --git a/res/drawable/infocard_border.xml b/res/drawable/infocard_border.xml
new file mode 100644
index 00000000..7c7ded57
--- /dev/null
+++ b/res/drawable/infocard_border.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <solid android:color="@color/primarybackground" />
+
+ <corners android:radius="2dp" />
+
+ <stroke
+ android:width="0.5dp"
+ android:color="@color/divider" >
+ </stroke>
+
+ <padding
+ android:bottom="0dp"
+ android:left="0dp"
+ android:right="0dp"
+ android:top="0dp" />
+
+</shape> \ No newline at end of file
diff --git a/res/drawable/section_header.xml b/res/drawable/section_header.xml
deleted file mode 100644
index 9db04f90..00000000
--- a/res/drawable/section_header.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
-
- <size
- android:height="1.5dp"
- android:width="2000dp" />
-
- <solid android:color="@color/divider" />
-
-</shape> \ No newline at end of file
diff --git a/res/layout-sw720dp/fragment_conversations_overview.xml b/res/layout-w360dp/fragment_conversations_overview.xml
index bc52ec46..a600118d 100644
--- a/res/layout-sw720dp/fragment_conversations_overview.xml
+++ b/res/layout-w360dp/fragment_conversations_overview.xml
@@ -1,11 +1,11 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/slidingpanelayout"
+ android:id="@+id/content_view_spl"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="288dp"
+ android:layout_width="300dp"
android:layout_height="match_parent"
android:background="@color/primarybackground"
android:orientation="vertical" >
diff --git a/res/layout-sw384dp/fragment_conversations_overview.xml b/res/layout-w384dp/fragment_conversations_overview.xml
index 2a422052..c3aa67ae 100644
--- a/res/layout-sw384dp/fragment_conversations_overview.xml
+++ b/res/layout-w384dp/fragment_conversations_overview.xml
@@ -1,5 +1,5 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/slidingpanelayout"
+ android:id="@+id/content_view_spl"
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/res/layout-sw360dp/fragment_conversations_overview.xml b/res/layout-w600dp/fragment_conversations_overview.xml
index ef8a1b6e..331fb1f0 100644
--- a/res/layout-sw360dp/fragment_conversations_overview.xml
+++ b/res/layout-w600dp/fragment_conversations_overview.xml
@@ -1,11 +1,11 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/slidingpanelayout"
+ android:id="@+id/content_view_spl"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="324dp"
+ android:layout_width="400dp"
android:layout_height="match_parent"
android:background="@color/primarybackground"
android:orientation="vertical" >
diff --git a/res/layout-sw600dp/fragment_conversations_overview.xml b/res/layout-w960dp/fragment_conversations_overview.xml
index 5dfdceff..2744f38e 100644
--- a/res/layout-sw600dp/fragment_conversations_overview.xml
+++ b/res/layout-w960dp/fragment_conversations_overview.xml
@@ -1,12 +1,14 @@
-<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/slidingpanelayout"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_view_ll"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="240dp"
+ android:layout_width="0dp"
android:layout_height="match_parent"
+ android:layout_weight="1"
android:background="@color/primarybackground"
android:orientation="vertical" >
@@ -21,10 +23,10 @@
<LinearLayout
android:id="@+id/selected_conversation"
- android:layout_width="fill_parent"
+ android:layout_width="0dp"
+ android:layout_weight="2"
android:layout_height="match_parent"
- android:layout_weight="1"
android:orientation="vertical" >
</LinearLayout>
-</android.support.v4.widget.SlidingPaneLayout> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/activity_contact_details.xml b/res/layout/activity_contact_details.xml
index f4fd9c1e..f7cb2198 100644
--- a/res/layout/activity_contact_details.xml
+++ b/res/layout/activity_contact_details.xml
@@ -1,126 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/primarybackground" >
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@color/secondarybackground" >
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:text="@string/action_contact_details"
- android:textColor="@color/primarytext" />
-
<RelativeLayout
- android:layout_width="wrap_content"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:minHeight="88dp"
- android:padding="8dp" >
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
+ android:padding="16dp" >
<QuickContactBadge
android:id="@+id/details_contact_badge"
android:layout_width="72dp"
android:layout_height="72dp"
- android:layout_centerVertical="true"
+ android:layout_alignParentTop="true"
android:scaleType="centerCrop" />
<LinearLayout
android:id="@+id/details_jidbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
+ android:layout_marginLeft="16dp"
android:layout_toRightOf="@+id/details_contact_badge"
- android:orientation="vertical"
- android:paddingLeft="8dp" >
+ android:orientation="vertical" >
<TextView
android:id="@+id/details_contactjid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="8dp"
- android:singleLine="true"
+ android:text="@string/account_settings_example_jabber_id"
android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeBody" />
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold" />
- <TextView
- android:id="@+id/details_contactstatus"
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/details_contactstatus"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text=" ยท "
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
+
+ <TextView
+ android:id="@+id/details_lastseen"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/details_send_presence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="16dp"
+ android:layout_marginTop="8dp"
+ android:text="@string/send_presence_updates"
android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeHeadline"
- android:textStyle="bold" />
+ android:textSize="?attr/TextSizeBody" />
- <TextView
- android:id="@+id/details_lastseen"
+ <CheckBox
+ android:id="@+id/details_receive_presence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="8dp"
- android:singleLine="true"
+ android:text="@string/receive_presence_updates"
android:textColor="@color/primarytext"
android:textSize="?attr/TextSizeBody" />
</LinearLayout>
- </RelativeLayout>
-
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:text="@string/your_account"
- android:textColor="@color/primarytext" />
-
- <TextView
- android:id="@+id/details_account"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="8dp"
- android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeBody" />
-
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:text="@string/subscriptions"
- android:textColor="@color/primarytext" />
-
- <CheckBox
- android:id="@+id/details_send_presence"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/send_presence_updates"
- android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeBody" />
- <CheckBox
- android:id="@+id/details_receive_presence"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/receive_presence_updates"
- android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeBody" />
-
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:text="@string/keys" />
+ <TextView
+ android:id="@+id/details_account"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_below="@+id/details_jidbox"
+ android:layout_marginTop="32dp"
+ android:text="@string/using_account"
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeInfo" />
+ </RelativeLayout>
<LinearLayout
android:id="@+id/details_contact_keys"
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
android:divider="?android:dividerHorizontal"
android:orientation="vertical"
+ android:padding="8dp"
android:showDividers="middle" >
</LinearLayout>
</LinearLayout>
diff --git a/res/layout/activity_edit_account.xml b/res/layout/activity_edit_account.xml
index 04f63795..97289628 100644
--- a/res/layout/activity_edit_account.xml
+++ b/res/layout/activity_edit_account.xml
@@ -2,7 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/primarybackground" >
+ android:background="@color/secondarybackground" >
<ScrollView
android:layout_width="fill_parent"
@@ -19,8 +19,10 @@
android:id="@+id/editor"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
android:orientation="vertical"
- android:padding="8dp" >
+ android:padding="16dp" >
<TextView
android:layout_width="wrap_content"
@@ -34,7 +36,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/account_settings_example_jabber_id"
- android:inputType="textEmailAddress" />
+ android:inputType="textEmailAddress"
+ android:textColor="@color/primarytext"
+ android:textColorHint="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
<TextView
android:layout_width="wrap_content"
@@ -49,7 +54,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:inputType="textPassword" />
+ android:inputType="textPassword"
+ android:textColor="@color/primarytext"
+ android:textColorHint="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
<CheckBox
android:id="@+id/account_register_new"
@@ -76,31 +84,25 @@
android:layout_marginTop="8dp"
android:hint="@string/confirm_password"
android:inputType="textPassword"
- android:visibility="gone" />
+ android:visibility="gone"
+ android:textColor="@color/primarytext"
+ android:textColorHint="@color/secondarytext"
+ android:textSize="?attr/TextSizeBody" />
</LinearLayout>
- <LinearLayout
+ <LinearLayout
android:id="@+id/stats"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:layout_marginTop="8dp"
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
android:orientation="vertical"
android:padding="16dp"
android:visibility="gone" >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:text="@string/additional_information"
- android:textColor="@color/secondarytext"
- android:textSize="?attr/TextSizeHeadline"
- android:textStyle="bold" />
-
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
android:stretchColumns="1" >
<TableRow
@@ -110,13 +112,17 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/server_info_session_established" />
+ android:text="@string/server_info_session_established"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
<TextView
android:id="@+id/session_est"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:layout_gravity="right"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
</TableRow>
<TableRow
@@ -127,13 +133,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_pep"
- android:textColor="@color/primarytext" />
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
<TextView
android:id="@+id/server_info_pep"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:layout_gravity="right"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
</TableRow>
<TableRow
@@ -143,13 +152,17 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/server_info_stream_management" />
+ android:text="@string/server_info_stream_management"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
<TextView
android:id="@+id/server_info_sm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:layout_gravity="right"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
</TableRow>
<TableRow
@@ -159,34 +172,64 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/server_info_carbon_messages" />
+ android:text="@string/server_info_carbon_messages"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
<TextView
android:id="@+id/server_info_carbons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:layout_gravity="right"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody" />
</TableRow>
</TableLayout>
- <TextView
- android:id="@+id/otr_fingerprint_headline"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:gravity="center_horizontal"
- android:text="@string/otr_fingerprint"
- android:textColor="@color/secondarytext"
- android:textSize="?attr/TextSizeHeadline"
- android:textStyle="bold" />
- <TextView
- android:id="@+id/otr_fingerprint"
+
+ <RelativeLayout
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:textSize="?attr/TextSizeBody"
- android:typeface="monospace" />
+ android:layout_height="match_parent"
+ android:id="@+id/otr_fingerprint_box"
+ android:layout_marginTop="32dp">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@+id/action_copy_to_clipboard"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/otr_fingerprint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/primarytext"
+ android:textSize="?attr/TextSizeBody"
+ android:typeface="monospace" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeInfo"
+ android:text="@string/otr_fingerprint"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/action_copy_to_clipboard"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:background="?android:selectableItemBackground"
+ android:padding="8dp"
+ android:src="@drawable/ic_action_copy"
+ android:visibility="visible" />
+ </RelativeLayout>
+
+
</LinearLayout>
</LinearLayout>
</ScrollView>
diff --git a/res/layout/activity_muc_details.xml b/res/layout/activity_muc_details.xml
index 1a676548..f689f10d 100644
--- a/res/layout/activity_muc_details.xml
+++ b/res/layout/activity_muc_details.xml
@@ -1,46 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/primarybackground" >
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@color/secondarybackground" >
<LinearLayout
- android:layout_width="wrap_content"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:text="@string/muc_details_conference" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
+ android:orientation="vertical"
+ android:padding="16dp" >
<TextView
android:id="@+id/muc_jabberid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:padding="8dp"
- android:singleLine="true"
android:text="@string/account_settings_example_jabber_id"
android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeBody" />
-
- <TextView
- style="@style/sectionHeader"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
- android:paddingTop="8dp"
- android:text="@string/you" />
-
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold"
+ android:layout_marginBottom="16dp"/>
+
<RelativeLayout
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/activatedBackgroundIndicator"
- android:padding="8dp"
- android:paddingBottom="8dp" >
+ android:layout_height="wrap_content">
<ImageView
android:id="@+id/your_photo"
@@ -85,22 +74,26 @@
android:padding="8dp"
android:src="@drawable/ic_action_edit_dark" />
</RelativeLayout>
-
- <LinearLayout
- android:id="@+id/muc_more_details"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
-
- <TextView
- android:id="@+id/muc_participants_header"
- style="@style/sectionHeader"
+ <TextView
+ android:id="@+id/details_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
- android:paddingTop="8dp"
- android:text="@string/muc_details_other_members" />
+ android:layout_gravity="right"
+ android:layout_marginTop="32dp"
+ android:text="@string/using_account"
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeInfo" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/muc_more_details"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:background="@drawable/infocard_border"
+ android:orientation="vertical"
+ android:padding="8dp" >
+
<LinearLayout
android:id="@+id/muc_members"
@@ -111,7 +104,6 @@
android:orientation="vertical"
android:showDividers="middle" >
</LinearLayout>
- </LinearLayout>
<Button
android:id="@+id/invite"
@@ -123,4 +115,5 @@
android:text="@string/invite_contact" />
</LinearLayout>
+</LinearLayout>
</ScrollView> \ No newline at end of file
diff --git a/res/layout/contact_key.xml b/res/layout/contact_key.xml
index e10f8420..7053857f 100644
--- a/res/layout/contact_key.xml
+++ b/res/layout/contact_key.xml
@@ -16,14 +16,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primarytext"
- android:textSize="?attr/TextSizeHeadline"
+ android:textSize="?attr/TextSizeBody"
android:typeface="monospace" />
<TextView
android:id="@+id/key_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/secondarytext" />
+ android:textColor="@color/secondarytext"
+ android:textSize="?attr/TextSizeInfo"/>
</LinearLayout>
<ImageButton
diff --git a/res/layout/fragment_conversations_overview.xml b/res/layout/fragment_conversations_overview.xml
index bc52ec46..d4145761 100644
--- a/res/layout/fragment_conversations_overview.xml
+++ b/res/layout/fragment_conversations_overview.xml
@@ -1,5 +1,5 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/slidingpanelayout"
+ android:id="@+id/content_view_spl"
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/res/layout/message_null.xml b/res/layout/message_null.xml
index 1e148585..0e0f1c92 100644
--- a/res/layout/message_null.xml
+++ b/res/layout/message_null.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="0dp"
+ android:background="#00000000">
</RelativeLayout> \ No newline at end of file
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 0c230ec6..72121774 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -261,5 +261,9 @@
<string name="pref_expert_options_other">Sonstiges</string>
<string name="pref_conference_name">Konferenz-Name</string>
<string name="pref_conference_name_summary">Konferenz-Thema statt Raum-JID als Name verwenden</string>
+ <string name="toast_message_otr_fingerprint">OTR Fingerabdruck in die Zwischenablage kopiert!</string>
+ <string name="conference_banned">Du wurdest aus dem Konferenzraum verbannt</string>
+ <string name="conference_members_only">Der Konferenzraum ist nur fรผr Mitglieder</string>
+ <string name="conference_kicked">Du wurdest aus dem Konferenzraum geworfen</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 85d1d784..7fdc95c0 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -261,5 +261,9 @@
<string name="pref_expert_options_other">Otros</string>
<string name="pref_conference_name">Nombre de conferencia</string>
<string name="pref_conference_name_summary">Usar el asunto de la conferencia en lugar del identificador jabber como nombre de conferencia</string>
+ <string name="toast_message_otr_fingerprint">ยกClave OTR copiada en el portapapeles!</string>
+ <string name="conference_banned">Tu entrada a esta conferencia ha sido prohibida</string>
+ <string name="conference_members_only">Esta conferencia es solo para miembros</string>
+ <string name="conference_kicked">Has sido expulsado de esta conferencia</string>
</resources> \ No newline at end of file
diff --git a/res/values-nl/styles.xml b/res/values-nl/styles.xml
deleted file mode 100644
index 1468283e..00000000
--- a/res/values-nl/styles.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="sectionHeader" parent="android:Widget.Holo.Light.TextView">
- <item name="android:drawableBottom">@drawable/section_header</item>
- <item name="android:drawablePadding">4dp</item>
- <item name="android:layout_marginTop">8dp</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textAllCaps">true</item>
- <item name="android:textColor">#5b5b5b</item>
- <item name="android:textStyle">bold</item>
- </style>
-
- <style name="Divider">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">1.5dp</item>
- <item name="android:background">#b7b7b7</item>
- </style>
-
-</resources> \ No newline at end of file
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
new file mode 100644
index 00000000..1a243079
--- /dev/null
+++ b/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,39 @@
+<?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>
+ <string-array name="mute_options_descriptions">
+ <item>30 ๅˆ†้’Ÿ</item>
+ <item>1 ๅฐๆ—ถ</item>
+ <item>2 ๅฐๆ—ถ</item>
+ <item>8 ๅฐๆ—ถ</item>
+ <item>็›ด่‡ณๅฆ่กŒๅ–ๆถˆ</item>
+ </string-array>
+
+ <integer-array name="mute_options_durations">
+ <item>1800</item>
+ <item>3600</item>
+ <item>7200</item>
+ <item>28800</item>
+ <item>-1</item>
+ </integer-array>
+
+</resources> \ No newline at end of file
diff --git a/res/values-zh/strings.xml b/res/values-zh-rCN/strings.xml
index a7898425..a7898425 100644
--- a/res/values-zh/strings.xml
+++ b/res/values-zh-rCN/strings.xml
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
new file mode 100644
index 00000000..b9c261ad
--- /dev/null
+++ b/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,39 @@
+<?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>
+ <string-array name="mute_options_descriptions">
+ <item>30 ๅˆ†้˜</item>
+ <item>1 ๅฐๆ™‚</item>
+ <item>2 ๅฐๆ™‚</item>
+ <item>8 ๅฐๆ™‚</item>
+ <item>็›ด่‡ณๅฆ่กŒๅ–ๆถˆ</item>
+ </string-array>
+
+ <integer-array name="mute_options_durations">
+ <item>1800</item>
+ <item>3600</item>
+ <item>7200</item>
+ <item>28800</item>
+ <item>-1</item>
+ </integer-array>
+
+</resources> \ No newline at end of file
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 00000000..2c3ea225
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,263 @@
+<?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_end_conversation">็ตๆŸๅฐ่ฉฑ</string>
+ <string name="action_contact_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_manage_accounts">็ฎก็†ๅธณๆˆถ</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">ๅˆ†ไบซๅฐ่ฉฑ</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="encrypted_message">ๆญฃๅœจ่งฃๅฏ†่จŠๆฏไธญ๏ผŒ่ซ‹็จๅ€™&#8230;</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="remove_contact_text">ไฝ ็ขบๅฎš่ฆๅฐ‡ %s ๅพž่ฏ็ตกไบบๆธ…ๅ–ฎไธญ็งป้™คๅ—Ž๏ผŸ่ˆ‡่ฉฒ่ฏ็ตกไบบ็š„ๅฐ่ฉฑๅฐ‡ไธๆœƒ่ขซๆธ…้™คใ€‚</string>
+ <string name="remove_bookmark_text">ไฝ ็ขบๅฎš่ฆๅฐ‡ %s ๅพžๆ›ธ็ฑคๆธ…ๅ–ฎไธญ็งป้™คๅ—Ž๏ผŸ่ˆ‡่ฉฒ่ฏ็ตกไบบ็š„ๅฐ่ฉฑๅฐ‡ไธๆœƒ่ขซๆธ…้™คใ€‚</string>
+ <string name="register_account">ๅœจไผบๆœๅ™จไธŠ่จปๅ†Šๆ–ฐๅธณๆˆถ</string>
+ <string name="share_with">ๅˆ†ไบซ</string>
+ <string name="start_conversation">้–‹ๅง‹ๅฐ่ฉฑ</string>
+ <string name="invite_contact">้‚€่ซ‹่ฏ็ตกไบบ</string>
+ <string name="contacts">่ฏ็ตกไบบ</string>
+ <string name="cancel">ๅ–ๆถˆ</string>
+ <string name="add">ๆ–ฐๅขž</string>
+ <string name="edit">็ทจ่ผฏ</string>
+ <string name="delete">ๅˆช้™ค</string>
+ <string name="save">ๅ„ฒๅญ˜</string>
+ <string name="ok">ๅฅฝ็š„</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="image_offered_for_download"><i>ๅฏไพ›ไธ‹่ผ‰็š„ๅœ–ๅƒๆ–‡ไปถ</i></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="pref_general">ไธ€่ˆฌ</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_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_ui_options">ไป‹้ข้ธ้ …</string>
+ <string name="openpgp_error">OpenKeychain ๅ›žๅ ฑไบ†ไธ€ๅ€‹้Œฏ่ชค</string>
+ <string name="error_decrypting_file">่งฃๅฏ†ๆ–‡ไปถๆ™‚ๅ‡บ็พ I/O ้Œฏ่ชค</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="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">ไฝ ็”จไพ†้ธๆ“‡ๅœ–็‰‡็š„ app ๆฒ’ๆœ‰็ตฆไบˆ่ถณๅค ๆฌŠ้™ๆˆ‘ๅ€‘ๅŽป่ฎ€ๅ–ๆ–‡ไปถใ€‚\n\n<small>่ซ‹ไฝฟ็”จๅฆไธ€ๆ–‡ไปถ็ฎก็†ๅ™จไพ†้ธๆ“‡ๅœ–็‰‡</small></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_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_publish_pgp">็™ผๅธƒ OpenPGP ๅ…ฌๅ…ฑ้‘ฐๅŒ™</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="attach_record_voice">้Œ„้Ÿณ</string>
+ <string name="account_settings_jabber_id">Jabber ID</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 ID ็„กๆ•ˆ</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_other_members">ๅ…ถไป–ๆˆๅ“ก</string>
+ <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string>
+ <string name="server_info_stream_management">XEP-0198: Stream Management</string>
+ <string name="server_info_pep">XEP-0163: PEP (Avatars)</string>
+ <string name="server_info_available">ๆ”ฏๆด</string>
+ <string name="server_info_unavailable">ไธๆ”ฏๆด</string>
+ <string name="missing_public_keys">ๆฒ’ๆœ‰ๅ…ฌไฝˆๅ…ฌ้‘ฐ่จŠๆฏใ€‚</string>
+ <string name="last_seen_now">ๅ‰›ๅ‰›ๆ›พๅœจ็ทšไธŠ</string>
+ <string name="last_seen_min">ไธ€ๅˆ†้˜ๅ‰ๆ›พๅœจ็ทšไธŠ</string>
+ <string name="last_seen_mins">%d ๅˆ†้˜ๅ‰ๆ›พๅœจ็ทšไธŠ</string>
+ <string name="last_seen_hour">ไธ€ๅฐๆ™‚ๅ‰ๆ›พๅœจ็ทšไธŠ</string>
+ <string name="last_seen_hours">%d ๅฐๆ™‚ๅ‰ๆ›พๅœจ็ทšไธŠ</string>
+ <string name="last_seen_day">ไธ€ๅคฉๅ‰ๆ›พๅœจ็ทšไธŠ</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="openpgp_messages_found">็™ผ็พไปฅ OpenPGP ๅŠ ๅฏ†็š„่จŠๆฏ</string>
+ <string name="reception_failed">ๆŽฅๆ”ถๅคฑๆ•—</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">็™ผไฝˆ</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>
+ <string name="connect">้€ฃๆŽฅ</string>
+ <string name="account_already_exists">่ฉฒๅธณๆˆถๅทฒๅญ˜ๅœจ</string>
+ <string name="next">ไธ‹ไธ€ๆญฅ</string>
+ <string name="server_info_session_established">ๅทฒๅปบ็ซ‹้€ฃๆŽฅ</string>
+ <string name="additional_information">ๅ…ถไป–่จŠๆฏ</string>
+ <string name="skip">็•ฅ้Ž</string>
+ <string name="disable_notifications">้—œ้–‰้€š็Ÿฅ</string>
+ <string name="disable_notifications_for_this_conversation">้—œ้–‰่ฉฒๅฐ่ฉฑๆถˆๆฏ</string>
+ <string name="notifications_disabled">้€š็Ÿฅๅทฒ้—œ้–‰</string>
+ <string name="enable">ๆ‰“้–‹้€š็Ÿฅ</string>
+ <string name="conference_requires_password">็พค็ต„่จญๆœ‰ๅฏ†็ขผ</string>
+ <string name="enter_password">่ผธๅ…ฅๅฏ†็ขผ</string>
+ <string name="missing_presence_updates">็ผบๅฐ‘่ฏ็ตกไบบ็‹€ๆ…‹่จŠๆฏ</string>
+ <string name="request_presence_updates">่ซ‹ๅ…ˆ็™ผ้€้—œๆณจ็‹€ๆ…‹่จŠๆฏ่ซ‹ๆฑ‚ใ€‚\n\n<small>้€™ๅฐ‡็”จไพ†ๅˆคๆ–ทๆ‚จ็š„่ฏ็ตกไบบๆ‰€็”จ็š„ๅฎขๆˆถ็ซฏ้กžๅž‹ใ€‚</small></string>
+ <string name="request_now">็พๅœจ็™ผ้€่ซ‹ๆฑ‚</string>
+ <string name="delete_fingerprint">ๅˆช้™คๆŒ‡็ด‹</string>
+ <string name="sure_delete_fingerprint">ไฝ ็ขบๅฎšๅˆช้™ค่ฉฒๆŒ‡็ด‹ๅ—Ž๏ผŸ</string>
+ <string name="ignore">ๅฟฝ็•ฅ</string>
+ <string name="without_mutual_presence_updates"><b>่ญฆๅ‘Š:</b> ๅœจๆฒ’ๆœ‰ไบ’็›ธ้—œๆณจ็‹€ๆ…‹่จŠๆฏ็š„ๆƒ…ๆณไธ‹็™ผ้€ๆˆ–ๆœƒๅผ•่ตทไธ่ƒฝ้ ่จˆ็š„ๅ•้กŒใ€‚\n\n<small>่ซ‹ๆชข่ฆ–่ฏ็ตกไบบ่ฉณๆƒ…้ ้ขไปฅ็ขบ่ชไฝ ๅ€‘็š„้—œๆณจ็‹€ๆ…‹ใ€‚</small></string>
+ <string name="pref_encryption_settings">ๅŠ ๅฏ†่จญๅฎš</string>
+ <string name="pref_force_encryption">ๅผทๅˆถ่ฆๆฑ‚็ซฏๅˆฐ็ซฏๅŠ ๅฏ†</string>
+ <string name="pref_force_encryption_summary">็ธฝๆ˜ฏ็™ผ้€ๅŠ ๅฏ†่จŠๆฏ (็พค็ต„่จŠๆฏ้™คๅค–)</string>
+ <string name="pref_dont_save_encrypted">ไธๅ„ฒๅญ˜ๅŠ ๅฏ†่จŠๆฏ</string>
+ <string name="pref_dont_save_encrypted_summary">่ญฆๅ‘Š: ๆญคๆ“ไฝœๆˆ–ๆœƒๅฐŽ่‡ด่จŠๆฏไธŸๅคฑ</string>
+ <string name="pref_expert_options">ๅฐˆๅฎถ้ธ้ …</string>
+ <string name="pref_expert_options_summary">่ซ‹ๅฐๅฟƒ่จญๅฎš</string>
+ <string name="pref_use_larger_font">ๅขžๅŠ ๅญ—้ซ”ๅคงๅฐ</string>
+ <string name="pref_use_larger_font_summary">่ฎ“ๆ•ดๅ€‹ app ็•Œ้ขไฝฟ็”จๆ›ดๅคง่™Ÿ็š„ๅญ—้ซ”</string>
+ <string name="pref_use_send_button_to_indicate_status">็”จใ€Œ็™ผ้€ใ€ๆŒ‰้ˆ•้กฏ็คบ็‹€ๆ…‹่จŠๆฏ</string>
+ <string name="pref_use_indicate_received">่ฆๆฑ‚่ฎ€ๅ–ๆ”ถๆ“š</string>
+ <string name="pref_use_indicate_received_summary">ๅทฒ่ขซ่ฎ€ๅ–็š„่จŠๆฏๆœƒไปฅ็ถ ่‰ฒๅ‹พ่™Ÿ่กจ็คบใ€‚่ซ‹ๆณจๆ„๏ผŒ้€™ๅ€‹ๅŠŸ่ƒฝๆœชๅฟ…ๆฏๆฌกๆœ‰ๆ•ˆใ€‚</string>
+ <string name="pref_use_send_button_to_indicate_status_summary">ๅฐ‡ใ€Œ็™ผ้€ใ€ๆŒ‰้ˆ•่จญๆˆไธๅŒ้ก่‰ฒ๏ผŒไปฅ่กจ็คบไธๅŒ็š„็‹€ๆ…‹่จŠๆฏใ€‚</string>
+ <string name="pref_expert_options_other">ๅ…ถไป–</string>
+ <string name="pref_conference_name">็พค็ต„ๅ็จฑ</string>
+ <string name="pref_conference_name_summary">ไฝฟ็”จ็พค็ต„็š„ๅ็จฑ่€Œไธๆ˜ฏ JID ไพ†่ญ˜ๅˆฅไน‹ใ€‚ </string>
+
+</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b1156e27..22622f60 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -261,5 +261,11 @@
<string name="pref_expert_options_other">Other</string>
<string name="pref_conference_name">Conference name</string>
<string name="pref_conference_name_summary">Use roomโ€™s subject instead of JID to identify conferences</string>
+ <string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
+ <string name="conference_banned">You are banned from this conference</string>
+ <string name="conference_members_only">This conference is members only</string>
+ <string name="conference_kicked">You have been kicked from this conference</string>
+ <string name="using_account">using account %s</string>
+ <string name="checking_image">Checking image on HTTP host</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a827fe36..64bde770 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,15 +1,4 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="sectionHeader" parent="android:Widget.Holo.Light.TextView">
- <item name="android:drawableBottom">@drawable/section_header</item>
- <item name="android:drawablePadding">4dp</item>
- <item name="android:layout_marginTop">8dp</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textAllCaps">true</item>
- <item name="android:textColor">@color/primarytext</item>
- <item name="android:textStyle">bold</item>
- </style>
-
<style name="Divider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1.5dp</item>
diff --git a/src/eu/siacs/conversations/Config.java b/src/eu/siacs/conversations/Config.java
index 1725eca6..a11e1763 100644
--- a/src/eu/siacs/conversations/Config.java
+++ b/src/eu/siacs/conversations/Config.java
@@ -10,7 +10,7 @@ public final class Config {
public static final int PING_MIN_INTERVAL = 30;
public static final int PING_TIMEOUT = 10;
public static final int CONNECT_TIMEOUT = 90;
- public static final int CARBON_GRACE_PERIOD = 60;
+ public static final int CARBON_GRACE_PERIOD = 120;
public static final int AVATAR_SIZE = 192;
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java
index e7058a68..07e8e472 100644
--- a/src/eu/siacs/conversations/crypto/PgpEngine.java
+++ b/src/eu/siacs/conversations/crypto/PgpEngine.java
@@ -19,10 +19,10 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.UiCallback;
-import eu.siacs.conversations.xmpp.jingle.JingleFile;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
@@ -76,7 +76,7 @@ public class PgpEngine {
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result
.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
- Log.d(Config.LOGTAG, error.getMessage());
+ Log.d(Config.LOGTAG,"openpgp error: "+error.getMessage());
callback.error(R.string.openpgp_error, message);
return;
default:
@@ -86,10 +86,10 @@ public class PgpEngine {
});
} else if (message.getType() == Message.TYPE_IMAGE) {
try {
- final JingleFile inputFile = this.mXmppConnectionService
- .getFileBackend().getJingleFile(message, false);
- final JingleFile outputFile = this.mXmppConnectionService
- .getFileBackend().getJingleFile(message, true);
+ final DownloadableFile inputFile = this.mXmppConnectionService
+ .getFileBackend().getConversationsFile(message, false);
+ final DownloadableFile outputFile = this.mXmppConnectionService
+ .getFileBackend().getConversationsFile(message, true);
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
@@ -110,9 +110,7 @@ public class PgpEngine {
+ ',' + imageWidth + ',' + imageHeight);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
PgpEngine.this.mXmppConnectionService
- .updateMessage(message);
- PgpEngine.this.mXmppConnectionService
- .updateConversationUi();
+ .updateMessage(message);;
callback.success(message);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@@ -197,10 +195,10 @@ public class PgpEngine {
});
} else if (message.getType() == Message.TYPE_IMAGE) {
try {
- JingleFile inputFile = this.mXmppConnectionService
- .getFileBackend().getJingleFile(message, true);
- JingleFile outputFile = this.mXmppConnectionService
- .getFileBackend().getJingleFile(message, false);
+ DownloadableFile inputFile = this.mXmppConnectionService
+ .getFileBackend().getConversationsFile(message, true);
+ DownloadableFile outputFile = this.mXmppConnectionService
+ .getFileBackend().getConversationsFile(message, false);
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java
index eacd172c..9b1cbcab 100644
--- a/src/eu/siacs/conversations/entities/Account.java
+++ b/src/eu/siacs/conversations/entities/Account.java
@@ -160,8 +160,12 @@ public class Account extends AbstractEntity {
}
public boolean hasErrorStatus() {
- return getStatus() > STATUS_NO_INTERNET
- && (getXmppConnection().getAttempt() >= 2);
+ if (getXmppConnection() == null) {
+ return false;
+ } else {
+ return getStatus() > STATUS_NO_INTERNET
+ && (getXmppConnection().getAttempt() >= 2);
+ }
}
public void setResource(String resource) {
diff --git a/src/eu/siacs/conversations/entities/Bookmark.java b/src/eu/siacs/conversations/entities/Bookmark.java
index 14f010e7..722fb6d9 100644
--- a/src/eu/siacs/conversations/entities/Bookmark.java
+++ b/src/eu/siacs/conversations/entities/Bookmark.java
@@ -7,46 +7,35 @@ import android.graphics.Bitmap;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element;
-public class Bookmark implements ListItem {
+public class Bookmark extends Element implements ListItem {
private Account account;
- private String jid;
- private String nick;
- private String name;
- private String password;
- private boolean autojoin;
- private boolean providePassword;
private Conversation mJoinedConversation;
public Bookmark(Account account, String jid) {
+ super("conference");
+ this.setAttribute("jid", jid);
+ this.account = account;
+ }
+
+ private Bookmark(Account account) {
+ super("conference");
this.account = account;
- this.jid = jid;
}
public static Bookmark parse(Element element, Account account) {
- Bookmark bookmark = new Bookmark(account, element.getAttribute("jid"));
- bookmark.setName(element.getAttribute("name"));
- String autojoin = element.getAttribute("autojoin");
- if (autojoin != null
- && (autojoin.equals("true") || autojoin.equals("1"))) {
- bookmark.setAutojoin(true);
- } else {
- bookmark.setAutojoin(false);
- }
- Element nick = element.findChild("nick");
- if (nick != null) {
- bookmark.setNick(nick.getContent());
- }
- Element password = element.findChild("password");
- if (password != null) {
- bookmark.setPassword(password.getContent());
- bookmark.setProvidePassword(true);
- }
+ Bookmark bookmark = new Bookmark(account);
+ bookmark.setAttributes(element.getAttributes());
+ bookmark.setChildren(element.getChildren());
return bookmark;
}
public void setAutojoin(boolean autojoin) {
- this.autojoin = autojoin;
+ if (autojoin) {
+ this.setAttribute("autojoin", "true");
+ } else {
+ this.setAttribute("autojoin", "false");
+ }
}
public void setName(String name) {
@@ -54,15 +43,18 @@ public class Bookmark implements ListItem {
}
public void setNick(String nick) {
- this.nick = nick;
+ Element element = this.findChild("nick");
+ if (element == null) {
+ element = this.addChild("nick");
+ }
+ element.setContent(nick);
}
public void setPassword(String password) {
- this.password = password;
- }
-
- private void setProvidePassword(boolean providePassword) {
- this.providePassword = providePassword;
+ Element element = this.findChild("password");
+ if (element != null) {
+ element.setContent(password);
+ }
}
@Override
@@ -76,32 +68,45 @@ public class Bookmark implements ListItem {
if (this.mJoinedConversation != null
&& (this.mJoinedConversation.getMucOptions().getSubject() != null)) {
return this.mJoinedConversation.getMucOptions().getSubject();
- } else if (name != null) {
- return name;
+ } else if (getName() != null) {
+ return getName();
} else {
- return this.jid.split("@")[0];
+ return this.getJid().split("@")[0];
}
}
@Override
public String getJid() {
- return this.jid.toLowerCase(Locale.US);
+ String jid = this.getAttribute("jid");
+ if (jid != null) {
+ return jid.toLowerCase(Locale.US);
+ } else {
+ return null;
+ }
}
public String getNick() {
- return this.nick;
+ Element nick = this.findChild("nick");
+ if (nick != null) {
+ return nick.getContent();
+ } else {
+ return null;
+ }
}
public boolean autojoin() {
- return autojoin;
+ String autojoin = this.getAttribute("autojoin");
+ return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin
+ .equalsIgnoreCase("1")));
}
public String getPassword() {
- return this.password;
- }
-
- public boolean isProvidePassword() {
- return this.providePassword;
+ Element password = this.findChild("password");
+ if (password != null) {
+ return password.getContent();
+ } else {
+ return null;
+ }
}
public boolean match(String needle) {
@@ -131,27 +136,7 @@ public class Bookmark implements ListItem {
}
public String getName() {
- return name;
- }
-
- public Element toElement() {
- Element element = new Element("conference");
- element.setAttribute("jid", this.getJid());
- if (this.getName() != null) {
- element.setAttribute("name", this.getName());
- }
- if (this.autojoin) {
- element.setAttribute("autojoin", "true");
- } else {
- element.setAttribute("autojoin", "false");
- }
- if (this.nick != null) {
- element.addChild("nick").setContent(this.nick);
- }
- if (this.password != null && isProvidePassword()) {
- element.addChild("password").setContent(this.password);
- }
- return element;
+ return this.getAttribute("name");
}
public void unregisterConversation() {
diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java
index dfd6c059..40eee73d 100644
--- a/src/eu/siacs/conversations/entities/Contact.java
+++ b/src/eu/siacs/conversations/entities/Contact.java
@@ -156,6 +156,7 @@ public class Contact implements ListItem {
public void clearPresences() {
this.presences.clearPresences();
+ this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
}
public int getMostAvailableStatus() {
@@ -373,4 +374,8 @@ public class Contact implements ListItem {
return false;
}
}
+
+ public boolean trusted() {
+ return getOption(Options.FROM) && getOption(Options.TO);
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java
index fedb0893..b4c99dc1 100644
--- a/src/eu/siacs/conversations/entities/Conversation.java
+++ b/src/eu/siacs/conversations/entities/Conversation.java
@@ -43,6 +43,7 @@ public class Conversation extends AbstractEntity {
public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption";
public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
+ public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
private String name;
private String contactUuid;
@@ -54,8 +55,6 @@ public class Conversation extends AbstractEntity {
private JSONObject attributes = new JSONObject();
- private long mutedTill = 0;
-
private String nextPresence;
private transient CopyOnWriteArrayList<Message> messages = null;
@@ -69,7 +68,7 @@ public class Conversation extends AbstractEntity {
private transient MucOptions mucOptions = null;
- private transient String latestMarkableMessageId;
+ //private transient String latestMarkableMessageId;
private byte[] symmetricKey;
@@ -139,10 +138,20 @@ public class Conversation extends AbstractEntity {
}
}
- public String popLatestMarkableMessageId() {
- String id = this.latestMarkableMessageId;
- this.latestMarkableMessageId = null;
- return id;
+ public String getLatestMarkableMessageId() {
+ if (this.messages == null) {
+ return null;
+ }
+ for(int i = this.messages.size() - 1; i >= 0; --i) {
+ if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED && this.messages.get(i).markable) {
+ if (this.messages.get(i).isRead()) {
+ return null;
+ } else {
+ return this.messages.get(i).getRemoteMsgId();
+ }
+ }
+ }
+ return null;
}
public Message getLatestMessage() {
@@ -406,12 +415,6 @@ public class Conversation extends AbstractEntity {
this.nextMessage = message;
}
- public void setLatestMarkableMessageId(String id) {
- if (id != null) {
- this.latestMarkableMessageId = id;
- }
- }
-
public void setSymmetricKey(byte[] key) {
this.symmetricKey = key;
}
@@ -452,12 +455,13 @@ public class Conversation extends AbstractEntity {
return false;
}
- public void setMutedTill(long mutedTill) {
- this.mutedTill = mutedTill;
+ public void setMutedTill(long value) {
+ this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
}
public boolean isMuted() {
- return SystemClock.elapsedRealtime() < this.mutedTill;
+ return SystemClock.elapsedRealtime() < this.getLongAttribute(
+ ATTRIBUTE_MUTED_TILL, 0);
}
public boolean setAttribute(String key, String value) {
@@ -489,4 +493,17 @@ public class Conversation extends AbstractEntity {
}
}
}
+
+ public long getLongAttribute(String key, long defaultValue) {
+ String value = this.getAttribute(key);
+ if (value == null) {
+ return defaultValue;
+ } else {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Downloadable.java b/src/eu/siacs/conversations/entities/Downloadable.java
index 8fb4977e..c8ee357d 100644
--- a/src/eu/siacs/conversations/entities/Downloadable.java
+++ b/src/eu/siacs/conversations/entities/Downloadable.java
@@ -1,5 +1,9 @@
package eu.siacs.conversations.entities;
public interface Downloadable {
+
+ public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" };
+ public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" };
+
public void start();
}
diff --git a/src/eu/siacs/conversations/entities/DownloadableFile.java b/src/eu/siacs/conversations/entities/DownloadableFile.java
new file mode 100644
index 00000000..14afc826
--- /dev/null
+++ b/src/eu/siacs/conversations/entities/DownloadableFile.java
@@ -0,0 +1,149 @@
+package eu.siacs.conversations.entities;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.CryptoHelper;
+import android.util.Log;
+
+public class DownloadableFile extends File {
+
+ private static final long serialVersionUID = 2247012619505115863L;
+
+ private long expectedSize = 0;
+ private String sha1sum;
+ private Key aeskey;
+
+ private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
+
+ public DownloadableFile(String path) {
+ super(path);
+ }
+
+ public long getSize() {
+ return super.length();
+ }
+
+ public long getExpectedSize() {
+ if (this.aeskey != null) {
+ return (this.expectedSize / 16 + 1) * 16;
+ } else {
+ return this.expectedSize;
+ }
+ }
+
+ public void setExpectedSize(long size) {
+ this.expectedSize = size;
+ }
+
+ public String getSha1Sum() {
+ return this.sha1sum;
+ }
+
+ public void setSha1Sum(String sum) {
+ this.sha1sum = sum;
+ }
+
+ public void setKey(byte[] key) {
+ if (key.length >= 32) {
+ byte[] secretKey = new byte[32];
+ System.arraycopy(key, 0, secretKey, 0, 32);
+ this.aeskey = new SecretKeySpec(secretKey, "AES");
+ } else if (key.length >= 16) {
+ byte[] secretKey = new byte[16];
+ System.arraycopy(key, 0, secretKey, 0, 16);
+ this.aeskey = new SecretKeySpec(secretKey, "AES");
+ } else {
+ Log.d(Config.LOGTAG, "weird key");
+ }
+ Log.d(Config.LOGTAG,
+ "using aes key "
+ + CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
+ }
+
+ public Key getKey() {
+ return this.aeskey;
+ }
+
+ public InputStream createInputStream() {
+ if (this.getKey() == null) {
+ try {
+ return new FileInputStream(this);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ } else {
+ try {
+ IvParameterSpec ips = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
+ Log.d(Config.LOGTAG, "opening encrypted input stream");
+ return new CipherInputStream(new FileInputStream(this), cipher);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
+ return null;
+ } catch (NoSuchPaddingException e) {
+ Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
+ return null;
+ } catch (InvalidKeyException e) {
+ Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
+ return null;
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ public OutputStream createOutputStream() {
+ if (this.getKey() == null) {
+ try {
+ return new FileOutputStream(this);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ } else {
+ try {
+ IvParameterSpec ips = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
+ Log.d(Config.LOGTAG, "opening encrypted output stream");
+ return new CipherOutputStream(new FileOutputStream(this),
+ cipher);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
+ return null;
+ } catch (NoSuchPaddingException e) {
+ Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
+ return null;
+ } catch (InvalidKeyException e) {
+ Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
+ return null;
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java
index 8f9885c5..863288bb 100644
--- a/src/eu/siacs/conversations/entities/Message.java
+++ b/src/eu/siacs/conversations/entities/Message.java
@@ -1,5 +1,9 @@
package eu.siacs.conversations.entities;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import android.content.ContentValues;
@@ -10,6 +14,7 @@ public class Message extends AbstractEntity {
public static final String TABLENAME = "messages";
+ public static final int STATUS_RECEIVED_CHECKING = -4;
public static final int STATUS_RECEPTION_FAILED = -3;
public static final int STATUS_RECEIVED_OFFER = -2;
public static final int STATUS_RECEIVING = -1;
@@ -57,9 +62,9 @@ public class Message extends AbstractEntity {
protected boolean read = true;
protected String remoteMsgId = null;
- protected transient Conversation conversation = null;
-
- protected transient Downloadable downloadable = null;
+ protected Conversation conversation = null;
+ protected Downloadable downloadable = null;
+ public boolean markable = false;
private Message() {
@@ -131,14 +136,8 @@ public class Message extends AbstractEntity {
if (this.trueCounterpart == null) {
return null;
} else {
- Account account = this.conversation.getAccount();
- Contact contact = account.getRoster().getContact(
+ return this.conversation.getAccount().getRoster().getContactFromRoster(
this.trueCounterpart);
- if (contact.showInRoster()) {
- return contact;
- } else {
- return null;
- }
}
}
}
@@ -369,4 +368,95 @@ public class Message extends AbstractEntity {
return prev.mergable(this);
}
}
+
+ public boolean bodyContainsDownloadable() {
+ Contact contact = this.getContact();
+ if (contact == null || !contact.trusted()) {
+ return false;
+ }
+ try {
+ URL url = new URL(this.getBody());
+ if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
+ return false;
+ }
+ if (url.getPath()==null) {
+ return false;
+ }
+ String[] pathParts = url.getPath().split("/");
+ String filename = pathParts[pathParts.length - 1];
+ String[] extensionParts = filename.split("\\.");
+ if (extensionParts.length == 2 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -1])) {
+ return true;
+ } else if (extensionParts.length == 3 && Arrays.asList(Downloadable.VALID_CRYPTO_EXTENSIONS).contains(extensionParts.length -1) && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -2])) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ }
+
+ public ImageParams getImageParams() {
+ ImageParams params = new ImageParams();
+ if (body==null) {
+ return params;
+ }
+ String parts[] = body.split(",");
+ if (parts.length==1) {
+ try {
+ params.size = Long.parseLong(parts[0]);
+ } catch (NumberFormatException e) {
+ params.origin = parts[0];
+ }
+ } else if (parts.length == 2) {
+ params.origin = parts[0];
+ try {
+ params.size = Long.parseLong(parts[1]);
+ } catch (NumberFormatException e) {
+ params.size = 0;
+ }
+ } else if (parts.length==3) {
+ try {
+ params.size = Long.parseLong(parts[0]);
+ } catch (NumberFormatException e) {
+ params.size = 0;
+ }
+ try {
+ params.width = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException e) {
+ params.width = 0;
+ }
+ try {
+ params.height = Integer.parseInt(parts[2]);
+ } catch (NumberFormatException e) {
+ params.height = 0;
+ }
+ } else if (parts.length == 4) {
+ params.origin = parts[0];
+ try {
+ params.size = Long.parseLong(parts[1]);
+ } catch (NumberFormatException e) {
+ params.size = 0;
+ }
+ try {
+ params.width = Integer.parseInt(parts[2]);
+ } catch (NumberFormatException e) {
+ params.width = 0;
+ }
+ try {
+ params.height = Integer.parseInt(parts[3]);
+ } catch (NumberFormatException e) {
+ params.height = 0;
+ }
+ }
+ return params;
+ }
+
+ public class ImageParams {
+ public long size = 0;
+ public int width = 0;
+ public int height = 0;
+ public String origin;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java
index 0294c8aa..12ea4e93 100644
--- a/src/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/eu/siacs/conversations/entities/MucOptions.java
@@ -15,6 +15,13 @@ public class MucOptions {
public static final int ERROR_NICK_IN_USE = 1;
public static final int ERROR_ROOM_NOT_FOUND = 2;
public static final int ERROR_PASSWORD_REQUIRED = 3;
+ public static final int ERROR_BANNED = 4;
+ public static final int ERROR_MEMBERS_ONLY = 5;
+
+ public static final int KICKED_FROM_ROOM = 9;
+
+ public static final String STATUS_CODE_BANNED = "301";
+ public static final String STATUS_CODE_KICKED = "307";
public interface OnRenameListener {
public void onRename(boolean success);
@@ -108,7 +115,6 @@ public class MucOptions {
private String subject = null;
private String joinnick;
private String password = null;
- private boolean passwordChanged = false;
public MucOptions(Account account) {
this.account = account;
@@ -158,10 +164,6 @@ public class MucOptions {
}
aboutToRename = false;
}
- if (conversation.getBookmark() != null
- && conversation.getBookmark().isProvidePassword()) {
- this.passwordChanged = false;
- }
} else {
addUser(user);
}
@@ -179,11 +181,27 @@ public class MucOptions {
x.getContent()));
}
}
+ } else if (type.equals("unavailable") && name.equals(this.joinnick)) {
+ Element x = packet.findChild("x",
+ "http://jabber.org/protocol/muc#user");
+ if (x != null) {
+ Element status = x.findChild("status");
+ if (status != null) {
+ String code = status.getAttribute("code");
+ if (STATUS_CODE_KICKED.equals(code)) {
+ this.isOnline = false;
+ this.error = KICKED_FROM_ROOM;
+ } else if (STATUS_CODE_BANNED.equals(code)) {
+ this.isOnline = false;
+ this.error = ERROR_BANNED;
+ }
+ }
+ }
} else if (type.equals("unavailable")) {
deleteUser(packet.getAttribute("from").split("/", 2)[1]);
} else if (type.equals("error")) {
Element error = packet.findChild("error");
- if (error.hasChild("conflict")) {
+ if (error != null && error.hasChild("conflict")) {
if (aboutToRename) {
if (renameListener != null) {
renameListener.onRename(false);
@@ -193,12 +211,13 @@ public class MucOptions {
} else {
this.error = ERROR_NICK_IN_USE;
}
- } else if (error.hasChild("not-authorized")) {
- if (conversation.getBookmark() != null
- && conversation.getBookmark().isProvidePassword()) {
- this.passwordChanged = true;
- }
+ } else if (error != null && error.hasChild("not-authorized")) {
this.error = ERROR_PASSWORD_REQUIRED;
+ } else if (error != null && error.hasChild("forbidden")) {
+ this.error = ERROR_BANNED;
+ } else if (error != null
+ && error.hasChild("registration-required")) {
+ this.error = ERROR_MEMBERS_ONLY;
}
}
}
@@ -334,8 +353,7 @@ public class MucOptions {
}
public void setPassword(String password) {
- if (conversation.getBookmark() != null
- && conversation.getBookmark().isProvidePassword()) {
+ if (conversation.getBookmark() != null) {
conversation.getBookmark().setPassword(password);
} else {
this.password = password;
@@ -343,9 +361,4 @@ public class MucOptions {
conversation
.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
}
-
- public boolean isPasswordChanged() {
- return this.passwordChanged;
- }
-
} \ No newline at end of file
diff --git a/src/eu/siacs/conversations/entities/Roster.java b/src/eu/siacs/conversations/entities/Roster.java
index b6908793..ef47c577 100644
--- a/src/eu/siacs/conversations/entities/Roster.java
+++ b/src/eu/siacs/conversations/entities/Roster.java
@@ -14,7 +14,7 @@ public class Roster {
this.account = account;
}
- public Contact getContactAsShownInRoster(String jid) {
+ public Contact getContactFromRoster(String jid) {
String cleanJid = jid.split("/", 2)[0];
Contact contact = contacts.get(cleanJid);
if (contact != null && contact.showInRoster()) {
diff --git a/src/eu/siacs/conversations/http/HttpConnection.java b/src/eu/siacs/conversations/http/HttpConnection.java
new file mode 100644
index 00000000..ff8037fb
--- /dev/null
+++ b/src/eu/siacs/conversations/http/HttpConnection.java
@@ -0,0 +1,165 @@
+package eu.siacs.conversations.http;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import android.graphics.BitmapFactory;
+
+import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+
+public class HttpConnection implements Downloadable {
+
+ private HttpConnectionManager mHttpConnectionManager;
+ private XmppConnectionService mXmppConnectionService;
+
+ private URL mUrl;
+ private Message message;
+ private DownloadableFile file;
+ private long mPreviousFileSize = Long.MIN_VALUE;
+
+ public HttpConnection(HttpConnectionManager manager) {
+ this.mHttpConnectionManager = manager;
+ this.mXmppConnectionService = manager.getXmppConnectionService();
+ }
+
+ @Override
+ public void start() {
+ new Thread(new FileDownloader()).start();
+ }
+
+ public void init(Message message) {
+ this.message = message;
+ this.message.setDownloadable(this);
+ try {
+ mUrl = new URL(message.getBody());
+ this.file = mXmppConnectionService.getFileBackend().getConversationsFile(message,false);
+ message.setType(Message.TYPE_IMAGE);
+ message.setStatus(Message.STATUS_RECEIVED_CHECKING);
+ mXmppConnectionService.updateConversationUi();
+ checkFileSize();
+ } catch (MalformedURLException e) {
+ this.cancel();
+ }
+ }
+
+ public void init(Message message, URL url) {
+ this.message = message;
+ this.message.setDownloadable(this);
+ this.mUrl = url;
+ this.file = mXmppConnectionService.getFileBackend().getConversationsFile(message,false);
+ this.mPreviousFileSize = message.getImageParams().size;
+ message.setType(Message.TYPE_IMAGE);
+ message.setStatus(Message.STATUS_RECEIVED_CHECKING);
+ mXmppConnectionService.updateConversationUi();
+ checkFileSize();
+ }
+
+ private void checkFileSize() {
+ new Thread(new FileSizeChecker()).start();
+ }
+
+ public void cancel() {
+ mXmppConnectionService.markMessage(message, Message.STATUS_RECEPTION_FAILED);
+ mHttpConnectionManager.finishConnection(this);
+ }
+
+ private void finish() {
+ message.setStatus(Message.STATUS_RECEIVED);
+ mXmppConnectionService.updateMessage(message);
+ mHttpConnectionManager.finishConnection(this);
+ }
+
+ private class FileSizeChecker implements Runnable {
+
+ @Override
+ public void run() {
+ long size;
+ try {
+ size = retrieveFileSize();
+ } catch (IOException e) {
+ cancel();
+ return;
+ }
+ file.setExpectedSize(size);
+ message.setBody(mUrl.toString()+","+String.valueOf(size));
+ if (size <= mHttpConnectionManager.getAutoAcceptFileSize() || size == mPreviousFileSize) {
+ mXmppConnectionService.updateMessage(message);
+ start();
+ } else {
+ message.setStatus(Message.STATUS_RECEIVED_OFFER);
+ mXmppConnectionService.updateMessage(message);
+ }
+ }
+
+ private long retrieveFileSize() throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("HEAD");
+ if (connection instanceof HttpsURLConnection) {
+
+ }
+ String contentLength = connection.getHeaderField("Content-Length");
+ if (contentLength == null) {
+ throw new IOException();
+ }
+ try {
+ return Long.parseLong(contentLength, 10);
+ } catch (NumberFormatException e) {
+ throw new IOException();
+ }
+ }
+
+ }
+
+ private class FileDownloader implements Runnable {
+
+ @Override
+ public void run() {
+ try {
+ mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING);
+ download();
+ updateImageBounds();
+ finish();
+ } catch (IOException e) {
+ cancel();
+ }
+ }
+
+ private void download() throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ if (connection instanceof HttpsURLConnection) {
+
+ }
+ BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
+ OutputStream os = file.createOutputStream();
+ int count = -1;
+ byte[] buffer = new byte[1024];
+ while ((count = is.read(buffer)) != -1) {
+ os.write(buffer, 0, count);
+ }
+ os.flush();
+ os.close();
+ is.close();
+ }
+
+ private void updateImageBounds() {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ int imageHeight = options.outHeight;
+ int imageWidth = options.outWidth;
+ message.setBody(mUrl.toString()+","+file.getSize() + ','
+ + imageWidth + ',' + imageHeight);
+
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/eu/siacs/conversations/http/HttpConnectionManager.java b/src/eu/siacs/conversations/http/HttpConnectionManager.java
new file mode 100644
index 00000000..7393cf36
--- /dev/null
+++ b/src/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -0,0 +1,37 @@
+package eu.siacs.conversations.http;
+
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.AbstractConnectionManager;
+import eu.siacs.conversations.services.XmppConnectionService;
+
+public class HttpConnectionManager extends AbstractConnectionManager {
+
+ public HttpConnectionManager(XmppConnectionService service) {
+ super(service);
+ }
+
+ private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>();
+
+
+ public HttpConnection createNewConnection(Message message) {
+ HttpConnection connection = new HttpConnection(this);
+ connection.init(message);
+ this.connections.add(connection);
+ return connection;
+ }
+
+ public HttpConnection createNewConnection(Message message, URL url) {
+ HttpConnection connection = new HttpConnection(this);
+ connection.init(message,url);
+ this.connections.add(connection);
+ return connection;
+ }
+
+ public void finishConnection(HttpConnection connection) {
+ this.connections.remove(connection);
+ }
+}
diff --git a/src/eu/siacs/conversations/parser/IqParser.java b/src/eu/siacs/conversations/parser/IqParser.java
index 592b77a4..df6754f2 100644
--- a/src/eu/siacs/conversations/parser/IqParser.java
+++ b/src/eu/siacs/conversations/parser/IqParser.java
@@ -73,6 +73,9 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
IqPacket response = mXmppConnectionService.getIqGenerator()
.discoResponse(packet);
account.getXmppConnection().sendIqPacket(response, null);
+ } else if (packet.hasChild("ping", "urn:xmpp:ping")) {
+ IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
+ mXmppConnectionService.sendIqPacket(account, response, null);
} else {
if ((packet.getType() == IqPacket.TYPE_GET)
|| (packet.getType() == IqPacket.TYPE_SET)) {
diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java
index af0c96e9..71346c7a 100644
--- a/src/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/eu/siacs/conversations/parser/MessageParser.java
@@ -1,9 +1,7 @@
package eu.siacs.conversations.parser;
-import android.os.SystemClock;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -18,9 +16,6 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageParser extends AbstractParser implements
OnMessagePacketReceived {
-
- private long lastCarbonMessageReceived = -(Config.CARBON_GRACE_PERIOD * 1000);
-
public MessageParser(XmppConnectionService service) {
super(service);
}
@@ -29,7 +24,6 @@ public class MessageParser extends AbstractParser implements
String[] fromParts = packet.getFrom().split("/", 2);
Conversation conversation = mXmppConnectionService
.findOrCreateConversation(account, fromParts[0], false);
- conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
updateLastseen(packet, account, true);
String pgpBody = getPgpBody(packet);
Message finishedMessage;
@@ -42,6 +36,7 @@ public class MessageParser extends AbstractParser implements
Message.STATUS_RECEIVED);
}
finishedMessage.setRemoteMsgId(packet.getId());
+ finishedMessage.markable = isMarkable(packet);
if (conversation.getMode() == Conversation.MODE_MULTI
&& fromParts.length >= 2) {
finishedMessage.setType(Message.TYPE_PRIVATE);
@@ -72,7 +67,7 @@ public class MessageParser extends AbstractParser implements
updateLastseen(packet, account, true);
String body = packet.getBody();
if (body.matches("^\\?OTRv\\d*\\?")) {
- conversation.resetOtrSession();
+ conversation.endOtrIfNeeded();
}
if (!conversation.hasValidOtrSession()) {
if (properlyAddressed) {
@@ -113,13 +108,12 @@ public class MessageParser extends AbstractParser implements
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
return null;
}
- conversation
- .setLatestMarkableMessageId(getMarkableMessageId(packet));
Message finishedMessage = new Message(conversation,
packet.getFrom(), body, Message.ENCRYPTION_OTR,
Message.STATUS_RECEIVED);
finishedMessage.setTime(getTimestamp(packet));
finishedMessage.setRemoteMsgId(packet.getId());
+ finishedMessage.markable = isMarkable(packet);
return finishedMessage;
} catch (Exception e) {
String receivedId = packet.getId();
@@ -161,7 +155,6 @@ public class MessageParser extends AbstractParser implements
status = Message.STATUS_RECEIVED;
}
String pgpBody = getPgpBody(packet);
- conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
Message finishedMessage;
if (pgpBody == null) {
finishedMessage = new Message(conversation, counterPart,
@@ -171,6 +164,7 @@ public class MessageParser extends AbstractParser implements
Message.ENCRYPTION_PGP, status);
}
finishedMessage.setRemoteMsgId(packet.getId());
+ finishedMessage.markable = isMarkable(packet);
if (status == Message.STATUS_RECEIVED) {
finishedMessage.setTrueCounterpart(conversation.getMucOptions()
.getTrueCounterpart(counterPart));
@@ -202,10 +196,24 @@ public class MessageParser extends AbstractParser implements
return null;
}
Element message = forwarded.findChild("message");
- if ((message == null) || (!message.hasChild("body"))) {
+ if (message == null) {
+ return null;
+ }
+ if (!message.hasChild("body")) {
if (status == Message.STATUS_RECEIVED
&& message.getAttribute("from") != null) {
parseNonMessage(message, account);
+ } else if (status == Message.STATUS_SEND
+ && message.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
+ String to = message.getAttribute("to");
+ if (to != null) {
+ Conversation conversation = mXmppConnectionService.find(
+ mXmppConnectionService.getConversations(), account,
+ to.split("/")[0]);
+ if (conversation != null) {
+ mXmppConnectionService.markRead(conversation, false);
+ }
+ }
}
return null;
}
@@ -225,8 +233,6 @@ public class MessageParser extends AbstractParser implements
String[] parts = fullJid.split("/", 2);
Conversation conversation = mXmppConnectionService
.findOrCreateConversation(account, parts[0], false);
- conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
-
String pgpBody = getPgpBody(message);
Message finishedMessage;
if (pgpBody != null) {
@@ -239,6 +245,7 @@ public class MessageParser extends AbstractParser implements
}
finishedMessage.setTime(getTimestamp(message));
finishedMessage.setRemoteMsgId(message.getAttribute("id"));
+ finishedMessage.markable = isMarkable(message);
if (conversation.getMode() == Conversation.MODE_MULTI
&& parts.length >= 2) {
finishedMessage.setType(Message.TYPE_PRIVATE);
@@ -376,12 +383,8 @@ public class MessageParser extends AbstractParser implements
}
}
- private String getMarkableMessageId(Element message) {
- if (message.hasChild("markable", "urn:xmpp:chat-markers:0")) {
- return message.getAttribute("id");
- } else {
- return null;
- }
+ private boolean isMarkable(Element message) {
+ return message.hasChild("markable", "urn:xmpp:chat-markers:0");
}
@Override
@@ -389,8 +392,6 @@ public class MessageParser extends AbstractParser implements
Message message = null;
boolean notify = mXmppConnectionService.getPreferences().getBoolean(
"show_notification", true);
- notify = notify
- && (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > (Config.CARBON_GRACE_PERIOD * 1000);
boolean alwaysNotifyInConference = notify
&& mXmppConnectionService.getPreferences().getBoolean(
"always_notify_in_conference", false);
@@ -404,7 +405,9 @@ public class MessageParser extends AbstractParser implements
if (message != null) {
message.markUnread();
}
- } else if (packet.hasChild("body")) {
+ } else if (packet.hasChild("body")
+ && !(packet.hasChild("x",
+ "http://jabber.org/protocol/muc#user"))) {
message = this.parseChat(packet, account);
if (message != null) {
message.markUnread();
@@ -414,10 +417,11 @@ public class MessageParser extends AbstractParser implements
message = this.parseCarbonMessage(packet, account);
if (message != null) {
if (message.getStatus() == Message.STATUS_SEND) {
- lastCarbonMessageReceived = SystemClock
- .elapsedRealtime();
+ mXmppConnectionService.getNotificationService()
+ .activateGracePeriod();
notify = false;
- mXmppConnectionService.markRead(message.getConversation());
+ mXmppConnectionService.markRead(
+ message.getConversation(), false);
} else {
message.markUnread();
}
@@ -434,8 +438,10 @@ public class MessageParser extends AbstractParser implements
|| NotificationService
.wasHighlightedOrPrivate(message);
} else {
- mXmppConnectionService.markRead(message.getConversation());
- lastCarbonMessageReceived = SystemClock.elapsedRealtime();
+ mXmppConnectionService.markRead(message.getConversation(),
+ false);
+ mXmppConnectionService.getNotificationService()
+ .activateGracePeriod();
notify = false;
}
}
@@ -472,6 +478,9 @@ public class MessageParser extends AbstractParser implements
mXmppConnectionService.databaseBackend.createMessage(message);
}
}
+ if (message.getStatus() == Message.STATUS_RECEIVED && message.bodyContainsDownloadable()) {
+ this.mXmppConnectionService.getHttpConnectionManager().createNewConnection(message);
+ }
notify = notify && !conversation.isMuted();
if (notify) {
mXmppConnectionService.getNotificationService().push(message);
diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java
index 507ebbd2..2c3a7dbc 100644
--- a/src/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/eu/siacs/conversations/parser/PresenceParser.java
@@ -58,6 +58,8 @@ public class PresenceParser extends AbstractParser implements
Presences.parseShow(packet.findChild("show")));
} else if (type.equals("unavailable")) {
account.removePresence(fromParts[1]);
+ mXmppConnectionService.getNotificationService()
+ .deactivateGracePeriod();
}
}
} else {
diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
index 0231c0e7..091c00bd 100644
--- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -11,6 +11,7 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Roster;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -210,6 +211,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
while (cursor.moveToNext()) {
list.add(Account.fromCursor(cursor));
}
+ cursor.close();
return list;
}
@@ -230,9 +232,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
+ Account.TABLENAME + " where not options & (1 <<1)", null);
- cursor.moveToFirst();
- int count = cursor.getInt(0);
- return (count > 0);
+ try {
+ cursor.moveToFirst();
+ int count = cursor.getInt(0);
+ cursor.close();
+ return (count > 0);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ return true; //better safe than sorry
+ }
}
@Override
@@ -258,6 +265,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
while (cursor.moveToNext()) {
roster.initContact(Contact.fromCursor(cursor));
}
+ cursor.close();
}
public void writeRoster(Roster roster) {
diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java
index 2b2aa86e..c8ae657d 100644
--- a/src/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/eu/siacs/conversations/persistance/FileBackend.java
@@ -32,11 +32,11 @@ import android.util.LruCache;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
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 {
@@ -66,11 +66,11 @@ public class FileBackend {
return thumbnailCache;
}
- public JingleFile getJingleFileLegacy(Message message) {
+ public DownloadableFile getJingleFileLegacy(Message message) {
return getJingleFileLegacy(message, true);
}
- public JingleFile getJingleFileLegacy(Message message, boolean decrypted) {
+ public DownloadableFile getJingleFileLegacy(Message message, boolean decrypted) {
Conversation conversation = message.getConversation();
String prefix = context.getFilesDir().getAbsolutePath();
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
@@ -85,14 +85,14 @@ public class FileBackend {
filename = message.getUuid() + ".webp.pgp";
}
}
- return new JingleFile(path + "/" + filename);
+ return new DownloadableFile(path + "/" + filename);
}
- public JingleFile getJingleFile(Message message) {
- return getJingleFile(message, true);
+ public DownloadableFile getJingleFile(Message message) {
+ return getConversationsFile(message, true);
}
- public JingleFile getJingleFile(Message message, boolean decrypted) {
+ public DownloadableFile getConversationsFile(Message message, boolean decrypted) {
StringBuilder filename = new StringBuilder();
filename.append(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath());
@@ -107,7 +107,7 @@ public class FileBackend {
filename.append(".webp.pgp");
}
}
- return new JingleFile(filename.toString());
+ return new DownloadableFile(filename.toString());
}
public Bitmap resize(Bitmap originalBitmap, int size) {
@@ -139,17 +139,17 @@ public class FileBackend {
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
}
- public JingleFile copyImageToPrivateStorage(Message message, Uri image)
+ public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
throws ImageCopyException {
return this.copyImageToPrivateStorage(message, image, 0);
}
- private JingleFile copyImageToPrivateStorage(Message message, Uri image,
+ private DownloadableFile copyImageToPrivateStorage(Message message, Uri image,
int sampleSize) throws ImageCopyException {
try {
InputStream is = context.getContentResolver()
.openInputStream(image);
- JingleFile file = getJingleFile(message);
+ DownloadableFile file = getJingleFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
Bitmap originalBitmap;
@@ -250,7 +250,10 @@ public class FileBackend {
if (!file.exists()) {
file = getJingleFileLegacy(message);
}
- Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath());
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(file, size);
+ Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
+ options);
if (fullsize == null) {
throw new FileNotFoundException();
}
@@ -414,6 +417,17 @@ public class FileBackend {
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(context.getContentResolver()
.openInputStream(image), null, options);
+ return calcSampleSize(options, size);
+ }
+
+ private int calcSampleSize(File image, int size) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(image.getAbsolutePath(), options);
+ return calcSampleSize(options, size);
+ }
+
+ private int calcSampleSize(BitmapFactory.Options options, int size) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
@@ -428,7 +442,6 @@ public class FileBackend {
}
}
return inSampleSize;
-
}
public Uri getJingleFileUri(Message message) {
diff --git a/src/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/eu/siacs/conversations/services/AbstractConnectionManager.java
new file mode 100644
index 00000000..a06be826
--- /dev/null
+++ b/src/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -0,0 +1,24 @@
+package eu.siacs.conversations.services;
+
+
+public class AbstractConnectionManager {
+ protected XmppConnectionService mXmppConnectionService;
+
+ public AbstractConnectionManager(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ public XmppConnectionService getXmppConnectionService() {
+ return this.mXmppConnectionService;
+ }
+
+ public long getAutoAcceptFileSize() {
+ String config = this.mXmppConnectionService.getPreferences().getString(
+ "auto_accept_file_size", "524288");
+ try {
+ return Long.parseLong(config);
+ } catch (NumberFormatException e) {
+ return 524288;
+ }
+ }
+}
diff --git a/src/eu/siacs/conversations/services/NotificationService.java b/src/eu/siacs/conversations/services/NotificationService.java
index 831ce51f..41656707 100644
--- a/src/eu/siacs/conversations/services/NotificationService.java
+++ b/src/eu/siacs/conversations/services/NotificationService.java
@@ -12,10 +12,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
+import android.os.PowerManager;
+import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.Html;
-import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -34,6 +35,8 @@ public class NotificationService {
private Conversation mOpenConversation;
private boolean mIsInForeground;
+ private long mEndGracePeriod = 0L;
+
public NotificationService(XmppConnectionService service) {
this.mXmppConnectionService = service;
this.mNotificationManager = (NotificationManager) service
@@ -41,12 +44,13 @@ public class NotificationService {
}
public synchronized void push(Message message) {
- if (this.mIsInForeground
+
+ PowerManager pm = (PowerManager) mXmppConnectionService
+ .getSystemService(Context.POWER_SERVICE);
+ boolean isScreenOn = pm.isScreenOn();
+ if (this.mIsInForeground && isScreenOn
&& this.mOpenConversation == message.getConversation()) {
- Log.d(Config.LOGTAG,"ignoring notification because foreground and conv matches");
- return; // simply ignore
- } else {
- Log.d(Config.LOGTAG,"pushed new notification");
+ return;
}
String conversationUuid = message.getConversationUuid();
if (notifications.containsKey(conversationUuid)) {
@@ -56,7 +60,8 @@ public class NotificationService {
mList.add(message);
notifications.put(conversationUuid, mList);
}
- updateNotification(!(this.mIsInForeground && this.mOpenConversation == null));
+ updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
+ && !inGracePeriod());
}
public void clear() {
@@ -120,10 +125,11 @@ public class NotificationService {
+ mXmppConnectionService
.getString(R.string.unread_conversations));
StringBuilder names = new StringBuilder();
+ Conversation conversation = null;
for (ArrayList<Message> messages : notifications.values()) {
if (messages.size() > 0) {
- String name = messages.get(0).getConversation()
- .getName();
+ conversation = messages.get(0).getConversation();
+ String name = conversation.getName();
style.addLine(Html.fromHtml("<b>"
+ name
+ "</b> "
@@ -142,6 +148,10 @@ public class NotificationService {
.getString(R.string.unread_conversations));
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
+ if (conversation != null) {
+ mBuilder.setContentIntent(createContentIntent(conversation
+ .getUuid()));
+ }
}
if (notify) {
if (vibrate) {
@@ -153,7 +163,10 @@ public class NotificationService {
mBuilder.setSound(Uri.parse(ringtone));
}
}
- mBuilder.setLights(0xffffffff, 2000, 4000);
+ mBuilder.setDeleteIntent(createDeleteIntent());
+ if (!inGracePeriod()) {
+ mBuilder.setLights(0xffffffff, 2000, 4000);
+ }
Notification notification = mBuilder.build();
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
@@ -178,9 +191,19 @@ public class NotificationService {
return resultPendingIntent;
}
+ private PendingIntent createDeleteIntent() {
+ Intent intent = new Intent(mXmppConnectionService,
+ XmppConnectionService.class);
+ intent.setAction("clear_notification");
+ return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
+ }
+
public static boolean wasHighlightedOrPrivate(Message message) {
String nick = message.getConversation().getMucOptions().getActualNick();
Pattern highlight = generateNickHighlightPattern(nick);
+ if (message.getBody() == null || nick == null) {
+ return false;
+ }
Matcher m = highlight.matcher(message.getBody());
return (m.find() || message.getType() == Message.TYPE_PRIVATE);
}
@@ -203,4 +226,16 @@ public class NotificationService {
this.mIsInForeground = foreground;
}
+ public void activateGracePeriod() {
+ this.mEndGracePeriod = SystemClock.elapsedRealtime()
+ + (Config.CARBON_GRACE_PERIOD * 1000);
+ }
+
+ public void deactivateGracePeriod() {
+ this.mEndGracePeriod = 0L;
+ }
+
+ private boolean inGracePeriod() {
+ return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
+ }
}
diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java
index b83be7fb..5b0d9ea5 100644
--- a/src/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/eu/siacs/conversations/services/XmppConnectionService.java
@@ -34,6 +34,7 @@ import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.generator.MessageGenerator;
import eu.siacs.conversations.generator.PresenceGenerator;
+import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.parser.MessageParser;
import eu.siacs.conversations.parser.PresenceParser;
@@ -90,6 +91,7 @@ public class XmppConnectionService extends Service {
public long startDate;
private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+ public static String ACTION_CLEAR_NOTIFICATION = "clear_notification";
private MemorizingTrustManager mMemorizingTrustManager;
@@ -105,6 +107,7 @@ public class XmppConnectionService extends Service {
private CopyOnWriteArrayList<Conversation> conversations = null;
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
this);
+ private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this);
private OnConversationUpdate mOnConversationUpdate = null;
private int convChangedListenerCount = 0;
@@ -318,14 +321,16 @@ public class XmppConnectionService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if ((intent != null)
- && (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
- mergePhoneContactsWithRoster();
- return START_STICKY;
- } else if ((intent != null)
- && (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) {
- logoutAndSave();
- return START_NOT_STICKY;
+ if (intent != null && intent.getAction() != null) {
+ if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) {
+ mergePhoneContactsWithRoster();
+ return START_STICKY;
+ } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+ logoutAndSave();
+ return START_NOT_STICKY;
+ } else if (intent.getAction().equals(ACTION_CLEAR_NOTIFICATION)) {
+ mNotificationService.clear();
+ }
}
this.wakeLock.acquire();
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
@@ -514,7 +519,8 @@ public class XmppConnectionService extends Service {
MessagePacket packet = null;
boolean saveInDb = true;
boolean send = false;
- if (account.getStatus() == Account.STATUS_ONLINE) {
+ if (account.getStatus() == Account.STATUS_ONLINE
+ && account.getXmppConnection() != null) {
if (message.getType() == Message.TYPE_IMAGE) {
if (message.getPresence() != null) {
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
@@ -564,6 +570,10 @@ public class XmppConnectionService extends Service {
send = true;
}
}
+ if (!account.getXmppConnection().getFeatures().sm()
+ && conv.getMode() != Conversation.MODE_MULTI) {
+ message.setStatus(Message.STATUS_SEND);
+ }
} else {
message.setStatus(Message.STATUS_WAITING);
if (message.getType() == Message.TYPE_TEXT) {
@@ -589,10 +599,6 @@ public class XmppConnectionService extends Service {
}
conv.getMessages().add(message);
- if (!account.getXmppConnection().getFeatures().sm()
- && conv.getMode() != Conversation.MODE_MULTI) {
- message.setStatus(Message.STATUS_SEND);
- }
if (saveInDb) {
if (message.getEncryption() == Message.ENCRYPTION_NONE
|| saveEncryptedMessages()) {
@@ -745,7 +751,7 @@ public class XmppConnectionService extends Service {
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
for (Bookmark bookmark : account.getBookmarks()) {
- storage.addChild(bookmark.toElement());
+ storage.addChild(bookmark);
}
sendIqPacket(account, iqPacket, null);
}
@@ -905,10 +911,12 @@ public class XmppConnectionService extends Service {
public void archiveConversation(Conversation conversation) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
- Bookmark bookmark = conversation.getBookmark();
- if (bookmark != null && bookmark.autojoin()) {
- bookmark.setAutojoin(false);
- pushBookmarks(bookmark.getAccount());
+ if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
+ Bookmark bookmark = conversation.getBookmark();
+ if (bookmark != null && bookmark.autojoin()) {
+ bookmark.setAutojoin(false);
+ pushBookmarks(bookmark.getAccount());
+ }
}
leaveMuc(conversation);
} else {
@@ -967,6 +975,11 @@ public class XmppConnectionService extends Service {
public void setOnConversationListChangedListener(
OnConversationUpdate listener) {
+ if (!isScreenOn()) {
+ Log.d(Config.LOGTAG,"ignoring setOnConversationListChangedListener");
+ return;
+ }
+ this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) {
switchToForeground();
}
@@ -987,6 +1000,11 @@ public class XmppConnectionService extends Service {
}
public void setOnAccountListChangedListener(OnAccountUpdate listener) {
+ if (!isScreenOn()) {
+ Log.d(Config.LOGTAG,"ignoring setOnAccountListChangedListener");
+ return;
+ }
+ this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) {
switchToForeground();
}
@@ -1005,6 +1023,11 @@ public class XmppConnectionService extends Service {
}
public void setOnRosterUpdateListener(OnRosterUpdate listener) {
+ if (!isScreenOn()) {
+ Log.d(Config.LOGTAG,"ignoring setOnRosterUpdateListener");
+ return;
+ }
+ this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) {
switchToForeground();
}
@@ -1052,6 +1075,11 @@ public class XmppConnectionService extends Service {
}
}
}
+
+ private boolean isScreenOn() {
+ PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
+ return pm.isScreenOn();
+ }
public void connectMultiModeConversations(Account account) {
List<Conversation> conversations = getConversations();
@@ -1117,11 +1145,8 @@ public class XmppConnectionService extends Service {
public void providePasswordForMuc(Conversation conversation, String password) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
- if (conversation.getBookmark() != null
- && conversation.getMucOptions().isPasswordChanged()) {
- if (!conversation.getBookmark().autojoin()) {
- conversation.getBookmark().setAutojoin(true);
- }
+ if (conversation.getBookmark() != null) {
+ conversation.getBookmark().setAutojoin(true);
pushBookmarks(conversation.getAccount());
}
databaseBackend.updateConversation(conversation);
@@ -1224,6 +1249,7 @@ public class XmppConnectionService extends Service {
public void updateMessage(Message message) {
databaseBackend.updateMessage(message);
+ updateConversationUi();
}
protected void syncDirtyContacts(Account account) {
@@ -1505,6 +1531,9 @@ public class XmppConnectionService extends Service {
thread.start();
scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2),
false);
+ } else {
+ account.getRoster().clearPresences();
+ account.setXmppConnection(null);
}
}
}).start();
@@ -1530,24 +1559,34 @@ public class XmppConnectionService extends Service {
public boolean markMessage(Account account, String recipient, String uuid,
int status) {
- for (Conversation conversation : getConversations()) {
- if (conversation.getContactJid().equals(recipient)
- && conversation.getAccount().equals(account)) {
- return markMessage(conversation, uuid, status);
+ if (uuid == null) {
+ return false;
+ } else {
+ for (Conversation conversation : getConversations()) {
+ if (conversation.getContactJid().equals(recipient)
+ && conversation.getAccount().equals(account)) {
+ return markMessage(conversation, uuid, status);
+ }
}
+ return false;
}
- return false;
}
public boolean markMessage(Conversation conversation, String uuid,
int status) {
- for (Message message : conversation.getMessages()) {
- if (message.getUuid().equals(uuid)) {
- markMessage(message, status);
- return true;
+ if (uuid == null) {
+ return false;
+ } else {
+ for (Message message : conversation.getMessages()) {
+ if (uuid.equals(message.getUuid())
+ || (message.getStatus() >= Message.STATUS_SEND && uuid
+ .equals(message.getRemoteMsgId()))) {
+ markMessage(message, status);
+ return true;
+ }
}
+ return false;
}
- return false;
}
public void markMessage(Message message, int status) {
@@ -1618,16 +1657,21 @@ public class XmppConnectionService extends Service {
return null;
}
- public void markRead(Conversation conversation) {
- conversation.markRead();
+ public void markRead(Conversation conversation, boolean calledByUi) {
mNotificationService.clear(conversation);
- String id = conversation.popLatestMarkableMessageId();
- if (confirmMessages() && id != null) {
+ String id = conversation.getLatestMarkableMessageId();
+ conversation.markRead();
+ if (confirmMessages() && id != null && calledByUi) {
+ Log.d(Config.LOGTAG, conversation.getAccount().getJid()
+ + ": sending read marker for " + conversation.getName());
Account account = conversation.getAccount();
String to = conversation.getContactJid();
this.sendMessagePacket(conversation.getAccount(),
mMessageGenerator.confirm(account, to, id));
}
+ if (!calledByUi) {
+ updateConversationUi();
+ }
}
public void failWaitingOtrMessages(Conversation conversation) {
@@ -1702,16 +1746,25 @@ public class XmppConnectionService extends Service {
}
public void sendMessagePacket(Account account, MessagePacket packet) {
- account.getXmppConnection().sendMessagePacket(packet);
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendMessagePacket(packet);
+ }
}
public void sendPresencePacket(Account account, PresencePacket packet) {
- account.getXmppConnection().sendPresencePacket(packet);
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendPresencePacket(packet);
+ }
}
public void sendIqPacket(Account account, IqPacket packet,
OnIqPacketReceived callback) {
- account.getXmppConnection().sendIqPacket(packet, callback);
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendIqPacket(packet, callback);
+ }
}
public MessageGenerator getMessageGenerator() {
@@ -1747,7 +1800,7 @@ public class XmppConnectionService extends Service {
for (Account account : getAccounts()) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
Contact contact = account.getRoster()
- .getContactAsShownInRoster(jid);
+ .getContactFromRoster(jid);
if (contact != null) {
contacts.add(contact);
}
@@ -1755,8 +1808,12 @@ public class XmppConnectionService extends Service {
}
return contacts;
}
-
+
public NotificationService getNotificationService() {
return this.mNotificationService;
}
+
+ public HttpConnectionManager getHttpConnectionManager() {
+ return this.mHttpConnectionManager;
+ }
}
diff --git a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 04059d52..deddcad7 100644
--- a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -41,6 +41,7 @@ public class ConferenceDetailsActivity extends XmppActivity {
private ImageButton mEditNickButton;
private TextView mRoleAffiliaton;
private TextView mFullJid;
+ private TextView mAccountJid;
private LinearLayout membersView;
private LinearLayout mMoreDetails;
private Button mInviteButton;
@@ -78,6 +79,7 @@ public class ConferenceDetailsActivity extends XmppActivity {
mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button);
mFullJid = (TextView) findViewById(R.id.muc_jabberid);
membersView = (LinearLayout) findViewById(R.id.muc_members);
+ mAccountJid = (TextView) findViewById(R.id.details_account);
mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details);
mMoreDetails.setVisibility(View.GONE);
mInviteButton = (Button) findViewById(R.id.invite);
@@ -169,36 +171,35 @@ public class ConferenceDetailsActivity extends XmppActivity {
}
protected void registerListener() {
- if (xmppConnectionServiceBound) {
- xmppConnectionService
- .setOnConversationListChangedListener(this.onConvChanged);
- xmppConnectionService.setOnRenameListener(new OnRenameListener() {
+ xmppConnectionService
+ .setOnConversationListChangedListener(this.onConvChanged);
+ 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() {
+ mAccountJid.setText(getString(R.string.using_account,conversation.getAccount().getJid()));
mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48));
setTitle(conversation.getName());
mFullJid.setText(conversation.getContactJid().split("/", 2)[0]);
diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 9d384c60..394aab6e 100644
--- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -39,8 +39,6 @@ import eu.siacs.conversations.utils.UIHelper;
public class ContactDetailsActivity extends XmppActivity {
public static final String ACTION_VIEW_CONTACT = "view_contact";
- protected ContactDetailsActivity activity = this;
-
private Contact contact;
private String accountJid;
@@ -58,8 +56,8 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(DialogInterface dialog, int which) {
- activity.xmppConnectionService.deleteContactOnServer(contact);
- activity.finish();
+ ContactDetailsActivity.this.xmppConnectionService.deleteContactOnServer(contact);
+ ContactDetailsActivity.this.finish();
}
};
@@ -73,14 +71,14 @@ public class ContactDetailsActivity extends XmppActivity {
intent.putExtra(Intents.Insert.IM_PROTOCOL,
CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true);
- activity.startActivityForResult(intent, 0);
+ ContactDetailsActivity.this.startActivityForResult(intent, 0);
}
};
private OnClickListener onBadgeClick = new OnClickListener() {
@Override
public void onClick(View v) {
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ AlertDialog.Builder builder = new AlertDialog.Builder(ContactDetailsActivity.this);
builder.setTitle(getString(R.string.action_add_phone_book));
builder.setMessage(getString(R.string.add_phone_book_text,
contact.getJid()));
@@ -206,7 +204,7 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onValueEdited(String value) {
contact.setServerName(value);
- activity.xmppConnectionService
+ ContactDetailsActivity.this.xmppConnectionService
.pushContactToServer(contact);
populateView();
}
@@ -311,7 +309,7 @@ public class ContactDetailsActivity extends XmppActivity {
} else {
contactJidTv.setText(contact.getJid());
}
- accountJidTv.setText(contact.getAccount().getJid());
+ accountJidTv.setText(getString(R.string.using_account,contact.getAccount().getJid()));
UIHelper.prepareContactBadge(this, badge, contact,
getApplicationContext());
@@ -321,9 +319,11 @@ public class ContactDetailsActivity extends XmppActivity {
}
keys.removeAllViews();
+ boolean hasKeys = false;
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (Iterator<String> iterator = contact.getOtrFingerprints()
.iterator(); iterator.hasNext();) {
+ hasKeys = true;
final String otrFingerprint = iterator.next();
View view = (View) inflater.inflate(R.layout.contact_key, keys,
false);
@@ -344,6 +344,7 @@ public class ContactDetailsActivity extends XmppActivity {
});
}
if (contact.getPgpKeyId() != 0) {
+ hasKeys = true;
View view = (View) inflater.inflate(R.layout.contact_key, keys,
false);
TextView key = (TextView) view.findViewById(R.id.key);
@@ -354,7 +355,7 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(View v) {
- PgpEngine pgp = activity.xmppConnectionService
+ PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService
.getPgpEngine();
if (pgp != null) {
PendingIntent intent = pgp.getIntentForKey(contact);
@@ -372,6 +373,11 @@ public class ContactDetailsActivity extends XmppActivity {
});
keys.addView(view);
}
+ if (hasKeys) {
+ keys.setVisibility(View.VISIBLE);
+ } else {
+ keys.setVisibility(View.GONE);
+ }
}
protected void confirmToDeleteFingerprint(final String fingerprint) {
diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java
index 03cf753d..40739387 100644
--- a/src/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/eu/siacs/conversations/ui/ConversationActivity.java
@@ -59,8 +59,13 @@ public class ConversationActivity extends XmppActivity implements
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;
+ private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
+ private static final String STATE_PANEL_OPEN = "state_panel_open";
- protected SlidingPaneLayout spl;
+ private String mOpenConverstaion = null;
+ private boolean mPanelOpen = true;
+
+ private View mContentView;
private List<Conversation> conversationList = new ArrayList<Conversation>();
private Conversation selectedConversation = null;
@@ -69,7 +74,6 @@ public class ConversationActivity extends XmppActivity implements
private boolean paneShouldBeOpen = true;
private ArrayAdapter<Conversation> listAdapter;
- protected ConversationActivity activity = this;
private Toast prepareImageToast;
private Uri pendingImageUri = null;
@@ -90,18 +94,52 @@ public class ConversationActivity extends XmppActivity implements
return this.listView;
}
- public SlidingPaneLayout getSlidingPaneLayout() {
- return this.spl;
- }
-
public boolean shouldPaneBeOpen() {
return paneShouldBeOpen;
}
+ public void showConversationsOverview() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.openPane();
+ }
+ }
+
+ public void hideConversationsOverview() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.closePane();
+ }
+ }
+
+ public boolean isConversationsOverviewHideable() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ return mSlidingPaneLayout.isSlideable();
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isConversationsOverviewVisable() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ return mSlidingPaneLayout.isOpen();
+ } else {
+ return true;
+ }
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mOpenConverstaion = savedInstanceState.getString(
+ STATE_OPEN_CONVERSATION, null);
+ mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
+ }
+
setContentView(R.layout.fragment_conversations_overview);
listView = (ListView) findViewById(R.id.list);
@@ -122,70 +160,79 @@ public class ConversationActivity extends XmppActivity implements
setSelectedConversation(conversationList.get(position));
swapConversationFragment();
} else {
- spl.closePane();
+ hideConversationsOverview();
}
}
});
- spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
- spl.setParallaxDistance(150);
- spl.setShadowResource(R.drawable.es_slidingpane_shadow);
- spl.setSliderFadeColor(0);
- spl.setPanelSlideListener(new PanelSlideListener() {
+ mContentView = findViewById(R.id.content_view_spl);
+ if (mContentView == null) {
+ mContentView = findViewById(R.id.content_view_ll);
+ }
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.setParallaxDistance(150);
+ mSlidingPaneLayout
+ .setShadowResource(R.drawable.es_slidingpane_shadow);
+ mSlidingPaneLayout.setSliderFadeColor(0);
+ mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
- @Override
- public void onPanelOpened(View arg0) {
- paneShouldBeOpen = true;
- ActionBar ab = getActionBar();
- if (ab != null) {
- ab.setDisplayHomeAsUpEnabled(false);
- ab.setHomeButtonEnabled(false);
- ab.setTitle(R.string.app_name);
- }
- invalidateOptionsMenu();
- hideKeyboard();
- if (xmppConnectionServiceBound) {
- xmppConnectionService.getNotificationService().setOpenConversation(null);
+ @Override
+ public void onPanelOpened(View arg0) {
+ paneShouldBeOpen = true;
+ ActionBar ab = getActionBar();
+ if (ab != null) {
+ ab.setDisplayHomeAsUpEnabled(false);
+ ab.setHomeButtonEnabled(false);
+ ab.setTitle(R.string.app_name);
+ }
+ invalidateOptionsMenu();
+ hideKeyboard();
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.getNotificationService()
+ .setOpenConversation(null);
+ }
}
- }
- @Override
- public void onPanelClosed(View arg0) {
- paneShouldBeOpen = false;
- if ((conversationList.size() > 0)
- && (getSelectedConversation() != null)) {
- openConversation(getSelectedConversation());
- if (!getSelectedConversation().isRead()) {
- xmppConnectionService
- .markRead(getSelectedConversation());
- listView.invalidateViews();
+ @Override
+ public void onPanelClosed(View arg0) {
+ paneShouldBeOpen = false;
+ if ((conversationList.size() > 0)
+ && (getSelectedConversation() != null)) {
+ openConversation(getSelectedConversation());
+ if (!getSelectedConversation().isRead()) {
+ xmppConnectionService.markRead(
+ getSelectedConversation(), true);
+ listView.invalidateViews();
+ }
}
}
- }
- @Override
- public void onPanelSlide(View arg0, float arg1) {
- // TODO Auto-generated method stub
+ @Override
+ public void onPanelSlide(View arg0, float arg1) {
+ // TODO Auto-generated method stub
- }
- });
+ }
+ });
+ }
}
-
+
public void openConversation(Conversation conversation) {
ActionBar ab = getActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeButtonEnabled(true);
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE
- || activity.useSubjectToIdentifyConference()) {
+ || ConversationActivity.this.useSubjectToIdentifyConference()) {
ab.setTitle(getSelectedConversation().getName());
} else {
- ab.setTitle(getSelectedConversation()
- .getContactJid().split("/")[0]);
+ ab.setTitle(getSelectedConversation().getContactJid()
+ .split("/")[0]);
}
}
invalidateOptionsMenu();
if (xmppConnectionServiceBound) {
- xmppConnectionService.getNotificationService().setOpenConversation(conversation);
+ xmppConnectionService.getNotificationService().setOpenConversation(
+ conversation);
}
}
@@ -206,7 +253,8 @@ public class ConversationActivity extends XmppActivity implements
.findItem(R.id.action_invite);
MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute);
- if ((spl.isOpen() && (spl.isSlideable()))) {
+ if (isConversationsOverviewVisable()
+ && isConversationsOverviewHideable()) {
menuArchive.setVisible(false);
menuMucDetails.setVisible(false);
menuContactDetails.setVisible(false);
@@ -216,7 +264,7 @@ public class ConversationActivity extends XmppActivity implements
menuClearHistory.setVisible(false);
menuMute.setVisible(false);
} else {
- menuAdd.setVisible(!spl.isSlideable());
+ menuAdd.setVisible(!isConversationsOverviewHideable());
if (this.getSelectedConversation() != null) {
if (this.getSelectedConversation().getLatestMessage()
.getEncryption() != Message.ENCRYPTION_NONE) {
@@ -325,7 +373,7 @@ public class ConversationActivity extends XmppActivity implements
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
- spl.openPane();
+ showConversationsOverview();
return true;
} else if (item.getItemId() == R.id.action_add) {
startActivity(new Intent(this, StartConversationActivity.class));
@@ -377,7 +425,7 @@ public class ConversationActivity extends XmppActivity implements
public void endConversation(Conversation conversation) {
conversation.setStatus(Conversation.STATUS_ARCHIVED);
paneShouldBeOpen = true;
- spl.openPane();
+ showConversationsOverview();
xmppConnectionService.archiveConversation(conversation);
if (conversationList.size() > 0) {
setSelectedConversation(conversationList.get(0));
@@ -401,7 +449,7 @@ public class ConversationActivity extends XmppActivity implements
@Override
public void onClick(DialogInterface dialog, int which) {
- activity.xmppConnectionService
+ ConversationActivity.this.xmppConnectionService
.clearConversationHistory(conversation);
if (endConversationCheckBox.isChecked()) {
endConversation(conversation);
@@ -536,6 +584,8 @@ public class ConversationActivity extends XmppActivity implements
+ (durations[which] * 1000);
}
conversation.setMutedTill(till);
+ ConversationActivity.this.xmppConnectionService.databaseBackend
+ .updateConversation(conversation);
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
.findFragmentByTag("conversation");
if (selectedFragment != null) {
@@ -563,8 +613,8 @@ public class ConversationActivity extends XmppActivity implements
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- if (!spl.isOpen()) {
- spl.openPane();
+ if (!isConversationsOverviewVisable()) {
+ showConversationsOverview();
return false;
}
}
@@ -612,73 +662,83 @@ public class ConversationActivity extends XmppActivity implements
xmppConnectionService.removeOnConversationListChangedListener();
xmppConnectionService.removeOnAccountListChangedListener();
xmppConnectionService.removeOnRosterUpdateListener();
- xmppConnectionService.getNotificationService().setOpenConversation(null);
+ xmppConnectionService.getNotificationService().setOpenConversation(
+ null);
}
super.onStop();
}
@Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ Conversation conversation = getSelectedConversation();
+ if (conversation != null) {
+ savedInstanceState.putString(STATE_OPEN_CONVERSATION,
+ conversation.getUuid());
+ }
+ savedInstanceState.putBoolean(STATE_PANEL_OPEN,
+ isConversationsOverviewVisable());
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ @Override
void onBackendConnected() {
this.registerListener();
- if (conversationList.size() == 0) {
- updateConversationList();
+ updateConversationList();
+
+ if (xmppConnectionService.getAccounts().size() == 0) {
+ startActivity(new Intent(this, EditAccountActivity.class));
+ } else if (conversationList.size() <= 0) {
+ startActivity(new Intent(this, StartConversationActivity.class));
+ finish();
+ } else if (mOpenConverstaion != null) {
+ selectConversationByUuid(mOpenConverstaion);
+ paneShouldBeOpen = mPanelOpen;
+ if (paneShouldBeOpen) {
+ showConversationsOverview();
+ }
+ swapConversationFragment();
+ mOpenConverstaion = null;
+ } else if (getIntent() != null
+ && VIEW_CONVERSATION.equals(getIntent().getType())) {
+ String uuid = (String) getIntent().getExtras().get(CONVERSATION);
+ String text = getIntent().getExtras().getString(TEXT, null);
+ selectConversationByUuid(uuid);
+ paneShouldBeOpen = false;
+ swapConversationFragment().setText(text);
+ setIntent(null);
+ } else {
+ showConversationsOverview();
+ ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (selectedFragment != null) {
+ selectedFragment.onBackendConnected();
+ } else {
+ pendingImageUri = null;
+ setSelectedConversation(conversationList.get(0));
+ swapConversationFragment();
+ }
}
- if (getSelectedConversation() != null && pendingImageUri != null) {
+ if (pendingImageUri != null) {
attachImageToConversation(getSelectedConversation(),
pendingImageUri);
pendingImageUri = null;
- } else {
- pendingImageUri = null;
}
+ ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
+ }
- if ((getIntent().getAction() != null)
- && (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
- if (getIntent().getType().equals(
- ConversationActivity.VIEW_CONVERSATION)) {
- handledViewIntent = true;
-
- String convToView = (String) getIntent().getExtras().get(
- CONVERSATION);
-
- for (int i = 0; i < conversationList.size(); ++i) {
- if (conversationList.get(i).getUuid().equals(convToView)) {
- setSelectedConversation(conversationList.get(i));
- }
- }
- paneShouldBeOpen = false;
- String text = getIntent().getExtras().getString(TEXT, null);
- swapConversationFragment().setText(text);
- }
- } else {
- if (xmppConnectionService.getAccounts().size() == 0) {
- startActivity(new Intent(this, EditAccountActivity.class));
- } else if (conversationList.size() <= 0) {
- // add no history
- startActivity(new Intent(this, StartConversationActivity.class));
- finish();
- } else {
- spl.openPane();
- // find currently loaded fragment
- ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
- .findFragmentByTag("conversation");
- if (selectedFragment != null) {
- selectedFragment.onBackendConnected();
- } else {
- setSelectedConversation(conversationList.get(0));
- swapConversationFragment();
- }
- ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
+ private void selectConversationByUuid(String uuid) {
+ for (int i = 0; i < conversationList.size(); ++i) {
+ if (conversationList.get(i).getUuid().equals(uuid)) {
+ setSelectedConversation(conversationList.get(i));
}
}
}
public void registerListener() {
- if (xmppConnectionServiceBound) {
- xmppConnectionService.setOnConversationListChangedListener(this);
- xmppConnectionService.setOnAccountListChangedListener(this);
- xmppConnectionService.setOnRosterUpdateListener(this);
- }
+ xmppConnectionService.setOnConversationListChangedListener(this);
+ xmppConnectionService.setOnAccountListChangedListener(this);
+ xmppConnectionService.setOnRosterUpdateListener(this);
}
@Override
@@ -691,6 +751,7 @@ public class ConversationActivity extends XmppActivity implements
.findFragmentByTag("conversation");
if (selectedFragment != null) {
selectedFragment.hideSnackbar();
+ selectedFragment.updateMessages();
}
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
pendingImageUri = data.getData();
@@ -792,7 +853,7 @@ public class ConversationActivity extends XmppActivity implements
@Override
public void userInputRequried(PendingIntent pi,
Message message) {
- activity.runIntent(pi,
+ ConversationActivity.this.runIntent(pi,
ConversationActivity.REQUEST_SEND_MESSAGE);
}
diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java
index 064b00be..75f1dac8 100644
--- a/src/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/eu/siacs/conversations/ui/ConversationFragment.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.ui;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
import net.java.otr4j.session.SessionStatus;
import eu.siacs.conversations.R;
@@ -71,6 +72,10 @@ public class ConversationFragment extends Fragment {
private boolean messagesLoaded = false;
private IntentSender askForPassphraseIntent = null;
+
+
+ private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<Message>();
+ private boolean mDecryptJobRunning = false;
private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
@@ -132,6 +137,14 @@ public class ConversationFragment extends Fragment {
}
};
+ private OnClickListener joinMuc = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.xmppConnectionService.joinMuc(conversation);
+ }
+ };
+
private OnClickListener enterPassword = new OnClickListener() {
@Override
@@ -170,6 +183,7 @@ public class ConversationFragment extends Fragment {
conversation, timestamp);
messageList.clear();
messageList.addAll(conversation.getMessages());
+ updateStatusMessages();
messageListAdapter.notifyDataSetChanged();
if (size != 0) {
messagesLoaded = true;
@@ -245,9 +259,7 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- if (activity.getSlidingPaneLayout().isSlideable()) {
- activity.getSlidingPaneLayout().closePane();
- }
+ activity.hideConversationsOverview();
}
});
mEditMessage.setOnEditorActionListener(mEditorActionListener);
@@ -349,6 +361,7 @@ public class ConversationFragment extends Fragment {
@Override
public void onStop() {
+ mDecryptJobRunning = false;
super.onStop();
if (this.conversation != null) {
this.conversation.setNextMessage(mEditMessage.getText().toString());
@@ -376,9 +389,9 @@ public class ConversationFragment extends Fragment {
int position = mEditMessage.length();
Editable etext = mEditMessage.getText();
Selection.setSelection(etext, position);
- if (activity.getSlidingPaneLayout().isSlideable()) {
+ if (activity.isConversationsOverviewHideable()) {
if (!activity.shouldPaneBeOpen()) {
- activity.getSlidingPaneLayout().closePane();
+ activity.hideConversationsOverview();
activity.openConversation(conversation);
}
}
@@ -388,34 +401,6 @@ public class ConversationFragment extends Fragment {
updateMessages();
}
- private void decryptMessage(Message message) {
- PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
- if (engine != null) {
- engine.decrypt(message, new UiCallback<Message>() {
-
- @Override
- public void userInputRequried(PendingIntent pi, Message message) {
- askForPassphraseIntent = pi.getIntentSender();
- showSnackbar(R.string.openpgp_messages_found,
- R.string.decrypt, clickToDecryptListener);
- }
-
- @Override
- public void success(Message message) {
- activity.xmppConnectionService.databaseBackend
- .updateMessage(message);
- updateMessages();
- }
-
- @Override
- public void error(int error, Message message) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- // updateMessages();
- }
- });
- }
- }
-
public void updateMessages() {
if (getView() == null) {
return;
@@ -431,6 +416,8 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
conversation.setMutedTill(0);
+ activity.xmppConnectionService.databaseBackend
+ .updateConversation(conversation);
updateMessages();
}
});
@@ -452,10 +439,12 @@ public class ConversationFragment extends Fragment {
if ((message.getEncryption() == Message.ENCRYPTION_PGP)
&& ((message.getStatus() == Message.STATUS_RECEIVED) || (message
.getStatus() == Message.STATUS_SEND))) {
- decryptMessage(message);
- break;
+ if (!mEncryptedMessages.contains(message)) {
+ mEncryptedMessages.add(message);
+ }
}
}
+ decryptNext();
this.messageList.clear();
if (this.conversation.getMessages().size() == 0) {
messagesLoaded = false;
@@ -486,6 +475,18 @@ public class ConversationFragment extends Fragment {
showSnackbar(R.string.conference_requires_password,
R.string.enter_password, enterPassword);
break;
+ case MucOptions.ERROR_BANNED:
+ showSnackbar(R.string.conference_banned,
+ R.string.leave, leaveMuc);
+ break;
+ case MucOptions.ERROR_MEMBERS_ONLY:
+ showSnackbar(R.string.conference_members_only,
+ R.string.leave, leaveMuc);
+ break;
+ case MucOptions.KICKED_FROM_ROOM:
+ showSnackbar(R.string.conference_kicked, R.string.join,
+ joinMuc);
+ break;
default:
break;
}
@@ -494,18 +495,51 @@ public class ConversationFragment extends Fragment {
getActivity().invalidateOptionsMenu();
updateChatMsgHint();
if (!activity.shouldPaneBeOpen()) {
- activity.xmppConnectionService.markRead(conversation);
+ activity.xmppConnectionService.markRead(conversation, true);
activity.updateConversationList();
}
this.updateSendButton();
}
}
+ private void decryptNext() {
+ Message next = this.mEncryptedMessages.peek();
+ PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
+
+ if (next!=null && engine != null && !mDecryptJobRunning) {
+ mDecryptJobRunning = true;
+ engine.decrypt(next, new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message message) {
+ mDecryptJobRunning = false;
+ askForPassphraseIntent = pi.getIntentSender();
+ showSnackbar(R.string.openpgp_messages_found,
+ R.string.decrypt, clickToDecryptListener);
+ }
+
+ @Override
+ public void success(Message message) {
+ mDecryptJobRunning = false;
+ mEncryptedMessages.remove();
+ activity.xmppConnectionService.updateMessage(message);
+ }
+
+ @Override
+ public void error(int error, Message message) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+ mDecryptJobRunning = false;
+ mEncryptedMessages.remove();
+ activity.updateConversationList();
+ activity.xmppConnectionService.updateConversationUi();
+ }
+ });
+ }
+ }
+
private void messageSent() {
int size = this.messageList.size();
- if (size >= 1 && this.messagesView.getLastVisiblePosition() != size - 1) {
- messagesView.setSelection(size - 1);
- }
+ messagesView.setSelection(size - 1);
mEditMessage.setText("");
updateChatMsgHint();
}
diff --git a/src/eu/siacs/conversations/ui/EditAccountActivity.java b/src/eu/siacs/conversations/ui/EditAccountActivity.java
index bc946115..1543d740 100644
--- a/src/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -1,8 +1,12 @@
package eu.siacs.conversations.ui;
import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipboardManager;
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;
@@ -10,9 +14,12 @@ import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
+import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.RelativeLayout;
import android.widget.TextView;
+import android.widget.Toast;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
@@ -37,7 +44,8 @@ public class EditAccountActivity extends XmppActivity {
private TextView mServerInfoPep;
private TextView mSessionEst;
private TextView mOtrFingerprint;
- private TextView mOtrFingerprintHeadline;
+ private RelativeLayout mOtrFingerprintBox;
+ private ImageButton mOtrFingerprintToClipboardButton;
private String jidToEdit;
private Account mAccount;
@@ -48,6 +56,12 @@ public class EditAccountActivity extends XmppActivity {
@Override
public void onClick(View v) {
+ if (mAccount != null
+ && mAccount.getStatus() == Account.STATUS_DISABLED) {
+ mAccount.setOption(Account.OPTION_DISABLED, false);
+ xmppConnectionService.updateAccount(mAccount);
+ return;
+ }
if (!Validator.isValidJid(mAccountJid.getText().toString())) {
mAccountJid.setError(getString(R.string.invalid_jid));
mAccountJid.requestFocus();
@@ -157,6 +171,25 @@ public class EditAccountActivity extends XmppActivity {
}
};
private KnownHostsAdapter mKnownHostsAdapter;
+ private TextWatcher mTextWatcher = new TextWatcher() {
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ updateSaveButton();
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ };
protected void finishInitialSetup(final Avatar avatar) {
runOnUiThread(new Runnable() {
@@ -197,6 +230,11 @@ public class EditAccountActivity extends XmppActivity {
this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting);
+ } else if (mAccount != null
+ && mAccount.getStatus() == Account.STATUS_DISABLED) {
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ this.mSaveButton.setText(R.string.enable);
} else {
this.mSaveButton.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor());
@@ -204,6 +242,10 @@ public class EditAccountActivity extends XmppActivity {
if (mAccount != null
&& mAccount.getStatus() == Account.STATUS_ONLINE) {
this.mSaveButton.setText(R.string.save);
+ if (!accountInfoEdited()) {
+ this.mSaveButton.setEnabled(false);
+ this.mSaveButton.setTextColor(getSecondaryTextColor());
+ }
} else {
this.mSaveButton.setText(R.string.connect);
}
@@ -213,12 +255,21 @@ public class EditAccountActivity extends XmppActivity {
}
}
+ protected boolean accountInfoEdited() {
+ return (!this.mAccount.getJid().equals(
+ this.mAccountJid.getText().toString()))
+ || (!this.mAccount.getPassword().equals(
+ this.mPassword.getText().toString()));
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_account);
this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
+ this.mAccountJid.addTextChangedListener(this.mTextWatcher);
this.mPassword = (EditText) findViewById(R.id.account_password);
+ this.mPassword.addTextChangedListener(this.mTextWatcher);
this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm);
this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
this.mStats = (LinearLayout) findViewById(R.id.stats);
@@ -227,7 +278,8 @@ public class EditAccountActivity extends XmppActivity {
this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
- this.mOtrFingerprintHeadline = (TextView) findViewById(R.id.otr_fingerprint_headline);
+ this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
+ this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
this.mSaveButton = (Button) findViewById(R.id.save_button);
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
@@ -255,7 +307,7 @@ public class EditAccountActivity extends XmppActivity {
this.jidToEdit = getIntent().getStringExtra("jid");
if (this.jidToEdit != null) {
this.mRegisterNew.setVisibility(View.GONE);
- getActionBar().setTitle(R.string.mgmt_account_edit);
+ getActionBar().setTitle(jidToEdit);
} else {
getActionBar().setTitle(R.string.action_add_account);
}
@@ -324,15 +376,29 @@ public class EditAccountActivity extends XmppActivity {
} else {
this.mServerInfoPep.setText(R.string.server_info_unavailable);
}
- String fingerprint = this.mAccount
+ final String fingerprint = this.mAccount
.getOtrFingerprint(xmppConnectionService);
if (fingerprint != null) {
- this.mOtrFingerprintHeadline.setVisibility(View.VISIBLE);
- this.mOtrFingerprint.setVisibility(View.VISIBLE);
+ this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
this.mOtrFingerprint.setText(fingerprint);
+ this.mOtrFingerprintToClipboardButton
+ .setVisibility(View.VISIBLE);
+ this.mOtrFingerprintToClipboardButton
+ .setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ if (OtrFingerprintToClipBoard(fingerprint)) {
+ Toast.makeText(
+ EditAccountActivity.this,
+ R.string.toast_message_otr_fingerprint,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
} else {
- this.mOtrFingerprint.setVisibility(View.GONE);
- this.mOtrFingerprintHeadline.setVisibility(View.GONE);
+ this.mOtrFingerprintBox.setVisibility(View.GONE);
}
} else {
if (this.mAccount.errorStatus()) {
@@ -343,4 +409,15 @@ public class EditAccountActivity extends XmppActivity {
this.mStats.setVisibility(View.GONE);
}
}
+
+ private boolean OtrFingerprintToClipBoard(String fingerprint) {
+ ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ String label = getResources().getString(R.string.otr_fingerprint);
+ if (mClipBoardManager != null) {
+ ClipData mClipData = ClipData.newPlainText(label, fingerprint);
+ mClipBoardManager.setPrimaryClip(mClipData);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java
index ca17eb0d..afe9e06e 100644
--- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -24,8 +24,6 @@ import android.widget.ListView;
public class ManageAccountActivity extends XmppActivity {
- protected ManageAccountActivity activity = this;
-
protected Account selectedAccount = null;
protected List<Account> accountList = new ArrayList<Account>();
@@ -72,7 +70,7 @@ public class ManageAccountActivity extends XmppActivity {
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
- activity.getMenuInflater().inflate(R.menu.manageaccounts_context, menu);
+ ManageAccountActivity.this.getMenuInflater().inflate(R.menu.manageaccounts_context, menu);
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
this.selectedAccount = accountList.get(acmi.position);
if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
@@ -181,7 +179,7 @@ public class ManageAccountActivity extends XmppActivity {
}
private void publishOpenPGPPublicKey(Account account) {
- if (activity.hasPgp()) {
+ if (ManageAccountActivity.this.hasPgp()) {
announcePgp(account, null);
} else {
this.showInstallPgpDialog();
@@ -189,7 +187,7 @@ public class ManageAccountActivity extends XmppActivity {
}
private void deleteAccount(final Account account) {
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ AlertDialog.Builder builder = new AlertDialog.Builder(ManageAccountActivity.this);
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));
diff --git a/src/eu/siacs/conversations/ui/StartConversationActivity.java b/src/eu/siacs/conversations/ui/StartConversationActivity.java
index 1a5fba95..a1a2d4c2 100644
--- a/src/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -359,8 +359,8 @@ public class StartConversationActivity extends XmppActivity {
jid.setError(getString(R.string.contact_already_exists));
} else {
xmppConnectionService.createContact(contact);
- switchToConversation(contact);
dialog.dismiss();
+ switchToConversation(contact);
}
} else {
jid.setError(getString(R.string.invalid_jid));
@@ -403,6 +403,10 @@ public class StartConversationActivity extends XmppActivity {
String conferenceJid = jid.getText().toString();
Account account = xmppConnectionService
.findAccountByJid(accountJid);
+ if (account == null) {
+ dialog.dismiss();
+ return;
+ }
if (bookmarkCheckBox.isChecked()) {
if (account.hasBookmarkFor(conferenceJid)) {
jid.setError(getString(R.string.bookmark_already_exists));
@@ -421,6 +425,7 @@ public class StartConversationActivity extends XmppActivity {
xmppConnectionService
.joinMuc(conversation);
}
+ dialog.dismiss();
switchToConversation(conversation);
}
} else {
@@ -430,6 +435,7 @@ public class StartConversationActivity extends XmppActivity {
if (!conversation.getMucOptions().online()) {
xmppConnectionService.joinMuc(conversation);
}
+ dialog.dismiss();
switchToConversation(conversation);
}
} else {
diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java
index 518f7b0b..cd77557c 100644
--- a/src/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/eu/siacs/conversations/ui/XmppActivity.java
@@ -59,6 +59,7 @@ public abstract class XmppActivity extends Activity {
protected int mPrimaryTextColor;
protected int mSecondaryTextColor;
+ protected int mSecondaryBackgroundColor;
protected int mColorRed;
protected int mColorOrange;
protected int mColorGreen;
@@ -207,6 +208,8 @@ public abstract class XmppActivity extends Activity {
mColorOrange = getResources().getColor(R.color.orange);
mColorGreen = getResources().getColor(R.color.green);
mPrimaryColor = getResources().getColor(R.color.primary);
+ mSecondaryBackgroundColor = getResources().getColor(
+ R.color.secondarybackground);
if (getPreferences().getBoolean("use_larger_font", false)) {
setTheme(R.style.ConversationsTheme_LargerText);
}
@@ -519,6 +522,10 @@ public abstract class XmppActivity extends Activity {
return this.mPrimaryColor;
}
+ public int getSecondaryBackgroundColor() {
+ return this.mSecondaryBackgroundColor;
+ }
+
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private Message message = null;
diff --git a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index 7b470faa..f74856b0 100644
--- a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -40,9 +40,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
Conversation conv = getItem(position);
if (this.activity instanceof ConversationActivity) {
ConversationActivity activity = (ConversationActivity) this.activity;
- if (!activity.getSlidingPaneLayout().isSlideable()) {
+ if (!activity.isConversationsOverviewHideable()) {
if (conv == activity.getSelectedConversation()) {
- view.setBackgroundColor(0xffdddddd);
+ view.setBackgroundColor(activity
+ .getSecondaryBackgroundColor());
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index 035d18c5..b09f97fc 100644
--- a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.ui.adapter;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.HashMap;
import java.util.List;
@@ -9,6 +11,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Message.ImageParams;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.utils.UIHelper;
import android.content.Context;
@@ -102,12 +105,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
if (message.getType() == Message.TYPE_IMAGE) {
- String[] fileParams = message.getBody().split(",");
- try {
- long size = Long.parseLong(fileParams[0]);
- filesize = size / 1024 + " KB";
- } catch (NumberFormatException e) {
- filesize = "0 KB";
+ ImageParams params = message.getImageParams();
+ if (params.size != 0) {
+ filesize = params.size / 1024 + " KB";
}
}
switch (message.getMergedStatus()) {
@@ -230,10 +230,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setVisibility(View.VISIBLE);
if (message.getBody() != null) {
if (message.getType() != Message.TYPE_PRIVATE) {
- String body = Config.PARSE_EMOTICONS ? UIHelper
- .transformAsciiEmoticons(message.getMergedBody())
- : message.getMergedBody();
- viewHolder.messageBody.setText(body);
+ if (message.getType() == Message.TYPE_IMAGE) {
+ String orign = message.getImageParams().origin;
+ if (orign!=null) {
+ viewHolder.messageBody.setText(orign);
+ } else {
+ viewHolder.messageBody.setText(message.getBody());
+ }
+ } else {
+ String body = Config.PARSE_EMOTICONS ? UIHelper
+ .transformAsciiEmoticons(message.getMergedBody())
+ : message.getMergedBody();
+ viewHolder.messageBody.setText(body);
+ }
} else {
String privateMarker;
if (message.getStatus() <= Message.STATUS_RECEIVED) {
@@ -275,23 +284,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
- String[] fileParams = message.getBody().split(",");
- if (fileParams.length == 3) {
- double target = metrics.density * 288;
- int w = Integer.parseInt(fileParams[1]);
- int h = Integer.parseInt(fileParams[2]);
- int scalledW;
- int scalledH;
- if (w <= h) {
- scalledW = (int) (w / ((double) h / target));
- scalledH = (int) target;
- } else {
- scalledW = (int) target;
- scalledH = (int) (h / ((double) w / target));
- }
- viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
- scalledW, scalledH));
+ ImageParams params = message.getImageParams();
+ double target = metrics.density * 288;
+ int scalledW;
+ int scalledH;
+ if (params.width <= params.height) {
+ scalledW = (int) (params.width / ((double) params.height / target));
+ scalledH = (int) target;
+ } else {
+ scalledW = (int) target;
+ scalledH = (int) (params.height / ((double) params.width / target));
}
+ viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
+ scalledW, scalledH));
activity.loadBitmap(message, viewHolder.image);
viewHolder.image.setOnClickListener(new OnClickListener() {
@@ -390,7 +395,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
item.getConversation().getContact(), getContext()));
- viewHolder.contact_picture.setAlpha(128);
+ viewHolder.contact_picture.setAlpha(0.5f);
viewHolder.contact_picture
.setOnClickListener(new OnClickListener() {
@@ -417,7 +422,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder = (ViewHolder) view.getTag();
}
- if (type == STATUS || type == NULL) {
+ if (type == STATUS) {
+ return view;
+ }
+ if (type == NULL) {
+ if (position == getCount() - 1) {
+ view.getLayoutParams().height = 1;
+ } else {
+ view.getLayoutParams().height = 0;
+
+ }
+ view.setLayoutParams(view.getLayoutParams());
return view;
}
@@ -471,6 +486,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (item.getType() == Message.TYPE_IMAGE) {
if (item.getStatus() == Message.STATUS_RECEIVING) {
displayInfoMessage(viewHolder, R.string.receiving_image);
+ } else if (item.getStatus() == Message.STATUS_RECEIVED_CHECKING) {
+ displayInfoMessage(viewHolder, R.string.checking_image);
+ } else if (item.getStatus() == Message.STATUS_RECEPTION_FAILED) {
+ displayTextMessage(viewHolder, item);
} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
@@ -480,10 +499,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
@Override
public void onClick(View v) {
- Downloadable downloadable = item
- .getDownloadable();
- if (downloadable != null) {
- downloadable.start();
+ if (!startDonwloadable(item)) {
+ activity.xmppConnectionService.markMessage(
+ item,
+ Message.STATUS_RECEPTION_FAILED);
}
}
});
@@ -524,6 +543,28 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return view;
}
+ public boolean startDonwloadable(Message message) {
+ Downloadable downloadable = message.getDownloadable();
+ if (downloadable != null) {
+ downloadable.start();
+ return true;
+ } else {
+ ImageParams params = message.getImageParams();
+ if (params.origin != null) {
+ try {
+ URL url = new URL(params.origin);
+ activity.xmppConnectionService.getHttpConnectionManager()
+ .createNewConnection(message, url);
+ return true;
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
private static class ViewHolder {
protected LinearLayout message_box;
diff --git a/src/eu/siacs/conversations/utils/DNSHelper.java b/src/eu/siacs/conversations/utils/DNSHelper.java
index fd3b1953..c51a75ac 100644
--- a/src/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/eu/siacs/conversations/utils/DNSHelper.java
@@ -30,17 +30,17 @@ public class DNSHelper {
String dns[] = client.findDNS();
if (dns != null) {
- // we have a list of DNS servers, let's go
for (String dnsserver : dns) {
InetAddress ip = InetAddress.getByName(dnsserver);
Bundle b = queryDNS(host, ip);
if (b.containsKey("name")) {
return b;
+ } else if (b.containsKey("error")
+ && "nosrv".equals(b.getString("error", null))) {
+ return b;
}
}
}
-
- // fallback
return queryDNS(host, InetAddress.getByName("8.8.8.8"));
}
@@ -164,10 +164,8 @@ public class DNSHelper {
}
} catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG, "timeout during dns");
namePort.putString("error", "timeout");
} catch (Exception e) {
- Log.d(Config.LOGTAG, "unhandled exception in sub project");
namePort.putString("error", "unhandled");
}
return namePort;
diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java
index b055e35a..54409be4 100644
--- a/src/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -166,8 +166,14 @@ public class XmppConnection implements Runnable {
+ ":" + srvRecordPort);
socket = new Socket(srvRecordServer, srvRecordPort);
}
- } else {
+ } else if (namePort.containsKey("error")
+ && "nosrv".equals(namePort.getString("error", null))) {
socket = new Socket(account.getServer(), 5222);
+ } else {
+ Log.d(Config.LOGTAG, account.getJid()
+ + ": timeout in DNS resolution");
+ changeStatus(Account.STATUS_OFFLINE);
+ return;
}
OutputStream out = socket.getOutputStream();
tagWriter.setOutputStream(out);
@@ -307,7 +313,8 @@ public class XmppConnection implements Runnable {
} catch (NumberFormatException e) {
}
- changeStatus(Account.STATUS_ONLINE);
+ sendInitialPing();
+
} else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag);
AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
@@ -348,6 +355,22 @@ public class XmppConnection implements Runnable {
}
}
+ private void sendInitialPing() {
+ Log.d(Config.LOGTAG, account.getJid() + ": sending intial ping");
+ IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
+ iq.setFrom(account.getFullJid());
+ iq.addChild("ping", "urn:xmpp:ping");
+ this.sendIqPacket(iq, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Log.d(Config.LOGTAG, account.getJid()
+ + ": online with resource " + account.getResource());
+ changeStatus(Account.STATUS_ONLINE);
+ }
+ });
+ }
+
private Element processPacket(Tag currentTag, int packetType)
throws XmlPullParserException, IOException {
Element element;
@@ -372,8 +395,11 @@ public class XmppConnection implements Runnable {
while (!nextTag.isEnd(element.getName())) {
if (!nextTag.isNo()) {
Element child = tagReader.readElement(nextTag);
- if ((packetType == PACKET_IQ)
- && ("jingle".equals(child.getName()))) {
+ String type = currentTag.getAttribute("type");
+ if (packetType == PACKET_IQ
+ && "jingle".equals(child.getName())
+ && ("set".equalsIgnoreCase(type) || "get"
+ .equalsIgnoreCase(type))) {
element = new JinglePacket();
element.setAttributes(currentTag.getAttributes());
}
@@ -410,7 +436,9 @@ public class XmppConnection implements Runnable {
}
packetCallbacks.remove(packet.getId());
- } else if (this.unregisteredIqListener != null) {
+ } else if ((packet.getType() == IqPacket.TYPE_GET || packet
+ .getType() == IqPacket.TYPE_SET)
+ && this.unregisteredIqListener != null) {
this.unregisteredIqListener.onIqPacketReceived(account, packet);
}
}
@@ -677,7 +705,7 @@ public class XmppConnection implements Runnable {
if (bindListener != null) {
bindListener.onBind(account);
}
- changeStatus(Account.STATUS_ONLINE);
+ sendInitialPing();
} else {
disconnect(true);
}
@@ -882,8 +910,7 @@ public class XmppConnection implements Runnable {
}
public void disconnect(boolean force) {
- changeStatus(Account.STATUS_OFFLINE);
- Log.d(Config.LOGTAG, "disconnecting");
+ Log.d(Config.LOGTAG, account.getJid()+": disconnecting");
try {
if (force) {
socket.close();
@@ -901,6 +928,7 @@ public class XmppConnection implements Runnable {
Thread.sleep(100);
}
tagWriter.writeTag(Tag.end("stream:stream"));
+ socket.close();
} catch (IOException e) {
Log.d(Config.LOGTAG,
"io exception during disconnect");
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index 92fdbe0b..4853d75c 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -16,6 +16,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
@@ -54,7 +55,7 @@ public class JingleConnection implements Downloadable {
private String transportId;
private Element fileOffer;
- private JingleFile file = null;
+ private DownloadableFile file = null;
private String contentName;
private String contentCreator;
@@ -83,7 +84,7 @@ public class JingleConnection implements Downloadable {
final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
@Override
- public void onFileTransmitted(JingleFile file) {
+ public void onFileTransmitted(DownloadableFile file) {
if (responder.equals(account.getFullJid())) {
sendSuccess();
if (acceptedAutomatically) {
@@ -323,7 +324,7 @@ public class JingleConnection implements Downloadable {
.push(message);
}
this.file = this.mXmppConnectionService.getFileBackend()
- .getJingleFile(message, false);
+ .getConversationsFile(message, false);
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
byte[] key = conversation.getSymmetricKey();
if (key == null) {
@@ -355,7 +356,7 @@ public class JingleConnection implements Downloadable {
if (message.getType() == Message.TYPE_IMAGE) {
content.setTransportId(this.transportId);
this.file = this.mXmppConnectionService.getFileBackend()
- .getJingleFile(message, false);
+ .getConversationsFile(message, false);
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
Conversation conversation = this.message.getConversation();
this.mXmppConnectionService.renewSymmetricKey(conversation);
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
index 79090af6..bb360204 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -10,16 +10,14 @@ import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-public class JingleConnectionManager {
-
- private XmppConnectionService xmppConnectionService;
-
+public class JingleConnectionManager extends AbstractConnectionManager {
private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>();
private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>();
@@ -28,7 +26,7 @@ public class JingleConnectionManager {
private SecureRandom random = new SecureRandom();
public JingleConnectionManager(XmppConnectionService service) {
- this.xmppConnectionService = service;
+ super(service);
}
public void deliverPacket(Account account, JinglePacket packet) {
@@ -68,10 +66,6 @@ public class JingleConnectionManager {
this.connections.remove(connection);
}
- public XmppConnectionService getXmppConnectionService() {
- return this.xmppConnectionService;
- }
-
public void getPrimaryCandidate(Account account,
final OnPrimaryCandidateFound listener) {
if (!this.primaryCandidates.containsKey(account.getJid())) {
@@ -128,16 +122,6 @@ public class JingleConnectionManager {
return new BigInteger(50, random).toString(32);
}
- public long getAutoAcceptFileSize() {
- String config = this.xmppConnectionService.getPreferences().getString(
- "auto_accept_file_size", "524288");
- try {
- return Long.parseLong(config);
- } catch (NumberFormatException e) {
- return 524288;
- }
- }
-
public void deliverIbbPacket(Account account, IqPacket packet) {
String sid = null;
Element payload = null;
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
deleted file mode 100644
index 9253814b..00000000
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package eu.siacs.conversations.xmpp.jingle;
-
-import java.io.File;
-import java.security.Key;
-
-import javax.crypto.spec.SecretKeySpec;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.CryptoHelper;
-import android.util.Log;
-
-public class JingleFile extends File {
-
- private static final long serialVersionUID = 2247012619505115863L;
-
- private long expectedSize = 0;
- private String sha1sum;
- private Key aeskey;
-
- public JingleFile(String path) {
- super(path);
- }
-
- public long getSize() {
- return super.length();
- }
-
- public long getExpectedSize() {
- if (this.aeskey != null) {
- return (this.expectedSize / 16 + 1) * 16;
- } else {
- return this.expectedSize;
- }
- }
-
- public void setExpectedSize(long size) {
- this.expectedSize = size;
- }
-
- public String getSha1Sum() {
- return this.sha1sum;
- }
-
- public void setSha1Sum(String sum) {
- this.sha1sum = sum;
- }
-
- public void setKey(byte[] key) {
- if (key.length >= 32) {
- byte[] secretKey = new byte[32];
- System.arraycopy(key, 0, secretKey, 0, 32);
- this.aeskey = new SecretKeySpec(secretKey, "AES");
- } else if (key.length >= 16) {
- byte[] secretKey = new byte[16];
- System.arraycopy(key, 0, secretKey, 0, 16);
- this.aeskey = new SecretKeySpec(secretKey, "AES");
- } else {
- Log.d(Config.LOGTAG, "weird key");
- }
- Log.d(Config.LOGTAG,
- "using aes key "
- + CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
- }
-
- public Key getKey() {
- return this.aeskey;
- }
-}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
index c5498075..e5016935 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.xmpp.jingle;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -10,6 +9,7 @@ import java.util.Arrays;
import android.util.Base64;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -26,7 +26,7 @@ public class JingleInbandTransport extends JingleTransport {
private boolean established = false;
- private JingleFile file;
+ private DownloadableFile file;
private InputStream fileInputStream = null;
private OutputStream fileOutputStream;
@@ -77,7 +77,7 @@ public class JingleInbandTransport extends JingleTransport {
}
@Override
- public void receive(JingleFile file,
+ public void receive(DownloadableFile file,
OnFileTransmissionStatusChanged callback) {
this.onFileTransmissionStatusChanged = callback;
this.file = file;
@@ -86,7 +86,7 @@ public class JingleInbandTransport extends JingleTransport {
digest.reset();
file.getParentFile().mkdirs();
file.createNewFile();
- this.fileOutputStream = getOutputStream(file);
+ this.fileOutputStream = file.createOutputStream();
if (this.fileOutputStream == null) {
callback.onFileTransferAborted();
return;
@@ -100,20 +100,18 @@ public class JingleInbandTransport extends JingleTransport {
}
@Override
- public void send(JingleFile file, OnFileTransmissionStatusChanged callback) {
+ public void send(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
this.onFileTransmissionStatusChanged = callback;
this.file = file;
try {
this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset();
- fileInputStream = this.getInputStream(file);
+ fileInputStream = this.file.createInputStream();
if (fileInputStream == null) {
callback.onFileTransferAborted();
return;
}
this.sendNextBlock();
- } catch (FileNotFoundException e) {
- callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index 63f5a507..f1dd1e51 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
@@ -10,6 +10,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.utils.CryptoHelper;
public class JingleSocks5Transport extends JingleTransport {
@@ -86,7 +87,7 @@ public class JingleSocks5Transport extends JingleTransport {
}
- public void send(final JingleFile file,
+ public void send(final DownloadableFile file,
final OnFileTransmissionStatusChanged callback) {
new Thread(new Runnable() {
@@ -96,7 +97,7 @@ public class JingleSocks5Transport extends JingleTransport {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- fileInputStream = getInputStream(file);
+ fileInputStream = file.createInputStream();
if (fileInputStream == null) {
callback.onFileTransferAborted();
return;
@@ -132,7 +133,7 @@ public class JingleSocks5Transport extends JingleTransport {
}
- public void receive(final JingleFile file,
+ public void receive(final DownloadableFile file,
final OnFileTransmissionStatusChanged callback) {
new Thread(new Runnable() {
@@ -145,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport {
socket.setSoTimeout(30000);
file.getParentFile().mkdirs();
file.createNewFile();
- OutputStream fileOutputStream = getOutputStream(file);
+ OutputStream fileOutputStream = file.createOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
return;
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
index 07dc8ecc..1374e61c 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
@@ -1,88 +1,13 @@
package eu.siacs.conversations.xmpp.jingle;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.CipherInputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-
-import eu.siacs.conversations.Config;
-
-import android.util.Log;
+import eu.siacs.conversations.entities.DownloadableFile;
public abstract class JingleTransport {
public abstract void connect(final OnTransportConnected callback);
- public abstract void receive(final JingleFile file,
+ public abstract void receive(final DownloadableFile file,
final OnFileTransmissionStatusChanged callback);
- public abstract void send(final JingleFile file,
+ public abstract void send(final DownloadableFile file,
final OnFileTransmissionStatusChanged callback);
-
- private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
- 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
-
- protected InputStream getInputStream(JingleFile file)
- throws FileNotFoundException {
- if (file.getKey() == null) {
- return new FileInputStream(file);
- } else {
- try {
- IvParameterSpec ips = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, file.getKey(), ips);
- Log.d(Config.LOGTAG, "opening encrypted input stream");
- return new CipherInputStream(new FileInputStream(file), cipher);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
- return null;
- } catch (NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
- return null;
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
- return null;
- }
- }
- }
-
- protected OutputStream getOutputStream(JingleFile file)
- throws FileNotFoundException {
- if (file.getKey() == null) {
- return new FileOutputStream(file);
- } else {
- try {
- IvParameterSpec ips = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.DECRYPT_MODE, file.getKey(), ips);
- Log.d(Config.LOGTAG, "opening encrypted output stream");
- return new CipherOutputStream(new FileOutputStream(file),
- cipher);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
- return null;
- } catch (NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
- return null;
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
- return null;
- }
- }
- }
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java
index 19fd4d97..e45e7441 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java
@@ -1,7 +1,9 @@
package eu.siacs.conversations.xmpp.jingle;
+import eu.siacs.conversations.entities.DownloadableFile;
+
public interface OnFileTransmissionStatusChanged {
- public void onFileTransmitted(JingleFile file);
+ public void onFileTransmitted(DownloadableFile file);
public void onFileTransferAborted();
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
index d19e6dfd..bcadbe77 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
@@ -1,7 +1,7 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.jingle.JingleFile;
public class Content extends Element {
@@ -25,7 +25,7 @@ public class Content extends Element {
this.transportId = sid;
}
- public void setFileOffer(JingleFile actualFile, boolean otr) {
+ public void setFileOffer(DownloadableFile actualFile, boolean otr) {
Element description = this.addChild("description",
"urn:xmpp:jingle:apps:file-transfer:3");
Element offer = description.addChild("offer");