From 2e3cc4169d1c1e2bcddd12800854ba74b3a422db Mon Sep 17 00:00:00 2001 From: Aran Ink Date: Fri, 13 Dec 2019 16:30:17 -0500 Subject: [PATCH] Show RemoteInput images in notification history. For cases where we can't show images, show "Image inserted" text instead. This will happen for work profile notifications, and for notifications that don't use MessagingStyle. Test: Updated unit tests for NotificationRemoteInputManagerTest to use new RemoteInputHistoryItem structure, and to ensure support for URIs and mime types. Manual testing to ensure images appear in notification history for MessagingStyle notifications under the primary user, and that "Image inserted" appears for work-profile and non-messaging notifications. Change-Id: I254b579aa710337039a7def6880f14a61bbd4d53 --- core/java/android/app/Notification.java | 83 +++++++++++++---- .../android/app/RemoteInputHistoryItem.java | 90 +++++++++++++++++++ .../internal/widget/MessagingLayout.java | 18 ++-- packages/SystemUI/res/values/strings.xml | 4 + .../NotificationRemoteInputManager.java | 42 ++++++--- .../collection/NotificationEntry.java | 8 +- .../row/NotificationInlineImageResolver.java | 10 ++- .../statusbar/policy/RemoteInputView.java | 10 ++- .../NotificationRemoteInputManagerTest.java | 75 +++++++++++++--- 9 files changed, 286 insertions(+), 54 deletions(-) create mode 100644 core/java/android/app/RemoteInputHistoryItem.java diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6f63eea1163d6..4315ae6614a04 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1004,6 +1004,31 @@ public class Notification implements Parcelable */ public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; + + /** + * {@link #extras} key: this is a remote input history which can include media messages + * in addition to text, as supplied to + * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or + * {@link Builder#setRemoteInputHistory(CharSequence[])}. + * + * SystemUI can populate this through + * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs + * that have been sent through a {@link RemoteInput} of this Notification. These items can + * represent either media content (specified by a URI and a MIME type) or a text message + * (described by a CharSequence). + * + * To maintain compatibility, this can also be set by apps with + * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a + * {@link RemoteInputHistoryItem} for each of the provided text-only messages. + * + * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most + * recent entry at the 0 index, the second most recent at the 1 index, etc. + * + * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) + * @hide + */ + public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; + /** * {@link #extras} key: boolean as supplied to * {@link Builder#setShowRemoteInputSpinner(boolean)}. @@ -3833,12 +3858,37 @@ public class Notification implements Parcelable if (text == null) { mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); } else { - final int N = Math.min(MAX_REPLY_HISTORY, text.length); - CharSequence[] safe = new CharSequence[N]; - for (int i = 0; i < N; i++) { + final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); + CharSequence[] safe = new CharSequence[itemCount]; + RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; + for (int i = 0; i < itemCount; i++) { safe[i] = safeCharSequence(text[i]); + items[i] = new RemoteInputHistoryItem(text[i]); } mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); + + // Also add these messages as structured history items. + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); + } + return this; + } + + /** + * Set the remote input history, with support for embedding URIs and mime types for + * images and other media. + * @hide + */ + @NonNull + public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { + if (items == null) { + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); + } else { + final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); + RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; + for (int i = 0; i < itemCount; i++) { + history[i] = items[i]; + } + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); } return this; } @@ -5246,16 +5296,17 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.actions_container, View.GONE); } - CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY); - if (validRemoteInput && replyText != null - && replyText.length > 0 && !TextUtils.isEmpty(replyText[0]) + RemoteInputHistoryItem[] replyText = (RemoteInputHistoryItem[]) + mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (validRemoteInput && replyText != null && replyText.length > 0 + && !TextUtils.isEmpty(replyText[0].getText()) && p.maxRemoteInputHistory > 0) { boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_1, - processTextSpans(replyText[0])); + processTextSpans(replyText[0].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); big.setViewVisibility(R.id.notification_material_reply_progress, showSpinner ? View.VISIBLE : View.GONE); @@ -5264,19 +5315,19 @@ public class Notification implements Parcelable ColorStateList.valueOf( isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); - if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1]) + if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) && p.maxRemoteInputHistory > 1) { big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_2, - processTextSpans(replyText[1])); + processTextSpans(replyText[1].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); - if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2]) + if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) && p.maxRemoteInputHistory > 2) { big.setViewVisibility( R.id.notification_material_reply_text_3, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_3, - processTextSpans(replyText[2])); + processTextSpans(replyText[2].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); } } @@ -7517,7 +7568,7 @@ public class Notification implements Parcelable @Nullable private final Person mSender; /** True if this message was generated from the extra - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY} + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} */ private final boolean mRemoteInputHistory; @@ -7569,7 +7620,7 @@ public class Notification implements Parcelable * Should be null for messages by the current user, in which case * the platform will insert the user set in {@code MessagingStyle(Person)}. * @param remoteInputHistory True if the messages was generated from the extra - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}. + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. *

* The person provided should contain an Icon, set with * {@link Person.Builder#setIcon(Icon)} and also have a name provided @@ -7676,7 +7727,7 @@ public class Notification implements Parcelable /** * @return True if the message was generated from - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}. + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. * @hide */ public boolean isRemoteInputHistory() { @@ -7906,8 +7957,8 @@ public class Notification implements Parcelable if (mBuilder.mActions.size() > 0) { maxRows--; } - CharSequence[] remoteInputHistory = mBuilder.mN.extras.getCharSequenceArray( - EXTRA_REMOTE_INPUT_HISTORY); + RemoteInputHistoryItem[] remoteInputHistory = (RemoteInputHistoryItem[]) + mBuilder.mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); if (remoteInputHistory != null && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { // Let's remove some messages to make room for the remote input history. diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java new file mode 100644 index 0000000000000..091db3f142aec --- /dev/null +++ b/core/java/android/app/RemoteInputHistoryItem.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Stores historical input from a RemoteInput attached to a Notification. + * + * History items represent either a text message (specified by providing a CharSequence, + * or a media message (specified by providing a URI and a MIME type). Media messages must also + * include text to insert when the image cannot be loaded, ex. when URI read permission has not been + * granted correctly. + * + * @hide + */ +public class RemoteInputHistoryItem implements Parcelable { + private CharSequence mText; + private String mMimeType; + private Uri mUri; + + public RemoteInputHistoryItem(String mimeType, Uri uri, CharSequence backupText) { + this.mMimeType = mimeType; + this.mUri = uri; + this.mText = Notification.safeCharSequence(backupText); + } + + public RemoteInputHistoryItem(CharSequence text) { + this.mText = Notification.safeCharSequence(text); + } + + protected RemoteInputHistoryItem(Parcel in) { + mText = in.readCharSequence(); + mMimeType = in.readStringNoHelper(); + mUri = in.readParcelable(Uri.class.getClassLoader()); + } + + public static final Creator CREATOR = + new Creator() { + @Override + public RemoteInputHistoryItem createFromParcel(Parcel in) { + return new RemoteInputHistoryItem(in); + } + + @Override + public RemoteInputHistoryItem[] newArray(int size) { + return new RemoteInputHistoryItem[size]; + } + }; + + public CharSequence getText() { + return mText; + } + + public String getMimeType() { + return mMimeType; + } + + public Uri getUri() { + return mUri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeCharSequence(mText); + dest.writeStringNoHelper(mMimeType); + dest.writeParcelable(mUri, flags); + } +} diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index 07d0d7d91997b..f6089589a994c 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.StyleRes; import android.app.Notification; import android.app.Person; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -161,8 +162,9 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer if (headerText != null) { mConversationTitle = headerText.getText(); } - addRemoteInputHistoryToMessages(newMessages, - extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY)); + RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + addRemoteInputHistoryToMessages(newMessages, history); boolean showSpinner = extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); bind(newMessages, newHistoricMessages, showSpinner); @@ -175,14 +177,18 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer private void addRemoteInputHistoryToMessages( List newMessages, - CharSequence[] remoteInputHistory) { + RemoteInputHistoryItem[] remoteInputHistory) { if (remoteInputHistory == null || remoteInputHistory.length == 0) { return; } for (int i = remoteInputHistory.length - 1; i >= 0; i--) { - CharSequence message = remoteInputHistory[i]; - newMessages.add(new Notification.MessagingStyle.Message( - message, 0, (Person) null, true /* remoteHistory */)); + RemoteInputHistoryItem historyMessage = remoteInputHistory[i]; + Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message( + historyMessage.getText(), 0, (Person) null, true /* remoteHistory */); + if (historyMessage.getUri() != null) { + message.setData(historyMessage.getMimeType(), historyMessage.getUri()); + } + newMessages.add(message); } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4f532b7b751d4..201fbd95e8763 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -194,6 +194,10 @@ Screenshot + + Image inserted + Saving screenshot\u2026 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 667e721ae37dd..f3783c83a3017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -25,8 +25,10 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -345,7 +347,8 @@ public class NotificationRemoteInputManager implements Dumpable { }); mSmartReplyController.setCallback((entry, reply) -> { StatusBarNotification newSbn = - rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */); + rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */, + null /* mimeType */, null /* uri */); mEntryManager.updateNotification(newSbn, null /* ranking */); }); } @@ -527,28 +530,36 @@ public class NotificationRemoteInputManager implements Dumpable { StatusBarNotification rebuildNotificationForCanceledSmartReplies( NotificationEntry entry) { return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, - false /* showSpinner */); + false /* showSpinner */, null /* mimeType */, null /* uri */); } @VisibleForTesting StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry, - CharSequence remoteInputText, boolean showSpinner) { + CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { StatusBarNotification sbn = entry.getSbn(); Notification.Builder b = Notification.Builder .recoverBuilder(mContext, sbn.getNotification().clone()); - if (remoteInputText != null) { - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; + if (remoteInputText != null || uri != null) { + RemoteInputHistoryItem[] oldHistoryItems = (RemoteInputHistoryItem[]) + sbn.getNotification().extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] newHistoryItems; + + if (oldHistoryItems == null) { + newHistoryItems = new RemoteInputHistoryItem[1]; } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + newHistoryItems = new RemoteInputHistoryItem[oldHistoryItems.length + 1]; + System.arraycopy(oldHistoryItems, 0, newHistoryItems, 1, oldHistoryItems.length); } - newHistory[0] = String.valueOf(remoteInputText); - b.setRemoteInputHistory(newHistory); + RemoteInputHistoryItem newItem; + if (uri != null) { + newItem = new RemoteInputHistoryItem(mimeType, uri, remoteInputText); + } else { + newItem = new RemoteInputHistoryItem(remoteInputText); + } + newHistoryItems[0] = newItem; + b.setRemoteInputHistory(newHistoryItems); } b.setShowRemoteInputSpinner(showSpinner); b.setHideSmartReplies(true); @@ -631,8 +642,11 @@ public class NotificationRemoteInputManager implements Dumpable { if (TextUtils.isEmpty(remoteInputText)) { remoteInputText = entry.remoteInputTextWhenReset; } + String remoteInputMimeType = entry.remoteInputMimeType; + Uri remoteInputUri = entry.remoteInputUri; StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, - remoteInputText, false /* showSpinner */); + remoteInputText, false /* showSpinner */, remoteInputMimeType, + remoteInputUri); entry.onRemoteInputInserted(); if (newSbn == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 28e486df29b9a..7478c3a05caf8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -37,8 +37,10 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.app.Person; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; @@ -119,6 +121,8 @@ public final class NotificationEntry extends ListEntry { public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; public CharSequence remoteInputText; + public String remoteInputMimeType; + public Uri remoteInputUri; private Notification.BubbleMetadata mBubbleMetadata; /** @@ -600,8 +604,8 @@ public final class NotificationEntry extends ListEntry { return false; } Bundle extras = mSbn.getNotification().extras; - CharSequence[] replyTexts = extras.getCharSequenceArray( - Notification.EXTRA_REMOTE_INPUT_HISTORY); + RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); if (!ArrayUtils.isEmpty(replyTexts)) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index a3e13053d169d..fa4bc2aba21a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -80,7 +80,15 @@ public class NotificationInlineImageResolver implements ImageResolver { public Drawable loadImage(Uri uri) { Drawable result = null; try { - result = hasCache() ? mImageCache.get(uri) : resolveImage(uri); + if (hasCache()) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); + } + result = mImageCache.get(uri); + } else { + result = resolveImage(uri); + } } catch (IOException | SecurityException ex) { Log.d(TAG, "loadImage: Can't load image from " + uri, ex); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 307e3bcbaba52..408b3a619ff12 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -161,6 +161,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, results); + + mEntry.remoteInputText = mEditText.getText(); + mEntry.remoteInputUri = null; + mEntry.remoteInputMimeType = null; + if (mEntry.editedSuggestionInfo == null) { RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); } else { @@ -177,6 +182,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results); + mEntry.remoteInputText = mContext.getString(R.string.remote_input_image_insertion_text); + mEntry.remoteInputMimeType = contentType; + mEntry.remoteInputUri = data; + return fillInIntent; } @@ -184,7 +193,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setEnabled(false); mSendButton.setVisibility(INVISIBLE); mProgressBar.setVisibility(VISIBLE); - mEntry.remoteInputText = mEditText.getText(); mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); mController.addSpinning(mEntry.getKey(), mToken); mController.removeRemoteInput(mEntry, mToken); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 64b10c816da79..1117646c482b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -10,7 +10,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.RemoteInputHistoryItem; import android.content.Context; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; @@ -149,14 +151,31 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { assertTrue(mRemoteInputManager.getEntriesKeptForRemoteInputActive().isEmpty()); } + @Test + public void testRebuildWithRemoteInput_noExistingInput_image() { + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, text, false, mimeType, uri); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(1, messages.length); + assertEquals(text, messages[0].getText()); + assertEquals(mimeType, messages[0].getMimeType()); + assertEquals(uri, messages[0].getUri()); + } + @Test public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", false, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(1, messages.length); - assertEquals("A Reply", messages[0]); + assertEquals("A Reply", messages[0].getText()); assertFalse(newSbn.getNotification().extras .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); assertTrue(newSbn.getNotification().extras @@ -166,11 +185,12 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { @Test public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(1, messages.length); - assertEquals("A Reply", messages[0]); + assertEquals("A Reply", messages[0].getText()); assertTrue(newSbn.getNotification().extras .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); assertTrue(newSbn.getNotification().extras @@ -181,18 +201,45 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { public void testRebuildWithRemoteInput_withExistingInput() { // Setup a notification entry with 1 remote input. StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", false, null, null); NotificationEntry entry = new NotificationEntryBuilder() .setSbn(newSbn) .build(); // Try rebuilding to add another reply. - newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(2, messages.length); - assertEquals("Reply 2", messages[0]); - assertEquals("A Reply", messages[1]); + assertEquals("Reply 2", messages[0].getText()); + assertEquals("A Reply", messages[1].getText()); + } + + @Test + public void testRebuildWithRemoteInput_withExistingInput_image() { + // Setup a notification entry with 1 remote input. + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, text, false, mimeType, uri); + NotificationEntry entry = new NotificationEntryBuilder() + .setSbn(newSbn) + .build(); + + // Try rebuilding to add another reply. + newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(2, messages.length); + assertEquals("Reply 2", messages[0].getText()); + assertEquals(text, messages[1].getText()); + assertEquals(mimeType, messages[1].getMimeType()); + assertEquals(uri, messages[1].getUri()); } @Test