Merge "Add setEditChoicesBeforeSending to RemoteInput"

This commit is contained in:
Milo Sredkov
2018-12-12 16:22:57 +00:00
committed by Android (Google) Code Review
4 changed files with 170 additions and 87 deletions

View File

@@ -5983,6 +5983,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();
@@ -5992,6 +5993,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
@@ -6006,6 +6010,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);
}

View File

@@ -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");
}
}
/**
@@ -168,6 +192,15 @@ public final class RemoteInput implements Parcelable {
return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0;
}
/**
* 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.
*/
@@ -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);
}

View File

@@ -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);
}
};
@@ -354,6 +273,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.

View File

@@ -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();