[RemoteInputView] Move "send" logic to Controller
Bonus: removes the BouncerChecker from the View Bug: 193539698 Test: receive notification and reply via RemoteInput Change-Id: Ifea0e4a39de90aca84bfc9c3e6718cc7a87c1152
This commit is contained in:
@@ -507,15 +507,19 @@ public class NotificationRemoteInputManager implements Dumpable {
|
||||
|
||||
riv.setRevealParameters(cx, cy, r);
|
||||
riv.setPendingIntent(pendingIntent);
|
||||
riv.getController().setPendingIntent(pendingIntent);
|
||||
riv.setRemoteInput(inputs, input, editedSuggestionInfo);
|
||||
riv.getController().setRemoteInput(input);
|
||||
riv.getController().setRemoteInputs(inputs);
|
||||
riv.focusAnimated();
|
||||
if (userMessageContent != null) {
|
||||
riv.setEditTextContent(userMessageContent);
|
||||
}
|
||||
if (deferBouncer) {
|
||||
final ExpandableNotificationRow finalRow = row;
|
||||
riv.setBouncerChecker(() -> !authBypassCheck.canSendRemoteInputWithoutBouncer()
|
||||
&& showBouncerForRemoteInput(view, pendingIntent, finalRow));
|
||||
riv.getController().setBouncerChecker(() ->
|
||||
!authBypassCheck.canSendRemoteInputWithoutBouncer()
|
||||
&& showBouncerForRemoteInput(view, pendingIntent, finalRow));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1289,8 +1289,8 @@ public class NotificationContentView extends FrameLayout {
|
||||
result.mView = riv;
|
||||
// Create a new controller for the view. The lifetime of the controller is 1:1
|
||||
// with that of the view.
|
||||
RemoteInputViewSubcomponent subcomponent =
|
||||
mRemoteInputSubcomponentFactory.create(result.mView);
|
||||
RemoteInputViewSubcomponent subcomponent = mRemoteInputSubcomponentFactory
|
||||
.create(result.mView, mRemoteInputController);
|
||||
result.mController = subcomponent.getController();
|
||||
result.mView.setController(result.mController);
|
||||
} else {
|
||||
@@ -1311,8 +1311,9 @@ public class NotificationContentView extends FrameLayout {
|
||||
Notification.Action[] actions = entry.getSbn().getNotification().actions;
|
||||
if (existingPendingIntent != null) {
|
||||
result.mView.setPendingIntent(existingPendingIntent);
|
||||
result.mController.setPendingIntent(existingPendingIntent);
|
||||
}
|
||||
if (result.mView.updatePendingIntentFromActions(actions)) {
|
||||
if (result.mController.updatePendingIntentFromActions(actions)) {
|
||||
if (!result.mView.isActive()) {
|
||||
result.mView.focus();
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.BlendMode;
|
||||
@@ -35,13 +33,9 @@ import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
@@ -78,7 +72,6 @@ import com.android.internal.logging.UiEvent;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
import com.android.systemui.statusbar.RemoteInputController;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
|
||||
@@ -89,7 +82,6 @@ import com.android.wm.shell.animation.Interpolators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -107,7 +99,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
|
||||
private final SendButtonTextWatcher mTextWatcher;
|
||||
private final TextView.OnEditorActionListener mEditorActionHandler;
|
||||
private final ArrayList<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
|
||||
private final ArrayList<Runnable> mOnSendListeners = new ArrayList<>();
|
||||
private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>();
|
||||
private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners =
|
||||
new ArrayList<>();
|
||||
@@ -133,7 +125,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
private PendingIntent mPendingIntent;
|
||||
private RemoteInput mRemoteInput;
|
||||
private RemoteInput[] mRemoteInputs;
|
||||
private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;
|
||||
private boolean mRemoved;
|
||||
private NotificationViewWrapper mWrapper;
|
||||
|
||||
@@ -178,6 +169,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
// TODO(b/193539698): move to Controller, since we're just directly accessing a system service
|
||||
/** Hide the IME, if visible. */
|
||||
public void hideIme() {
|
||||
mEditText.hideIme();
|
||||
}
|
||||
|
||||
private ColorStateList colorStateListWithDisabledAlpha(int color, int disabledAlpha) {
|
||||
return new ColorStateList(new int[][]{
|
||||
new int[]{-com.android.internal.R.attr.state_enabled}, // disabled
|
||||
@@ -303,6 +300,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
return mViewController;
|
||||
}
|
||||
|
||||
/** Clear the attachment, if present. */
|
||||
public void clearAttachment() {
|
||||
setAttachment(null);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void setAttachment(ContentInfo item) {
|
||||
if (mEntry.remoteInputAttachment != null && mEntry.remoteInputAttachment != item) {
|
||||
@@ -339,121 +341,18 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
updateSendButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply intent
|
||||
* @return returns intent with granted URI permissions that should be used immediately
|
||||
*/
|
||||
private Intent prepareRemoteInput() {
|
||||
return mEntry.remoteInputAttachment == null
|
||||
? prepareRemoteInputFromText()
|
||||
: prepareRemoteInputFromData(mEntry.remoteInputMimeType, mEntry.remoteInputUri);
|
||||
}
|
||||
|
||||
private Intent prepareRemoteInputFromText() {
|
||||
Bundle results = new Bundle();
|
||||
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
|
||||
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
|
||||
results);
|
||||
|
||||
mEntry.remoteInputText = mEditText.getText().toString();
|
||||
setAttachment(null);
|
||||
mEntry.remoteInputUri = null;
|
||||
mEntry.remoteInputMimeType = null;
|
||||
|
||||
if (mEntry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
|
||||
} else {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
|
||||
}
|
||||
|
||||
return fillInIntent;
|
||||
}
|
||||
|
||||
private Intent prepareRemoteInputFromData(String contentType, Uri data) {
|
||||
HashMap<String, Uri> results = new HashMap<>();
|
||||
results.put(contentType, data);
|
||||
// grant for the target app.
|
||||
mController.grantInlineReplyUriPermission(mEntry.getSbn(), data);
|
||||
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
|
||||
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
|
||||
bundle);
|
||||
|
||||
CharSequence attachmentText =
|
||||
mEntry.remoteInputAttachment.getClip().getDescription().getLabel();
|
||||
|
||||
CharSequence attachmentLabel = TextUtils.isEmpty(attachmentText)
|
||||
? mContext.getString(R.string.remote_input_image_insertion_text)
|
||||
: attachmentText;
|
||||
// add content description to reply text for context
|
||||
CharSequence fullText = TextUtils.isEmpty(mEditText.getText())
|
||||
? attachmentLabel
|
||||
: "\"" + attachmentLabel + "\" " + mEditText.getText();
|
||||
|
||||
mEntry.remoteInputText = fullText;
|
||||
|
||||
// mirror prepareRemoteInputFromText for text input
|
||||
if (mEntry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
|
||||
} else if (mEntry.remoteInputAttachment == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
|
||||
}
|
||||
|
||||
return fillInIntent;
|
||||
}
|
||||
|
||||
private void sendRemoteInput(Intent intent) {
|
||||
if (mBouncerChecker != null && mBouncerChecker.showBouncerIfNecessary()) {
|
||||
mEditText.hideIme();
|
||||
for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
|
||||
listener.onSendRequestBounced();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** Show the "sending in-progress" UI. */
|
||||
public void startSending() {
|
||||
mEditText.setEnabled(false);
|
||||
mSendButton.setVisibility(INVISIBLE);
|
||||
mProgressBar.setVisibility(VISIBLE);
|
||||
mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
|
||||
mEntry.mRemoteEditImeAnimatingAway = true;
|
||||
mController.addSpinning(mEntry.getKey(), mToken);
|
||||
mController.removeRemoteInput(mEntry, mToken);
|
||||
mEditText.mShowImeOnInputConnection = false;
|
||||
mController.remoteInputSent(mEntry);
|
||||
mEntry.setHasSentReply();
|
||||
}
|
||||
|
||||
for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
|
||||
listener.onSendRemoteInput();
|
||||
private void sendRemoteInput() {
|
||||
for (Runnable listener : new ArrayList<>(mOnSendListeners)) {
|
||||
listener.run();
|
||||
}
|
||||
|
||||
// Tell ShortcutManager that this package has been "activated". ShortcutManager
|
||||
// will reset the throttling for this package.
|
||||
// Strictly speaking, the intent receiver may be different from the notification publisher,
|
||||
// but that's an edge case, and also because we can't always know which package will receive
|
||||
// an intent, so we just reset for the publisher.
|
||||
getContext().getSystemService(ShortcutManager.class).onApplicationActive(
|
||||
mEntry.getSbn().getPackageName(),
|
||||
mEntry.getSbn().getUser().getIdentifier());
|
||||
|
||||
mUiEventLogger.logWithInstanceId(
|
||||
NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_SEND,
|
||||
mEntry.getSbn().getUid(), mEntry.getSbn().getPackageName(),
|
||||
mEntry.getSbn().getInstanceId());
|
||||
try {
|
||||
mPendingIntent.send(mContext, 0, intent);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.i(TAG, "Unable to send remote input result", e);
|
||||
mUiEventLogger.logWithInstanceId(
|
||||
NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_FAILURE,
|
||||
mEntry.getSbn().getUid(), mEntry.getSbn().getPackageName(),
|
||||
mEntry.getSbn().getInstanceId());
|
||||
}
|
||||
|
||||
setAttachment(null);
|
||||
}
|
||||
|
||||
public CharSequence getText() {
|
||||
@@ -478,7 +377,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == mSendButton) {
|
||||
sendRemoteInput(prepareRemoteInput());
|
||||
sendRemoteInput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,55 +586,18 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
return mEditText.isFocused() && mEditText.isEnabled();
|
||||
}
|
||||
|
||||
// TODO(b/193539698): move this to the controller
|
||||
public void stealFocusFrom(RemoteInputView other) {
|
||||
other.close();
|
||||
setPendingIntent(other.mPendingIntent);
|
||||
setRemoteInput(other.mRemoteInputs, other.mRemoteInput, mEntry.editedSuggestionInfo);
|
||||
setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR);
|
||||
getController().setPendingIntent(other.mPendingIntent);
|
||||
getController().setRemoteInput(other.mRemoteInput);
|
||||
getController().setRemoteInputs(other.mRemoteInputs);
|
||||
focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find an action in {@param actions} that matches the current pending intent
|
||||
* of this view and updates its state to that of the found action
|
||||
*
|
||||
* @return true if a matching action was found, false otherwise
|
||||
*/
|
||||
public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
|
||||
if (mPendingIntent == null || actions == null) {
|
||||
return false;
|
||||
}
|
||||
Intent current = mPendingIntent.getIntent();
|
||||
if (current == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Notification.Action a : actions) {
|
||||
RemoteInput[] inputs = a.getRemoteInputs();
|
||||
if (a.actionIntent == null || inputs == null) {
|
||||
continue;
|
||||
}
|
||||
Intent candidate = a.actionIntent.getIntent();
|
||||
if (!current.filterEquals(candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoteInput input = null;
|
||||
for (RemoteInput i : inputs) {
|
||||
if (i.getAllowFreeFormInput()) {
|
||||
input = i;
|
||||
}
|
||||
}
|
||||
if (input == null) {
|
||||
continue;
|
||||
}
|
||||
setPendingIntent(a.actionIntent);
|
||||
setRemoteInput(inputs, input, null /* editedSuggestionInfo*/);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public PendingIntent getPendingIntent() {
|
||||
return mPendingIntent;
|
||||
}
|
||||
@@ -814,16 +676,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
return getVisibility() == VISIBLE && mController.isSpinning(mEntry.getKey(), mToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link com.android.systemui.statusbar.NotificationRemoteInputManager.BouncerChecker}
|
||||
* that will be used to determine if the device needs to be unlocked before sending the
|
||||
* RemoteInput.
|
||||
*/
|
||||
public void setBouncerChecker(
|
||||
@Nullable NotificationRemoteInputManager.BouncerChecker bouncerChecker) {
|
||||
mBouncerChecker = bouncerChecker;
|
||||
}
|
||||
|
||||
/** Registers a listener for focus-change events on the EditText */
|
||||
public void addOnEditTextFocusChangedListener(View.OnFocusChangeListener listener) {
|
||||
mEditTextFocusChangeListeners.add(listener);
|
||||
@@ -846,26 +698,15 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
}
|
||||
|
||||
/** Registers a listener for send events on this RemoteInputView */
|
||||
public void addOnSendRemoteInputListener(OnSendRemoteInputListener listener) {
|
||||
public void addOnSendRemoteInputListener(Runnable listener) {
|
||||
mOnSendListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Removes a previously-added listener for send events on this RemoteInputView */
|
||||
public void removeOnSendRemoteInputListener(OnSendRemoteInputListener listener) {
|
||||
public void removeOnSendRemoteInputListener(Runnable listener) {
|
||||
mOnSendListeners.remove(listener);
|
||||
}
|
||||
|
||||
/** Listener for send events */
|
||||
public interface OnSendRemoteInputListener {
|
||||
/** Invoked when the remote input has been sent successfully. */
|
||||
void onSendRemoteInput();
|
||||
/**
|
||||
* Invoked when the user had requested to send the remote input, but authentication was
|
||||
* required and the bouncer was shown instead.
|
||||
*/
|
||||
void onSendRequestBounced();
|
||||
}
|
||||
|
||||
/** Handler for button click on send action in IME. */
|
||||
private class EditorActionHandler implements TextView.OnEditorActionListener {
|
||||
|
||||
@@ -881,7 +722,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
|
||||
if (isSoftImeEvent || isKeyboardEnterKey) {
|
||||
if (mEditText.length() > 0 || mEntry.remoteInputAttachment != null) {
|
||||
sendRemoteInput(prepareRemoteInput());
|
||||
sendRemoteInput();
|
||||
}
|
||||
// Consume action to prevent IME from closing.
|
||||
return true;
|
||||
|
||||
@@ -16,28 +16,102 @@
|
||||
|
||||
package com.android.systemui.statusbar.policy
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.RemoteInput
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.util.ArraySet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager
|
||||
import com.android.systemui.statusbar.RemoteInputController
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.policy.RemoteInputView.NotificationRemoteInputEvent
|
||||
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewScope
|
||||
import javax.inject.Inject
|
||||
|
||||
interface RemoteInputViewController {
|
||||
fun bind()
|
||||
fun unbind()
|
||||
|
||||
/**
|
||||
* A [NotificationRemoteInputManager.BouncerChecker] that will be used to determine if the
|
||||
* device needs to be unlocked before sending the RemoteInput.
|
||||
*/
|
||||
var bouncerChecker: NotificationRemoteInputManager.BouncerChecker?
|
||||
|
||||
// TODO(b/193539698): these properties probably shouldn't be nullable
|
||||
/** A [PendingIntent] to be used to send the RemoteInput. */
|
||||
var pendingIntent: PendingIntent?
|
||||
/** The [RemoteInput] data backing this Controller. */
|
||||
var remoteInput: RemoteInput?
|
||||
/** Other [RemoteInput]s from the notification associated with this Controller. */
|
||||
var remoteInputs: Array<RemoteInput>?
|
||||
|
||||
/**
|
||||
* Tries to find an action in {@param actions} that matches the current pending intent
|
||||
* of this view and updates its state to that of the found action
|
||||
*
|
||||
* @return true if a matching action was found, false otherwise
|
||||
*/
|
||||
fun updatePendingIntentFromActions(actions: Array<Notification.Action>?): Boolean
|
||||
|
||||
/** Registers a listener for send events. */
|
||||
fun addOnSendRemoteInputListener(listener: OnSendRemoteInputListener)
|
||||
|
||||
/** Unregisters a listener previously registered via [addOnSendRemoteInputListener] */
|
||||
fun removeOnSendRemoteInputListener(listener: OnSendRemoteInputListener)
|
||||
}
|
||||
|
||||
/** Listener for send events */
|
||||
interface OnSendRemoteInputListener {
|
||||
|
||||
/** Invoked when the remote input has been sent successfully. */
|
||||
fun onSendRemoteInput()
|
||||
|
||||
/**
|
||||
* Invoked when the user had requested to send the remote input, but authentication was
|
||||
* required and the bouncer was shown instead.
|
||||
*/
|
||||
fun onSendRequestBounced()
|
||||
}
|
||||
|
||||
private const val TAG = "RemoteInput"
|
||||
|
||||
@RemoteInputViewScope
|
||||
class RemoteInputViewControllerImpl @Inject constructor(
|
||||
private val view: RemoteInputView,
|
||||
private val remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler
|
||||
private val entry: NotificationEntry,
|
||||
private val remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
|
||||
private val remoteInputController: RemoteInputController,
|
||||
private val shortcutManager: ShortcutManager,
|
||||
private val uiEventLogger: UiEventLogger
|
||||
) : RemoteInputViewController {
|
||||
|
||||
private object Token
|
||||
private val onSendListeners = ArraySet<OnSendRemoteInputListener>()
|
||||
private val resources get() = view.resources
|
||||
|
||||
private var isBound = false
|
||||
|
||||
override var pendingIntent: PendingIntent? = null
|
||||
override var bouncerChecker: NotificationRemoteInputManager.BouncerChecker? = null
|
||||
override var remoteInput: RemoteInput? = null
|
||||
override var remoteInputs: Array<RemoteInput>? = null
|
||||
|
||||
override fun bind() {
|
||||
if (isBound) return
|
||||
isBound = true
|
||||
|
||||
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
|
||||
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
|
||||
}
|
||||
|
||||
override fun unbind() {
|
||||
@@ -45,9 +119,159 @@ class RemoteInputViewControllerImpl @Inject constructor(
|
||||
isBound = false
|
||||
|
||||
view.removeOnEditTextFocusChangedListener(onFocusChangeListener)
|
||||
view.removeOnSendRemoteInputListener(onSendRemoteInputListener)
|
||||
}
|
||||
|
||||
override fun updatePendingIntentFromActions(actions: Array<Notification.Action>?): Boolean {
|
||||
actions ?: return false
|
||||
val current: Intent = pendingIntent?.intent ?: return false
|
||||
for (a in actions) {
|
||||
val actionIntent = a.actionIntent ?: continue
|
||||
val inputs = a.remoteInputs ?: continue
|
||||
if (!current.filterEquals(actionIntent.intent)) continue
|
||||
val input = inputs.firstOrNull { it.allowFreeFormInput } ?: continue
|
||||
pendingIntent = actionIntent
|
||||
remoteInput = input
|
||||
remoteInputs = inputs
|
||||
view.pendingIntent = actionIntent
|
||||
view.setRemoteInput(inputs, input, null /* editedSuggestionInfo */)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun addOnSendRemoteInputListener(listener: OnSendRemoteInputListener) {
|
||||
onSendListeners.add(listener)
|
||||
}
|
||||
|
||||
/** Removes a previously-added listener for send events on this RemoteInputView */
|
||||
override fun removeOnSendRemoteInputListener(listener: OnSendRemoteInputListener) {
|
||||
onSendListeners.remove(listener)
|
||||
}
|
||||
|
||||
private val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
|
||||
remoteInputQuickSettingsDisabler.setRemoteInputActive(hasFocus)
|
||||
}
|
||||
}
|
||||
|
||||
private val onSendRemoteInputListener = Runnable {
|
||||
val remoteInput = remoteInput ?: run {
|
||||
Log.e(TAG, "cannot send remote input, RemoteInput data is null")
|
||||
return@Runnable
|
||||
}
|
||||
val pendingIntent = pendingIntent ?: run {
|
||||
Log.e(TAG, "cannot send remote input, PendingIntent is null")
|
||||
return@Runnable
|
||||
}
|
||||
val intent = prepareRemoteInput(remoteInput)
|
||||
sendRemoteInput(pendingIntent, intent)
|
||||
}
|
||||
|
||||
private fun sendRemoteInput(pendingIntent: PendingIntent, intent: Intent) {
|
||||
if (bouncerChecker?.showBouncerIfNecessary() == true) {
|
||||
view.hideIme()
|
||||
for (listener in onSendListeners.toList()) {
|
||||
listener.onSendRequestBounced()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
view.startSending()
|
||||
|
||||
entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
|
||||
entry.mRemoteEditImeAnimatingAway = true
|
||||
remoteInputController.addSpinning(entry.key, Token)
|
||||
remoteInputController.removeRemoteInput(entry, Token)
|
||||
remoteInputController.remoteInputSent(entry)
|
||||
entry.setHasSentReply()
|
||||
|
||||
for (listener in onSendListeners.toList()) {
|
||||
listener.onSendRemoteInput()
|
||||
}
|
||||
|
||||
// Tell ShortcutManager that this package has been "activated". ShortcutManager will reset
|
||||
// the throttling for this package.
|
||||
// Strictly speaking, the intent receiver may be different from the notification publisher,
|
||||
// but that's an edge case, and also because we can't always know which package will receive
|
||||
// an intent, so we just reset for the publisher.
|
||||
shortcutManager.onApplicationActive(entry.sbn.packageName, entry.sbn.user.identifier)
|
||||
|
||||
uiEventLogger.logWithInstanceId(
|
||||
NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_SEND,
|
||||
entry.sbn.uid, entry.sbn.packageName,
|
||||
entry.sbn.instanceId)
|
||||
|
||||
try {
|
||||
pendingIntent.send(view.context, 0, intent)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
Log.i(TAG, "Unable to send remote input result", e)
|
||||
uiEventLogger.logWithInstanceId(
|
||||
NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_FAILURE,
|
||||
entry.sbn.uid, entry.sbn.packageName,
|
||||
entry.sbn.instanceId)
|
||||
}
|
||||
|
||||
view.clearAttachment()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply intent
|
||||
* @return returns intent with granted URI permissions that should be used immediately
|
||||
*/
|
||||
private fun prepareRemoteInput(remoteInput: RemoteInput): Intent =
|
||||
if (entry.remoteInputAttachment == null) prepareRemoteInputFromText(remoteInput)
|
||||
else prepareRemoteInputFromData(
|
||||
remoteInput,
|
||||
entry.remoteInputMimeType,
|
||||
entry.remoteInputUri)
|
||||
|
||||
private fun prepareRemoteInputFromText(remoteInput: RemoteInput): Intent {
|
||||
val results = Bundle()
|
||||
results.putString(remoteInput.resultKey, view.text.toString())
|
||||
val fillInIntent = Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
RemoteInput.addResultsToIntent(remoteInputs, fillInIntent, results)
|
||||
entry.remoteInputText = view.text
|
||||
view.clearAttachment()
|
||||
entry.remoteInputUri = null
|
||||
entry.remoteInputMimeType = null
|
||||
if (entry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT)
|
||||
} else {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE)
|
||||
}
|
||||
return fillInIntent
|
||||
}
|
||||
|
||||
private fun prepareRemoteInputFromData(
|
||||
remoteInput: RemoteInput,
|
||||
contentType: String,
|
||||
data: Uri
|
||||
): Intent {
|
||||
val results = HashMap<String, Uri>()
|
||||
results[contentType] = data
|
||||
// grant for the target app.
|
||||
remoteInputController.grantInlineReplyUriPermission(entry.sbn, data)
|
||||
val fillInIntent = Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(remoteInput.resultKey, view.text.toString())
|
||||
RemoteInput.addResultsToIntent(remoteInputs, fillInIntent, bundle)
|
||||
val attachmentText: CharSequence = entry.remoteInputAttachment.clip.description.label
|
||||
val attachmentLabel =
|
||||
if (TextUtils.isEmpty(attachmentText))
|
||||
resources.getString(R.string.remote_input_image_insertion_text)
|
||||
else attachmentText
|
||||
// add content description to reply text for context
|
||||
val fullText =
|
||||
if (TextUtils.isEmpty(view.text)) attachmentLabel
|
||||
else "\"" + attachmentLabel + "\" " + view.text
|
||||
entry.remoteInputText = fullText
|
||||
|
||||
// mirror prepareRemoteInputFromText for text input
|
||||
if (entry.editedSuggestionInfo == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT)
|
||||
} else if (entry.remoteInputAttachment == null) {
|
||||
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE)
|
||||
}
|
||||
return fillInIntent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.statusbar.policy.dagger
|
||||
|
||||
import com.android.systemui.statusbar.RemoteInputController
|
||||
import com.android.systemui.statusbar.policy.RemoteInputView
|
||||
import com.android.systemui.statusbar.policy.RemoteInputViewController
|
||||
import com.android.systemui.statusbar.policy.RemoteInputViewControllerImpl
|
||||
@@ -32,7 +33,10 @@ interface RemoteInputViewSubcomponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance view: RemoteInputView): RemoteInputViewSubcomponent
|
||||
fun create(
|
||||
@BindsInstance view: RemoteInputView,
|
||||
@BindsInstance remoteInputController: RemoteInputController
|
||||
): RemoteInputViewSubcomponent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +48,4 @@ private interface InternalRemoteInputViewModule {
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class RemoteInputViewScope
|
||||
internal annotation class RemoteInputViewScope
|
||||
|
||||
Reference in New Issue
Block a user