Merge branch 'development'

This commit is contained in:
iNPUTmice 2014-10-10 10:52:21 +02:00
commit 15c05dc3c3
68 changed files with 1690 additions and 654 deletions

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.siacs.conversations" package="eu.siacs.conversations"
android:versionCode="28" android:versionCode="31"
android:versionName="0.7.2" > android:versionName="0.7.3" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="14"
@ -40,7 +40,6 @@
<activity <activity
android:name="eu.siacs.conversations.ui.ConversationActivity" android:name="eu.siacs.conversations.ui.ConversationActivity"
android:configChanges="orientation|screenSize"
android:label="@string/title_activity_conversations" android:label="@string/title_activity_conversations"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="stateHidden" > android:windowSoftInputMode="stateHidden" >
@ -63,6 +62,14 @@
<data android:scheme="imto" /> <data android:scheme="imto" />
<data android:host="jabber" /> <data android:host="jabber" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="xmpp" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name="eu.siacs.conversations.ui.SettingsActivity" android:name="eu.siacs.conversations.ui.SettingsActivity"

View file

@ -1,5 +1,10 @@
###Changelog ###Changelog
####Version 0.7.3
* revised tablet ui
* internal rewrites
* bug fixes
####Version 0.7.2 ####Version 0.7.2
* show full timestamp in messages * show full timestamp in messages
* brought back option to use JID to identify conferences * brought back option to use JID to identify conferences

View file

@ -69,6 +69,7 @@ These XEPs are - as of now:
* [Ilia Rostovtsev](https://github.com/qooob) (Russian) * [Ilia Rostovtsev](https://github.com/qooob) (Russian)
* [Jelmer Vernooij](https://github.com/jelmer) (Dutch) * [Jelmer Vernooij](https://github.com/jelmer) (Dutch)
* [Anders Sandblad](https://github.com/andersruneson) (Swedish) * [Anders Sandblad](https://github.com/andersruneson) (Swedish)
* [Aizaz AZ](http://www.linkedin.com/in/aizazhaider) (Chinese)
##FAQ ##FAQ
###General ###General
@ -81,7 +82,7 @@ The more convenient way - which not only gives you automatic updates but also
supports the further development of Conversations - is to buy the App in the Google supports the further development of Conversations - is to buy the App in the Google
[Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations). [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations).
####I don't have a Google Account but I would still like to make a contribution ####I don't have a Google Account but I would still like to make a contribution
I accept donations over PayPal and BitCoin. For donations via PayPal you can use the email address donate@siacs.eu or the button below. I accept donations over PayPal, BitCoin and Flattr. For donations via PayPal you can use the email address donate@siacs.eu or the button below.
[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL) [![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL)
@ -91,6 +92,9 @@ transfer (SEPA).
My Bitcoin Address is: 1NxSU1YxYzJVDpX1rcESAA3NJki7kRgeeu 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)
####How do I create an account? ####How do I create an account?
XMPP like email for example is a federated protocol which means that there is XMPP like email for example is a federated protocol which means that there is
not one company you can create your 'official xmpp account' with but there are not one company you can create your 'official xmpp account' with but there are

108
art/ic_action_copy.svg Normal file
View file

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg4066"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="ic_action_copy.svg">
<defs
id="defs4068" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.54117647"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.67"
inkscape:cx="51.750573"
inkscape:cy="57.547291"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
borderlayer="false"
inkscape:window-width="1035"
inkscape:window-height="853"
inkscape:window-x="369"
inkscape:window-y="3"
inkscape:window-maximized="0" />
<metadata
id="metadata4071">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-924.36218)">
<rect
ry="0"
height="91.708199"
width="71.625328"
stroke-miterlimit="4"
y="952.36743"
x="42.730034"
id="rect10"
style="fill:none;stroke:#000000;stroke-width:8.6679945;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.54117647;stroke-dasharray:none"
inkscape:transform-center-x="-21.391573"
inkscape:transform-center-y="28.294015" />
<path
style="fill:#000000;fill-opacity:0.5411765;fill-rule:evenodd;stroke:#000000;stroke-width:0.41999999999999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117649999999995;stroke-miterlimit:4;stroke-dasharray:none"
d="m 20.552281,933.36985 0,0.0209 -0.128276,0 -0.399078,99.83215 0.213792,0 0,0.1463 13.212333,-0.021 0.05701,-8.1392 -4.076297,0.011 0.327814,-84.87039 58.436429,0 0.0285,3.427 11.10293,0 -0.0855,-9.25707 -0.057,0 0,-1.1493 -78.63263,0 z"
id="rect12-6"
inkscape:connector-curvature="0" />
<rect
height="4.7259107"
width="37.242958"
y="967.49921"
x="50.137043"
id="rect12"
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.23799089px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
<rect
style="fill:#000000;fill-opacity:0.54117647000000002;fill-rule:evenodd;stroke:#000000;stroke-width:0.274;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647000000002;stroke-miterlimit:4;stroke-dasharray:none"
id="rect4272"
x="50.137043"
y="982.49921"
width="49.452484"
height="4.7259107" />
<rect
height="4.7259107"
width="43.542446"
y="997.49921"
x="50.137043"
id="rect4274"
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.2573325px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
<rect
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.25050664px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647"
id="rect4276"
x="50.137043"
y="1012.4992"
width="41.263123"
height="4.7259107" />
<rect
height="4.7259107"
width="49.397911"
y="1027.4993"
x="50.137043"
id="rect4278"
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.27408957px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,6 +1,6 @@
#!/bin/env ruby #!/bin/env ruby
resolutions={'mdpi'=> 1, 'hdpi' => 1.5, 'xhdpi' => 2, 'xxhdpi' => 3} 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], 'ic_action_copy.svg' => ['ic_action_copy', 32] }
images.each do |source, result| images.each do |source, result|
resolutions.each do |name, factor| resolutions.each do |name, factor|
size = factor * result[1] size = factor * result[1]

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,11 +1,11 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent" >
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="324dp" android:layout_width="300dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/primarybackground" android:background="@color/primarybackground"
android:orientation="vertical" > android:orientation="vertical" >

View file

@ -1,5 +1,5 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent" >

View file

@ -1,11 +1,11 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent" >
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp" android:layout_width="400dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/primarybackground" android:background="@color/primarybackground"
android:orientation="vertical" > android:orientation="vertical" >

View file

@ -1,12 +1,14 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/slidingpanelayout" android:id="@+id/content_view_ll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dp"
android:layout_width="288dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/primarybackground" android:background="@color/primarybackground"
android:orientation="vertical" > android:orientation="vertical" >
@ -21,10 +23,10 @@
<LinearLayout <LinearLayout
android:id="@+id/selected_conversation" 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_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" > android:orientation="vertical" >
</LinearLayout> </LinearLayout>
</android.support.v4.widget.SlidingPaneLayout> </LinearLayout>

View file

@ -180,13 +180,38 @@
android:textSize="?attr/TextSizeHeadline" android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <RelativeLayout
android:id="@+id/otr_fingerprint"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginTop="8dp" android:layout_marginTop="8dp">
android:textSize="?attr/TextSizeBody" <LinearLayout
android:typeface="monospace" /> 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"
android:layout_centerVertical="true">
<TextView
android:id="@+id/otr_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace" />
</LinearLayout>
<ImageButton
android:id="@+id/action_copy_to_clipboard"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="?android:selectableItemBackground"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:padding="4dp"
android:scaleType="fitXY"
android:src="@drawable/ic_action_copy"
android:visibility="invisible" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -11,7 +11,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="8dp" android:paddingTop="8dp"
android:text="Jabber ID" android:text="@string/account_settings_jabber_id"
android:textColor="@color/primarytext" android:textColor="@color/primarytext"
android:textSize="?attr/TextSizeHeadline" /> android:textSize="?attr/TextSizeHeadline" />

View file

@ -37,7 +37,7 @@
android:layout_toLeftOf="@+id/textSendButton" android:layout_toLeftOf="@+id/textSendButton"
android:background="@color/primarybackground" android:background="@color/primarybackground"
android:ems="10" android:ems="10"
android:imeOptions="flagNoExtractUi" android:imeOptions="flagNoExtractUi|actionSend"
android:inputType="textShortMessage|textMultiLine|textCapSentences" android:inputType="textShortMessage|textMultiLine|textCapSentences"
android:minHeight="48dp" android:minHeight="48dp"
android:minLines="1" android:minLines="1"

View file

@ -1,5 +1,5 @@
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent" >

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" > android:layout_height="0dp"
android:background="#00000000">
</RelativeLayout> </RelativeLayout>

View file

@ -54,7 +54,7 @@
android:text="@string/download_image" android:text="@string/download_image"
android:visibility="gone" /> android:visibility="gone" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
@ -65,12 +65,10 @@
android:layout_width="?attr/TextSizeInfo" android:layout_width="?attr/TextSizeInfo"
android:layout_height="?attr/TextSizeInfo" android:layout_height="?attr/TextSizeInfo"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:src="@drawable/ic_secure_indicator"
android:layout_marginRight="4sp" android:layout_marginRight="4sp"
android:alpha="0.54"/> android:alpha="0.54"
android:gravity="center_vertical"
android:src="@drawable/ic_secure_indicator" />
<TextView <TextView
android:id="@+id/message_time" android:id="@+id/message_time"

View file

@ -63,27 +63,25 @@
android:textColor="@color/secondarytext" android:textColor="@color/secondarytext"
android:textSize="?attr/TextSizeInfo" /> android:textSize="?attr/TextSizeInfo" />
<ImageView <ImageView
android:id="@+id/security_indicator" android:id="@+id/security_indicator"
android:layout_width="?attr/TextSizeInfo" android:layout_width="?attr/TextSizeInfo"
android:layout_height="?attr/TextSizeInfo" android:layout_height="?attr/TextSizeInfo"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:src="@drawable/ic_secure_indicator"
android:layout_marginLeft="4sp" android:layout_marginLeft="4sp"
android:alpha="0.54"/> android:alpha="0.54"
android:gravity="center_vertical"
android:src="@drawable/ic_secure_indicator" />
<ImageView <ImageView
android:id="@+id/indicator_received" android:id="@+id/indicator_received"
android:layout_width="?attr/TextSizeInfo" android:layout_width="?attr/TextSizeInfo"
android:layout_height="?attr/TextSizeInfo" android:layout_height="?attr/TextSizeInfo"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:src="@drawable/ic_received_indicator"
android:layout_marginLeft="4sp" android:layout_marginLeft="4sp"
android:alpha="0.54"/> android:alpha="0.54"
android:gravity="center_vertical"
android:src="@drawable/ic_received_indicator" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -20,5 +20,12 @@
<item>524288</item> <item>524288</item>
<item>1048576</item> <item>1048576</item>
</string-array> </string-array>
<string-array name="mute_options_descriptions">
<item>30 Minuten</item>
<item>eine Stunde</item>
<item>2 Stunden</item>
<item>8 Stunden</item>
<item>bis auf Widerruf</item>
</string-array>
</resources> </resources>

View file

@ -259,5 +259,11 @@
<string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string> <string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string>
<string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string> <string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string>
<string name="pref_expert_options_other">Sonstiges</string> <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> </resources>

View file

@ -254,5 +254,16 @@
<string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string> <string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string>
<string name="pref_use_larger_font">Incrementar tamaño de fuente</string> <string name="pref_use_larger_font">Incrementar tamaño de fuente</string>
<string name="pref_use_larger_font_summary">Usar fuentes grandes en toda la aplicación</string> <string name="pref_use_larger_font_summary">Usar fuentes grandes en toda la aplicación</string>
<string name="pref_use_send_button_to_indicate_status">Botón enviar indica estado</string>
<string name="pref_use_indicate_received">Solicitar entrega de mensaje</string>
<string name="pref_use_indicate_received_summary">Cuando el contacto reciba el mensaje será indicado con una marca verde. Cuidado, esto podría no funcionar en todos los casos.</string>
<string name="pref_use_send_button_to_indicate_status_summary">El color del botón enviar indica el estado del contacto</string>
<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> </resources>

View file

@ -26,8 +26,9 @@
<item>2 ordu</item> <item>2 ordu</item>
<item>8 ordu</item> <item>8 ordu</item>
<item>abisatu arte</item> <item>abisatu arte</item>
</string-array> </string-array>
<integer-array name="mute_options_durations">
<integer-array name="mute_options_durations">
<item>1800</item> <item>1800</item>
<item>3600</item> <item>3600</item>
<item>7200</item> <item>7200</item>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Conversations</string>
<string name="action_settings">設定</string>
<string name="action_add">新對話</string>
<string name="action_accounts">管理帳戶</string>
<string name="action_end_conversation">結束對話</string>
<string name="action_contact_details">聯絡人詳情</string>
<string name="action_secure">安全對話</string>
<string name="action_add_account">新增帳戶</string>
<string name="action_edit_contact">編輯姓名</string>
<string name="action_add_phone_book">新增到手機通訊錄</string>
<string name="action_delete_contact">從列表中刪除</string>
<string name="title_activity_manage_accounts">管理帳戶</string>
<string name="title_activity_conference_details">群組詳情</string>
<string name="title_activity_contact_details">聯絡人詳情</string>
<string name="title_activity_conversations">對話</string>
<string name="title_activity_sharewith">分享對話</string>
<string name="title_activity_start_conversation">開始對話</string>
<string name="title_activity_choose_contact">選擇聯絡人</string>
<string name="just_now">剛剛</string>
<string name="minute_ago">1 分鐘前</string>
<string name="minutes_ago">%d 分鐘前</string>
<string name="unread_conversations">未讀對話</string>
<string name="sending">正在發送&#8230;</string>
<string name="encrypted_message">正在解密訊息中,請稍候&#8230;</string>
<string name="nick_in_use">該用戶名稱已被使用</string>
<string name="admin">管理員</string>
<string name="owner">擁有人</string>
<string name="moderator">版主</string>
<string name="participant">成員</string>
<string name="visitor">訪客</string>
<string name="remove_contact_text">你確定要將 %s 從聯絡人清單中移除嗎?與該聯絡人的對話將不會被清除。</string>
<string name="remove_bookmark_text">你確定要將 %s 從書籤清單中移除嗎?與該聯絡人的對話將不會被清除。</string>
<string name="register_account">在伺服器上註冊新帳戶</string>
<string name="share_with">分享</string>
<string name="start_conversation">開始對話</string>
<string name="invite_contact">邀請聯絡人</string>
<string name="contacts">聯絡人</string>
<string name="cancel">取消</string>
<string name="add">新增</string>
<string name="edit">編輯</string>
<string name="delete">刪除</string>
<string name="save">儲存</string>
<string name="ok">好的</string>
<string name="crash_report_title">Conversations 停止運行</string>
<string name="crash_report_message">發送「堆疊追蹤」給 Conversations 的開發人員能幫助改進本程式。\n<b>警告:</b> 你的 XMPP 帳戶將被用作發送有關訊息之用。</string>
<string name="send_now">現在發送</string>
<string name="send_never">不再詢問</string>
<string name="problem_connecting_to_account">無法連接至帳戶</string>
<string name="problem_connecting_to_accounts">無法連接至多個帳戶</string>
<string name="touch_to_fix">點擊此處管理帳戶。</string>
<string name="attach_file">附件</string>
<string name="not_in_roster">該聯絡人不在你的聯絡人清單上,需要加為聯絡人嗎?</string>
<string name="add_contact">新增聯絡人</string>
<string name="send_failed">傳遞失敗</string>
<string name="send_rejected">拒絕</string>
<string name="receiving_image">接收圖片文件中,請稍候&#8230;</string>
<string name="preparing_image">準備傳輸圖片</string>
<string name="action_clear_history">清除歷史記錄</string>
<string name="clear_conversation_history">清除對話記錄</string>
<string name="clear_histor_msg">你確定要刪除該對話中所有訊息嗎?\n\n<b>警告:</b> 這將不會影響其他設備或伺服器儲存的訊息。</string>
<string name="delete_messages">刪除訊息</string>
<string name="also_end_conversation">之後結束這對話</string>
<string name="choose_presence">選擇狀態訊息</string>
<string name="send_plain_text_message">發送純文字訊息</string>
<string name="send_otr_message">發送 OTR 加密訊息</string>
<string name="send_pgp_message">發送 OpenPGP 加密訊息</string>
<string name="your_nick_has_been_changed">用戶名稱修改成功</string>
<string name="download_image">下載圖片</string>
<string name="image_offered_for_download"><i>可供下載的圖像文件</i></string>
<string name="send_unencrypted">不加密發送</string>
<string name="decryption_failed">解密失敗,可能是私鑰不正確。</string>
<string name="openkeychain_required">OpenKeychain</string>
<string name="openkeychain_required_long">Conversations 使用一個名為 <b>OpenKeychain</b> 的第三方程式來加密、解碼訊息以及管理您的公鑰。\n\nOpenKeychain 以 GPLv3 釋出,並可在 F-Droid 和 Google Play 上下載。\n\n<small>(之後請重新啟動 Conversations。)</small></string>
<string name="restart">重新啟動</string>
<string name="install">安裝</string>
<string name="offering">提供中&#8230;</string>
<string name="waiting">等待中&#8230;</string>
<string name="no_pgp_key">找不到 OpenPGP 鑰匙</string>
<string name="contact_has_no_pgp_key">Conversations 不能將你的訊息加密,因為聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string>
<string name="no_pgp_keys">找不到多條 OpenPGP 鑰匙</string>
<string name="contacts_have_no_pgp_keys">Conversations 不能將你的訊息加密,因為多位聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string>
<string name="encrypted_message_received"><i>已收到加密訊息,點擊進行解密和查看。</i></string>
<string name="encrypted_image_received"><i>已收到加密圖片,點擊進行解密和查看。</i></string>
<string name="image_file"><i>已收到圖片,點擊查看</i></string>
<string name="pref_general">一般</string>
<string name="pref_xmpp_resource">XMPP 資源</string>
<string name="pref_xmpp_resource_summary">客戶端標示名稱</string>
<string name="pref_accept_files">接收文件</string>
<string name="pref_accept_files_summary">自動接收小於 &#8230; 的文件</string>
<string name="pref_notification_settings">通知設定</string>
<string name="pref_notifications">通知</string>
<string name="pref_notifications_summary">收到新訊息時通知</string>
<string name="pref_vibrate">震動</string>
<string name="pref_vibrate_summary">收到新訊息時震動</string>
<string name="pref_sound">聲音</string>
<string name="pref_sound_summary">收到新訊息時播放鈴聲</string>
<string name="pref_conference_notifications">群組通知</string>
<string name="pref_conference_notifications_summary">當有新訊息時總是通知,而不是被標記時才通知</string>
<string name="pref_notification_grace_period">通知限期</string>
<string name="pref_notification_grace_period_summary">收到副本後,關閉通知一小段時間</string>
<string name="pref_advanced_options">進階選項</string>
<string name="pref_never_send_crash">總是不發送故障報告</string>
<string name="pref_never_send_crash_summary">發送「堆疊追蹤」給 Conversations 的開發人員能幫助改進本程式</string>
<string name="pref_confirm_messages">確認訊息</string>
<string name="pref_confirm_messages_summary">讓你的聯絡人知道你已收到及閱讀訊息</string>
<string name="pref_ui_options">介面選項</string>
<string name="openpgp_error">OpenKeychain 回報了一個錯誤</string>
<string name="error_decrypting_file">解密文件時出現 I/O 錯誤</string>
<string name="accept">接受</string>
<string name="error">發生了一個錯誤</string>
<string name="pref_grant_presence_updates">同意更新狀態訊息</string>
<string name="pref_grant_presence_updates_summary">預先更新狀態訊息並關注聯絡人的狀態訊息</string>
<string name="subscriptions">關注</string>
<string name="your_account">你的帳戶</string>
<string name="keys">鑰匙</string>
<string name="send_presence_updates">發送狀態訊息</string>
<string name="receive_presence_updates">接收狀態訊息</string>
<string name="ask_for_presence_updates">關注狀態訊息</string>
<string name="attach_choose_picture">選擇圖片</string>
<string name="attach_take_picture">拍照</string>
<string name="preemptively_grant">預先同意關注請求</string>
<string name="error_not_an_image_file">您選擇的文件不是圖片</string>
<string name="error_compressing_image">轉換圖片時發生錯誤</string>
<string name="error_file_not_found">找不到文件</string>
<string name="error_io_exception">一般的 I/O 錯誤。是存儲空間不足嗎?</string>
<string name="error_security_exception_during_image_copy">你用來選擇圖片的 app 沒有給予足夠權限我們去讀取文件。\n\n<small>請使用另一文件管理器來選擇圖片</small></string>
<string name="account_status_unknown">未知</string>
<string name="account_status_disabled">暫時停用</string>
<string name="account_status_online">在線</string>
<string name="account_status_connecting">連接中\u2026</string>
<string name="account_status_offline">離線</string>
<string name="account_status_unauthorized">未授權</string>
<string name="account_status_not_found">未找到伺服器</string>
<string name="account_status_no_internet">未連接網絡</string>
<string name="account_status_regis_fail">註冊失敗</string>
<string name="account_status_regis_conflict">該用戶名稱已被使用</string>
<string name="account_status_regis_success">註冊完成</string>
<string name="account_status_regis_not_sup">伺服器不支持註冊</string>
<string name="encryption_choice_none">純文字內容</string>
<string name="encryption_choice_otr">OTR</string>
<string name="encryption_choice_pgp">OpenPGP</string>
<string name="mgmt_account_edit">編輯帳戶</string>
<string name="mgmt_account_delete">刪除帳戶</string>
<string name="mgmt_account_disable">暫時停用</string>
<string name="mgmt_account_publish_avatar">發佈頭像</string>
<string name="mgmt_account_publish_pgp">發布 OpenPGP 公共鑰匙</string>
<string name="mgmt_account_enable">啟用帳戶</string>
<string name="mgmt_account_are_you_sure">你確定嗎?</string>
<string name="mgmt_account_delete_confirm_text">如果刪除帳戶,則所有對話訊息將會被刪除</string>
<string name="attach_record_voice">錄音</string>
<string name="account_settings_jabber_id">Jabber ID</string>
<string name="account_settings_password">密碼</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="account_settings_confirm_password">確認密碼</string>
<string name="password">密碼</string>
<string name="confirm_password">確認密碼</string>
<string name="passwords_do_not_match">密碼不一致</string>
<string name="invalid_jid">該 Jabber ID 無效</string>
<string name="error_out_of_memory">空間不足,圖片過大</string>
<string name="add_phone_book_text">你確定要新增 %s 為聯絡人嗎?</string>
<string name="contact_status_online">線上</string>
<string name="contact_status_free_to_chat">目前有空</string>
<string name="contact_status_away">離開</string>
<string name="contact_status_extended_away">長時間離開</string>
<string name="contact_status_do_not_disturb">請勿打擾</string>
<string name="contact_status_offline">離線</string>
<string name="muc_details_conference">群組</string>
<string name="muc_details_other_members">其他成員</string>
<string name="server_info_carbon_messages">XEP-0280: Message Carbons</string>
<string name="server_info_stream_management">XEP-0198: Stream Management</string>
<string name="server_info_pep">XEP-0163: PEP (Avatars)</string>
<string name="server_info_available">支援</string>
<string name="server_info_unavailable">不支援</string>
<string name="missing_public_keys">沒有公佈公鑰訊息。</string>
<string name="last_seen_now">剛剛曾在線上</string>
<string name="last_seen_min">一分鐘前曾在線上</string>
<string name="last_seen_mins">%d 分鐘前曾在線上</string>
<string name="last_seen_hour">一小時前曾在線上</string>
<string name="last_seen_hours">%d 小時前曾在線上</string>
<string name="last_seen_day">一天前曾在線上</string>
<string name="last_seen_days">%d 天前曾在線上</string>
<string name="never_seen">未曾上線</string>
<string name="install_openkeychain">加密的訊息。請安裝 OpenKeychain 以解密。</string>
<string name="unknown_otr_fingerprint">未知的 OTR 指紋</string>
<string name="openpgp_messages_found">發現以 OpenPGP 加密的訊息</string>
<string name="reception_failed">接收失敗</string>
<string name="your_fingerprint">你的指紋</string>
<string name="otr_fingerprint">OTR 指紋</string>
<string name="verify">驗證</string>
<string name="decrypt">解密</string>
<string name="conferences">群組</string>
<string name="search">查找</string>
<string name="create_contact">新增聯絡人</string>
<string name="join_conference">加入群組</string>
<string name="delete_contact">刪除聯絡人</string>
<string name="view_contact_details">查看聯絡人詳細訊息</string>
<string name="create">新增</string>
<string name="contact_already_exists">聯絡人已存在</string>
<string name="join">加入</string>
<string name="conference_address">群組地址</string>
<string name="conference_address_example">room@conference.example.com</string>
<string name="save_as_bookmark">儲存為書籤</string>
<string name="delete_bookmark">刪除書籤</string>
<string name="bookmark_already_exists">該書籤已存在</string>
<string name="you"></string>
<string name="action_edit_subject">編輯群組主題</string>
<string name="conference_not_found">群組未找到</string>
<string name="leave">離開</string>
<string name="contact_added_you">聯絡人已新增你到聯絡人列表</string>
<string name="add_back">新增為聯絡人</string>
<string name="contact_has_read_up_to_this_point">%s 讀到此處</string>
<string name="publish">發佈</string>
<string name="touch_to_choose_picture">點擊頭像可選擇頭像</string>
<string name="publish_avatar_explanation">請注意: 所有關注你狀態訊息的人將看到該圖像。</string>
<string name="publishing">發佈中&#8230;</string>
<string name="error_publish_avatar_server_reject">伺服器拒絕了你的發佈請求</string>
<string name="error_publish_avatar_converting">發佈頭像時發生錯誤</string>
<string name="error_saving_avatar">將頭像儲存至硬碟時發生錯誤</string>
<string name="or_long_press_for_default">(或長按以回復預設頭像)</string>
<string name="error_publish_avatar_no_server_support">你的伺服器不支持發佈頭像</string>
<string name="private_message">私密聊天</string>
<string name="private_message_to">給 %s</string>
<string name="send_private_message_to">發送私密消息給 %s</string>
<string name="connect">連接</string>
<string name="account_already_exists">該帳戶已存在</string>
<string name="next">下一步</string>
<string name="server_info_session_established">已建立連接</string>
<string name="additional_information">其他訊息</string>
<string name="skip">略過</string>
<string name="disable_notifications">關閉通知</string>
<string name="disable_notifications_for_this_conversation">關閉該對話消息</string>
<string name="notifications_disabled">通知已關閉</string>
<string name="enable">打開通知</string>
<string name="conference_requires_password">群組設有密碼</string>
<string name="enter_password">輸入密碼</string>
<string name="missing_presence_updates">缺少聯絡人狀態訊息</string>
<string name="request_presence_updates">請先發送關注狀態訊息請求。\n\n<small>這將用來判斷您的聯絡人所用的客戶端類型。</small></string>
<string name="request_now">現在發送請求</string>
<string name="delete_fingerprint">刪除指紋</string>
<string name="sure_delete_fingerprint">你確定刪除該指紋嗎?</string>
<string name="ignore">忽略</string>
<string name="without_mutual_presence_updates"><b>警告:</b> 在沒有互相關注狀態訊息的情況下發送或會引起不能預計的問題。\n\n<small>請檢視聯絡人詳情頁面以確認你們的關注狀態。</small></string>
<string name="pref_encryption_settings">加密設定</string>
<string name="pref_force_encryption">強制要求端到端加密</string>
<string name="pref_force_encryption_summary">總是發送加密訊息 (群組訊息除外)</string>
<string name="pref_dont_save_encrypted">不儲存加密訊息</string>
<string name="pref_dont_save_encrypted_summary">警告: 此操作或會導致訊息丟失</string>
<string name="pref_expert_options">專家選項</string>
<string name="pref_expert_options_summary">請小心設定</string>
<string name="pref_use_larger_font">增加字體大小</string>
<string name="pref_use_larger_font_summary">讓整個 app 界面使用更大號的字體</string>
<string name="pref_use_send_button_to_indicate_status">用「發送」按鈕顯示狀態訊息</string>
<string name="pref_use_indicate_received">要求讀取收據</string>
<string name="pref_use_indicate_received_summary">已被讀取的訊息會以綠色勾號表示。請注意,這個功能未必每次有效。</string>
<string name="pref_use_send_button_to_indicate_status_summary">將「發送」按鈕設成不同顏色,以表示不同的狀態訊息。</string>
<string name="pref_expert_options_other">其他</string>
<string name="pref_conference_name">群組名稱</string>
<string name="pref_conference_name_summary">使用群組的名稱而不是 JID 來識別之。 </string>
</resources>

View file

@ -261,5 +261,9 @@
<string name="pref_expert_options_other">Other</string> <string name="pref_expert_options_other">Other</string>
<string name="pref_conference_name">Conference name</string> <string name="pref_conference_name">Conference name</string>
<string name="pref_conference_name_summary">Use rooms subject instead of JID to identify conferences</string> <string name="pref_conference_name_summary">Use rooms 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>
</resources> </resources>

View file

@ -52,15 +52,9 @@
<CheckBoxPreference <CheckBoxPreference
android:dependency="show_notification" android:dependency="show_notification"
android:key="notify_in_conversation_when_highlighted" android:key="always_notify_in_conference"
android:summary="@string/pref_conference_notifications_summary" android:summary="@string/pref_conference_notifications_summary"
android:title="@string/pref_conference_notifications" /> android:title="@string/pref_conference_notifications" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="show_notification"
android:key="notification_grace_period_after_carbon_received"
android:summary="@string/pref_notification_grace_period_summary"
android:title="@string/pref_notification_grace_period" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_ui_options" > <PreferenceCategory android:title="@string/pref_ui_options" >
<CheckBoxPreference <CheckBoxPreference
@ -95,13 +89,13 @@
android:summary="@string/pref_dont_save_encrypted_summary" android:summary="@string/pref_dont_save_encrypted_summary"
android:title="@string/pref_dont_save_encrypted" /> android:title="@string/pref_dont_save_encrypted" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_expert_options_other" > <PreferenceCategory android:title="@string/pref_expert_options_other" >
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="indicate_received" android:key="indicate_received"
android:summary="@string/pref_use_indicate_received_summary" android:summary="@string/pref_use_indicate_received_summary"
android:title="@string/pref_use_indicate_received" /> android:title="@string/pref_use_indicate_received" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
<CheckBoxPreference <CheckBoxPreference

View file

@ -10,7 +10,7 @@ public final class Config {
public static final int PING_MIN_INTERVAL = 30; public static final int PING_MIN_INTERVAL = 30;
public static final int PING_TIMEOUT = 10; public static final int PING_TIMEOUT = 10;
public static final int CONNECT_TIMEOUT = 90; 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 int AVATAR_SIZE = 192;
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;

View file

@ -7,46 +7,35 @@ import android.graphics.Bitmap;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
public class Bookmark implements ListItem { public class Bookmark extends Element implements ListItem {
private Account account; private Account account;
private String jid;
private String nick;
private String name;
private String password;
private boolean autojoin;
private boolean providePassword;
private Conversation mJoinedConversation; private Conversation mJoinedConversation;
public Bookmark(Account account, String jid) { 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.account = account;
this.jid = jid;
} }
public static Bookmark parse(Element element, Account account) { public static Bookmark parse(Element element, Account account) {
Bookmark bookmark = new Bookmark(account, element.getAttribute("jid")); Bookmark bookmark = new Bookmark(account);
bookmark.setName(element.getAttribute("name")); bookmark.setAttributes(element.getAttributes());
String autojoin = element.getAttribute("autojoin"); bookmark.setChildren(element.getChildren());
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);
}
return bookmark; return bookmark;
} }
public void setAutojoin(boolean autojoin) { public void setAutojoin(boolean autojoin) {
this.autojoin = autojoin; if (autojoin) {
this.setAttribute("autojoin", "true");
} else {
this.setAttribute("autojoin", "false");
}
} }
public void setName(String name) { public void setName(String name) {
@ -54,15 +43,18 @@ public class Bookmark implements ListItem {
} }
public void setNick(String nick) { 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) { public void setPassword(String password) {
this.password = password; Element element = this.findChild("password");
} if (element != null) {
element.setContent(password);
private void setProvidePassword(boolean providePassword) { }
this.providePassword = providePassword;
} }
@Override @Override
@ -76,32 +68,45 @@ public class Bookmark implements ListItem {
if (this.mJoinedConversation != null if (this.mJoinedConversation != null
&& (this.mJoinedConversation.getMucOptions().getSubject() != null)) { && (this.mJoinedConversation.getMucOptions().getSubject() != null)) {
return this.mJoinedConversation.getMucOptions().getSubject(); return this.mJoinedConversation.getMucOptions().getSubject();
} else if (name != null) { } else if (getName() != null) {
return name; return getName();
} else { } else {
return this.jid.split("@")[0]; return this.getJid().split("@")[0];
} }
} }
@Override @Override
public String getJid() { 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() { public String getNick() {
return this.nick; Element nick = this.findChild("nick");
if (nick != null) {
return nick.getContent();
} else {
return null;
}
} }
public boolean autojoin() { public boolean autojoin() {
return autojoin; String autojoin = this.getAttribute("autojoin");
return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin
.equalsIgnoreCase("1")));
} }
public String getPassword() { public String getPassword() {
return this.password; Element password = this.findChild("password");
} if (password != null) {
return password.getContent();
public boolean isProvidePassword() { } else {
return this.providePassword; return null;
}
} }
public boolean match(String needle) { public boolean match(String needle) {
@ -131,27 +136,7 @@ public class Bookmark implements ListItem {
} }
public String getName() { public String getName() {
return name; return this.getAttribute("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;
} }
public void unregisterConversation() { public void unregisterConversation() {

View file

@ -156,6 +156,7 @@ public class Contact implements ListItem {
public void clearPresences() { public void clearPresences() {
this.presences.clearPresences(); this.presences.clearPresences();
this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
} }
public int getMostAvailableStatus() { public int getMostAvailableStatus() {

View file

@ -4,6 +4,9 @@ import java.security.interfaces.DSAPublicKey;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import org.json.JSONException;
import org.json.JSONObject;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
@ -36,6 +39,11 @@ public class Conversation extends AbstractEntity {
public static final String STATUS = "status"; public static final String STATUS = "status";
public static final String CREATED = "created"; public static final String CREATED = "created";
public static final String MODE = "mode"; public static final String MODE = "mode";
public static final String ATTRIBUTES = "attributes";
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 name;
private String contactUuid; private String contactUuid;
@ -45,7 +53,7 @@ public class Conversation extends AbstractEntity {
private long created; private long created;
private int mode; private int mode;
private long mutedTill = 0; private JSONObject attributes = new JSONObject();
private String nextPresence; private String nextPresence;
@ -56,12 +64,11 @@ public class Conversation extends AbstractEntity {
private transient String otrFingerprint = null; private transient String otrFingerprint = null;
private int nextMessageEncryption = -1;
private String nextMessage; private String nextMessage;
private transient MucOptions mucOptions = null; private transient MucOptions mucOptions = null;
private transient String latestMarkableMessageId; //private transient String latestMarkableMessageId;
private byte[] symmetricKey; private byte[] symmetricKey;
@ -73,13 +80,13 @@ public class Conversation extends AbstractEntity {
int mode) { int mode) {
this(java.util.UUID.randomUUID().toString(), name, null, account this(java.util.UUID.randomUUID().toString(), name, null, account
.getUuid(), contactJid, System.currentTimeMillis(), .getUuid(), contactJid, System.currentTimeMillis(),
STATUS_AVAILABLE, mode); STATUS_AVAILABLE, mode, "");
this.account = account; this.account = account;
} }
public Conversation(String uuid, String name, String contactUuid, public Conversation(String uuid, String name, String contactUuid,
String accountUuid, String contactJid, long created, int status, String accountUuid, String contactJid, long created, int status,
int mode) { int mode, String attributes) {
this.uuid = uuid; this.uuid = uuid;
this.name = name; this.name = name;
this.contactUuid = contactUuid; this.contactUuid = contactUuid;
@ -88,6 +95,14 @@ public class Conversation extends AbstractEntity {
this.created = created; this.created = created;
this.status = status; this.status = status;
this.mode = mode; this.mode = mode;
try {
if (attributes == null) {
attributes = new String();
}
this.attributes = new JSONObject(attributes);
} catch (JSONException e) {
this.attributes = new JSONObject();
}
} }
public List<Message> getMessages() { public List<Message> getMessages() {
@ -123,10 +138,20 @@ public class Conversation extends AbstractEntity {
} }
} }
public String popLatestMarkableMessageId() { public String getLatestMarkableMessageId() {
String id = this.latestMarkableMessageId; if (this.messages == null) {
this.latestMarkableMessageId = null; return null;
return id; }
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() { public Message getLatestMessage() {
@ -198,6 +223,7 @@ public class Conversation extends AbstractEntity {
values.put(CREATED, created); values.put(CREATED, created);
values.put(STATUS, status); values.put(STATUS, status);
values.put(MODE, mode); values.put(MODE, mode);
values.put(ATTRIBUTES, attributes.toString());
return values; return values;
} }
@ -209,7 +235,8 @@ public class Conversation extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(CONTACTJID)), cursor.getString(cursor.getColumnIndex(CONTACTJID)),
cursor.getLong(cursor.getColumnIndex(CREATED)), cursor.getLong(cursor.getColumnIndex(CREATED)),
cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(STATUS)),
cursor.getInt(cursor.getColumnIndex(MODE))); cursor.getInt(cursor.getColumnIndex(MODE)),
cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
} }
public void setStatus(int status) { public void setStatus(int status) {
@ -229,8 +256,8 @@ public class Conversation extends AbstractEntity {
if (this.otrSession != null) { if (this.otrSession != null) {
return this.otrSession; return this.otrSession;
} else { } else {
SessionID sessionId = new SessionID( SessionID sessionId = new SessionID(this.getContactJid().split("/",
this.getContactJid().split("/",2)[0], presence, "xmpp"); 2)[0], presence, "xmpp");
this.otrSession = new SessionImpl(sessionId, getAccount() this.otrSession = new SessionImpl(sessionId, getAccount()
.getOtrEngine(service)); .getOtrEngine(service));
try { try {
@ -345,7 +372,8 @@ public class Conversation extends AbstractEntity {
} }
public int getNextEncryption(boolean force) { public int getNextEncryption(boolean force) {
if (this.nextMessageEncryption == -1) { int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
if (next == -1) {
int latest = this.getLatestEncryption(); int latest = this.getLatestEncryption();
if (latest == Message.ENCRYPTION_NONE) { if (latest == Message.ENCRYPTION_NONE) {
if (force && getMode() == MODE_SINGLE) { if (force && getMode() == MODE_SINGLE) {
@ -363,16 +391,16 @@ public class Conversation extends AbstractEntity {
return latest; return latest;
} }
} }
if (this.nextMessageEncryption == Message.ENCRYPTION_NONE && force if (next == Message.ENCRYPTION_NONE && force
&& getMode() == MODE_SINGLE) { && getMode() == MODE_SINGLE) {
return Message.ENCRYPTION_OTR; return Message.ENCRYPTION_OTR;
} else { } else {
return this.nextMessageEncryption; return next;
} }
} }
public void setNextEncryption(int encryption) { public void setNextEncryption(int encryption) {
this.nextMessageEncryption = encryption; this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
} }
public String getNextMessage() { public String getNextMessage() {
@ -387,12 +415,6 @@ public class Conversation extends AbstractEntity {
this.nextMessage = message; this.nextMessage = message;
} }
public void setLatestMarkableMessageId(String id) {
if (id != null) {
this.latestMarkableMessageId = id;
}
}
public void setSymmetricKey(byte[] key) { public void setSymmetricKey(byte[] key) {
this.symmetricKey = key; this.symmetricKey = key;
} }
@ -433,11 +455,55 @@ public class Conversation extends AbstractEntity {
return false; return false;
} }
public void setMutedTill(long mutedTill) { public void setMutedTill(long value) {
this.mutedTill = mutedTill; this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
} }
public boolean isMuted() { public boolean isMuted() {
return SystemClock.elapsedRealtime() < this.mutedTill; return SystemClock.elapsedRealtime() < this.getLongAttribute(
ATTRIBUTE_MUTED_TILL, 0);
}
public boolean setAttribute(String key, String value) {
try {
this.attributes.put(key, value);
return true;
} catch (JSONException e) {
return false;
}
}
public String getAttribute(String key) {
try {
return this.attributes.getString(key);
} catch (JSONException e) {
return null;
}
}
public int getIntAttribute(String key, int defaultValue) {
String value = this.getAttribute(key);
if (value == null) {
return defaultValue;
} else {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
}
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;
}
}
} }
} }

View file

@ -57,9 +57,9 @@ public class Message extends AbstractEntity {
protected boolean read = true; protected boolean read = true;
protected String remoteMsgId = null; protected String remoteMsgId = null;
protected transient Conversation conversation = null; protected Conversation conversation = null;
protected Downloadable downloadable = null;
protected transient Downloadable downloadable = null; public boolean markable = false;
private Message() { private Message() {

View file

@ -15,6 +15,13 @@ public class MucOptions {
public static final int ERROR_NICK_IN_USE = 1; public static final int ERROR_NICK_IN_USE = 1;
public static final int ERROR_ROOM_NOT_FOUND = 2; public static final int ERROR_ROOM_NOT_FOUND = 2;
public static final int ERROR_PASSWORD_REQUIRED = 3; 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 interface OnRenameListener {
public void onRename(boolean success); public void onRename(boolean success);
@ -108,7 +115,6 @@ public class MucOptions {
private String subject = null; private String subject = null;
private String joinnick; private String joinnick;
private String password = null; private String password = null;
private boolean passwordChanged = false;
public MucOptions(Account account) { public MucOptions(Account account) {
this.account = account; this.account = account;
@ -134,7 +140,7 @@ public class MucOptions {
} }
public void processPacket(PresencePacket packet, PgpEngine pgp) { public void processPacket(PresencePacket packet, PgpEngine pgp) {
String[] fromParts = packet.getFrom().split("/",2); String[] fromParts = packet.getFrom().split("/", 2);
if (fromParts.length >= 2) { if (fromParts.length >= 2) {
String name = fromParts[1]; String name = fromParts[1];
String type = packet.getAttribute("type"); String type = packet.getAttribute("type");
@ -158,10 +164,6 @@ public class MucOptions {
} }
aboutToRename = false; aboutToRename = false;
} }
if (conversation.getBookmark() != null
&& conversation.getBookmark().isProvidePassword()) {
this.passwordChanged = false;
}
} else { } else {
addUser(user); addUser(user);
} }
@ -179,11 +181,27 @@ public class MucOptions {
x.getContent())); 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")) { } else if (type.equals("unavailable")) {
deleteUser(packet.getAttribute("from").split("/",2)[1]); deleteUser(packet.getAttribute("from").split("/", 2)[1]);
} else if (type.equals("error")) { } else if (type.equals("error")) {
Element error = packet.findChild("error"); Element error = packet.findChild("error");
if (error.hasChild("conflict")) { if (error != null && error.hasChild("conflict")) {
if (aboutToRename) { if (aboutToRename) {
if (renameListener != null) { if (renameListener != null) {
renameListener.onRename(false); renameListener.onRename(false);
@ -193,12 +211,13 @@ public class MucOptions {
} else { } else {
this.error = ERROR_NICK_IN_USE; this.error = ERROR_NICK_IN_USE;
} }
} else if (error.hasChild("not-authorized")) { } else if (error != null && error.hasChild("not-authorized")) {
if (conversation.getBookmark() != null
&& conversation.getBookmark().isProvidePassword()) {
this.passwordChanged = true;
}
this.error = ERROR_PASSWORD_REQUIRED; 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;
} }
} }
} }
@ -209,7 +228,7 @@ public class MucOptions {
} }
public String getProposedNick() { public String getProposedNick() {
String[] mucParts = conversation.getContactJid().split("/",2); String[] mucParts = conversation.getContactJid().split("/", 2);
if (conversation.getBookmark() != null if (conversation.getBookmark() != null
&& conversation.getBookmark().getNick() != null) { && conversation.getBookmark().getNick() != null) {
return conversation.getBookmark().getNick(); return conversation.getBookmark().getNick();
@ -309,7 +328,7 @@ public class MucOptions {
} }
public String getJoinJid() { public String getJoinJid() {
return this.conversation.getContactJid().split("/",2)[0] + "/" return this.conversation.getContactJid().split("/", 2)[0] + "/"
+ this.joinnick; + this.joinnick;
} }
@ -323,7 +342,9 @@ public class MucOptions {
} }
public String getPassword() { public String getPassword() {
if (conversation.getBookmark() != null this.password = conversation
.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
if (this.password == null && conversation.getBookmark() != null
&& conversation.getBookmark().getPassword() != null) { && conversation.getBookmark().getPassword() != null) {
return conversation.getBookmark().getPassword(); return conversation.getBookmark().getPassword();
} else { } else {
@ -332,16 +353,12 @@ public class MucOptions {
} }
public void setPassword(String password) { public void setPassword(String password) {
if (conversation.getBookmark() != null if (conversation.getBookmark() != null) {
&& conversation.getBookmark().isProvidePassword()) {
conversation.getBookmark().setPassword(password); conversation.getBookmark().setPassword(password);
} else { } else {
this.password = password; this.password = password;
} }
conversation
.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
} }
public boolean isPasswordChanged() {
return this.passwordChanged;
}
} }

View file

@ -14,13 +14,18 @@ public class Roster {
this.account = account; this.account = account;
} }
public boolean hasContact(String jid) { public Contact getContactAsShownInRoster(String jid) {
String cleanJid = jid.split("/",2)[0]; String cleanJid = jid.split("/", 2)[0];
return contacts.containsKey(cleanJid); Contact contact = contacts.get(cleanJid);
if (contact != null && contact.showInRoster()) {
return contact;
} else {
return null;
}
} }
public Contact getContact(String jid) { public Contact getContact(String jid) {
String cleanJid = jid.split("/",2)[0].toLowerCase(Locale.getDefault()); String cleanJid = jid.split("/", 2)[0].toLowerCase(Locale.getDefault());
if (contacts.containsKey(cleanJid)) { if (contacts.containsKey(cleanJid)) {
return contacts.get(cleanJid); return contacts.get(cleanJid);
} else { } else {

View file

@ -34,7 +34,7 @@ public class MessageGenerator extends AbstractGenerator {
packet.setTo(message.getCounterpart()); packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(MessagePacket.TYPE_CHAT);
} else { } else {
packet.setTo(message.getCounterpart().split("/",2)[0]); packet.setTo(message.getCounterpart().split("/", 2)[0]);
packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setType(MessagePacket.TYPE_GROUPCHAT);
} }
packet.setFrom(account.getFullJid()); packet.setFrom(account.getFullJid());
@ -134,7 +134,7 @@ public class MessageGenerator extends AbstractGenerator {
String subject) { String subject) {
MessagePacket packet = new MessagePacket(); MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setType(MessagePacket.TYPE_GROUPCHAT);
packet.setTo(conversation.getContactJid().split("/",2)[0]); packet.setTo(conversation.getContactJid().split("/", 2)[0]);
Element subjectChild = new Element("subject"); Element subjectChild = new Element("subject");
subjectChild.setContent(subject); subjectChild.setContent(subject);
packet.addChild(subjectChild); packet.addChild(subjectChild);
@ -148,13 +148,13 @@ public class MessageGenerator extends AbstractGenerator {
packet.setTo(contact); packet.setTo(contact);
packet.setFrom(conversation.getAccount().getFullJid()); packet.setFrom(conversation.getAccount().getFullJid());
Element x = packet.addChild("x", "jabber:x:conference"); Element x = packet.addChild("x", "jabber:x:conference");
x.setAttribute("jid", conversation.getContactJid().split("/",2)[0]); x.setAttribute("jid", conversation.getContactJid().split("/", 2)[0]);
return packet; return packet;
} }
public MessagePacket invite(Conversation conversation, String contact) { public MessagePacket invite(Conversation conversation, String contact) {
MessagePacket packet = new MessagePacket(); MessagePacket packet = new MessagePacket();
packet.setTo(conversation.getContactJid().split("/",2)[0]); packet.setTo(conversation.getContactJid().split("/", 2)[0]);
packet.setFrom(conversation.getAccount().getFullJid()); packet.setFrom(conversation.getAccount().getFullJid());
Element x = new Element("x"); Element x = new Element("x");
x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");

View file

@ -60,7 +60,7 @@ public abstract class AbstractParser {
protected void updateLastseen(Element packet, Account account, protected void updateLastseen(Element packet, Account account,
boolean presenceOverwrite) { boolean presenceOverwrite) {
String[] fromParts = packet.getAttribute("from").split("/",2); String[] fromParts = packet.getAttribute("from").split("/", 2);
String from = fromParts[0]; String from = fromParts[0];
String presence = null; String presence = null;
if (fromParts.length >= 2) { if (fromParts.length >= 2) {

View file

@ -73,6 +73,9 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
IqPacket response = mXmppConnectionService.getIqGenerator() IqPacket response = mXmppConnectionService.getIqGenerator()
.discoResponse(packet); .discoResponse(packet);
account.getXmppConnection().sendIqPacket(response, null); 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 { } else {
if ((packet.getType() == IqPacket.TYPE_GET) if ((packet.getType() == IqPacket.TYPE_GET)
|| (packet.getType() == IqPacket.TYPE_SET)) { || (packet.getType() == IqPacket.TYPE_SET)) {

View file

@ -1,13 +1,12 @@
package eu.siacs.conversations.parser; package eu.siacs.conversations.parser;
import android.os.SystemClock;
import net.java.otr4j.session.Session; import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.NotificationService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
@ -17,9 +16,6 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageParser extends AbstractParser implements public class MessageParser extends AbstractParser implements
OnMessagePacketReceived { OnMessagePacketReceived {
private long lastCarbonMessageReceived = -(Config.CARBON_GRACE_PERIOD * 1000);
public MessageParser(XmppConnectionService service) { public MessageParser(XmppConnectionService service) {
super(service); super(service);
} }
@ -28,7 +24,6 @@ public class MessageParser extends AbstractParser implements
String[] fromParts = packet.getFrom().split("/", 2); String[] fromParts = packet.getFrom().split("/", 2);
Conversation conversation = mXmppConnectionService Conversation conversation = mXmppConnectionService
.findOrCreateConversation(account, fromParts[0], false); .findOrCreateConversation(account, fromParts[0], false);
conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
updateLastseen(packet, account, true); updateLastseen(packet, account, true);
String pgpBody = getPgpBody(packet); String pgpBody = getPgpBody(packet);
Message finishedMessage; Message finishedMessage;
@ -41,6 +36,7 @@ public class MessageParser extends AbstractParser implements
Message.STATUS_RECEIVED); Message.STATUS_RECEIVED);
} }
finishedMessage.setRemoteMsgId(packet.getId()); finishedMessage.setRemoteMsgId(packet.getId());
finishedMessage.markable = isMarkable(packet);
if (conversation.getMode() == Conversation.MODE_MULTI if (conversation.getMode() == Conversation.MODE_MULTI
&& fromParts.length >= 2) { && fromParts.length >= 2) {
finishedMessage.setType(Message.TYPE_PRIVATE); finishedMessage.setType(Message.TYPE_PRIVATE);
@ -71,7 +67,7 @@ public class MessageParser extends AbstractParser implements
updateLastseen(packet, account, true); updateLastseen(packet, account, true);
String body = packet.getBody(); String body = packet.getBody();
if (body.matches("^\\?OTRv\\d*\\?")) { if (body.matches("^\\?OTRv\\d*\\?")) {
conversation.resetOtrSession(); conversation.endOtrIfNeeded();
} }
if (!conversation.hasValidOtrSession()) { if (!conversation.hasValidOtrSession()) {
if (properlyAddressed) { if (properlyAddressed) {
@ -112,13 +108,12 @@ public class MessageParser extends AbstractParser implements
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key)); conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
return null; return null;
} }
conversation
.setLatestMarkableMessageId(getMarkableMessageId(packet));
Message finishedMessage = new Message(conversation, Message finishedMessage = new Message(conversation,
packet.getFrom(), body, Message.ENCRYPTION_OTR, packet.getFrom(), body, Message.ENCRYPTION_OTR,
Message.STATUS_RECEIVED); Message.STATUS_RECEIVED);
finishedMessage.setTime(getTimestamp(packet)); finishedMessage.setTime(getTimestamp(packet));
finishedMessage.setRemoteMsgId(packet.getId()); finishedMessage.setRemoteMsgId(packet.getId());
finishedMessage.markable = isMarkable(packet);
return finishedMessage; return finishedMessage;
} catch (Exception e) { } catch (Exception e) {
String receivedId = packet.getId(); String receivedId = packet.getId();
@ -160,7 +155,6 @@ public class MessageParser extends AbstractParser implements
status = Message.STATUS_RECEIVED; status = Message.STATUS_RECEIVED;
} }
String pgpBody = getPgpBody(packet); String pgpBody = getPgpBody(packet);
conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
Message finishedMessage; Message finishedMessage;
if (pgpBody == null) { if (pgpBody == null) {
finishedMessage = new Message(conversation, counterPart, finishedMessage = new Message(conversation, counterPart,
@ -170,6 +164,7 @@ public class MessageParser extends AbstractParser implements
Message.ENCRYPTION_PGP, status); Message.ENCRYPTION_PGP, status);
} }
finishedMessage.setRemoteMsgId(packet.getId()); finishedMessage.setRemoteMsgId(packet.getId());
finishedMessage.markable = isMarkable(packet);
if (status == Message.STATUS_RECEIVED) { if (status == Message.STATUS_RECEIVED) {
finishedMessage.setTrueCounterpart(conversation.getMucOptions() finishedMessage.setTrueCounterpart(conversation.getMucOptions()
.getTrueCounterpart(counterPart)); .getTrueCounterpart(counterPart));
@ -201,10 +196,24 @@ public class MessageParser extends AbstractParser implements
return null; return null;
} }
Element message = forwarded.findChild("message"); 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 if (status == Message.STATUS_RECEIVED
&& message.getAttribute("from") != null) { && message.getAttribute("from") != null) {
parseNonMessage(message, account); 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; return null;
} }
@ -224,8 +233,6 @@ public class MessageParser extends AbstractParser implements
String[] parts = fullJid.split("/", 2); String[] parts = fullJid.split("/", 2);
Conversation conversation = mXmppConnectionService Conversation conversation = mXmppConnectionService
.findOrCreateConversation(account, parts[0], false); .findOrCreateConversation(account, parts[0], false);
conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
String pgpBody = getPgpBody(message); String pgpBody = getPgpBody(message);
Message finishedMessage; Message finishedMessage;
if (pgpBody != null) { if (pgpBody != null) {
@ -238,6 +245,7 @@ public class MessageParser extends AbstractParser implements
} }
finishedMessage.setTime(getTimestamp(message)); finishedMessage.setTime(getTimestamp(message));
finishedMessage.setRemoteMsgId(message.getAttribute("id")); finishedMessage.setRemoteMsgId(message.getAttribute("id"));
finishedMessage.markable = isMarkable(message);
if (conversation.getMode() == Conversation.MODE_MULTI if (conversation.getMode() == Conversation.MODE_MULTI
&& parts.length >= 2) { && parts.length >= 2) {
finishedMessage.setType(Message.TYPE_PRIVATE); finishedMessage.setType(Message.TYPE_PRIVATE);
@ -298,6 +306,8 @@ public class MessageParser extends AbstractParser implements
Element password = x.findChild("password"); Element password = x.findChild("password");
conversation.getMucOptions().setPassword( conversation.getMucOptions().setPassword(
password.getContent()); password.getContent());
mXmppConnectionService.databaseBackend
.updateConversation(conversation);
} }
mXmppConnectionService.joinMuc(conversation); mXmppConnectionService.joinMuc(conversation);
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();
@ -313,6 +323,8 @@ public class MessageParser extends AbstractParser implements
if (!conversation.getMucOptions().online()) { if (!conversation.getMucOptions().online()) {
if (password != null) { if (password != null) {
conversation.getMucOptions().setPassword(password); conversation.getMucOptions().setPassword(password);
mXmppConnectionService.databaseBackend
.updateConversation(conversation);
} }
mXmppConnectionService.joinMuc(conversation); mXmppConnectionService.joinMuc(conversation);
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();
@ -371,22 +383,18 @@ public class MessageParser extends AbstractParser implements
} }
} }
private String getMarkableMessageId(Element message) { private boolean isMarkable(Element message) {
if (message.hasChild("markable", "urn:xmpp:chat-markers:0")) { return message.hasChild("markable", "urn:xmpp:chat-markers:0");
return message.getAttribute("id");
} else {
return null;
}
} }
@Override @Override
public void onMessagePacketReceived(Account account, MessagePacket packet) { public void onMessagePacketReceived(Account account, MessagePacket packet) {
Message message = null; Message message = null;
boolean notify = true; boolean notify = mXmppConnectionService.getPreferences().getBoolean(
if (mXmppConnectionService.getPreferences().getBoolean( "show_notification", true);
"notification_grace_period_after_carbon_received", true)) { boolean alwaysNotifyInConference = notify
notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > (Config.CARBON_GRACE_PERIOD * 1000); && mXmppConnectionService.getPreferences().getBoolean(
} "always_notify_in_conference", false);
this.parseNick(packet, account); this.parseNick(packet, account);
@ -397,7 +405,9 @@ public class MessageParser extends AbstractParser implements
if (message != null) { if (message != null) {
message.markUnread(); 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); message = this.parseChat(packet, account);
if (message != null) { if (message != null) {
message.markUnread(); message.markUnread();
@ -407,10 +417,11 @@ public class MessageParser extends AbstractParser implements
message = this.parseCarbonMessage(packet, account); message = this.parseCarbonMessage(packet, account);
if (message != null) { if (message != null) {
if (message.getStatus() == Message.STATUS_SEND) { if (message.getStatus() == Message.STATUS_SEND) {
lastCarbonMessageReceived = SystemClock mXmppConnectionService.getNotificationService()
.elapsedRealtime(); .activateGracePeriod();
notify = false; notify = false;
message.getConversation().markRead(); mXmppConnectionService.markRead(
message.getConversation(), false);
} else { } else {
message.markUnread(); message.markUnread();
} }
@ -423,9 +434,14 @@ public class MessageParser extends AbstractParser implements
if (message != null) { if (message != null) {
if (message.getStatus() == Message.STATUS_RECEIVED) { if (message.getStatus() == Message.STATUS_RECEIVED) {
message.markUnread(); message.markUnread();
notify = alwaysNotifyInConference
|| NotificationService
.wasHighlightedOrPrivate(message);
} else { } else {
message.getConversation().markRead(); mXmppConnectionService.markRead(message.getConversation(),
lastCarbonMessageReceived = SystemClock.elapsedRealtime(); false);
mXmppConnectionService.getNotificationService()
.activateGracePeriod();
notify = false; notify = false;
} }
} }
@ -463,7 +479,10 @@ public class MessageParser extends AbstractParser implements
} }
} }
notify = notify && !conversation.isMuted(); notify = notify && !conversation.isMuted();
mXmppConnectionService.notifyUi(conversation, notify); if (notify) {
mXmppConnectionService.getNotificationService().push(message);
}
mXmppConnectionService.updateConversationUi();
} }
private void parseHeadline(MessagePacket packet, Account account) { private void parseHeadline(MessagePacket packet, Account account) {

View file

@ -22,7 +22,7 @@ public class PresenceParser extends AbstractParser implements
PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine();
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
Conversation muc = mXmppConnectionService.find(account, packet Conversation muc = mXmppConnectionService.find(account, packet
.getAttribute("from").split("/",2)[0]); .getAttribute("from").split("/", 2)[0]);
if (muc != null) { if (muc != null) {
boolean before = muc.getMucOptions().online(); boolean before = muc.getMucOptions().online();
muc.getMucOptions().processPacket(packet, mPgpEngine); muc.getMucOptions().processPacket(packet, mPgpEngine);
@ -32,7 +32,7 @@ public class PresenceParser extends AbstractParser implements
} }
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
Conversation muc = mXmppConnectionService.find(account, packet Conversation muc = mXmppConnectionService.find(account, packet
.getAttribute("from").split("/",2)[0]); .getAttribute("from").split("/", 2)[0]);
if (muc != null) { if (muc != null) {
boolean before = muc.getMucOptions().online(); boolean before = muc.getMucOptions().online();
muc.getMucOptions().processPacket(packet, mPgpEngine); muc.getMucOptions().processPacket(packet, mPgpEngine);
@ -58,6 +58,8 @@ public class PresenceParser extends AbstractParser implements
Presences.parseShow(packet.findChild("show"))); Presences.parseShow(packet.findChild("show")));
} else if (type.equals("unavailable")) { } else if (type.equals("unavailable")) {
account.removePresence(fromParts[1]); account.removePresence(fromParts[1]);
mXmppConnectionService.getNotificationService()
.deactivateGracePeriod();
} }
} }
} else { } else {

View file

@ -19,7 +19,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null; private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history"; private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 7; private static final int DATABASE_VERSION = 8;
private static String CREATE_CONTATCS_STATEMENT = "create table " private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -50,10 +50,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ " TEXT, " + Conversation.CONTACT + " TEXT, " + " TEXT, " + Conversation.CONTACT + " TEXT, "
+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
+ " TEXT, " + Conversation.CREATED + " NUMBER, " + " TEXT, " + Conversation.CREATED + " NUMBER, "
+ Conversation.STATUS + " NUMBER," + Conversation.MODE + Conversation.STATUS + " NUMBER, " + Conversation.MODE
+ " NUMBER," + "FOREIGN KEY(" + Conversation.ACCOUNT + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
+ ") ON DELETE CASCADE);"); + "(" + Account.UUID + ") ON DELETE CASCADE);");
db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, " + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
@ -96,6 +96,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
+ Account.AVATAR + " TEXT"); + Account.AVATAR + " TEXT");
} }
if (oldVersion < 8 && newVersion >= 8) {
db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
+ Conversation.ATTRIBUTES + " TEXT");
}
} }
public static synchronized DatabaseBackend getInstance(Context context) { public static synchronized DatabaseBackend getInstance(Context context) {
@ -206,6 +210,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
list.add(Account.fromCursor(cursor)); list.add(Account.fromCursor(cursor));
} }
cursor.close();
return list; return list;
} }
@ -224,10 +229,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public boolean hasEnabledAccounts() { public boolean hasEnabledAccounts() {
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor= db.rawQuery("select count("+Account.UUID+") from "+Account.TABLENAME+" where not options & (1 <<1)", null); Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
+ Account.TABLENAME + " where not options & (1 <<1)", null);
cursor.moveToFirst(); cursor.moveToFirst();
int count = cursor.getInt(0); int count = cursor.getInt(0);
return (count>0); cursor.close();
return (count > 0);
} }
@Override @Override
@ -253,6 +260,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
roster.initContact(Contact.fromCursor(cursor)); roster.initContact(Contact.fromCursor(cursor));
} }
cursor.close();
} }
public void writeRoster(Roster roster) { public void writeRoster(Roster roster) {

View file

@ -250,7 +250,10 @@ public class FileBackend {
if (!file.exists()) { if (!file.exists()) {
file = getJingleFileLegacy(message); 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) { if (fullsize == null) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
@ -414,6 +417,17 @@ public class FileBackend {
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(context.getContentResolver() BitmapFactory.decodeStream(context.getContentResolver()
.openInputStream(image), null, options); .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 height = options.outHeight;
int width = options.outWidth; int width = options.outWidth;
int inSampleSize = 1; int inSampleSize = 1;
@ -428,7 +442,6 @@ public class FileBackend {
} }
} }
return inSampleSize; return inSampleSize;
} }
public Uri getJingleFileUri(Message message) { public Uri getJingleFileUri(Message message) {

View file

@ -15,7 +15,8 @@ public class EventReceiver extends BroadcastReceiver {
} else { } else {
mIntentForService.setAction("other"); mIntentForService.setAction("other");
} }
if (intent.getAction().equals("ui") || DatabaseBackend.getInstance(context).hasEnabledAccounts()) { if (intent.getAction().equals("ui")
|| DatabaseBackend.getInstance(context).hasEnabledAccounts()) {
context.startService(mIntentForService); context.startService(mIntentForService);
} }
} }

View file

@ -31,7 +31,7 @@ public class ImageProvider extends ContentProvider {
if (uuids == null) { if (uuids == null) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
String[] uuidsSplited = uuids.split("/",2); String[] uuidsSplited = uuids.split("/", 2);
if (uuidsSplited.length != 3) { if (uuidsSplited.length != 3) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }

View file

@ -0,0 +1,241 @@
package eu.siacs.conversations.services;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
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 eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
public class NotificationService {
private XmppConnectionService mXmppConnectionService;
private NotificationManager mNotificationManager;
private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
public int NOTIFICATION_ID = 0x2342;
private Conversation mOpenConversation;
private boolean mIsInForeground;
private long mEndGracePeriod = 0L;
public NotificationService(XmppConnectionService service) {
this.mXmppConnectionService = service;
this.mNotificationManager = (NotificationManager) service
.getSystemService(Context.NOTIFICATION_SERVICE);
}
public synchronized void push(Message message) {
PowerManager pm = (PowerManager) mXmppConnectionService
.getSystemService(Context.POWER_SERVICE);
boolean isScreenOn = pm.isScreenOn();
if (this.mIsInForeground && isScreenOn
&& this.mOpenConversation == message.getConversation()) {
return;
}
String conversationUuid = message.getConversationUuid();
if (notifications.containsKey(conversationUuid)) {
notifications.get(conversationUuid).add(message);
} else {
ArrayList<Message> mList = new ArrayList<Message>();
mList.add(message);
notifications.put(conversationUuid, mList);
}
updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
&& !inGracePeriod());
}
public void clear() {
notifications.clear();
updateNotification(false);
}
public void clear(Conversation conversation) {
notifications.remove(conversation.getUuid());
updateNotification(false);
}
private void updateNotification(boolean notify) {
SharedPreferences preferences = mXmppConnectionService.getPreferences();
String ringtone = preferences.getString("notification_ringtone", null);
boolean vibrate = preferences.getBoolean("vibrate_on_notification",
true);
if (notifications.size() == 0) {
mNotificationManager.cancel(NOTIFICATION_ID);
} else {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
mXmppConnectionService);
mBuilder.setSmallIcon(R.drawable.ic_notification);
if (notifications.size() == 1) {
ArrayList<Message> messages = notifications.values().iterator()
.next();
if (messages.size() >= 1) {
Conversation conversation = messages.get(0)
.getConversation();
mBuilder.setLargeIcon(conversation.getImage(
mXmppConnectionService, 64));
mBuilder.setContentTitle(conversation.getName());
StringBuilder text = new StringBuilder();
for (int i = 0; i < messages.size(); ++i) {
text.append(messages.get(i).getReadableBody(
mXmppConnectionService));
if (i != messages.size() - 1) {
text.append("\n");
}
}
mBuilder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text.toString()));
mBuilder.setContentText(messages.get(0).getReadableBody(
mXmppConnectionService));
if (notify) {
mBuilder.setTicker(messages.get(messages.size() - 1)
.getReadableBody(mXmppConnectionService));
}
mBuilder.setContentIntent(createContentIntent(conversation
.getUuid()));
} else {
mNotificationManager.cancel(NOTIFICATION_ID);
return;
}
} else {
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(notifications.size()
+ " "
+ mXmppConnectionService
.getString(R.string.unread_conversations));
StringBuilder names = new StringBuilder();
Conversation conversation = null;
for (ArrayList<Message> messages : notifications.values()) {
if (messages.size() > 0) {
conversation = messages.get(0).getConversation();
String name = conversation.getName();
style.addLine(Html.fromHtml("<b>"
+ name
+ "</b> "
+ messages.get(0).getReadableBody(
mXmppConnectionService)));
names.append(name);
names.append(", ");
}
}
if (names.length() >= 2) {
names.delete(names.length() - 2, names.length());
}
mBuilder.setContentTitle(notifications.size()
+ " "
+ mXmppConnectionService
.getString(R.string.unread_conversations));
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
if (conversation != null) {
mBuilder.setContentIntent(createContentIntent(conversation
.getUuid()));
}
}
if (notify) {
if (vibrate) {
int dat = 70;
long[] pattern = { 0, 3 * dat, dat, dat };
mBuilder.setVibrate(pattern);
}
if (ringtone != null) {
mBuilder.setSound(Uri.parse(ringtone));
}
}
mBuilder.setDeleteIntent(createDeleteIntent());
if (!inGracePeriod()) {
mBuilder.setLights(0xffffffff, 2000, 4000);
}
Notification notification = mBuilder.build();
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
}
private PendingIntent createContentIntent(String conversationUuid) {
TaskStackBuilder stackBuilder = TaskStackBuilder
.create(mXmppConnectionService);
stackBuilder.addParentStack(ConversationActivity.class);
Intent viewConversationIntent = new Intent(mXmppConnectionService,
ConversationActivity.class);
viewConversationIntent.setAction(Intent.ACTION_VIEW);
viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
conversationUuid);
viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
stackBuilder.addNextIntent(viewConversationIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT);
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);
}
private static Pattern generateNickHighlightPattern(String nick) {
// We expect a word boundary, i.e. space or start of string, followed by
// the
// nick (matched in case-insensitive manner), followed by optional
// punctuation (for example "bob: i disagree" or "how are you alice?"),
// followed by another word boundary.
return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
public void setOpenConversation(Conversation conversation) {
this.mOpenConversation = conversation;
}
public void setIsInForeground(boolean foreground) {
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;
}
}

View file

@ -90,9 +90,12 @@ public class XmppConnectionService extends Service {
public long startDate; public long startDate;
private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
public static String ACTION_CLEAR_NOTIFICATION = "clear_notification";
private MemorizingTrustManager mMemorizingTrustManager; private MemorizingTrustManager mMemorizingTrustManager;
private NotificationService mNotificationService;
private MessageParser mMessageParser = new MessageParser(this); private MessageParser mMessageParser = new MessageParser(this);
private PresenceParser mPresenceParser = new PresenceParser(this); private PresenceParser mPresenceParser = new PresenceParser(this);
private IqParser mIqParser = new IqParser(this); private IqParser mIqParser = new IqParser(this);
@ -316,14 +319,16 @@ public class XmppConnectionService extends Service {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if ((intent != null) if (intent != null && intent.getAction() != null) {
&& (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) { if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) {
mergePhoneContactsWithRoster(); mergePhoneContactsWithRoster();
return START_STICKY; return START_STICKY;
} else if ((intent != null) } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
&& (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) { logoutAndSave();
logoutAndSave(); return START_NOT_STICKY;
return START_NOT_STICKY; } else if (intent.getAction().equals(ACTION_CLEAR_NOTIFICATION)) {
mNotificationService.clear();
}
} }
this.wakeLock.acquire(); this.wakeLock.acquire();
ConnectivityManager cm = (ConnectivityManager) getApplicationContext() ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
@ -401,6 +406,7 @@ public class XmppConnectionService extends Service {
this.mRandom = new SecureRandom(); this.mRandom = new SecureRandom();
this.mMemorizingTrustManager = new MemorizingTrustManager( this.mMemorizingTrustManager = new MemorizingTrustManager(
getApplicationContext()); getApplicationContext());
this.mNotificationService = new NotificationService(this);
this.databaseBackend = DatabaseBackend this.databaseBackend = DatabaseBackend
.getInstance(getApplicationContext()); .getInstance(getApplicationContext());
this.fileBackend = new FileBackend(getApplicationContext()); this.fileBackend = new FileBackend(getApplicationContext());
@ -511,7 +517,8 @@ public class XmppConnectionService extends Service {
MessagePacket packet = null; MessagePacket packet = null;
boolean saveInDb = true; boolean saveInDb = true;
boolean send = false; 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.getType() == Message.TYPE_IMAGE) {
if (message.getPresence() != null) { if (message.getPresence() != null) {
if (message.getEncryption() == Message.ENCRYPTION_OTR) { if (message.getEncryption() == Message.ENCRYPTION_OTR) {
@ -561,6 +568,10 @@ public class XmppConnectionService extends Service {
send = true; send = true;
} }
} }
if (!account.getXmppConnection().getFeatures().sm()
&& conv.getMode() != Conversation.MODE_MULTI) {
message.setStatus(Message.STATUS_SEND);
}
} else { } else {
message.setStatus(Message.STATUS_WAITING); message.setStatus(Message.STATUS_WAITING);
if (message.getType() == Message.TYPE_TEXT) { if (message.getType() == Message.TYPE_TEXT) {
@ -586,10 +597,6 @@ public class XmppConnectionService extends Service {
} }
conv.getMessages().add(message); conv.getMessages().add(message);
if (!account.getXmppConnection().getFeatures().sm()
&& conv.getMode() != Conversation.MODE_MULTI) {
message.setStatus(Message.STATUS_SEND);
}
if (saveInDb) { if (saveInDb) {
if (message.getEncryption() == Message.ENCRYPTION_NONE if (message.getEncryption() == Message.ENCRYPTION_NONE
|| saveEncryptedMessages()) { || saveEncryptedMessages()) {
@ -742,7 +749,7 @@ public class XmppConnectionService extends Service {
Element query = iqPacket.query("jabber:iq:private"); Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks"); Element storage = query.addChild("storage", "storage:bookmarks");
for (Bookmark bookmark : account.getBookmarks()) { for (Bookmark bookmark : account.getBookmarks()) {
storage.addChild(bookmark.toElement()); storage.addChild(bookmark);
} }
sendIqPacket(account, iqPacket, null); sendIqPacket(account, iqPacket, null);
} }
@ -851,8 +858,9 @@ public class XmppConnectionService extends Service {
public Conversation find(List<Conversation> haystack, Account account, public Conversation find(List<Conversation> haystack, Account account,
String jid) { String jid) {
for (Conversation conversation : haystack) { for (Conversation conversation : haystack) {
if ((conversation.getAccount().equals(account)) if ((account == null || conversation.getAccount().equals(account))
&& (conversation.getContactJid().split("/",2)[0].equals(jid))) { && (conversation.getContactJid().split("/", 2)[0]
.equals(jid))) {
return conversation; return conversation;
} }
} }
@ -901,10 +909,12 @@ public class XmppConnectionService extends Service {
public void archiveConversation(Conversation conversation) { public void archiveConversation(Conversation conversation) {
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
Bookmark bookmark = conversation.getBookmark(); if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
if (bookmark != null && bookmark.autojoin()) { Bookmark bookmark = conversation.getBookmark();
bookmark.setAutojoin(false); if (bookmark != null && bookmark.autojoin()) {
pushBookmarks(bookmark.getAccount()); bookmark.setAutojoin(false);
pushBookmarks(bookmark.getAccount());
}
} }
leaveMuc(conversation); leaveMuc(conversation);
} else { } else {
@ -963,10 +973,12 @@ public class XmppConnectionService extends Service {
public void setOnConversationListChangedListener( public void setOnConversationListChangedListener(
OnConversationUpdate listener) { OnConversationUpdate listener) {
this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) { if (checkListeners()) {
switchToForeground(); switchToForeground();
} }
this.mOnConversationUpdate = listener; this.mOnConversationUpdate = listener;
this.mNotificationService.setIsInForeground(true);
this.convChangedListenerCount++; this.convChangedListenerCount++;
} }
@ -974,6 +986,7 @@ public class XmppConnectionService extends Service {
this.convChangedListenerCount--; this.convChangedListenerCount--;
if (this.convChangedListenerCount == 0) { if (this.convChangedListenerCount == 0) {
this.mOnConversationUpdate = null; this.mOnConversationUpdate = null;
this.mNotificationService.setIsInForeground(false);
if (checkListeners()) { if (checkListeners()) {
switchToBackground(); switchToBackground();
} }
@ -981,6 +994,7 @@ public class XmppConnectionService extends Service {
} }
public void setOnAccountListChangedListener(OnAccountUpdate listener) { public void setOnAccountListChangedListener(OnAccountUpdate listener) {
this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) { if (checkListeners()) {
switchToForeground(); switchToForeground();
} }
@ -999,6 +1013,7 @@ public class XmppConnectionService extends Service {
} }
public void setOnRosterUpdateListener(OnRosterUpdate listener) { public void setOnRosterUpdateListener(OnRosterUpdate listener) {
this.mNotificationService.deactivateGracePeriod();
if (checkListeners()) { if (checkListeners()) {
switchToForeground(); switchToForeground();
} }
@ -1111,13 +1126,11 @@ public class XmppConnectionService extends Service {
public void providePasswordForMuc(Conversation conversation, String password) { public void providePasswordForMuc(Conversation conversation, String password) {
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password); conversation.getMucOptions().setPassword(password);
if (conversation.getBookmark() != null if (conversation.getBookmark() != null) {
&& conversation.getMucOptions().isPasswordChanged()) { conversation.getBookmark().setAutojoin(true);
if (!conversation.getBookmark().autojoin()) {
conversation.getBookmark().setAutojoin(true);
}
pushBookmarks(conversation.getAccount()); pushBookmarks(conversation.getAccount());
} }
databaseBackend.updateConversation(conversation);
joinMuc(conversation); joinMuc(conversation);
} }
} }
@ -1266,7 +1279,7 @@ public class XmppConnectionService extends Service {
} }
} }
} }
notifyUi(conversation, false); updateConversationUi();
} }
public boolean renewSymmetricKey(Conversation conversation) { public boolean renewSymmetricKey(Conversation conversation) {
@ -1498,6 +1511,9 @@ public class XmppConnectionService extends Service {
thread.start(); thread.start();
scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2), scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2),
false); false);
} else {
account.getRoster().clearPresences();
account.setXmppConnection(null);
} }
} }
}).start(); }).start();
@ -1523,24 +1539,34 @@ public class XmppConnectionService extends Service {
public boolean markMessage(Account account, String recipient, String uuid, public boolean markMessage(Account account, String recipient, String uuid,
int status) { int status) {
for (Conversation conversation : getConversations()) { if (uuid == null) {
if (conversation.getContactJid().equals(recipient) return false;
&& conversation.getAccount().equals(account)) { } else {
return markMessage(conversation, uuid, status); 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, public boolean markMessage(Conversation conversation, String uuid,
int status) { int status) {
for (Message message : conversation.getMessages()) { if (uuid == null) {
if (message.getUuid().equals(uuid)) { return false;
markMessage(message, status); } else {
return true; 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) { public void markMessage(Message message, int status) {
@ -1575,15 +1601,6 @@ public class XmppConnectionService extends Service {
return getPreferences().getBoolean("indicate_received", false); return getPreferences().getBoolean("indicate_received", false);
} }
public void notifyUi(Conversation conversation, boolean notify) {
if (mOnConversationUpdate != null) {
mOnConversationUpdate.onConversationUpdate();
} else {
UIHelper.updateNotification(getApplicationContext(),
getConversations(), conversation, notify);
}
}
public void updateConversationUi() { public void updateConversationUi() {
if (mOnConversationUpdate != null) { if (mOnConversationUpdate != null) {
mOnConversationUpdate.onConversationUpdate(); mOnConversationUpdate.onConversationUpdate();
@ -1620,15 +1637,21 @@ public class XmppConnectionService extends Service {
return null; return null;
} }
public void markRead(Conversation conversation) { public void markRead(Conversation conversation, boolean calledByUi) {
mNotificationService.clear(conversation);
String id = conversation.getLatestMarkableMessageId();
conversation.markRead(); conversation.markRead();
String id = conversation.popLatestMarkableMessageId(); if (confirmMessages() && id != null && calledByUi) {
if (confirmMessages() && id != null) { Log.d(Config.LOGTAG, conversation.getAccount().getJid()
+ ": sending read marker for " + conversation.getName());
Account account = conversation.getAccount(); Account account = conversation.getAccount();
String to = conversation.getContactJid(); String to = conversation.getContactJid();
this.sendMessagePacket(conversation.getAccount(), this.sendMessagePacket(conversation.getAccount(),
mMessageGenerator.confirm(account, to, id)); mMessageGenerator.confirm(account, to, id));
} }
if (!calledByUi) {
updateConversationUi();
}
} }
public void failWaitingOtrMessages(Conversation conversation) { public void failWaitingOtrMessages(Conversation conversation) {
@ -1703,16 +1726,25 @@ public class XmppConnectionService extends Service {
} }
public void sendMessagePacket(Account account, MessagePacket packet) { 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) { 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, public void sendIqPacket(Account account, IqPacket packet,
OnIqPacketReceived callback) { OnIqPacketReceived callback) {
account.getXmppConnection().sendIqPacket(packet, callback); XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.sendIqPacket(packet, callback);
}
} }
public MessageGenerator getMessageGenerator() { public MessageGenerator getMessageGenerator() {
@ -1742,4 +1774,22 @@ public class XmppConnectionService extends Service {
public interface OnRosterUpdate { public interface OnRosterUpdate {
public void onRosterUpdate(); public void onRosterUpdate();
} }
public List<Contact> findContacts(String jid) {
ArrayList<Contact> contacts = new ArrayList<Contact>();
for (Account account : getAccounts()) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
Contact contact = account.getRoster()
.getContactAsShownInRoster(jid);
if (contact != null) {
contacts.add(contact);
}
}
}
return contacts;
}
public NotificationService getNotificationService() {
return this.mNotificationService;
}
} }

View file

@ -201,7 +201,7 @@ public class ConferenceDetailsActivity extends XmppActivity {
private void populateView() { private void populateView() {
mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48)); mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48));
setTitle(conversation.getName()); setTitle(conversation.getName());
mFullJid.setText(conversation.getContactJid().split("/",2)[0]); mFullJid.setText(conversation.getContactJid().split("/", 2)[0]);
mYourNick.setText(conversation.getMucOptions().getActualNick()); mYourNick.setText(conversation.getMucOptions().getActualNick());
mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); mRoleAffiliaton = (TextView) findViewById(R.id.muc_role);
if (conversation.getMucOptions().online()) { if (conversation.getMucOptions().online()) {

View file

@ -39,8 +39,6 @@ import eu.siacs.conversations.utils.UIHelper;
public class ContactDetailsActivity extends XmppActivity { public class ContactDetailsActivity extends XmppActivity {
public static final String ACTION_VIEW_CONTACT = "view_contact"; public static final String ACTION_VIEW_CONTACT = "view_contact";
protected ContactDetailsActivity activity = this;
private Contact contact; private Contact contact;
private String accountJid; private String accountJid;
@ -58,8 +56,8 @@ public class ContactDetailsActivity extends XmppActivity {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
activity.xmppConnectionService.deleteContactOnServer(contact); ContactDetailsActivity.this.xmppConnectionService.deleteContactOnServer(contact);
activity.finish(); ContactDetailsActivity.this.finish();
} }
}; };
@ -73,14 +71,14 @@ public class ContactDetailsActivity extends XmppActivity {
intent.putExtra(Intents.Insert.IM_PROTOCOL, intent.putExtra(Intents.Insert.IM_PROTOCOL,
CommonDataKinds.Im.PROTOCOL_JABBER); CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true); intent.putExtra("finishActivityOnSaveCompleted", true);
activity.startActivityForResult(intent, 0); ContactDetailsActivity.this.startActivityForResult(intent, 0);
} }
}; };
private OnClickListener onBadgeClick = new OnClickListener() { private OnClickListener onBadgeClick = new OnClickListener() {
@Override @Override
public void onClick(View v) { 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.setTitle(getString(R.string.action_add_phone_book));
builder.setMessage(getString(R.string.add_phone_book_text, builder.setMessage(getString(R.string.add_phone_book_text,
contact.getJid())); contact.getJid()));
@ -206,7 +204,7 @@ public class ContactDetailsActivity extends XmppActivity {
@Override @Override
public void onValueEdited(String value) { public void onValueEdited(String value) {
contact.setServerName(value); contact.setServerName(value);
activity.xmppConnectionService ContactDetailsActivity.this.xmppConnectionService
.pushContactToServer(contact); .pushContactToServer(contact);
populateView(); populateView();
} }
@ -354,7 +352,7 @@ public class ContactDetailsActivity extends XmppActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
PgpEngine pgp = activity.xmppConnectionService PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService
.getPgpEngine(); .getPgpEngine();
if (pgp != null) { if (pgp != null) {
PendingIntent intent = pgp.getIntentForKey(contact); PendingIntent intent = pgp.getIntentForKey(contact);

View file

@ -12,11 +12,11 @@ import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdat
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.ui.adapter.ConversationAdapter;
import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.UIHelper;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.annotation.SuppressLint;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
@ -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_CHOOSE_IMAGE = 0x0301;
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0303; 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 List<Conversation> conversationList = new ArrayList<Conversation>();
private Conversation selectedConversation = null; private Conversation selectedConversation = null;
@ -69,7 +74,6 @@ public class ConversationActivity extends XmppActivity implements
private boolean paneShouldBeOpen = true; private boolean paneShouldBeOpen = true;
private ArrayAdapter<Conversation> listAdapter; private ArrayAdapter<Conversation> listAdapter;
protected ConversationActivity activity = this;
private Toast prepareImageToast; private Toast prepareImageToast;
private Uri pendingImageUri = null; private Uri pendingImageUri = null;
@ -90,18 +94,52 @@ public class ConversationActivity extends XmppActivity implements
return this.listView; return this.listView;
} }
public SlidingPaneLayout getSlidingPaneLayout() {
return this.spl;
}
public boolean shouldPaneBeOpen() { public boolean shouldPaneBeOpen() {
return paneShouldBeOpen; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.fragment_conversations_overview);
listView = (ListView) findViewById(R.id.list); listView = (ListView) findViewById(R.id.list);
@ -122,63 +160,80 @@ public class ConversationActivity extends XmppActivity implements
setSelectedConversation(conversationList.get(position)); setSelectedConversation(conversationList.get(position));
swapConversationFragment(); swapConversationFragment();
} else { } else {
spl.closePane(); hideConversationsOverview();
} }
} }
}); });
spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout); mContentView = findViewById(R.id.content_view_spl);
spl.setParallaxDistance(150); if (mContentView == null) {
spl.setShadowResource(R.drawable.es_slidingpane_shadow); mContentView = findViewById(R.id.content_view_ll);
spl.setSliderFadeColor(0); }
spl.setPanelSlideListener(new PanelSlideListener() { 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 @Override
public void onPanelOpened(View arg0) { public void onPanelOpened(View arg0) {
paneShouldBeOpen = true; paneShouldBeOpen = true;
ActionBar ab = getActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(false);
ab.setHomeButtonEnabled(false);
ab.setTitle(R.string.app_name);
}
invalidateOptionsMenu();
hideKeyboard();
}
@Override
public void onPanelClosed(View arg0) {
paneShouldBeOpen = false;
if ((conversationList.size() > 0)
&& (getSelectedConversation() != null)) {
ActionBar ab = getActionBar(); ActionBar ab = getActionBar();
if (ab != null) { if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true); ab.setDisplayHomeAsUpEnabled(false);
ab.setHomeButtonEnabled(true); ab.setHomeButtonEnabled(false);
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE ab.setTitle(R.string.app_name);
|| activity.useSubjectToIdentifyConference()) {
ab.setTitle(getSelectedConversation().getName());
} else {
ab.setTitle(getSelectedConversation()
.getContactJid().split("/")[0]);
}
} }
invalidateOptionsMenu(); invalidateOptionsMenu();
if (!getSelectedConversation().isRead()) { hideKeyboard();
xmppConnectionService if (xmppConnectionServiceBound) {
.markRead(getSelectedConversation()); xmppConnectionService.getNotificationService()
UIHelper.updateNotification(getApplicationContext(), .setOpenConversation(null);
getConversationList(), null, false);
listView.invalidateViews();
} }
} }
}
@Override @Override
public void onPanelSlide(View arg0, float arg1) { public void onPanelClosed(View arg0) {
// TODO Auto-generated method stub 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
}
});
}
}
public void openConversation(Conversation conversation) {
ActionBar ab = getActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeButtonEnabled(true);
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE
|| ConversationActivity.this.useSubjectToIdentifyConference()) {
ab.setTitle(getSelectedConversation().getName());
} else {
ab.setTitle(getSelectedConversation().getContactJid()
.split("/")[0]);
} }
}); }
invalidateOptionsMenu();
if (xmppConnectionServiceBound) {
xmppConnectionService.getNotificationService().setOpenConversation(
conversation);
}
} }
@Override @Override
@ -198,7 +253,8 @@ public class ConversationActivity extends XmppActivity implements
.findItem(R.id.action_invite); .findItem(R.id.action_invite);
MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute); MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute);
if ((spl.isOpen() && (spl.isSlideable()))) { if (isConversationsOverviewVisable()
&& isConversationsOverviewHideable()) {
menuArchive.setVisible(false); menuArchive.setVisible(false);
menuMucDetails.setVisible(false); menuMucDetails.setVisible(false);
menuContactDetails.setVisible(false); menuContactDetails.setVisible(false);
@ -208,7 +264,7 @@ public class ConversationActivity extends XmppActivity implements
menuClearHistory.setVisible(false); menuClearHistory.setVisible(false);
menuMute.setVisible(false); menuMute.setVisible(false);
} else { } else {
menuAdd.setVisible(!spl.isSlideable()); menuAdd.setVisible(!isConversationsOverviewHideable());
if (this.getSelectedConversation() != null) { if (this.getSelectedConversation() != null) {
if (this.getSelectedConversation().getLatestMessage() if (this.getSelectedConversation().getLatestMessage()
.getEncryption() != Message.ENCRYPTION_NONE) { .getEncryption() != Message.ENCRYPTION_NONE) {
@ -296,6 +352,8 @@ public class ConversationActivity extends XmppActivity implements
int which) { int which) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_NONE); .setNextEncryption(Message.ENCRYPTION_NONE);
xmppConnectionService.databaseBackend
.updateConversation(conversation);
selectPresenceToAttachFile(attachmentChoice); selectPresenceToAttachFile(attachmentChoice);
} }
}); });
@ -315,7 +373,7 @@ public class ConversationActivity extends XmppActivity implements
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
spl.openPane(); showConversationsOverview();
return true; return true;
} else if (item.getItemId() == R.id.action_add) { } else if (item.getItemId() == R.id.action_add) {
startActivity(new Intent(this, StartConversationActivity.class)); startActivity(new Intent(this, StartConversationActivity.class));
@ -367,7 +425,7 @@ public class ConversationActivity extends XmppActivity implements
public void endConversation(Conversation conversation) { public void endConversation(Conversation conversation) {
conversation.setStatus(Conversation.STATUS_ARCHIVED); conversation.setStatus(Conversation.STATUS_ARCHIVED);
paneShouldBeOpen = true; paneShouldBeOpen = true;
spl.openPane(); showConversationsOverview();
xmppConnectionService.archiveConversation(conversation); xmppConnectionService.archiveConversation(conversation);
if (conversationList.size() > 0) { if (conversationList.size() > 0) {
setSelectedConversation(conversationList.get(0)); setSelectedConversation(conversationList.get(0));
@ -376,6 +434,7 @@ public class ConversationActivity extends XmppActivity implements
} }
} }
@SuppressLint("InflateParams")
protected void clearHistoryDialog(final Conversation conversation) { protected void clearHistoryDialog(final Conversation conversation) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.clear_conversation_history)); builder.setTitle(getString(R.string.clear_conversation_history));
@ -390,7 +449,7 @@ public class ConversationActivity extends XmppActivity implements
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
activity.xmppConnectionService ConversationActivity.this.xmppConnectionService
.clearConversationHistory(conversation); .clearConversationHistory(conversation);
if (endConversationCheckBox.isChecked()) { if (endConversationCheckBox.isChecked()) {
endConversation(conversation); endConversation(conversation);
@ -470,6 +529,8 @@ public class ConversationActivity extends XmppActivity implements
conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.setNextEncryption(Message.ENCRYPTION_NONE);
break; break;
} }
xmppConnectionService.databaseBackend
.updateConversation(conversation);
fragment.updateChatMsgHint(); fragment.updateChatMsgHint();
return true; return true;
} }
@ -523,6 +584,8 @@ public class ConversationActivity extends XmppActivity implements
+ (durations[which] * 1000); + (durations[which] * 1000);
} }
conversation.setMutedTill(till); conversation.setMutedTill(till);
ConversationActivity.this.xmppConnectionService.databaseBackend
.updateConversation(conversation);
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager() ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
.findFragmentByTag("conversation"); .findFragmentByTag("conversation");
if (selectedFragment != null) { if (selectedFragment != null) {
@ -550,8 +613,8 @@ public class ConversationActivity extends XmppActivity implements
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
if (!spl.isOpen()) { if (!isConversationsOverviewVisable()) {
spl.openPane(); showConversationsOverview();
return false; return false;
} }
} }
@ -599,62 +662,75 @@ public class ConversationActivity extends XmppActivity implements
xmppConnectionService.removeOnConversationListChangedListener(); xmppConnectionService.removeOnConversationListChangedListener();
xmppConnectionService.removeOnAccountListChangedListener(); xmppConnectionService.removeOnAccountListChangedListener();
xmppConnectionService.removeOnRosterUpdateListener(); xmppConnectionService.removeOnRosterUpdateListener();
xmppConnectionService.getNotificationService().setOpenConversation(
null);
} }
super.onStop(); 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 @Override
void onBackendConnected() { void onBackendConnected() {
this.registerListener(); 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(), attachImageToConversation(getSelectedConversation(),
pendingImageUri); pendingImageUri);
pendingImageUri = null; pendingImageUri = null;
} else {
pendingImageUri = null;
} }
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
}
if ((getIntent().getAction() != null) private void selectConversationByUuid(String uuid) {
&& (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) { for (int i = 0; i < conversationList.size(); ++i) {
if (getIntent().getType().equals( if (conversationList.get(i).getUuid().equals(uuid)) {
ConversationActivity.VIEW_CONVERSATION)) { setSelectedConversation(conversationList.get(i));
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);
} }
} }
} }
@ -778,7 +854,7 @@ public class ConversationActivity extends XmppActivity implements
@Override @Override
public void userInputRequried(PendingIntent pi, public void userInputRequried(PendingIntent pi,
Message message) { Message message) {
activity.runIntent(pi, ConversationActivity.this.runIntent(pi,
ConversationActivity.REQUEST_SEND_MESSAGE); ConversationActivity.REQUEST_SEND_MESSAGE);
} }

View file

@ -76,10 +76,11 @@ public class ConversationFragment extends Fragment {
@Override @Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_SEND) {
InputMethodManager imm = (InputMethodManager) v.getContext() InputMethodManager imm = (InputMethodManager) v.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE); .getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0); imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
sendMessage();
return true; return true;
} else { } else {
return false; return false;
@ -131,6 +132,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() { private OnClickListener enterPassword = new OnClickListener() {
@Override @Override
@ -169,6 +178,7 @@ public class ConversationFragment extends Fragment {
conversation, timestamp); conversation, timestamp);
messageList.clear(); messageList.clear();
messageList.addAll(conversation.getMessages()); messageList.addAll(conversation.getMessages());
updateStatusMessages();
messageListAdapter.notifyDataSetChanged(); messageListAdapter.notifyDataSetChanged();
if (size != 0) { if (size != 0) {
messagesLoaded = true; messagesLoaded = true;
@ -244,9 +254,7 @@ public class ConversationFragment extends Fragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (activity.getSlidingPaneLayout().isSlideable()) { activity.hideConversationsOverview();
activity.getSlidingPaneLayout().closePane();
}
} }
}); });
mEditMessage.setOnEditorActionListener(mEditorActionListener); mEditMessage.setOnEditorActionListener(mEditorActionListener);
@ -375,17 +383,10 @@ public class ConversationFragment extends Fragment {
int position = mEditMessage.length(); int position = mEditMessage.length();
Editable etext = mEditMessage.getText(); Editable etext = mEditMessage.getText();
Selection.setSelection(etext, position); Selection.setSelection(etext, position);
if (activity.getSlidingPaneLayout().isSlideable()) { if (activity.isConversationsOverviewHideable()) {
if (!activity.shouldPaneBeOpen()) { if (!activity.shouldPaneBeOpen()) {
activity.getSlidingPaneLayout().closePane(); activity.hideConversationsOverview();
activity.getActionBar().setDisplayHomeAsUpEnabled(true); activity.openConversation(conversation);
activity.getActionBar().setHomeButtonEnabled(true);
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
activity.getActionBar().setTitle(conversation.getName());
} else {
activity.getActionBar().setTitle(conversation.getContactJid().split("/")[0]);
}
activity.invalidateOptionsMenu();
} }
} }
if (this.conversation.getMode() == Conversation.MODE_MULTI) { if (this.conversation.getMode() == Conversation.MODE_MULTI) {
@ -437,6 +438,8 @@ public class ConversationFragment extends Fragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
conversation.setMutedTill(0); conversation.setMutedTill(0);
activity.xmppConnectionService.databaseBackend
.updateConversation(conversation);
updateMessages(); updateMessages();
} }
}); });
@ -492,6 +495,18 @@ public class ConversationFragment extends Fragment {
showSnackbar(R.string.conference_requires_password, showSnackbar(R.string.conference_requires_password,
R.string.enter_password, enterPassword); R.string.enter_password, enterPassword);
break; 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: default:
break; break;
} }
@ -500,9 +515,7 @@ public class ConversationFragment extends Fragment {
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
updateChatMsgHint(); updateChatMsgHint();
if (!activity.shouldPaneBeOpen()) { if (!activity.shouldPaneBeOpen()) {
activity.xmppConnectionService.markRead(conversation); activity.xmppConnectionService.markRead(conversation, true);
UIHelper.updateNotification(getActivity(),
activity.getConversationList(), null, false);
activity.updateConversationList(); activity.updateConversationList();
} }
this.updateSendButton(); this.updateSendButton();
@ -511,9 +524,7 @@ public class ConversationFragment extends Fragment {
private void messageSent() { private void messageSent() {
int size = this.messageList.size(); int size = this.messageList.size();
if (size >= 1 && this.messagesView.getLastVisiblePosition() != size - 1) { messagesView.setSelection(size - 1);
messagesView.setSelection(size - 1);
}
mEditMessage.setText(""); mEditMessage.setText("");
updateChatMsgHint(); updateChatMsgHint();
} }
@ -667,6 +678,8 @@ public class ConversationFragment extends Fragment {
int which) { int which) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_NONE); .setNextEncryption(Message.ENCRYPTION_NONE);
xmppService.databaseBackend
.updateConversation(conversation);
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage(message); xmppService.sendMessage(message);
messageSent(); messageSent();
@ -695,6 +708,8 @@ public class ConversationFragment extends Fragment {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_NONE); .setNextEncryption(Message.ENCRYPTION_NONE);
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.databaseBackend
.updateConversation(conversation);
xmppService.sendMessage(message); xmppService.sendMessage(message);
messageSent(); messageSent();
} }

View file

@ -1,8 +1,12 @@
package eu.siacs.conversations.ui; package eu.siacs.conversations.ui;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
@ -10,9 +14,11 @@ import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
@ -38,6 +44,7 @@ public class EditAccountActivity extends XmppActivity {
private TextView mSessionEst; private TextView mSessionEst;
private TextView mOtrFingerprint; private TextView mOtrFingerprint;
private TextView mOtrFingerprintHeadline; private TextView mOtrFingerprintHeadline;
private ImageButton mOtrFingerprintToClipboardButton;
private String jidToEdit; private String jidToEdit;
private Account mAccount; private Account mAccount;
@ -48,6 +55,12 @@ public class EditAccountActivity extends XmppActivity {
@Override @Override
public void onClick(View v) { 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())) { if (!Validator.isValidJid(mAccountJid.getText().toString())) {
mAccountJid.setError(getString(R.string.invalid_jid)); mAccountJid.setError(getString(R.string.invalid_jid));
mAccountJid.requestFocus(); mAccountJid.requestFocus();
@ -157,6 +170,25 @@ public class EditAccountActivity extends XmppActivity {
} }
}; };
private KnownHostsAdapter mKnownHostsAdapter; 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) { protected void finishInitialSetup(final Avatar avatar) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@ -197,6 +229,11 @@ public class EditAccountActivity extends XmppActivity {
this.mSaveButton.setEnabled(false); this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting); 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 { } else {
this.mSaveButton.setEnabled(true); this.mSaveButton.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setTextColor(getPrimaryTextColor());
@ -204,6 +241,10 @@ public class EditAccountActivity extends XmppActivity {
if (mAccount != null if (mAccount != null
&& mAccount.getStatus() == Account.STATUS_ONLINE) { && mAccount.getStatus() == Account.STATUS_ONLINE) {
this.mSaveButton.setText(R.string.save); this.mSaveButton.setText(R.string.save);
if (!accountInfoEdited()) {
this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor());
}
} else { } else {
this.mSaveButton.setText(R.string.connect); this.mSaveButton.setText(R.string.connect);
} }
@ -213,12 +254,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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_account); setContentView(R.layout.activity_edit_account);
this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
this.mAccountJid.addTextChangedListener(this.mTextWatcher);
this.mPassword = (EditText) findViewById(R.id.account_password); this.mPassword = (EditText) findViewById(R.id.account_password);
this.mPassword.addTextChangedListener(this.mTextWatcher);
this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm);
this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
this.mStats = (LinearLayout) findViewById(R.id.stats); this.mStats = (LinearLayout) findViewById(R.id.stats);
@ -228,6 +278,7 @@ public class EditAccountActivity extends XmppActivity {
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
this.mOtrFingerprintHeadline = (TextView) findViewById(R.id.otr_fingerprint_headline); this.mOtrFingerprintHeadline = (TextView) findViewById(R.id.otr_fingerprint_headline);
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
this.mSaveButton = (Button) findViewById(R.id.save_button); this.mSaveButton = (Button) findViewById(R.id.save_button);
this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button);
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
@ -255,7 +306,7 @@ public class EditAccountActivity extends XmppActivity {
this.jidToEdit = getIntent().getStringExtra("jid"); this.jidToEdit = getIntent().getStringExtra("jid");
if (this.jidToEdit != null) { if (this.jidToEdit != null) {
this.mRegisterNew.setVisibility(View.GONE); this.mRegisterNew.setVisibility(View.GONE);
getActionBar().setTitle(R.string.mgmt_account_edit); getActionBar().setTitle(jidToEdit);
} else { } else {
getActionBar().setTitle(R.string.action_add_account); getActionBar().setTitle(R.string.action_add_account);
} }
@ -324,13 +375,30 @@ public class EditAccountActivity extends XmppActivity {
} else { } else {
this.mServerInfoPep.setText(R.string.server_info_unavailable); this.mServerInfoPep.setText(R.string.server_info_unavailable);
} }
String fingerprint = this.mAccount final String fingerprint = this.mAccount
.getOtrFingerprint(xmppConnectionService); .getOtrFingerprint(xmppConnectionService);
if (fingerprint != null) { if (fingerprint != null) {
this.mOtrFingerprintHeadline.setVisibility(View.VISIBLE); this.mOtrFingerprintHeadline.setVisibility(View.VISIBLE);
this.mOtrFingerprint.setVisibility(View.VISIBLE); this.mOtrFingerprint.setVisibility(View.VISIBLE);
this.mOtrFingerprint.setText(fingerprint); 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 { } else {
this.mOtrFingerprintToClipboardButton.setVisibility(View.GONE);
this.mOtrFingerprint.setVisibility(View.GONE); this.mOtrFingerprint.setVisibility(View.GONE);
this.mOtrFingerprintHeadline.setVisibility(View.GONE); this.mOtrFingerprintHeadline.setVisibility(View.GONE);
} }
@ -343,4 +411,15 @@ public class EditAccountActivity extends XmppActivity {
this.mStats.setVisibility(View.GONE); 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;
}
} }

View file

@ -24,8 +24,6 @@ import android.widget.ListView;
public class ManageAccountActivity extends XmppActivity { public class ManageAccountActivity extends XmppActivity {
protected ManageAccountActivity activity = this;
protected Account selectedAccount = null; protected Account selectedAccount = null;
protected List<Account> accountList = new ArrayList<Account>(); protected List<Account> accountList = new ArrayList<Account>();
@ -72,7 +70,7 @@ public class ManageAccountActivity extends XmppActivity {
public void onCreateContextMenu(ContextMenu menu, View v, public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) { ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, 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; AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
this.selectedAccount = accountList.get(acmi.position); this.selectedAccount = accountList.get(acmi.position);
if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) { if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
@ -181,7 +179,7 @@ public class ManageAccountActivity extends XmppActivity {
} }
private void publishOpenPGPPublicKey(Account account) { private void publishOpenPGPPublicKey(Account account) {
if (activity.hasPgp()) { if (ManageAccountActivity.this.hasPgp()) {
announcePgp(account, null); announcePgp(account, null);
} else { } else {
this.showInstallPgpDialog(); this.showInstallPgpDialog();
@ -189,7 +187,7 @@ public class ManageAccountActivity extends XmppActivity {
} }
private void deleteAccount(final Account account) { 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.setTitle(getString(R.string.mgmt_account_are_you_sure));
builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text)); builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));

View file

@ -21,7 +21,7 @@ public class SettingsActivity extends XmppActivity implements
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mSettingsFragment = new SettingsFragment(); mSettingsFragment = new SettingsFragment();
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.replace(android.R.id.content,mSettingsFragment).commit(); .replace(android.R.id.content, mSettingsFragment).commit();
} }
@Override @Override
@ -34,12 +34,16 @@ public class SettingsActivity extends XmppActivity implements
super.onStart(); super.onStart();
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this); .registerOnSharedPreferenceChangeListener(this);
ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource"); ListPreference resources = (ListPreference) mSettingsFragment
if (resources!=null) { .findPreference("resource");
ArrayList<CharSequence> entries = new ArrayList<CharSequence>(Arrays.asList(resources.getEntries())); if (resources != null) {
entries.add(0,Build.MODEL); ArrayList<CharSequence> entries = new ArrayList<CharSequence>(
resources.setEntries(entries.toArray(new CharSequence[entries.size()])); Arrays.asList(resources.getEntries()));
resources.setEntryValues(entries.toArray(new CharSequence[entries.size()])); entries.add(0, Build.MODEL);
resources.setEntries(entries.toArray(new CharSequence[entries
.size()]));
resources.setEntryValues(entries.toArray(new CharSequence[entries
.size()]));
} }
} }

View file

@ -1,9 +1,12 @@
package eu.siacs.conversations.ui; package eu.siacs.conversations.ui;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import android.annotation.SuppressLint;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.ActionBar.Tab; import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener; import android.app.ActionBar.TabListener;
@ -14,6 +17,8 @@ import android.app.ListFragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v13.app.FragmentPagerAdapter; import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
@ -157,6 +162,8 @@ public class StartConversationActivity extends XmppActivity {
}); });
} }
}; };
private MenuItem mMenuSearchView;
private String mInitialJid;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -308,7 +315,8 @@ public class StartConversationActivity extends XmppActivity {
} }
protected void showCreateContactDialog() { @SuppressLint("InflateParams")
protected void showCreateContactDialog(String prefilledJid) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.create_contact); builder.setTitle(R.string.create_contact);
View dialogView = getLayoutInflater().inflate( View dialogView = getLayoutInflater().inflate(
@ -318,6 +326,9 @@ public class StartConversationActivity extends XmppActivity {
.findViewById(R.id.jid); .findViewById(R.id.jid);
jid.setAdapter(new KnownHostsAdapter(this, jid.setAdapter(new KnownHostsAdapter(this,
android.R.layout.simple_list_item_1, mKnownHosts)); android.R.layout.simple_list_item_1, mKnownHosts));
if (prefilledJid != null) {
jid.append(prefilledJid);
}
populateAccountSpinner(spinner); populateAccountSpinner(spinner);
builder.setView(dialogView); builder.setView(dialogView);
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
@ -348,8 +359,8 @@ public class StartConversationActivity extends XmppActivity {
jid.setError(getString(R.string.contact_already_exists)); jid.setError(getString(R.string.contact_already_exists));
} else { } else {
xmppConnectionService.createContact(contact); xmppConnectionService.createContact(contact);
switchToConversation(contact);
dialog.dismiss(); dialog.dismiss();
switchToConversation(contact);
} }
} else { } else {
jid.setError(getString(R.string.invalid_jid)); jid.setError(getString(R.string.invalid_jid));
@ -359,6 +370,7 @@ public class StartConversationActivity extends XmppActivity {
} }
@SuppressLint("InflateParams")
protected void showJoinConferenceDialog() { protected void showJoinConferenceDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.join_conference); builder.setTitle(R.string.join_conference);
@ -391,6 +403,10 @@ public class StartConversationActivity extends XmppActivity {
String conferenceJid = jid.getText().toString(); String conferenceJid = jid.getText().toString();
Account account = xmppConnectionService Account account = xmppConnectionService
.findAccountByJid(accountJid); .findAccountByJid(accountJid);
if (account == null) {
dialog.dismiss();
return;
}
if (bookmarkCheckBox.isChecked()) { if (bookmarkCheckBox.isChecked()) {
if (account.hasBookmarkFor(conferenceJid)) { if (account.hasBookmarkFor(conferenceJid)) {
jid.setError(getString(R.string.bookmark_already_exists)); jid.setError(getString(R.string.bookmark_already_exists));
@ -409,6 +425,7 @@ public class StartConversationActivity extends XmppActivity {
xmppConnectionService xmppConnectionService
.joinMuc(conversation); .joinMuc(conversation);
} }
dialog.dismiss();
switchToConversation(conversation); switchToConversation(conversation);
} }
} else { } else {
@ -418,6 +435,7 @@ public class StartConversationActivity extends XmppActivity {
if (!conversation.getMucOptions().online()) { if (!conversation.getMucOptions().online()) {
xmppConnectionService.joinMuc(conversation); xmppConnectionService.joinMuc(conversation);
} }
dialog.dismiss();
switchToConversation(conversation); switchToConversation(conversation);
} }
} else { } else {
@ -449,9 +467,9 @@ public class StartConversationActivity extends XmppActivity {
.findItem(R.id.action_create_contact); .findItem(R.id.action_create_contact);
MenuItem menuCreateConference = (MenuItem) menu MenuItem menuCreateConference = (MenuItem) menu
.findItem(R.id.action_join_conference); .findItem(R.id.action_join_conference);
MenuItem menuSearchView = (MenuItem) menu.findItem(R.id.action_search); mMenuSearchView = (MenuItem) menu.findItem(R.id.action_search);
menuSearchView.setOnActionExpandListener(mOnActionExpandListener); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
View mSearchView = menuSearchView.getActionView(); View mSearchView = mMenuSearchView.getActionView();
mSearchEditText = (EditText) mSearchView mSearchEditText = (EditText) mSearchView
.findViewById(R.id.search_field); .findViewById(R.id.search_field);
mSearchEditText.addTextChangedListener(mSearchTextWatcher); mSearchEditText.addTextChangedListener(mSearchTextWatcher);
@ -460,6 +478,11 @@ public class StartConversationActivity extends XmppActivity {
} else { } else {
menuCreateContact.setVisible(false); menuCreateContact.setVisible(false);
} }
if (mInitialJid != null) {
mMenuSearchView.expandActionView();
mSearchEditText.append(mInitialJid);
filter(mInitialJid);
}
return true; return true;
} }
@ -467,7 +490,7 @@ public class StartConversationActivity extends XmppActivity {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_create_contact: case R.id.action_create_contact:
showCreateContactDialog(); showCreateContactDialog(null);
break; break;
case R.id.action_join_conference: case R.id.action_join_conference:
showJoinConferenceDialog(); showJoinConferenceDialog();
@ -486,13 +509,8 @@ public class StartConversationActivity extends XmppActivity {
} }
@Override @Override
void onBackendConnected() { protected void onBackendConnected() {
xmppConnectionService.setOnRosterUpdateListener(this.onRosterUpdate); xmppConnectionService.setOnRosterUpdateListener(this.onRosterUpdate);
if (mSearchEditText != null) {
filter(mSearchEditText.getText().toString());
} else {
filter(null);
}
this.mActivatedAccounts.clear(); this.mActivatedAccounts.clear();
for (Account account : xmppConnectionService.getAccounts()) { for (Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.STATUS_DISABLED) { if (account.getStatus() != Account.STATUS_DISABLED) {
@ -502,6 +520,55 @@ public class StartConversationActivity extends XmppActivity {
this.mKnownHosts = xmppConnectionService.getKnownHosts(); this.mKnownHosts = xmppConnectionService.getKnownHosts();
this.mKnownConferenceHosts = xmppConnectionService this.mKnownConferenceHosts = xmppConnectionService
.getKnownConferenceHosts(); .getKnownConferenceHosts();
if (!startByIntent()) {
if (mSearchEditText != null) {
filter(mSearchEditText.getText().toString());
} else {
filter(null);
}
}
}
protected boolean startByIntent() {
if (getIntent() != null
&& Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
try {
String jid = URLDecoder.decode(
getIntent().getData().getEncodedPath(), "UTF-8").split(
"/")[1];
setIntent(null);
return handleJid(jid);
} catch (UnsupportedEncodingException e) {
setIntent(null);
return false;
}
} else if (getIntent() != null
&& Intent.ACTION_VIEW.equals(getIntent().getAction())) {
Uri uri = getIntent().getData();
String jid = uri.getSchemeSpecificPart().split("\\?")[0];
return handleJid(jid);
}
return false;
}
private boolean handleJid(String jid) {
List<Contact> contacts = xmppConnectionService.findContacts(jid);
if (contacts.size() == 0) {
showCreateContactDialog(jid);
return false;
} else if (contacts.size() == 1) {
switchToConversation(contacts.get(0));
return true;
} else {
if (mMenuSearchView != null) {
mMenuSearchView.expandActionView();
mSearchEditText.setText(jid);
filter(jid);
} else {
mInitialJid = jid;
}
return true;
}
} }
protected void filter(String needle) { protected void filter(String needle) {

View file

@ -15,6 +15,7 @@ import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.ExceptionHelper;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -58,6 +59,7 @@ public abstract class XmppActivity extends Activity {
protected int mPrimaryTextColor; protected int mPrimaryTextColor;
protected int mSecondaryTextColor; protected int mSecondaryTextColor;
protected int mSecondaryBackgroundColor;
protected int mColorRed; protected int mColorRed;
protected int mColorOrange; protected int mColorOrange;
protected int mColorGreen; protected int mColorGreen;
@ -206,6 +208,8 @@ public abstract class XmppActivity extends Activity {
mColorOrange = getResources().getColor(R.color.orange); mColorOrange = getResources().getColor(R.color.orange);
mColorGreen = getResources().getColor(R.color.green); mColorGreen = getResources().getColor(R.color.green);
mPrimaryColor = getResources().getColor(R.color.primary); mPrimaryColor = getResources().getColor(R.color.primary);
mSecondaryBackgroundColor = getResources().getColor(
R.color.secondarybackground);
if (getPreferences().getBoolean("use_larger_font", false)) { if (getPreferences().getBoolean("use_larger_font", false)) {
setTheme(R.style.ConversationsTheme_LargerText); setTheme(R.style.ConversationsTheme_LargerText);
} }
@ -245,6 +249,7 @@ public abstract class XmppActivity extends Activity {
| Intent.FLAG_ACTIVITY_CLEAR_TOP); | Intent.FLAG_ACTIVITY_CLEAR_TOP);
} }
startActivity(viewConversationIntent); startActivity(viewConversationIntent);
finish();
} }
public void switchToContactDetails(Contact contact) { public void switchToContactDetails(Contact contact) {
@ -292,6 +297,8 @@ public abstract class XmppActivity extends Activity {
if (conversation != null) { if (conversation != null) {
conversation conversation
.setNextEncryption(Message.ENCRYPTION_PGP); .setNextEncryption(Message.ENCRYPTION_PGP);
xmppConnectionService.databaseBackend
.updateConversation(conversation);
} }
} }
@ -389,6 +396,7 @@ public abstract class XmppActivity extends Activity {
quickEdit(previousValue, callback, true); quickEdit(previousValue, callback, true);
} }
@SuppressLint("InflateParams")
private void quickEdit(final String previousValue, private void quickEdit(final String previousValue,
final OnValueEdited callback, boolean password) { final OnValueEdited callback, boolean password) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
@ -514,6 +522,10 @@ public abstract class XmppActivity extends Activity {
return this.mPrimaryColor; return this.mPrimaryColor;
} }
public int getSecondaryBackgroundColor() {
return this.mSecondaryBackgroundColor;
}
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference; private final WeakReference<ImageView> imageViewReference;
private Message message = null; private Message message = null;

View file

@ -40,9 +40,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
Conversation conv = getItem(position); Conversation conv = getItem(position);
if (this.activity instanceof ConversationActivity) { if (this.activity instanceof ConversationActivity) {
ConversationActivity activity = (ConversationActivity) this.activity; ConversationActivity activity = (ConversationActivity) this.activity;
if (!activity.getSlidingPaneLayout().isSlideable()) { if (!activity.isConversationsOverviewHideable()) {
if (conv == activity.getSelectedConversation()) { if (conv == activity.getSelectedConversation()) {
view.setBackgroundColor(0xffdddddd); view.setBackgroundColor(activity
.getSecondaryBackgroundColor());
} else { } else {
view.setBackgroundColor(Color.TRANSPARENT); view.setBackgroundColor(Color.TRANSPARENT);
} }
@ -52,7 +53,8 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
} }
TextView convName = (TextView) view TextView convName = (TextView) view
.findViewById(R.id.conversation_name); .findViewById(R.id.conversation_name);
if (conv.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { if (conv.getMode() == Conversation.MODE_SINGLE
|| activity.useSubjectToIdentifyConference()) {
convName.setText(conv.getName()); convName.setText(conv.getName());
} else { } else {
convName.setText(conv.getContactJid().split("/")[0]); convName.setText(conv.getContactJid().split("/")[0]);

View file

@ -24,7 +24,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ListItem item = getItem(position); ListItem item = getItem(position);
if (view == null) { if (view == null) {
view = (View) inflater.inflate(R.layout.contact, null); view = (View) inflater.inflate(R.layout.contact, parent, false);
} }
TextView name = (TextView) view.findViewById(R.id.contact_display_name); TextView name = (TextView) view.findViewById(R.id.contact_display_name);
TextView jid = (TextView) view.findViewById(R.id.contact_jid); TextView jid = (TextView) view.findViewById(R.id.contact_jid);

View file

@ -417,7 +417,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder = (ViewHolder) view.getTag(); 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; return view;
} }

View file

@ -30,17 +30,17 @@ public class DNSHelper {
String dns[] = client.findDNS(); String dns[] = client.findDNS();
if (dns != null) { if (dns != null) {
// we have a list of DNS servers, let's go
for (String dnsserver : dns) { for (String dnsserver : dns) {
InetAddress ip = InetAddress.getByName(dnsserver); InetAddress ip = InetAddress.getByName(dnsserver);
Bundle b = queryDNS(host, ip); Bundle b = queryDNS(host, ip);
if (b.containsKey("name")) { if (b.containsKey("name")) {
return b; 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")); return queryDNS(host, InetAddress.getByName("8.8.8.8"));
} }
@ -164,10 +164,8 @@ public class DNSHelper {
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
Log.d(Config.LOGTAG, "timeout during dns");
namePort.putString("error", "timeout"); namePort.putString("error", "timeout");
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG, "unhandled exception in sub project");
namePort.putString("error", "unhandled"); namePort.putString("error", "unhandled");
} }
return namePort; return namePort;

View file

@ -7,16 +7,15 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.Matcher;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.entities.MucOptions.User;
import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.ui.ManageAccountActivity;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Notification; import android.app.Notification;
@ -26,7 +25,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -34,13 +32,11 @@ import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder; import android.support.v4.app.TaskStackBuilder;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.text.Html;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -330,177 +326,6 @@ public class UIHelper {
mNotificationManager.notify(1111, notification); mNotificationManager.notify(1111, notification);
} }
private static Pattern generateNickHighlightPattern(String nick) {
// We expect a word boundary, i.e. space or start of string, followed by
// the
// nick (matched in case-insensitive manner), followed by optional
// punctuation (for example "bob: i disagree" or "how are you alice?"),
// followed by another word boundary.
return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
public static void updateNotification(Context context,
List<Conversation> conversations, Conversation currentCon,
boolean notify) {
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(context);
boolean showNofifications = preferences.getBoolean("show_notification",
true);
boolean vibrate = preferences.getBoolean("vibrate_on_notification",
true);
boolean alwaysNotify = preferences.getBoolean(
"notify_in_conversation_when_highlighted", false);
if (!showNofifications) {
mNotificationManager.cancel(2342);
return;
}
String targetUuid = "";
if ((currentCon != null)
&& (currentCon.getMode() == Conversation.MODE_MULTI)
&& (!alwaysNotify) && notify) {
String nick = currentCon.getMucOptions().getActualNick();
Pattern highlight = generateNickHighlightPattern(nick);
Matcher m = highlight.matcher(currentCon.getLatestMessage()
.getBody());
notify = m.find()
|| (currentCon.getLatestMessage().getType() == Message.TYPE_PRIVATE);
}
List<Conversation> unread = new ArrayList<Conversation>();
for (Conversation conversation : conversations) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
if ((!conversation.isRead())
&& ((wasHighlightedOrPrivate(conversation) || (alwaysNotify)))) {
unread.add(conversation);
}
} else {
if (!conversation.isRead()) {
unread.add(conversation);
}
}
}
String ringtone = preferences.getString("notification_ringtone", null);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
context);
if (unread.size() == 0) {
mNotificationManager.cancel(2342);
return;
} else if (unread.size() == 1) {
Conversation conversation = unread.get(0);
targetUuid = conversation.getUuid();
mBuilder.setLargeIcon(conversation.getImage(context, 64));
mBuilder.setContentTitle(conversation.getName());
if (notify) {
mBuilder.setTicker(conversation.getLatestMessage()
.getReadableBody(context));
}
StringBuilder bigText = new StringBuilder();
List<Message> messages = conversation.getMessages();
String firstLine = "";
for (int i = messages.size() - 1; i >= 0; --i) {
if (!messages.get(i).isRead()) {
if (i == messages.size() - 1) {
firstLine = messages.get(i).getReadableBody(context);
bigText.append(firstLine);
} else {
firstLine = messages.get(i).getReadableBody(context);
bigText.insert(0, firstLine + "\n");
}
} else {
break;
}
}
mBuilder.setContentText(firstLine);
mBuilder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(bigText.toString()));
} else {
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(unread.size() + " "
+ context.getString(R.string.unread_conversations));
StringBuilder names = new StringBuilder();
for (int i = 0; i < unread.size(); ++i) {
targetUuid = unread.get(i).getUuid();
if (i < unread.size() - 1) {
names.append(unread.get(i).getName() + ", ");
} else {
names.append(unread.get(i).getName());
}
style.addLine(Html.fromHtml("<b>"
+ unread.get(i).getName()
+ "</b> "
+ unread.get(i).getLatestMessage()
.getReadableBody(context)));
}
mBuilder.setContentTitle(unread.size() + " "
+ context.getString(R.string.unread_conversations));
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
}
if ((currentCon != null) && (notify)) {
targetUuid = currentCon.getUuid();
}
if (unread.size() != 0) {
mBuilder.setSmallIcon(R.drawable.ic_notification);
if (notify) {
if (vibrate) {
int dat = 70;
long[] pattern = { 0, 3 * dat, dat, dat };
mBuilder.setVibrate(pattern);
}
mBuilder.setLights(0xffffffff, 2000, 4000);
if (ringtone != null) {
mBuilder.setSound(Uri.parse(ringtone));
}
}
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(ConversationActivity.class);
Intent viewConversationIntent = new Intent(context,
ConversationActivity.class);
viewConversationIntent.setAction(Intent.ACTION_VIEW);
viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
targetUuid);
viewConversationIntent
.setType(ConversationActivity.VIEW_CONVERSATION);
stackBuilder.addNextIntent(viewConversationIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(resultPendingIntent);
Notification notification = mBuilder.build();
mNotificationManager.notify(2342, notification);
}
}
private static boolean wasHighlightedOrPrivate(Conversation conversation) {
List<Message> messages = conversation.getMessages();
String nick = conversation.getMucOptions().getActualNick();
Pattern highlight = generateNickHighlightPattern(nick);
for (int i = messages.size() - 1; i >= 0; --i) {
if (messages.get(i).isRead()) {
break;
} else {
Matcher m = highlight.matcher(messages.get(i).getBody());
if (m.find()
|| messages.get(i).getType() == Message.TYPE_PRIVATE) {
return true;
}
}
}
return false;
}
public static void prepareContactBadge(final Activity activity, public static void prepareContactBadge(final Activity activity,
QuickContactBadge badge, final Contact contact, Context context) { QuickContactBadge badge, final Contact contact, Context context) {
if (contact.getSystemAccount() != null) { if (contact.getSystemAccount() != null) {
@ -511,6 +336,7 @@ public class UIHelper {
badge.setImageBitmap(contact.getImage(72, context)); badge.setImageBitmap(contact.getImage(72, context));
} }
@SuppressLint("InflateParams")
public static AlertDialog getVerifyFingerprintDialog( public static AlertDialog getVerifyFingerprintDialog(
final ConversationActivity activity, final ConversationActivity activity,
final Conversation conversation, final View msg) { final Conversation conversation, final View msg) {

View file

@ -166,8 +166,14 @@ public class XmppConnection implements Runnable {
+ ":" + srvRecordPort); + ":" + srvRecordPort);
socket = new Socket(srvRecordServer, srvRecordPort); socket = new Socket(srvRecordServer, srvRecordPort);
} }
} else { } else if (namePort.containsKey("error")
&& "nosrv".equals(namePort.getString("error", null))) {
socket = new Socket(account.getServer(), 5222); 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(); OutputStream out = socket.getOutputStream();
tagWriter.setOutputStream(out); tagWriter.setOutputStream(out);
@ -307,7 +313,8 @@ public class XmppConnection implements Runnable {
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }
changeStatus(Account.STATUS_ONLINE); sendInitialPing();
} else if (nextTag.isStart("r")) { } else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag); tagReader.readElement(nextTag);
AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); 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) private Element processPacket(Tag currentTag, int packetType)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
Element element; Element element;
@ -372,8 +395,11 @@ public class XmppConnection implements Runnable {
while (!nextTag.isEnd(element.getName())) { while (!nextTag.isEnd(element.getName())) {
if (!nextTag.isNo()) { if (!nextTag.isNo()) {
Element child = tagReader.readElement(nextTag); Element child = tagReader.readElement(nextTag);
if ((packetType == PACKET_IQ) String type = currentTag.getAttribute("type");
&& ("jingle".equals(child.getName()))) { if (packetType == PACKET_IQ
&& "jingle".equals(child.getName())
&& ("set".equalsIgnoreCase(type) || "get"
.equalsIgnoreCase(type))) {
element = new JinglePacket(); element = new JinglePacket();
element.setAttributes(currentTag.getAttributes()); element.setAttributes(currentTag.getAttributes());
} }
@ -410,7 +436,9 @@ public class XmppConnection implements Runnable {
} }
packetCallbacks.remove(packet.getId()); 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); this.unregisteredIqListener.onIqPacketReceived(account, packet);
} }
} }
@ -657,7 +685,7 @@ public class XmppConnection implements Runnable {
if (bind != null) { if (bind != null) {
Element jid = bind.findChild("jid"); Element jid = bind.findChild("jid");
if (jid != null && jid.getContent() != null) { if (jid != null && jid.getContent() != null) {
account.setResource(jid.getContent().split("/",2)[1]); account.setResource(jid.getContent().split("/", 2)[1]);
if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
smVersion = 3; smVersion = 3;
EnablePacket enable = new EnablePacket(smVersion); EnablePacket enable = new EnablePacket(smVersion);
@ -677,7 +705,7 @@ public class XmppConnection implements Runnable {
if (bindListener != null) { if (bindListener != null) {
bindListener.onBind(account); bindListener.onBind(account);
} }
changeStatus(Account.STATUS_ONLINE); sendInitialPing();
} else { } else {
disconnect(true); disconnect(true);
} }
@ -882,8 +910,7 @@ public class XmppConnection implements Runnable {
} }
public void disconnect(boolean force) { public void disconnect(boolean force) {
changeStatus(Account.STATUS_OFFLINE); Log.d(Config.LOGTAG, account.getJid()+": disconnecting");
Log.d(Config.LOGTAG, "disconnecting");
try { try {
if (force) { if (force) {
socket.close(); socket.close();
@ -901,6 +928,7 @@ public class XmppConnection implements Runnable {
Thread.sleep(100); Thread.sleep(100);
} }
tagWriter.writeTag(Tag.end("stream:stream")); tagWriter.writeTag(Tag.end("stream:stream"));
socket.close();
} catch (IOException e) { } catch (IOException e) {
Log.d(Config.LOGTAG, Log.d(Config.LOGTAG,
"io exception during disconnect"); "io exception during disconnect");

View file

@ -88,8 +88,8 @@ public class JingleConnection implements Downloadable {
sendSuccess(); sendSuccess();
if (acceptedAutomatically) { if (acceptedAutomatically) {
message.markUnread(); message.markUnread();
JingleConnection.this.mXmppConnectionService.notifyUi( JingleConnection.this.mXmppConnectionService
message.getConversation(), true); .getNotificationService().push(message);
} }
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
@ -256,12 +256,12 @@ public class JingleConnection implements Downloadable {
this.status = STATUS_INITIATED; this.status = STATUS_INITIATED;
Conversation conversation = this.mXmppConnectionService Conversation conversation = this.mXmppConnectionService
.findOrCreateConversation(account, .findOrCreateConversation(account,
packet.getFrom().split("/",2)[0], false); packet.getFrom().split("/", 2)[0], false);
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
this.message.setType(Message.TYPE_IMAGE); this.message.setType(Message.TYPE_IMAGE);
this.message.setStatus(Message.STATUS_RECEIVED_OFFER); this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
this.message.setDownloadable(this); this.message.setDownloadable(this);
String[] fromParts = packet.getFrom().split("/",2); String[] fromParts = packet.getFrom().split("/", 2);
this.message.setPresence(fromParts[1]); this.message.setPresence(fromParts[1]);
this.account = account; this.account = account;
this.initiator = packet.getFrom(); this.initiator = packet.getFrom();
@ -319,8 +319,8 @@ public class JingleConnection implements Downloadable {
+ " allowed size:" + " allowed size:"
+ this.mJingleConnectionManager + this.mJingleConnectionManager
.getAutoAcceptFileSize()); .getAutoAcceptFileSize());
this.mXmppConnectionService this.mXmppConnectionService.getNotificationService()
.notifyUi(conversation, true); .push(message);
} }
this.file = this.mXmppConnectionService.getFileBackend() this.file = this.mXmppConnectionService.getFileBackend()
.getJingleFile(message, false); .getJingleFile(message, false);