offer quick reply on android N

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/NotificationService.java   | 59 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 39 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        | 26 
src/main/java/eu/siacs/conversations/utils/UIHelper.java                 | 29 
4 files changed, 109 insertions(+), 44 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -13,6 +13,7 @@ import android.os.SystemClock;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationCompat.BigPictureStyle;
 import android.support.v4.app.NotificationCompat.Builder;
+import android.support.v4.app.RemoteInput;
 import android.support.v4.app.TaskStackBuilder;
 import android.text.Html;
 import android.util.DisplayMetrics;
@@ -118,6 +119,13 @@ public class NotificationService {
 		}
 	}
 
+	public void pushFromDirectReply(final Message message) {
+		synchronized (notifications) {
+			pushToStack(message);
+			updateNotification(false);
+		}
+	}
+
 	public void finishBacklog(boolean notify) {
 		synchronized (notifications) {
 			mXmppConnectionService.updateUnreadCountBadge();
@@ -170,6 +178,8 @@ public class NotificationService {
 	public void clear(final Conversation conversation) {
 		synchronized (notifications) {
 			notifications.remove(conversation.getUuid());
+			final NotificationManager nm = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
+			nm.cancel(conversation.getUuid(), NOTIFICATION_ID);
 			updateNotification(false);
 		}
 	}
@@ -190,7 +200,7 @@ public class NotificationService {
 				this.markLastNotification();
 			}
 			final Builder mBuilder;
-			if (notifications.size() == 1) {
+			if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
 				mBuilder = buildSingleConversations(notifications.values().iterator().next());
 				modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
 				notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
@@ -202,7 +212,7 @@ public class NotificationService {
 					Builder singleBuilder = buildSingleConversations(entry.getValue());
 					singleBuilder.setGroup(CONVERSATIONS_GROUP);
 					modifyForSoundVibrationAndLight(singleBuilder,notify,preferences);
-					notificationManager.notify(entry.getKey().hashCode() % 435301 ,singleBuilder.build());
+					notificationManager.notify(entry.getKey(), NOTIFICATION_ID ,singleBuilder.build());
 				}
 			}
 		}
@@ -294,14 +304,19 @@ public class NotificationService {
 				} else {
 					modifyForTextOnly(mBuilder, messages);
 				}
-				if ((message = getFirstDownloadableMessage(messages)) != null) {
-					mBuilder.addAction(
-							Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
-									R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download,
-							mXmppConnectionService.getResources().getString(R.string.download_x_file,
-									UIHelper.getFileDescriptionString(mXmppConnectionService, message)),
-							createDownloadIntent(message)
-					);
+				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+					RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
+					NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation)).addRemoteInput(remoteInput).build();
+					mBuilder.addAction(action);
+					if ((message = getFirstDownloadableMessage(messages)) != null) {
+						mBuilder.addAction(
+								Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
+										R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download,
+								mXmppConnectionService.getResources().getString(R.string.download_x_file,
+										UIHelper.getFileDescriptionString(mXmppConnectionService, message)),
+								createDownloadIntent(message)
+						);
+					}
 				}
 				if ((message = getFirstLocationMessage(messages)) != null) {
 					mBuilder.addAction(R.drawable.ic_room_white_24dp,
@@ -332,8 +347,9 @@ public class NotificationService {
 			final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
 			bigPictureStyle.bigPicture(bitmap);
 			if (tmp.size() > 0) {
-				bigPictureStyle.setSummaryText(getMergedBodies(tmp));
-				builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, tmp.get(0)).first);
+				CharSequence text = getMergedBodies(tmp);
+				bigPictureStyle.setSummaryText(text);
+				builder.setContentText(text);
 			} else {
 				builder.setContentText(mXmppConnectionService.getString(
 						R.string.received_x_file,
@@ -354,7 +370,7 @@ public class NotificationService {
 			}
 			for (Message message : messages) {
 				String sender = message.getStatus() == Message.STATUS_RECEIVED ? UIHelper.getMessageDisplayName(message) : null;
-				messagingStyle.addMessage(message.getBody().trim(), message.getTimeSent(), sender);
+				messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService,message).first, message.getTimeSent(), sender);
 			}
 			builder.setStyle(messagingStyle);
 		} else {
@@ -364,15 +380,19 @@ public class NotificationService {
 	}
 
 	private Message getImage(final Iterable<Message> messages) {
+		Message image = null;
 		for (final Message message : messages) {
+			if (message.getStatus() != Message.STATUS_RECEIVED) {
+				return null;
+			}
 			if (message.getType() != Message.TYPE_TEXT
 					&& message.getTransferable() == null
 					&& message.getEncryption() != Message.ENCRYPTION_PGP
 					&& message.getFileParams().height > 0) {
-				return message;
+				image = message;
 			}
 		}
-		return null;
+		return image;
 	}
 
 	private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
@@ -448,11 +468,18 @@ public class NotificationService {
 		intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
 		if (conversation != null) {
 			intent.putExtra("uuid", conversation.getUuid());
-			return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 47528, intent, 0);
+			return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 247527, intent, 0);
 		}
 		return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
 	}
 
+	private PendingIntent createReplyIntent(Conversation conversation) {
+		final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
+		intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
+		intent.putExtra("uuid",conversation.getUuid());
+		return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 402361, intent, 0);
+	}
+
 	private PendingIntent createDisableForeground() {
 		final Intent intent = new Intent(mXmppConnectionService,
 				XmppConnectionService.class);

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -26,6 +26,7 @@ import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.provider.ContactsContract;
 import android.security.KeyChain;
+import android.support.v4.app.RemoteInput;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LruCache;
@@ -128,6 +129,7 @@ import me.leolin.shortcutbadger.ShortcutBadger;
 
 public class XmppConnectionService extends Service {
 
+	public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations";
 	public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
 	public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground";
 	public static final String ACTION_TRY_AGAIN = "try_again";
@@ -526,6 +528,7 @@ public class XmppConnectionService extends Service {
 		final String action = intent == null ? null : intent.getAction();
 		boolean interactive = false;
 		if (action != null) {
+			final Conversation c = findConversationByUuid(intent.getStringExtra("uuid"));
 			switch (action) {
 				case ConnectivityManager.CONNECTIVITY_ACTION:
 					if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
@@ -541,7 +544,6 @@ public class XmppConnectionService extends Service {
 					logoutAndSave(true);
 					return START_NOT_STICKY;
 				case ACTION_CLEAR_NOTIFICATION:
-					final Conversation c = findConversationByUuid(intent.getStringExtra("uuid"));
 					if (c != null) {
 						mNotificationService.clear(c);
 					} else {
@@ -568,6 +570,14 @@ public class XmppConnectionService extends Service {
 						break;
 					}
 					break;
+				case ACTION_REPLY_TO_CONVERSATION:
+					Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+					if (remoteInput != null && c != null) {
+
+						String body = remoteInput.getString("text_reply");
+						directReply(c,body);
+					}
+					break;
 				case AudioManager.RINGER_MODE_CHANGED_ACTION:
 					if (xaOnSilentMode()) {
 						refreshAllPresences();
@@ -687,6 +697,33 @@ public class XmppConnectionService extends Service {
 		return START_STICKY;
 	}
 
+	private void directReply(Conversation conversation, String body) {
+		Message message = new Message(conversation,body,conversation.getNextEncryption());
+		if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+			getPgpEngine().encrypt(message, new UiCallback<Message>() {
+				@Override
+				public void success(Message message) {
+					message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+					sendMessage(message);
+					mNotificationService.pushFromDirectReply(message);
+				}
+
+				@Override
+				public void error(int errorCode, Message object) {
+
+				}
+
+				@Override
+				public void userInputRequried(PendingIntent pi, Message object) {
+
+				}
+			});
+		} else {
+			sendMessage(message);
+			mNotificationService.pushFromDirectReply(message);
+		}
+	}
+
 	private boolean xaOnSilentMode() {
 		return getPreferences().getBoolean("xa_on_silent_mode", false);
 	}

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -363,31 +363,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 		} else if (multi && !conversation.getMucOptions().participating()) {
 			this.mEditMessage.setHint(R.string.you_are_not_participating);
 		} else {
-			switch (conversation.getNextEncryption()) {
-				case Message.ENCRYPTION_NONE:
-					if (Config.multipleEncryptionChoices()) {
-						mEditMessage.setHint(getString(R.string.send_unencrypted_message));
-					} else {
-						mEditMessage.setHint(getString(R.string.send_message_to_x,conversation.getName()));
-					}
-					break;
-				case Message.ENCRYPTION_OTR:
-					mEditMessage.setHint(getString(R.string.send_otr_message));
-					break;
-				case Message.ENCRYPTION_AXOLOTL:
-					AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
-					if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) {
-						mEditMessage.setHint(getString(R.string.send_omemo_x509_message));
-					} else {
-						mEditMessage.setHint(getString(R.string.send_omemo_message));
-					}
-					break;
-				case Message.ENCRYPTION_PGP:
-					mEditMessage.setHint(getString(R.string.send_pgp_message));
-					break;
-				default:
-					break;
-			}
+			this.mEditMessage.setHint(UIHelper.getMessageHint(activity,conversation));
 			getActivity().invalidateOptionsMenu();
 		}
 	}

src/main/java/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -11,7 +11,9 @@ import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.ListItem;
@@ -74,8 +76,7 @@ public class UIHelper {
 		} else if (difference < 60 * 2) {
 			return context.getString(R.string.minute_ago);
 		} else if (difference < 60 * 15) {
-			return context.getString(R.string.minutes_ago,
-					Math.round(difference / 60.0));
+			return context.getString(R.string.minutes_ago,Math.round(difference / 60.0));
 		} else if (today(date)) {
 			java.text.DateFormat df = DateFormat.getTimeFormat(context);
 			return df.format(date);
@@ -252,6 +253,30 @@ public class UIHelper {
 		}
 	}
 
+	public static String getMessageHint(Context context, Conversation conversation) {
+		switch (conversation.getNextEncryption()) {
+			case Message.ENCRYPTION_NONE:
+				if (Config.multipleEncryptionChoices()) {
+					return context.getString(R.string.send_unencrypted_message);
+				} else {
+					return context.getString(R.string.send_message_to_x,conversation.getName());
+				}
+			case Message.ENCRYPTION_OTR:
+				return context.getString(R.string.send_otr_message);
+			case Message.ENCRYPTION_AXOLOTL:
+				AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
+				if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) {
+					return context.getString(R.string.send_omemo_x509_message);
+				} else {
+					return context.getString(R.string.send_omemo_message);
+				}
+			case Message.ENCRYPTION_PGP:
+				return context.getString(R.string.send_pgp_message);
+			default:
+				return "";
+		}
+	}
+
 	public static String getDisplayedMucCounterpart(final Jid counterpart) {
 		if (counterpart==null) {
 			return "";