Merge https://github.com/siacs/Conversations into development

Beriain created

Change summary

AndroidManifest.xml                                            |   4 
CHANGELOG.md                                                   |   7 
README.md                                                      |   2 
res/layout/otr_fingerprint.xml                                 |  10 
res/menu/conversations.xml                                     |   4 
res/values-de/strings.xml                                      |   2 
res/values-es/strings.xml                                      |  75 +
res/values/strings.xml                                         |  36 
res/xml/preferences.xml                                        |  44 
src/eu/siacs/conversations/crypto/OnPgpEngineResult.java       |  11 
src/eu/siacs/conversations/crypto/PgpEngine.java               | 137 +-
src/eu/siacs/conversations/entities/Message.java               |  16 
src/eu/siacs/conversations/persistance/FileBackend.java        |  12 
src/eu/siacs/conversations/services/XmppConnectionService.java |  48 
src/eu/siacs/conversations/ui/ContactDetailsActivity.java      |  29 
src/eu/siacs/conversations/ui/ConversationActivity.java        | 143 +++
src/eu/siacs/conversations/ui/ConversationFragment.java        |  65 -
src/eu/siacs/conversations/ui/ManageAccountActivity.java       |  10 
src/eu/siacs/conversations/ui/UiCallback.java                  |   9 
src/eu/siacs/conversations/ui/XmppActivity.java                |  32 
src/eu/siacs/conversations/utils/UIHelper.java                 |   8 
21 files changed, 439 insertions(+), 265 deletions(-)

Detailed changes

AndroidManifest.xml 馃敆

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="eu.siacs.conversations"
-    android:versionCode="9"
-    android:versionName="0.1.3" >
+    android:versionCode="11"
+    android:versionName="0.2" >
 
     <uses-sdk
         android:minSdkVersion="14"

CHANGELOG.md 馃敆

@@ -1,5 +1,12 @@
 ###Changelog
 
+####Version 0.2
+* Image file transfer
+* Better integration with OpenKeychain (PGP encryption)
+* Nicer conversation tiles for conferences
+* Ability to clear conversation history
+* A lot of bug fixes and code clean up
+
 ####Version 0.1.3
 * Switched to minidns library to resolve SRV records
 * Faster DNS in some cases

README.md 馃敆

@@ -1,5 +1,5 @@
 #Conversations
-Conversations is an open source XMPP (formally known as Jabber) client for
+Conversations is an open source XMPP (formerly known as Jabber) client for
 Android 4.0+ smart phones.
 [![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
 

res/layout/otr_fingerprint.xml 馃敆

@@ -5,12 +5,20 @@
     android:orientation="vertical"
     android:padding="8dp">
 
+    <TextView
+        android:id="@+id/otr_no_fingerprint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="20sp"
+        android:text="@string/no_otr_fingerprint"
+        android:visibility="visible"/>
+    
     <TextView
         android:id="@+id/otr_fingerprint"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textSize="20sp"
         android:typeface="monospace"
-        android:text="@string/no_otr_fingerprint"/>
+        android:visibility="gone"/>
 
 </LinearLayout>

res/menu/conversations.xml 馃敆

@@ -4,13 +4,13 @@
         android:id="@+id/action_add"
         android:orderInCategory="10"
         android:icon="@drawable/ic_action_add"
-        android:showAsAction="always"
+        android:showAsAction="ifRoom"
         android:title="@string/action_add" />
     
     <item
         android:id="@+id/action_security"
         android:orderInCategory="20"
-        android:showAsAction="always"
+        android:showAsAction="ifRoom"
         android:icon="@drawable/ic_action_unsecure"
         android:title="@string/action_secure" />
     

res/values-de/strings.xml 馃敆

@@ -28,7 +28,7 @@
     <string name="share_with">Teile mit&#8230;</string>
     <string name="ask_again"><u>Klick um noch einmal zu fragen</u></string>
     <string name="show_otr_key">OTR Fingerabdruck</string>
-    <string name="no_otr_fingerprint">Es wurde noch kein OTR Fingerabdruck erzeugt. Beginne einfach eine verschl眉sselte Unterhaltung an.</string>
+    <string name="no_otr_fingerprint">Es wurde noch kein OTR-Fingerabdruck erzeugt. Beginne einfach eine verschl眉sselte Unterhaltung um einen Fingerabdruck zu erzeugen.</string>
     <string name="start_conversation">Beginne Unterhaltung</string>
     <string name="invite_contacts">Kontakte einladen</string>
     <string name="invite_contacts_to_existing">Lade zu bestehender Konferenz ein</string>

res/values-es/strings.xml 馃敆

@@ -2,10 +2,10 @@
 <resources>
 
     <string name="app_name">Conversations</string>
-    <string name="action_settings">Configuraci贸n</string>
+    <string name="action_settings">Ajustes</string>
     <string name="action_add">Nueva conversaci贸n</string>
     <string name="action_accounts">Gestionar cuentas</string>
-    <string name="action_end_conversation">Terminar esta conversaci贸n</string>
+    <string name="action_end_conversation">Terminar conversaci贸n</string>
     <string name="action_contact_details">Detalles del contacto</string>
     <string name="action_muc_details">Detalles de la conferencia</string>
     <string name="action_secure">Conversaci贸n segura</string>
@@ -21,66 +21,75 @@
     <string name="participant">Participante</string>
     <string name="visitor">Visitante</string>
     <string name="enter_new_name">Introduce un nuevo nombre:</string>
-    <string name="remove_contact_text">Quieres eliminar a %s de tu lista. La conversaci贸n asociada a esta cuenta no se eliminar谩.</string>
-    <string name="untrusted_cert_hint">El servidor %s te present贸 con un certificado no confiable, posiblemente auto firmado.</string>
-    <string name="account_info">Info del servidor</string>
-    <string name="register_account">Registrar nueva cuenta en el servidor</string>
+    <string name="remove_contact_text">驴Quieres eliminar a %s de tu lista?. La conversaci贸n asociada a esta cuenta no se eliminar谩.</string>
+    <string name="untrusted_cert_hint">El servidor %s presenta un certificado no confiable, posiblemente auto firmado.</string>
+    <string name="account_info">Informaci贸n del servidor</string>
+    <string name="register_account">Registrar nueva cuenta en servidor</string>
     <string name="share_with">Compartir con</string>
     <string name="ask_again"><u>Pulsa para preguntar otra vez</u></string>
     <string name="show_otr_key">Huella dactilar OTR</string>
-    <string name="no_otr_fingerprint">No se ha generado una huella dactilar OTR Fingerprint. Simplemente contin煤a y comienza una conversaci贸n encriptada</string>
+    <string name="no_otr_fingerprint">No se ha generado una huella dactilar OTR. Simplemente contin煤a y comienza una conversaci贸n encriptada</string>
     <string name="start_conversation">Comenzar conversaci贸n</string>
     <string name="invite_contacts">Invitar contactos</string>
     <string name="invite_contacts_to_existing">Invitar a conferencia existente</string>
     <string name="new_conference">Crear nueva conferencia</string>
     <string name="cancel">Cancelar</string>
     <string name="create_invite">Crear \u0026 Invitar</string>
-	<string name="new_conference_explained">驴Quieres crear una nueva conferencia con una direcci贸n generada aleatoriamente e invitar a los contactos seleccionados a ella?</string>
+    <string name="new_conference_explained">驴Quieres crear una nueva conferencia con una direcci贸n generada aleatoriamente e invitar a los contactos seleccionados a ella?</string>
     <string name="no_open_mucs">No hay conferencias existentes</string>
     <string name="invitation_sent">Invitaci贸n enviada</string>
-    <string name="account_offline">Cuenta fuera de l铆nea</string>
-    <string name="cant_invite_while_offline">Tienes que estar en l铆nea para invitar a gente a las conferencias</string>
+    <string name="account_offline">Cuenta desconectada</string>
+    <string name="cant_invite_while_offline">Debes estar conectado para invitar a contactos a la conferencia</string>
     <string name="crash_report_title">Conversations se ha detenido.</string>
-	<string name="crash_report_message">Enviando volcados de pilas ayudas al continuo desarrollo de Conversations\n<b>Aviso:</b> Esto usar谩 tu cuenta XMPP para enviar el volcado de pila al desarrollador.</string>
+    <string name="crash_report_message">Enviando volcados de pilas ayudas al desarrollo de Conversations\n<b>Aviso:</b> Esto usar谩 tu cuenta XMPP para enviar el volcado de pila al desarrollador.</string>
     <string name="send_now">Enviar ahora</string>
     <string name="send_never">No preguntar de nuevo</string>
     <string name="problem_connecting_to_account">No se ha podido conectar a la cuenta</string>
     <string name="problem_connecting_to_accounts">No se ha podido conectar a m煤ltiples cuentas</string>
-    <string name="touch_to_fix">Toca aqu铆 para gestionar tus cuentas</string>
-    <string name="attach_file">Adjuntar archivo</string>
+    <string name="touch_to_fix">Pulsa aqu铆 para gestionar tus cuentas</string>
+    <string name="attach_file">Enviar archivo</string>
     <string name="not_in_roster">El contacto no est谩 en tu lista. 驴Quieres a帽adirlo?</string>
     <string name="add_contact">A帽adir contacto</string>
-    <string name="send_failed">entrega infructuosa</string>
+    <string name="send_failed">Error al enviar</string>
     <string name="send_rejected">rechazado</string>
     <string name="receiving_image">Recibiendo archivo de imagen. Espera por favor&#8230;</string>
-    <string name="preparing_image">Preparando imagen para transmisi贸n. Espera por favor&#8230;</string>
-    <string name="action_clear_history">Limpiar historia</string>
-    <string name="clear_conversation_history">Limpiar historia de conversaci贸n</string>
-	<string name="clear_histor_msg">驴Quieres borrar todos los mensajes de esta conversaci贸n?\n\n<b>Aviso:</b> Esto no tendr谩 influencia en los mensajes guardados en otros dispositivos o servidores.</string>
+    <string name="preparing_image">Preparando imagen para enviar</string>
+    <string name="action_clear_history">Limpiar historial</string>
+    <string name="clear_conversation_history">Limpiar historial de conversaci贸n</string>
+    <string name="clear_histor_msg">驴Quieres borrar todos los mensajes de esta conversaci贸n?\n\n<b>Aviso:</b> Esto no afectar谩 a los mensajes guardados en otros dispositivos o servidores.</string>
     <string name="delete_messages">Borrar mensajes</string>
     <string name="also_end_conversation">Terminar esta conversaci贸n m谩s tarde</string>
-    <string name="choose_presence">Elegir presencia para contacto</string>
+    <string name="choose_presence">Selecciona recurso del contacto</string>
     <string name="send_message_to_conference">Enviar mensaje a conferencia</string>
-    <string name="send_plain_text_message">Enviar mensaje en texto plano</string>
+    <string name="send_plain_text_message">Enviar mensaje de texto</string>
     <string name="send_otr_message">Enviar mensaje encriptado con OTR</string>
     <string name="send_pgp_message">Enviar mensaje encriptado con openPGP</string>
-    <string name="your_nick_has_been_changed">Tu apodo se ha cambiado</string>
+    <string name="your_nick_has_been_changed">Tu apodo se ha modificado</string>
     <string name="download_image">Descargar imagen</string>
     <string name="error_loading_image">Error cargando imagen (Archivo no encontrado)</string>
-    <string name="image_offered_for_download"><i>Archivo de imagen ofrecida para descarga</i></string>
+    <string name="image_offered_for_download"><i>Archivo de imagen ofrecido para descarga</i></string>
     <string name="not_connected">No conectado</string>
-	<string name="you_are_offline">Tienes que estar en l铆nea para enviar %s pero tu cuenta asociada a esta conversaci贸n est谩 fuera de l铆nea.</string>
-    <string name="you_are_offline_blank">No puede ejecutar esta acci贸n estando fuera de l铆nea</string>
+    <string name="you_are_offline">Debes estar conectado para enviar %s pero tu cuenta asociada a esta conversaci贸n est谩 desconectada.</string>
+    <string name="you_are_offline_blank">No puedes ejecutar esta acci贸n estando desconectado</string>
     <string name="files">archivos</string>
     <string name="otr_messages">Mensajes encriptados con OTR</string>
     <string name="manage_account">Gestionar cuenta</string>
-    <string name="contact_offline">Tu contacto est谩 fuera de l铆nea</string>
-	<string name="contact_offline_otr">Desgraciadamente no es posible enviar mensajes encriptados con OTR a un contacto fuera de l铆nea.\n驴Quieres enviar el mensaje en texto plano?</string>
-    <string name="contact_offline_file">Desgraciadamente no es posible enviar archivos a un contacto fuera de l铆nea.</string>
+    <string name="contact_offline">Tu contacto est谩 desconectado</string>
+    <string name="contact_offline_otr">Desgraciadamente no es posible enviar mensajes encriptados con OTR a un contacto desconectado.\n驴Quieres enviar el mensaje en texto plano?</string>
+    <string name="contact_offline_file">Desgraciadamente no es posible enviar archivos a un contacto desconectado.</string>
     <string name="send_unencrypted">Enviar sin encriptar</string>
-	<string name="decryption_failed">Fall贸 la desencriptaci贸n. Tal vez no tengas la clave privada apropiada.</string>
-	<string name="openkeychain_required">OpenKeychain</string>
-	<string name="openkeychain_required_long">Conversations utiliza una tercera app llamada <b>OpenKeychain</b> para encriptar y desencriptar mensajes y para gestionar tus claves publicas.\n\nOpenKeychain est谩 licenciado bajo GPLv3 y disponible en F-Droid y Google Play.\n\n<small>(Por favor reinicia Conversations despu茅s)</small></string>
-	<string name="restart">Reiniciar</string>
-	<string name="install">Instalar</string>
-</resources>
+    <string name="decryption_failed">Fall贸 la desencriptaci贸n. Tal vez no tengas la clave privada apropiada.</string>
+    <string name="openkeychain_required">OpenKeychain</string>
+    <string name="openkeychain_required_long">Conversations utiliza una aplicaci贸n de terceros llamada <b>OpenKeychain</b> para encriptar y desencriptar mensajes y gestionar tus claves p煤blicas.\n\nOpenKeychain est谩 publicado bajo licencia GPLv3 y disponible on F-Droid y Google Play.\n\n<small>(Por favor, reinicie Conversations despu茅s.)</small></string>
+    <string name="restart">Reiniciar</string>
+    <string name="install">Instalar</string>
+    <string name="offering">ofreciendo&#8230;</string>
+    <string name="no_pgp_key">Clave openPGP no encontrada</string>
+    <string name="contact_has_no_pgp_key">Conversations no ha podido encriptar tus mensajes porque tu contacto no est谩 anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure openPGP.</small></string>
+    <string name="encrypted_message_received"><i>Mensaje encriptado recibido. Pulsa para desencriptar y ver.</i></string>
+    <string name="encrypted_image_received"><i>Imagen encriptada recibida. Pulsa para desencriptar y ver.</i></string>
+    <string name="image_file"><i>Imagen recibida. Pulsa para ver</i></string>
+    <string name="otr_file_transfer">Encriptaci贸n con OTR no disponible</string>
+    <string name="otr_file_transfer_msg">Desafortunadamente la encriptaci贸n con OTR no est谩 disponible para transferencia de archivos. Puedes selecionar encriptaci贸n con openPGP o no usar encriptaci贸n.</string>
+    <string name="use_pgp_encryption">Usa encriptaci贸n con openPGP</string>
+</resources>

res/values/strings.xml 馃敆

@@ -53,7 +53,7 @@
     <string name="send_failed">unsuccessful delivery</string>
     <string name="send_rejected">rejected</string>
     <string name="receiving_image">Receiving image file. Please wait&#8230;</string>
-    <string name="preparing_image">Preparing image for transmission. Please wait&#8230;</string>
+    <string name="preparing_image">Preparing image for transmission</string>
     <string name="action_clear_history">Clear history</string>
     <string name="clear_conversation_history">Clear Conversation History</string>
     <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string>
@@ -86,4 +86,38 @@
     <string name="offering">offering&#8230;</string>
     <string name="no_pgp_key">No openPGP Key found</string>
     <string name="contact_has_no_pgp_key">Conversations is unable to encrypt your messages because your contact is not announcing his or hers public key.\n\n<small>Please ask your contact to setup openPGP.</small></string>
+    <string name="encrypted_message_received"><i>Encrypted message received. Touch to view and decrypt.</i></string>
+    <string name="encrypted_image_received"><i>Encrypted image received. Touch to view and decrypt.</i></string>
+    <string name="image_file"><i>Image received. Touch to view</i></string>
+    <string name="otr_file_transfer">OTR encryption not available</string>
+    <string name="otr_file_transfer_msg">Unfortunaly OTR encryption is not available for file transfer. You can choose either openPGP or no encryption.</string>
+    <string name="use_pgp_encryption">Use openPGP encryption</string>
+    <string name="pref_xmpp_resource">XMPP resource</string>
+    <string name="pref_xmpp_resource_summary">The name this client identifies itself with</string>
+    <string name="pref_accept_files">Accept files</string>
+    <string name="pref_accept_files_summary">Automatically accept files smaller than&#8230;</string>
+    <string name="pref_notification_settings">Notification Settings</string>
+    <string name="pref_notifications">Notifications</string>
+    <string name="pref_notifications_summary">Notify when a new message arrives</string>
+    <string name="pref_vibrate">Vibrate</string>
+    <string name="pref_vibrate_summary">Also vibrate when a new message arrives</string>
+    <string name="pref_sound">Sound</string>
+    <string name="pref_sound_summary">Play ringtone with notification</string>
+    <string name="pref_conference_notifications">Conference notifications</string>
+    <string name="pref_conference_notifications_summary">Always notify when a new conference message arrives instead of only when highlighted</string>
+    <string name="pref_notification_grace_period">Notification grace period</string>
+    <string name="pref_notification_grace_period_summary">Disable notifications for a short time after a carbon copy was received</string>
+    <string name="pref_ui_options">UI Options</string>
+    <string name="pref_use_phone_self_picture">Use Phones self contact picture</string>
+    <string name="pref_use_phone_sefl_picture_summary">You may no longer be able to distinguish which account you are using in a conversation</string>
+    <string name="pref_conference_name">Conference name</string>
+    <string name="pref_conference_name_summary">Use room鈥檚 subject to identify Conferences</string>
+    <string name="pref_advanced_options">Advanced Options</string>
+    <string name="pref_never_send_crash">Never send crash reports</string>
+    <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string>
+    <string name="openpgp_error">OpenKeychain reporeted an error</string>
+    <string name="error_decrypting_file">I/O Error decrypting file</string>
+    <string name="error_copying_image_file">Error copying image file.</string>
+    <string name="accept">Accept</string>
+    <string name="error">An error has occurred</string>
 </resources>

res/xml/preferences.xml 馃敆

@@ -10,69 +10,69 @@
             />
         <ListPreference 
             android:key="resource"
-            android:title="XMPP Resource"
-            android:summary="The name this client identifies itself"
+            android:title="@string/pref_xmpp_resource"
+            android:summary="@string/pref_xmpp_resource_summary"
             android:entries="@array/resources"
             android:entryValues="@array/resources"
             android:defaultValue="Mobile"/>
         <ListPreference 
             android:key="auto_accept_file_size"
-            android:title="Accept files"
-            android:summary="Automatically accept files smaller than"
+            android:title="@string/pref_accept_files"
+            android:summary="@string/pref_accept_files_summary"
             android:entries="@array/filesizes"
             android:entryValues="@array/filesizes_values"
             android:defaultValue="524288"/>
     </PreferenceCategory>
     <PreferenceCategory 
-        android:title="Notification Settings">
+        android:title="@string/pref_notification_settings">
         <CheckBoxPreference 
             android:key="show_notification"
-            android:title="Notification"
-            android:summary="Notify when a new message arrives"
+            android:title="@string/pref_notifications"
+            android:summary="@string/pref_notifications_summary"
             android:defaultValue="true"
             />
         <CheckBoxPreference
             android:key="vibrate_on_notification"
             android:dependency="show_notification"
-            android:title="Vibrate"
-            android:summary="Also vibrate when a new message arrives"
+            android:title="@string/pref_vibrate"
+            android:summary="@string/pref_vibrate_summary"
             android:defaultValue="true"/>
         <RingtonePreference 
             android:key="notification_ringtone"
-            android:title="Sound"
+            android:title="@string/pref_sound"
             android:ringtoneType="notification"
             android:dependency="show_notification"
-            android:summary="Play ringtone with notification"
+            android:summary="@string/pref_sound_summary"
             android:defaultValue="content://settings/system/notification_sound"/>
         <CheckBoxPreference 
             android:key="notify_in_conversation_when_highlighted"
-            android:title="Conference notification"
-            android:summary="Always notify when a new conference message arrives instead of only when highlighted"/>
+            android:title="@string/pref_conference_notifications"
+            android:summary="@string/pref_conference_notifications_summary"/>
         <CheckBoxPreference
             android:key="notification_grace_period_after_carbon_received"
-            android:title="Notification grace period"
-            android:summary="Disable notifications for a short time after a carbon copy was received"
+            android:title="@string/pref_notification_grace_period"
+            android:summary="@string/pref_notification_grace_period_summary"
             android:defaultValue="true"/>
     </PreferenceCategory>
     <PreferenceCategory 
-        android:title="UI Options">
+        android:title="@string/pref_ui_options">
         <CheckBoxPreference 
             android:key="show_phone_selfcontact_picture"
-            android:title="Use Phones self contact picture"
-            android:summary="You may no longer be able to distinguish which account you are using in a conversation"
+            android:title="@string/pref_use_phone_self_picture"
+            android:summary="@string/pref_use_phone_sefl_picture_summary"
             android:defaultValue="true"/>
         <CheckBoxPreference 
             android:key="use_subject_in_muc"
-            android:title="Conference Name"
-            android:summary="Use room鈥檚 subject to identify Conferences"
+            android:title="@string/pref_conference_name"
+            android:summary="@string/pref_conference_name_summary"
             android:defaultValue="true"/>
     </PreferenceCategory>
     <PreferenceCategory
         android:title="Advanced Options">
         <CheckBoxPreference 
             android:key="never_send"
-            android:title="Never send crash reports"
-            android:summary="By sending in stack traces you are helping the ongoing development of Conversations"
+            android:title="@string/pref_never_send_crash"
+            android:summary="@string/pref_never_send_crash_summary"
             android:defaultValue="false"/>
     </PreferenceCategory>
 </PreferenceScreen>

src/eu/siacs/conversations/crypto/OnPgpEngineResult.java 馃敆

@@ -1,11 +0,0 @@
-package eu.siacs.conversations.crypto;
-
-import org.openintents.openpgp.OpenPgpError;
-
-import android.app.PendingIntent;
-
-public interface OnPgpEngineResult {
-	public void success();
-	public void error(OpenPgpError openPgpError);
-	public void userInputRequried(PendingIntent pi);
-}

src/eu/siacs/conversations/crypto/PgpEngine.java 馃敆

@@ -14,12 +14,13 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
 import org.openintents.openpgp.util.OpenPgpApi;
 import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
 
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
 import eu.siacs.conversations.xmpp.jingle.JingleFile;
-
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.graphics.BitmapFactory;
@@ -34,7 +35,7 @@ public class PgpEngine {
 		this.mXmppConnectionService = service;
 	}
 
-	public void decrypt(final Message message, final OnPgpEngineResult callback) {
+	public void decrypt(final Message message, final UiCallback callback) {
 		Log.d("xmppService","decrypting message "+message.getUuid());
 		Intent params = new Intent();
 		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
@@ -59,8 +60,7 @@ public class PgpEngine {
 								.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
 						return;
 					case OpenPgpApi.RESULT_CODE_ERROR:
-						callback.error((OpenPgpError) result
-								.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+						callback.error(R.string.openpgp_error);
 						return;
 					default:
 						return;
@@ -97,8 +97,7 @@ public class PgpEngine {
 									.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
 							return;
 						case OpenPgpApi.RESULT_CODE_ERROR:
-							callback.error((OpenPgpError) result
-									.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+							callback.error(R.string.openpgp_error);
 							return;
 						default:
 							return;
@@ -106,67 +105,26 @@ public class PgpEngine {
 					}
 				});
 			} catch (FileNotFoundException e) {
-				callback.error(new OpenPgpError(0, "file not found: "+e.getMessage()));
+				callback.error(R.string.error_decrypting_file);
 			} catch (IOException e) {
-				callback.error(new OpenPgpError(0, "io exception: "+e.getMessage()));
+				callback.error(R.string.error_decrypting_file);
 			}
 			
 		}
 	}
 
-	public void encrypt(Account account, final Message message,
-			final OnPgpEngineResult callback) {
+	public void encrypt(final Message message,final UiCallback callback) {
 		long[] keys = { message.getConversation().getContact().getPgpKeyId() };
 		Intent params = new Intent();
 		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
 		params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
-		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
-
-		InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
-		final OutputStream os = new ByteArrayOutputStream();
-		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-			
-			@Override
-			public void onReturn(Intent result) {
-				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
-						OpenPgpApi.RESULT_CODE_ERROR)) {
-				case OpenPgpApi.RESULT_CODE_SUCCESS:
-					StringBuilder encryptedMessageBody = new StringBuilder();
-					String[] lines = os.toString().split("\n");
-					for (int i = 3; i < lines.length - 1; ++i) {
-						encryptedMessageBody.append(lines[i].trim());
-					}
-					message.setEncryptedBody(encryptedMessageBody.toString());
-					callback.success();
-					break;
-				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-					callback.userInputRequried((PendingIntent) result
-							.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
-					break;
-				case OpenPgpApi.RESULT_CODE_ERROR:
-					callback.error((OpenPgpError) result
-							.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
-					break;
-				}
-			}
-		});
+		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message.getConversation().getAccount().getJid());
 		
-	}
+		if (message.getType() == Message.TYPE_TEXT) {
+			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
 	
-	public void encrypt(final Message message, final OnPgpEngineResult callback) {
-		try {
-			Log.d("xmppService","calling to encrypt file");
-			JingleFile inputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, true);
-			JingleFile outputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, false);
-			outputFile.createNewFile();
-			long[] keys = { message.getConversation().getContact().getPgpKeyId() };
-			Intent params = new Intent();
-			params.setAction(OpenPgpApi.ACTION_ENCRYPT);
-			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
-			params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message.getConversation().getAccount().getJid());
-			InputStream is = new FileInputStream(inputFile);
-			OutputStream os = new FileOutputStream(outputFile);
+			InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
+			final OutputStream os = new ByteArrayOutputStream();
 			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
 				
 				@Override
@@ -174,6 +132,12 @@ public class PgpEngine {
 					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 							OpenPgpApi.RESULT_CODE_ERROR)) {
 					case OpenPgpApi.RESULT_CODE_SUCCESS:
+						StringBuilder encryptedMessageBody = new StringBuilder();
+						String[] lines = os.toString().split("\n");
+						for (int i = 3; i < lines.length - 1; ++i) {
+							encryptedMessageBody.append(lines[i].trim());
+						}
+						message.setEncryptedBody(encryptedMessageBody.toString());
 						callback.success();
 						break;
 					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@@ -181,19 +145,45 @@ public class PgpEngine {
 								.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
 						break;
 					case OpenPgpApi.RESULT_CODE_ERROR:
-						callback.error((OpenPgpError) result
-								.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+						callback.error(R.string.openpgp_error);
 						break;
 					}
 				}
 			});
-		} catch (FileNotFoundException e) {
-			Log.d("xmppService","file not found: "+e.getMessage());
-		} catch (IOException e) {
-			Log.d("xmppService","io exception during file encrypt");
+		} else if (message.getType() == Message.TYPE_IMAGE) {
+			try {
+				JingleFile inputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, true);
+				JingleFile outputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, false);
+				outputFile.createNewFile();
+				InputStream is = new FileInputStream(inputFile);
+				OutputStream os = new FileOutputStream(outputFile);
+				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
+					
+					@Override
+					public void onReturn(Intent result) {
+						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
+								OpenPgpApi.RESULT_CODE_ERROR)) {
+						case OpenPgpApi.RESULT_CODE_SUCCESS:
+							callback.success();
+							break;
+						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+							callback.userInputRequried((PendingIntent) result
+									.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+							break;
+						case OpenPgpApi.RESULT_CODE_ERROR:
+							callback.error(R.string.openpgp_error);
+							break;
+						}
+					}
+				});
+			} catch (FileNotFoundException e) {
+				Log.d("xmppService","file not found: "+e.getMessage());
+			} catch (IOException e) {
+				Log.d("xmppService","io exception during file encrypt");
+			}
 		}
 	}
-
+	
 	public long fetchKeyId(Account account, String status, String signature) {
 		if ((signature == null) || (api == null)) {
 			return 0;
@@ -242,7 +232,7 @@ public class PgpEngine {
 	}
 
 	public void generateSignature(final Account account, String status,
-			final OnPgpEngineResult callback) {
+			final UiCallback callback) {
 		Intent params = new Intent();
 		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
 		params.setAction(OpenPgpApi.ACTION_SIGN);
@@ -268,22 +258,19 @@ public class PgpEngine {
 							.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
 					return;
 				case OpenPgpApi.RESULT_CODE_ERROR:
-					callback.error((OpenPgpError) result
-							.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+					callback.error(R.string.openpgp_error);
 					return;
 				}
 			}
 		});
 	}
 	
-	public void hasKey(Contact contact, final OnPgpEngineResult callback) {
+	public void hasKey(Contact contact, final UiCallback callback) {
 		Intent params = new Intent();
 		params.setAction(OpenPgpApi.ACTION_GET_KEY);
 		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
 		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount().getJid());
-		InputStream is = new ByteArrayInputStream(new byte[0]);
-		OutputStream os = new ByteArrayOutputStream();
-		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
+		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
 			
 			@Override
 			public void onReturn(Intent result) {
@@ -296,11 +283,19 @@ public class PgpEngine {
 							.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
 					return;
 				case OpenPgpApi.RESULT_CODE_ERROR:
-					callback.error((OpenPgpError) result
-							.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+					callback.error(R.string.openpgp_error);
 					return;
 				}
 			}
 		});
 	}
+	
+	public PendingIntent getIntentForKey(Contact contact) {
+		Intent params = new Intent();
+		params.setAction(OpenPgpApi.ACTION_GET_KEY);
+		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
+		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount().getJid());
+		Intent result = api.executeApi(params, null, null);
+		return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+	}
 }

src/eu/siacs/conversations/entities/Message.java 馃敆

@@ -1,7 +1,9 @@
 package eu.siacs.conversations.entities;
 
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.xmpp.jingle.JingleConnection;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 
 public class Message extends AbstractEntity {
@@ -103,6 +105,20 @@ public class Message extends AbstractEntity {
 	public String getBody() {
 		return body;
 	}
+	
+	public String getReadableBody(Context context) {
+		if ((encryption == ENCRYPTION_PGP)&&(type == TYPE_TEXT)) {
+			return ""+context.getText(R.string.encrypted_message_received);
+		} else if ((encryption == ENCRYPTION_OTR)&&(type == TYPE_IMAGE)) {
+			return ""+context.getText(R.string.encrypted_image_received);
+		} else if (encryption == ENCRYPTION_DECRYPTION_FAILED) {
+			return ""+context.getText(R.string.decryption_failed);
+		} else if (type == TYPE_IMAGE) {
+			return ""+context.getText(R.string.image_file);
+		} else {
+			return body.trim();
+		}
+	}
 
 	public long getTimeSent() {
 		return timeSent;

src/eu/siacs/conversations/persistance/FileBackend.java 馃敆

@@ -102,7 +102,7 @@ public class FileBackend {
 			boolean success = scalledBitmap.compress(
 					Bitmap.CompressFormat.WEBP, 75, os);
 			if (!success) {
-				// Log.d("xmppService", "couldnt compress");
+				return null;
 			}
 			os.flush();
 			os.close();
@@ -112,14 +112,12 @@ public class FileBackend {
 			message.setBody(""+size+","+width+","+height);
 			return file;
 		} catch (FileNotFoundException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
+			return null;
 		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
+			return null;
+		} catch (SecurityException e) {
+			return null;
 		}
-
-		return null;
 	}
 
 	public Bitmap getImageFromMessage(Message message) {

src/eu/siacs/conversations/services/XmppConnectionService.java 馃敆

@@ -17,8 +17,7 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
 import net.java.otr4j.OtrException;
 import net.java.otr4j.session.Session;
 import net.java.otr4j.session.SessionStatus;
-
-import eu.siacs.conversations.crypto.OnPgpEngineResult;
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
@@ -33,6 +32,7 @@ import eu.siacs.conversations.persistance.OnPhoneContactsMerged;
 import eu.siacs.conversations.ui.OnAccountListChangedListener;
 import eu.siacs.conversations.ui.OnConversationListChangedListener;
 import eu.siacs.conversations.ui.OnRosterFetchedListener;
+import eu.siacs.conversations.ui.UiCallback;
 import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.MessageParser;
 import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
@@ -446,45 +446,35 @@ public class XmppConnectionService extends Service {
 		return this.fileBackend;
 	}
 
-	public Message attachImageToConversation(final Conversation conversation,
-			final String presence, final Uri uri) {
-		final Message message = new Message(conversation, "",Message.ENCRYPTION_NONE);
-		message.setPresence(presence);
+	public Message attachImageToConversation(final Conversation conversation, final Uri uri,  final UiCallback callback) {
+		final Message message;
+		if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+			message = new Message(conversation, "",Message.ENCRYPTION_DECRYPTED);
+		} else {
+			message = new Message(conversation, "", Message.ENCRYPTION_NONE);
+		}
+		message.setPresence(conversation.getNextPresence());
 		message.setType(Message.TYPE_IMAGE);
 		message.setStatus(Message.STATUS_OFFERED);
 		new Thread(new Runnable() {
 
 			@Override
 			public void run() {
-				getFileBackend().copyImageToPrivateStorage(message, uri);
-				databaseBackend.createMessage(message);
-				conversation.getMessages().add(message);
-				if (convChangedListener != null) {
-					convChangedListener.onConversationListChanged();
+				JingleFile file = getFileBackend().copyImageToPrivateStorage(message, uri);
+				if (file==null) {
+					callback.error(R.string.error_copying_image_file);
+				} else {
+					if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+						getPgpEngine().encrypt(message, callback);
+					} else {
+						callback.success();
+					}
 				}
-				sendMessage(message, null);
 			}
 		}).start();
 		return message;
 	}
 	
-	public Message attachEncryptedImageToConversation(final Conversation conversation, final String presence, final Uri uri, final OnPgpEngineResult callback) {
-		Log.d(LOGTAG,"attach encrypted image");
-		final Message message = new Message(conversation, "",Message.ENCRYPTION_DECRYPTED);
-		message.setPresence(presence);
-		message.setType(Message.TYPE_IMAGE);
-		message.setStatus(Message.STATUS_OFFERED);
-		new Thread(new Runnable() {
-
-			@Override
-			public void run() {
-				getFileBackend().copyImageToPrivateStorage(message, uri);
-				getPgpEngine().encrypt(message, callback);
-			}
-		}).start();
-		return message;
-	}
-
 	protected Conversation findMuc(String name, Account account) {
 		for (Conversation conversation : this.conversations) {
 			if (conversation.getContactJid().split("/")[0].equals(name)

src/eu/siacs/conversations/ui/ContactDetailsActivity.java 馃敆

@@ -4,10 +4,14 @@ import java.math.BigInteger;
 import java.util.Iterator;
 import java.util.Locale;
 
+import org.openintents.openpgp.util.OpenPgpUtils;
+
 import android.app.AlertDialog;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.CommonDataKinds;
@@ -26,6 +30,7 @@ import android.widget.QuickContactBadge;
 import android.widget.TextView;
 import android.widget.Toast;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Presences;
 import eu.siacs.conversations.utils.UIHelper;
@@ -252,17 +257,29 @@ public class ContactDetailsActivity extends XmppActivity {
 			key.setText(otrFingerprint);
 			keys.addView(view);
 		}
-		Log.d("gultsch", "pgp key id " + contact.getPgpKeyId());
 		if (contact.getPgpKeyId() != 0) {
 			View view = (View) inflater.inflate(R.layout.contact_key, null);
 			TextView key = (TextView) view.findViewById(R.id.key);
 			TextView keyType = (TextView) view.findViewById(R.id.key_type);
 			keyType.setText("PGP Key ID");
-			BigInteger bi = new BigInteger("" + contact.getPgpKeyId());
-			StringBuilder builder = new StringBuilder(bi.toString(16)
-					.toUpperCase(Locale.US));
-			builder.insert(8, " ");
-			key.setText(builder.toString());
+			key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
+			view.setOnClickListener(new OnClickListener() {
+				
+				@Override
+				public void onClick(View v) {
+					PgpEngine pgp = activity.xmppConnectionService.getPgpEngine();
+					if (pgp!=null) {
+						PendingIntent intent = pgp.getIntentForKey(contact);
+						if (intent!=null) {
+							try {
+								startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
+							} catch (SendIntentException e) {
+								
+							}
+						}
+					}
+				}
+			});
 			keys.addView(view);
 		}
 	}

src/eu/siacs/conversations/ui/ConversationActivity.java 馃敆

@@ -9,13 +9,13 @@ import java.util.List;
 import org.openintents.openpgp.OpenPgpError;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.OnPgpEngineResult;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.UIHelper;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
@@ -53,6 +53,7 @@ import android.widget.PopupMenu;
 import android.widget.PopupMenu.OnMenuItemClickListener;
 import android.widget.TextView;
 import android.widget.ImageView;
+import android.widget.Toast;
 
 public class ConversationActivity extends XmppActivity {
 
@@ -63,8 +64,10 @@ public class ConversationActivity extends XmppActivity {
 
 	public static final int REQUEST_SEND_MESSAGE = 0x75441;
 	public static final int REQUEST_DECRYPT_PGP = 0x76783;
-	private static final int ATTACH_FILE = 0x48502;
+	private static final int REQUEST_ATTACH_FILE_DIALOG = 0x48502;
 	private static final int REQUEST_SEND_PGP_IMAGE = 0x53883;
+	private static final int REQUEST_ATTACH_FILE = 0x73824;
+	public static final int REQUEST_ENCRYPT_MESSAGE = 0x378018;
 
 	protected SlidingPaneLayout spl;
 
@@ -108,6 +111,7 @@ public class ConversationActivity extends XmppActivity {
 	
 	protected ConversationActivity activity = this;
 	private DisplayMetrics metrics;
+	private Toast prepareImageToast;
 
 	public List<Conversation> getConversationList() {
 		return this.conversationList;
@@ -174,7 +178,11 @@ public class ConversationActivity extends XmppActivity {
 				Message latestMessage = conv.getLatestMessage();
 				
 				if (latestMessage.getType() == Message.TYPE_TEXT) {
-					convLastMsg.setText(conv.getLatestMessage().getBody());
+					if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP)&&(latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
+						convLastMsg.setText(conv.getLatestMessage().getBody());
+					} else {
+						convLastMsg.setText(getText(R.string.encrypted_message_received));
+					}
 					convLastMsg.setVisibility(View.VISIBLE);
 					imagePreview.setVisibility(View.GONE);
 				} else if (latestMessage.getType() == Message.TYPE_IMAGE) {
@@ -329,7 +337,7 @@ public class ConversationActivity extends XmppActivity {
 					attachFileIntent.setType("image/*");
 					attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
 					Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
-					startActivityForResult(chooser,	ATTACH_FILE);
+					startActivityForResult(chooser,	REQUEST_ATTACH_FILE_DIALOG);
 				}
 			}
 
@@ -345,11 +353,11 @@ public class ConversationActivity extends XmppActivity {
 		if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
 			if (hasPgp()) {
 				if (conversation.getContact().getPgpKeyId()!=0) {
-					xmppConnectionService.getPgpEngine().hasKey(conversation.getContact(), new OnPgpEngineResult() {
+					xmppConnectionService.getPgpEngine().hasKey(conversation.getContact(), new UiCallback() {
 						
 						@Override
 						public void userInputRequried(PendingIntent pi) {
-							ConversationActivity.this.runIntent(pi, REQUEST_SEND_PGP_IMAGE);
+							ConversationActivity.this.runIntent(pi, REQUEST_ATTACH_FILE);
 						}
 						
 						@Override
@@ -358,7 +366,7 @@ public class ConversationActivity extends XmppActivity {
 						}
 						
 						@Override
-						public void error(OpenPgpError openPgpError) {
+						public void error(int error) {
 							// TODO Auto-generated method stub
 							
 						}
@@ -380,6 +388,31 @@ public class ConversationActivity extends XmppActivity {
 			}
 		} else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) {
 			attachFileDialog();
+		} else {
+			AlertDialog.Builder builder = new AlertDialog.Builder(this);
+			builder.setTitle(getString(R.string.otr_file_transfer));
+			builder.setMessage(getString(R.string.otr_file_transfer_msg));
+			builder.setNegativeButton(getString(R.string.cancel), null);
+			if (conversation.getContact().getPgpKeyId()==0) {
+				builder.setPositiveButton(getString(R.string.send_unencrypted), new OnClickListener() {
+					
+					@Override
+					public void onClick(DialogInterface dialog, int which) {
+						conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+						attachFile();
+					}
+				});
+			} else {
+				builder.setPositiveButton(getString(R.string.use_pgp_encryption), new OnClickListener() {
+					
+					@Override
+					public void onClick(DialogInterface dialog, int which) {
+						conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+						attachFile();
+					}
+				});
+			}
+			builder.create().show();
 		}
 	}
 	
@@ -448,7 +481,7 @@ public class ConversationActivity extends XmppActivity {
 									conversation.setNextEncryption(Message.ENCRYPTION_PGP);
 									item.setChecked(true);
 								} else {
-									announcePgp(conversation.getAccount());
+									announcePgp(conversation.getAccount(),conversation);
 								}
 							}
 							break;
@@ -634,41 +667,78 @@ public class ConversationActivity extends XmppActivity {
 				if (selectedFragment != null) {
 					selectedFragment.hidePgpPassphraseBox();
 				}
-			} else if (requestCode == ATTACH_FILE) {
+			} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
+				prepareImageToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
 				final Conversation conversation = getSelectedConversation();
-				String presence = conversation.getNextPresence();
 				if (conversation.getNextEncryption() == Message.ENCRYPTION_NONE) {
-					xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
-				} else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
-					pendingMessage = xmppConnectionService.attachEncryptedImageToConversation(conversation, presence, data.getData(), new OnPgpEngineResult() {
+					prepareImageToast.show();
+					this.pendingMessage = xmppConnectionService.attachImageToConversation(conversation, data.getData(),new UiCallback() {
 						
 						@Override
 						public void userInputRequried(PendingIntent pi) {
-							ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE);
+							
 						}
 						
 						@Override
 						public void success() {
-							conversation.getMessages().add(pendingMessage);
-							pendingMessage.setStatus(Message.STATUS_OFFERED);
-							xmppConnectionService.databaseBackend.createMessage(pendingMessage);
-							xmppConnectionService.sendMessage(pendingMessage, null);
-							xmppConnectionService.updateUi(conversation, false);
-							pendingMessage = null;
+							sendPendingImageMessage();
 						}
 						
 						@Override
-						public void error(OpenPgpError openPgpError) {
-							Log.d(LOGTAG,"pgp error"+openPgpError.getMessage());
+						public void error(int error) {
+							pendingMessage = null;
+							displayErrorDialog(error);
 						}
 					});
+				} else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+					prepareImageToast.show();
+					attachPgpFile(conversation,data.getData());
 				} else {
 					Log.d(LOGTAG,"unknown next message encryption: "+conversation.getNextEncryption());
 				}
+			} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
+				
+			} else if (requestCode == REQUEST_ATTACH_FILE) {
+				attachFile();
+			} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
+				announcePgp(getSelectedConversation().getAccount(),getSelectedConversation());
+			} else if (requestCode == REQUEST_ENCRYPT_MESSAGE) {
+				encryptTextMessage();
+			} else {
+				Log.d(LOGTAG,"unknown result code:"+requestCode);
 			}
 		}
 	}
+	
+	private void attachPgpFile(Conversation conversation, Uri uri) {
+			pendingMessage = xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() {
+				
+				@Override
+				public void userInputRequried(PendingIntent pi) {
+					ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE);
+				}
+				
+				@Override
+				public void success() {
+					sendPendingImageMessage();
+				}
+				
+				@Override
+				public void error(int error) {
+					pendingMessage = null;
+					displayErrorDialog(error);
+				}
+			});
+	}
 
+	private void sendPendingImageMessage() {
+		pendingMessage.getConversation().getMessages().add(pendingMessage);
+		xmppConnectionService.databaseBackend.createMessage(pendingMessage);
+		xmppConnectionService.sendMessage(pendingMessage, null);
+		xmppConnectionService.updateUi(pendingMessage.getConversation(), false);
+		pendingMessage = null;
+	}
+	
 	public void updateConversationList() {
 		conversationList.clear();
 		conversationList.addAll(xmppConnectionService.getConversations());
@@ -872,4 +942,33 @@ public class ConversationActivity extends XmppActivity {
 	        return bitmapWorkerTaskReference.get();
 	    }
 	}
+
+	public void encryptTextMessage() {
+		xmppConnectionService.getPgpEngine().encrypt(this.pendingMessage, new UiCallback() {
+
+					@Override
+					public void userInputRequried(
+							PendingIntent pi) {
+						activity.runIntent(
+								pi,
+								ConversationActivity.REQUEST_SEND_MESSAGE);
+					}
+
+					@Override
+					public void success() {
+						xmppConnectionService.sendMessage(pendingMessage, null);
+						pendingMessage = null;
+						ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
+								.findFragmentByTag("conversation");
+						if (selectedFragment != null) {
+							selectedFragment.clearInputField();
+						}
+					}
+
+					@Override
+					public void error(int error) {
+
+					}
+				});
+	}
 }

src/eu/siacs/conversations/ui/ConversationFragment.java 馃敆

@@ -8,9 +8,7 @@ import java.util.Set;
 import org.openintents.openpgp.OpenPgpError;
 
 import net.java.otr4j.session.SessionStatus;
-
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.OnPgpEngineResult;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
@@ -198,8 +196,12 @@ public class ConversationFragment extends Fragment {
 				boolean error = false;
 				if (message.getType() == Message.TYPE_IMAGE) {
 					String[] fileParams = message.getBody().split(",");
-					long size = Long.parseLong(fileParams[0]);
-					filesize = size / 1024 + " KB";
+					try {
+						long size = Long.parseLong(fileParams[0]);
+						filesize = size / 1024 + " KB";
+					} catch (NumberFormatException e) {
+						filesize = "0 KB";
+					}
 				}
 				switch (message.getStatus()) {
 				case Message.STATUS_UNSEND:
@@ -492,7 +494,9 @@ public class ConversationFragment extends Fragment {
 	@Override
 	public void onStop() {
 		super.onStop();
-		this.conversation.setNextMessage(chatMsg.getText().toString());
+		if (this.conversation!=null) {
+			this.conversation.setNextMessage(chatMsg.getText().toString());
+		}
 	}
 
 	public void onBackendConnected() {
@@ -554,7 +558,7 @@ public class ConversationFragment extends Fragment {
 	private void decryptMessage(final Message message) {
 		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
 		if (engine != null) {
-			engine.decrypt(message, new OnPgpEngineResult() {
+			engine.decrypt(message, new UiCallback() {
 
 				@Override
 				public void userInputRequried(PendingIntent pi) {
@@ -570,9 +574,7 @@ public class ConversationFragment extends Fragment {
 				}
 
 				@Override
-				public void error(OpenPgpError openPgpError) {
-					Log.d("xmppService",
-							"decryption error" + openPgpError.getMessage());
+				public void error(int error) {
 					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
 					// updateMessages();
 				}
@@ -583,6 +585,9 @@ public class ConversationFragment extends Fragment {
 	}
 
 	public void updateMessages() {
+		if (getView()==null) {
+			return;
+		}
 		ConversationActivity activity = (ConversationActivity) getActivity();
 		if (this.conversation != null) {
 			for (Message message : this.conversation.getMessages()) {
@@ -664,56 +669,30 @@ public class ConversationFragment extends Fragment {
 	}
 
 	protected void sendPgpMessage(final Message message) {
+		activity.pendingMessage = message;
 		final ConversationActivity activity = (ConversationActivity) getActivity();
 		final XmppConnectionService xmppService = activity.xmppConnectionService;
 		final Contact contact = message.getConversation().getContact();
-		final Account account = message.getConversation().getAccount();
 		if (activity.hasPgp()) {
 			if (contact.getPgpKeyId() != 0) {
 				xmppService.getPgpEngine().hasKey(contact,
-						new OnPgpEngineResult() {
+						new UiCallback() {
 
 							@Override
 							public void userInputRequried(PendingIntent pi) {
 								activity.runIntent(
 										pi,
-										ConversationActivity.REQUEST_SEND_MESSAGE);
+										ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
 							}
 
 							@Override
 							public void success() {
-								xmppService.getPgpEngine().encrypt(account,
-										message, new OnPgpEngineResult() {
-
-											@Override
-											public void userInputRequried(
-													PendingIntent pi) {
-												activity.runIntent(
-														pi,
-														ConversationActivity.REQUEST_SEND_MESSAGE);
-											}
-
-											@Override
-											public void success() {
-												xmppService.sendMessage(
-														message, null);
-												chatMsg.setText("");
-											}
-
-											@Override
-											public void error(
-													OpenPgpError openPgpError) {
-												// TODO Auto-generated method
-												// stub
-
-											}
-										});
+								activity.encryptTextMessage();
 							}
 
 							@Override
-							public void error(OpenPgpError openPgpError) {
-								Log.d("xmppService", "openpgp error"
-										+ openPgpError.getMessage());
+							public void error(int error) {
+								
 							}
 						});
 
@@ -809,4 +788,8 @@ public class ConversationFragment extends Fragment {
 	public void setText(String text) {
 		this.pastedText = text;
 	}
+
+	public void clearInputField() {
+		this.chatMsg.setText("");
+	}
 }

src/eu/siacs/conversations/ui/ManageAccountActivity.java 馃敆

@@ -6,7 +6,6 @@ import java.util.List;
 import org.openintents.openpgp.OpenPgpError;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.OnPgpEngineResult;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.ui.EditAccount.EditAccountListener;
@@ -279,7 +278,7 @@ public class ManageAccountActivity extends XmppActivity {
 							} else if (item.getItemId()==R.id.mgmt_account_announce_pgp) {
 								if (activity.hasPgp()) {
 									mode.finish();
-									announcePgp(selectedAccountForActionMode);
+									announcePgp(selectedAccountForActionMode,null);
 								}
 							} else if (item.getItemId() == R.id.mgmt_otr_key) {
 								AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -288,7 +287,10 @@ public class ManageAccountActivity extends XmppActivity {
 								View view = (View) getLayoutInflater().inflate(R.layout.otr_fingerprint, null);
 								if (fingerprintTxt!=null) {
 									TextView fingerprint = (TextView) view.findViewById(R.id.otr_fingerprint);
+									TextView noFingerprintView = (TextView) view.findViewById(R.id.otr_no_fingerprint);
 									fingerprint.setText(fingerprintTxt);
+									fingerprint.setVisibility(View.VISIBLE);
+									noFingerprintView.setVisibility(View.GONE);
 								}
 								builder.setView(view);
 								builder.setPositiveButton("Done", null);
@@ -458,8 +460,8 @@ public class ManageAccountActivity extends XmppActivity {
 		 super.onActivityResult(requestCode, resultCode, data);
 		 if (resultCode == RESULT_OK) {
 			if (requestCode == REQUEST_ANNOUNCE_PGP) {
-				announcePgp(selectedAccountForActionMode);
-			 }
+				announcePgp(selectedAccountForActionMode,null);
+			}
 		 }
 	 }
 }

src/eu/siacs/conversations/ui/UiCallback.java 馃敆

@@ -0,0 +1,9 @@
+package eu.siacs.conversations.ui;
+
+import android.app.PendingIntent;
+
+public interface UiCallback {
+	public void success();
+	public void error(int errorCode);
+	public void userInputRequried(PendingIntent pi);
+}

src/eu/siacs/conversations/ui/XmppActivity.java 馃敆

@@ -1,11 +1,11 @@
 package eu.siacs.conversations.ui;
 
-import org.openintents.openpgp.OpenPgpError;
+import java.nio.channels.AlreadyConnectedException;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.OnPgpEngineResult;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 import eu.siacs.conversations.utils.ExceptionHelper;
@@ -162,8 +162,8 @@ public abstract class XmppActivity extends Activity {
 		startActivity(viewConversationIntent);
 	}
 	
-	protected void announcePgp(final Account account) {
-		xmppConnectionService.getPgpEngine().generateSignature(account, "online", new OnPgpEngineResult() {
+	protected void announcePgp(final Account account, final Conversation conversation) {
+		xmppConnectionService.getPgpEngine().generateSignature(account, "online", new UiCallback() {
 			
 			@Override
 			public void userInputRequried(PendingIntent pi) {
@@ -178,13 +178,31 @@ public abstract class XmppActivity extends Activity {
 			public void success() {
 				xmppConnectionService.databaseBackend.updateAccount(account);
 				xmppConnectionService.sendPgpPresence(account, account.getPgpSignature());
+				if (conversation!=null) {
+					conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+				}
 			}
 			
 			@Override
-			public void error(OpenPgpError openPgpError) {
-				// TODO Auto-generated method stub
-				
+			public void error(int error) {
+				displayErrorDialog(error);
+			}
+		});
+	}
+	
+	protected void displayErrorDialog(final int errorCode) {
+		runOnUiThread(new Runnable() {
+			
+			@Override
+			public void run() {
+				AlertDialog.Builder builder = new AlertDialog.Builder(XmppActivity.this);
+				builder.setIconAttribute(android.R.attr.alertDialogIcon);
+				builder.setTitle(getString(R.string.error));
+				builder.setMessage(errorCode);
+				builder.setNeutralButton(R.string.accept, null);
+				builder.create().show();
 			}
 		});
+		
 	}
 }

src/eu/siacs/conversations/utils/UIHelper.java 馃敆

@@ -389,7 +389,7 @@ public class UIHelper {
 							context, true));
 			mBuilder.setContentTitle(conversation.getName(useSubject));
 			if (notify) {
-				mBuilder.setTicker(conversation.getLatestMessage().getBody().trim());
+				mBuilder.setTicker(conversation.getLatestMessage().getReadableBody(context));
 			}
 			StringBuilder bigText = new StringBuilder();
 			List<Message> messages = conversation.getMessages();
@@ -397,10 +397,10 @@ public class UIHelper {
 			for (int i = messages.size() - 1; i >= 0; --i) {
 				if (!messages.get(i).isRead()) {
 					if (i == messages.size() - 1) {
-						firstLine = messages.get(i).getBody().trim();
+						firstLine = messages.get(i).getReadableBody(context);
 						bigText.append(firstLine);
 					} else {
-						firstLine = messages.get(i).getBody().trim();
+						firstLine = messages.get(i).getReadableBody(context);
 						bigText.insert(0, firstLine + "\n");
 					}
 				} else {
@@ -422,7 +422,7 @@ public class UIHelper {
 					names.append(unread.get(i).getName(useSubject));
 				}
 				style.addLine(Html.fromHtml("<b>" + unread.get(i).getName(useSubject)
-						+ "</b> " + unread.get(i).getLatestMessage().getBody().trim()));
+						+ "</b> " + unread.get(i).getLatestMessage().getReadableBody(context)));
 			}
 			mBuilder.setContentTitle(unread.size() + " unread Conversations");
 			mBuilder.setContentText(names.toString());