diff options
4 files changed, 170 insertions, 87 deletions
diff --git a/api/current.txt b/api/current.txt index 725548302e4e..80ff3de7d981 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5974,6 +5974,7 @@ package android.app { method public java.util.Set<java.lang.String> getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String); + method public int getEditChoicesBeforeSending(); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); @@ -5983,6 +5984,9 @@ package android.app { method public static void setResultsSource(android.content.Intent, int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR; + field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0 + field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1 + field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2 field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results"; field public static final int SOURCE_CHOICE = 1; // 0x1 @@ -5997,6 +6001,7 @@ package android.app { method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); + method public android.app.RemoteInput.Builder setEditChoicesBeforeSending(int); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); } diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 85fe99d95969..ebbf317ad05d 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -93,6 +93,22 @@ public final class RemoteInput implements Parcelable { /** The user selected one of the choices from {@link #getChoices}. */ public static final int SOURCE_CHOICE = 1; + /** @hide */ + @IntDef(prefix = {"EDIT_CHOICES_BEFORE_SENDING_"}, + value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED, + EDIT_CHOICES_BEFORE_SENDING_ENABLED}) + @Retention(RetentionPolicy.SOURCE) + public @interface EditChoicesBeforeSending {} + + /** The platform will determine whether choices will be edited before being sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; + + /** Tapping on a choice should send the input immediately, without letting the user edit it. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; + + /** Tapping on a choice should let the user edit the input before it is sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -103,17 +119,25 @@ public final class RemoteInput implements Parcelable { private final CharSequence mLabel; private final CharSequence[] mChoices; private final int mFlags; + @EditChoicesBeforeSending private final int mEditChoicesBeforeSending; private final Bundle mExtras; private final ArraySet<String> mAllowedDataTypes; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - int flags, Bundle extras, ArraySet<String> allowedDataTypes) { + int flags, int editChoicesBeforeSending, Bundle extras, + ArraySet<String> allowedDataTypes) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; this.mFlags = flags; + this.mEditChoicesBeforeSending = editChoicesBeforeSending; this.mExtras = extras; this.mAllowedDataTypes = allowedDataTypes; + if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED + && !getAllowFreeFormInput()) { + throw new IllegalArgumentException( + "setEditChoicesBeforeSending requires setAllowFreeFormInput"); + } } /** @@ -169,6 +193,15 @@ public final class RemoteInput implements Parcelable { } /** + * Gets whether tapping on a choice should let the user edit the input before it is sent to the + * app. + */ + @EditChoicesBeforeSending + public int getEditChoicesBeforeSending() { + return mEditChoicesBeforeSending; + } + + /** * Get additional metadata carried around with this remote input. */ public Bundle getExtras() { @@ -185,6 +218,8 @@ public final class RemoteInput implements Parcelable { private CharSequence mLabel; private CharSequence[] mChoices; private int mFlags = DEFAULT_FLAGS; + @EditChoicesBeforeSending + private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO; /** * Create a builder object for {@link RemoteInput} objects. @@ -269,7 +304,20 @@ public final class RemoteInput implements Parcelable { */ @NonNull public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) { - setFlag(mFlags, allowFreeFormTextInput); + setFlag(FLAG_ALLOW_FREE_FORM_INPUT, allowFreeFormTextInput); + return this; + } + + /** + * Specifies whether tapping on a choice should let the user edit the input before it is + * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}. + * + * It cannot be used if {@link #setAllowFreeFormInput} has been set to false. + */ + @NonNull + public Builder setEditChoicesBeforeSending( + @EditChoicesBeforeSending int editChoicesBeforeSending) { + mEditChoicesBeforeSending = editChoicesBeforeSending; return this; } @@ -312,8 +360,8 @@ public final class RemoteInput implements Parcelable { */ @NonNull public RemoteInput build() { - return new RemoteInput( - mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes); + return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mEditChoicesBeforeSending, + mExtras, mAllowedDataTypes); } } @@ -322,6 +370,7 @@ public final class RemoteInput implements Parcelable { mLabel = in.readCharSequence(); mChoices = in.readCharSequenceArray(); mFlags = in.readInt(); + mEditChoicesBeforeSending = in.readInt(); mExtras = in.readBundle(); mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null); } @@ -507,6 +556,7 @@ public final class RemoteInput implements Parcelable { out.writeCharSequence(mLabel); out.writeCharSequenceArray(mChoices); out.writeInt(mFlags); + out.writeInt(mEditChoicesBeforeSending); out.writeBundle(mExtras); out.writeArraySet(mAllowedDataTypes); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index ba69f3bb1bc2..9391737fe23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -218,88 +218,7 @@ public class NotificationRemoteInputManager implements Dumpable { return false; } - ViewParent p = view.getParent(); - RemoteInputView riv = null; - while (p != null) { - if (p instanceof View) { - View pv = (View) p; - if (pv.isRootNamespace()) { - riv = findRemoteInputView(pv); - break; - } - } - p = p.getParent(); - } - ExpandableNotificationRow row = null; - while (p != null) { - if (p instanceof ExpandableNotificationRow) { - row = (ExpandableNotificationRow) p; - break; - } - p = p.getParent(); - } - - if (row == null) { - return false; - } - - row.setUserExpanded(true); - - if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { - final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); - if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { - mCallback.onLockedRemoteInput(row, view); - return true; - } - if (mUserManager.getUserInfo(userId).isManagedProfile() - && mKeyguardManager.isDeviceLocked(userId)) { - mCallback.onLockedWorkRemoteInput(userId, row, view); - return true; - } - } - - if (riv == null) { - riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); - if (riv == null) { - return false; - } - if (!row.getPrivateLayout().getExpandedChild().isShown()) { - mCallback.onMakeExpandedVisibleForRemoteInput(row, view); - return true; - } - } - - int width = view.getWidth(); - if (view instanceof TextView) { - // Center the reveal on the text which might be off-center from the TextView - TextView tv = (TextView) view; - if (tv.getLayout() != null) { - int innerWidth = (int) tv.getLayout().getLineWidth(0); - innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); - width = Math.min(width, innerWidth); - } - } - int cx = view.getLeft() + width / 2; - int cy = view.getTop() + view.getHeight() / 2; - int w = riv.getWidth(); - int h = riv.getHeight(); - int r = Math.max( - Math.max(cx + cy, cx + (h - cy)), - Math.max((w - cx) + cy, (w - cx) + (h - cy))); - - riv.setRevealParameters(cx, cy, r); - riv.setPendingIntent(pendingIntent); - riv.setRemoteInput(inputs, input); - riv.focusAnimated(); - - return true; - } - - private RemoteInputView findRemoteInputView(View v) { - if (v == null) { - return null; - } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + return activateRemoteInput(view, inputs, input, pendingIntent); } }; @@ -355,6 +274,102 @@ public class NotificationRemoteInputManager implements Dumpable { } /** + * Activates a given {@link RemoteInput} + * + * @param view The view of the action button or suggestion chip that was tapped. + * @param inputs The remote inputs that need to be sent to the app. + * @param input The remote input that needs to be activated. + * @param pendingIntent The pending intent to be sent to the app. + * @return Whether the {@link RemoteInput} was activated. + */ + public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input, + PendingIntent pendingIntent) { + + ViewParent p = view.getParent(); + RemoteInputView riv = null; + while (p != null) { + if (p instanceof View) { + View pv = (View) p; + if (pv.isRootNamespace()) { + riv = findRemoteInputView(pv); + break; + } + } + p = p.getParent(); + } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } + + if (row == null) { + return false; + } + + row.setUserExpanded(true); + + if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { + final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { + mCallback.onLockedRemoteInput(row, view); + return true; + } + if (mUserManager.getUserInfo(userId).isManagedProfile() + && mKeyguardManager.isDeviceLocked(userId)) { + mCallback.onLockedWorkRemoteInput(userId, row, view); + return true; + } + } + + if (riv == null) { + riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); + if (riv == null) { + return false; + } + if (!row.getPrivateLayout().getExpandedChild().isShown()) { + mCallback.onMakeExpandedVisibleForRemoteInput(row, view); + return true; + } + } + + int width = view.getWidth(); + if (view instanceof TextView) { + // Center the reveal on the text which might be off-center from the TextView + TextView tv = (TextView) view; + if (tv.getLayout() != null) { + int innerWidth = (int) tv.getLayout().getLineWidth(0); + innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); + width = Math.min(width, innerWidth); + } + } + int cx = view.getLeft() + width / 2; + int cy = view.getTop() + view.getHeight() / 2; + int w = riv.getWidth(); + int h = riv.getHeight(); + int r = Math.max( + Math.max(cx + cy, cx + (h - cy)), + Math.max((w - cx) + cy, (w - cx) + (h - cy))); + + riv.setRevealParameters(cx, cy, r); + riv.setPendingIntent(pendingIntent); + riv.setRemoteInput(inputs, input); + riv.focusAnimated(); + + return true; + } + + private RemoteInputView findRemoteInputView(View v) { + if (v == null) { + return null; + } + return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + } + + /** * Adds all the notification lifetime extenders. Each extender represents a reason for the * NotificationRemoteInputManager to keep a notification lifetime extended. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 913b2ae90cf6..4fa8321fa1e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -36,6 +36,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -63,6 +64,7 @@ public class SmartReplyView extends ViewGroup { private final SmartReplyConstants mConstants; private final KeyguardDismissUtil mKeyguardDismissUtil; + private final NotificationRemoteInputManager mRemoteInputManager; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); /** @@ -112,6 +114,7 @@ public class SmartReplyView extends ViewGroup { super(context, attrs); mConstants = Dependency.get(SmartReplyConstants.class); mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class); + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext, R.dimen.smart_reply_button_max_height); @@ -242,12 +245,22 @@ public class SmartReplyView extends ViewGroup { b.setText(choice); OnDismissAction action = () -> { + // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags. + if (smartReplies.remoteInput.getEditChoicesBeforeSending() + == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) { + entry.remoteInputText = choice; + mRemoteInputManager.activateRemoteInput(b, + new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput, + smartReplies.pendingIntent); + return false; + } + smartReplyController.smartReplySent( entry, replyIndex, b.getText(), smartReplies.fromAssistant); Bundle results = new Bundle(); results.putString(smartReplies.remoteInput.getResultKey(), choice.toString()); Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent, + RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent, results); RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE); entry.setHasSentReply(); |