diff options
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 @@ -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 Binary files differnew file mode 100644 index 00000000..a8ab6176 --- /dev/null +++ b/art/logo.png 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 Binary files differnew file mode 100644 index 00000000..22327391 --- /dev/null +++ b/res/drawable-hdpi/ic_action_copy.png diff --git a/res/drawable-mdpi/ic_action_copy.png b/res/drawable-mdpi/ic_action_copy.png Binary files differnew file mode 100644 index 00000000..71348202 --- /dev/null +++ b/res/drawable-mdpi/ic_action_copy.png diff --git a/res/drawable-xhdpi/ic_action_copy.png b/res/drawable-xhdpi/ic_action_copy.png Binary files differnew file mode 100644 index 00000000..5ddf1513 --- /dev/null +++ b/res/drawable-xhdpi/ic_action_copy.png diff --git a/res/drawable-xxhdpi/ic_action_copy.png b/res/drawable-xxhdpi/ic_action_copy.png Binary files differnew file mode 100644 index 00000000..a0508df8 --- /dev/null +++ b/res/drawable-xxhdpi/ic_action_copy.png 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">正在發送…</string> + <string name="encrypted_message">正在解密訊息中,請稍候…</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">接收圖片文件中,請稍候…</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">提供中…</string> + <string name="waiting">等待中…</string> + <string name="no_pgp_key">找不到 OpenPGP 鑰匙</string> + <string name="contact_has_no_pgp_key">Conversations 不能將你的訊息加密,因為聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> + <string name="no_pgp_keys">找不到多條 OpenPGP 鑰匙</string> + <string name="contacts_have_no_pgp_keys">Conversations 不能將你的訊息加密,因為多位聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> + <string name="encrypted_message_received"><i>已收到加密訊息,點擊進行解密和查看。</i></string> + <string name="encrypted_image_received"><i>已收到加密圖片,點擊進行解密和查看。</i></string> + <string name="image_file"><i>已收到圖片,點擊查看</i></string> + <string name="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">自動接收小於 … 的文件</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">發佈中…</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"); |