Merge "Hides the Save UI while handling a pending intent from CustomDescription." into oc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
55309e79cf
@@ -1863,8 +1863,18 @@ public class Activity extends ContextThemeWrapper
|
||||
getApplication().dispatchActivityStopped(this);
|
||||
mTranslucentCallback = null;
|
||||
mCalled = true;
|
||||
if (isFinishing() && mAutoFillResetNeeded) {
|
||||
getAutofillManager().commit();
|
||||
|
||||
if (isFinishing()) {
|
||||
if (mAutoFillResetNeeded) {
|
||||
getAutofillManager().commit();
|
||||
} else if (mIntent != null
|
||||
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
|
||||
// Activity was launched when user tapped a link in the Autofill Save UI - since
|
||||
// user launched another activity, the Save UI should not be restored when this
|
||||
// activity is finished.
|
||||
getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
|
||||
mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5500,6 +5510,13 @@ public class Activity extends ContextThemeWrapper
|
||||
} else {
|
||||
mParent.finishFromChild(this);
|
||||
}
|
||||
|
||||
// Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
|
||||
// be restored now.
|
||||
if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
|
||||
getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_RESTORE,
|
||||
mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6234,6 +6251,11 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
|
||||
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
|
||||
|
||||
final AutofillManager afm = getAutofillManager();
|
||||
if (afm != null) {
|
||||
afm.dump(prefix, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,8 @@ package android.service.autofill;
|
||||
import static android.view.autofill.Helper.sDebug;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
@@ -130,6 +132,18 @@ public final class CustomDescription implements Parcelable {
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* <p><b>Note:</b> If any child view of presentation triggers a
|
||||
* {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
|
||||
* on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
|
||||
* it might not be triggered or the Save affordance might not be shown when its activity
|
||||
* is finished:
|
||||
* <ul>
|
||||
* <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
|
||||
* <li>It must be a PendingIntent for an {@link Activity}.
|
||||
* <li>The activity must call {@link Activity#finish()} when done.
|
||||
* <li>The activity should not launch other activities.
|
||||
* </ul>
|
||||
*
|
||||
* @param parentPresentation template presentation with (optional) children views.
|
||||
*/
|
||||
public Builder(RemoteViews parentPresentation) {
|
||||
|
||||
@@ -30,12 +30,14 @@ import android.content.IntentSender;
|
||||
import android.graphics.Rect;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.service.autofill.AutofillService;
|
||||
import android.service.autofill.FillEventHistory;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
@@ -44,6 +46,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -154,8 +157,15 @@ public final class AutofillManager {
|
||||
public static final String EXTRA_CLIENT_STATE =
|
||||
"android.view.autofill.extra.CLIENT_STATE";
|
||||
|
||||
static final String SESSION_ID_TAG = "android:sessionId";
|
||||
static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
|
||||
|
||||
/** @hide */
|
||||
public static final String EXTRA_RESTORE_SESSION_TOKEN =
|
||||
"android.view.autofill.extra.RESTORE_SESSION_TOKEN";
|
||||
|
||||
private static final String SESSION_ID_TAG = "android:sessionId";
|
||||
private static final String STATE_TAG = "android:state";
|
||||
private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
|
||||
|
||||
|
||||
/** @hide */ public static final int ACTION_START_SESSION = 1;
|
||||
/** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
|
||||
@@ -174,6 +184,44 @@ public final class AutofillManager {
|
||||
/** @hide The index for an undefined data set */
|
||||
public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int PENDING_UI_OPERATION_CANCEL = 1;
|
||||
|
||||
/**
|
||||
* Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int PENDING_UI_OPERATION_RESTORE = 2;
|
||||
|
||||
/**
|
||||
* Initial state of the autofill context, set when there is no session (i.e., when
|
||||
* {@link #mSessionId} is {@link #NO_SESSION}).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_UNKNOWN = 1;
|
||||
|
||||
/**
|
||||
* State where the autofill context hasn't been {@link #commit() finished} nor
|
||||
* {@link #cancel() canceled} yet.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_ACTIVE = 2;
|
||||
|
||||
/**
|
||||
* State where the autofill context has been {@link #commit() finished} but the server still has
|
||||
* a session because the Save UI hasn't been dismissed yet.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_SHOWING_SAVE_UI = 4;
|
||||
|
||||
/**
|
||||
* Makes an authentication id from a request id and a dataset id.
|
||||
*
|
||||
@@ -232,6 +280,9 @@ public final class AutofillManager {
|
||||
@GuardedBy("mLock")
|
||||
private int mSessionId = NO_SESSION;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mState = STATE_UNKNOWN;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mEnabled;
|
||||
|
||||
@@ -344,12 +395,13 @@ public final class AutofillManager {
|
||||
synchronized (mLock) {
|
||||
mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
|
||||
|
||||
if (mSessionId != NO_SESSION) {
|
||||
if (isActiveLocked()) {
|
||||
Log.w(TAG, "New session was started before onCreate()");
|
||||
return;
|
||||
}
|
||||
|
||||
mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
|
||||
mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN);
|
||||
|
||||
if (mSessionId != NO_SESSION) {
|
||||
ensureServiceClientAddedIfNeededLocked();
|
||||
@@ -363,6 +415,7 @@ public final class AutofillManager {
|
||||
if (!sessionWasRestored) {
|
||||
Log.w(TAG, "Session " + mSessionId + " could not be restored");
|
||||
mSessionId = NO_SESSION;
|
||||
mState = STATE_UNKNOWN;
|
||||
} else {
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "session " + mSessionId + " was restored");
|
||||
@@ -387,7 +440,7 @@ public final class AutofillManager {
|
||||
*/
|
||||
public void onVisibleForAutofill() {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
|
||||
if (mEnabled && isActiveLocked() && mTrackedViews != null) {
|
||||
mTrackedViews.onVisibleForAutofillLocked();
|
||||
}
|
||||
}
|
||||
@@ -408,7 +461,9 @@ public final class AutofillManager {
|
||||
if (mSessionId != NO_SESSION) {
|
||||
outState.putInt(SESSION_ID_TAG, mSessionId);
|
||||
}
|
||||
|
||||
if (mState != STATE_UNKNOWN) {
|
||||
outState.putInt(STATE_TAG, mState);
|
||||
}
|
||||
if (mLastAutofilledData != null) {
|
||||
outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
|
||||
}
|
||||
@@ -514,7 +569,7 @@ public final class AutofillManager {
|
||||
final AutofillId id = getAutofillId(view);
|
||||
final AutofillValue value = view.getAutofillValue();
|
||||
|
||||
if (mSessionId == NO_SESSION) {
|
||||
if (!isActiveLocked()) {
|
||||
// Starts new session.
|
||||
startSessionLocked(id, null, value, flags);
|
||||
} else {
|
||||
@@ -541,7 +596,7 @@ public final class AutofillManager {
|
||||
synchronized (mLock) {
|
||||
ensureServiceClientAddedIfNeededLocked();
|
||||
|
||||
if (mEnabled && mSessionId != NO_SESSION) {
|
||||
if (mEnabled && isActiveLocked()) {
|
||||
final AutofillId id = getAutofillId(view);
|
||||
|
||||
// Update focus on existing session.
|
||||
@@ -582,7 +637,7 @@ public final class AutofillManager {
|
||||
private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId,
|
||||
boolean isVisible, boolean virtual) {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled && mSessionId != NO_SESSION) {
|
||||
if (mEnabled && isActiveLocked()) {
|
||||
final AutofillId id = virtual ? getAutofillId(view, virtualId)
|
||||
: view.getAutofillId();
|
||||
if (!isVisible && mFillableIds != null) {
|
||||
@@ -636,7 +691,7 @@ public final class AutofillManager {
|
||||
} else {
|
||||
final AutofillId id = getAutofillId(view, virtualId);
|
||||
|
||||
if (mSessionId == NO_SESSION) {
|
||||
if (!isActiveLocked()) {
|
||||
// Starts new session.
|
||||
startSessionLocked(id, bounds, null, flags);
|
||||
} else {
|
||||
@@ -665,7 +720,7 @@ public final class AutofillManager {
|
||||
synchronized (mLock) {
|
||||
ensureServiceClientAddedIfNeededLocked();
|
||||
|
||||
if (mEnabled && mSessionId != NO_SESSION) {
|
||||
if (mEnabled && isActiveLocked()) {
|
||||
final AutofillId id = getAutofillId(view, virtualId);
|
||||
|
||||
// Update focus on existing session.
|
||||
@@ -709,7 +764,7 @@ public final class AutofillManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mEnabled || mSessionId == NO_SESSION) {
|
||||
if (!mEnabled || !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -737,7 +792,7 @@ public final class AutofillManager {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mEnabled || mSessionId == NO_SESSION) {
|
||||
if (!mEnabled || !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -762,7 +817,7 @@ public final class AutofillManager {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mEnabled && mSessionId == NO_SESSION) {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -786,7 +841,7 @@ public final class AutofillManager {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mEnabled && mSessionId == NO_SESSION) {
|
||||
if (!mEnabled && !isActiveLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -868,7 +923,7 @@ public final class AutofillManager {
|
||||
if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data);
|
||||
|
||||
synchronized (mLock) {
|
||||
if (mSessionId == NO_SESSION || data == null) {
|
||||
if (!isActiveLocked() || data == null) {
|
||||
return;
|
||||
}
|
||||
final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
|
||||
@@ -895,13 +950,19 @@ public final class AutofillManager {
|
||||
@NonNull AutofillValue value, int flags) {
|
||||
if (sVerbose) {
|
||||
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
|
||||
+ ", flags=" + flags);
|
||||
+ ", flags=" + flags + ", state=" + mState);
|
||||
}
|
||||
if (mState != STATE_UNKNOWN) {
|
||||
if (sDebug) Log.d(TAG, "not starting session for " + id + " on state " + mState);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mSessionId = mService.startSession(mContext.getActivityToken(),
|
||||
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
|
||||
mCallback != null, flags, mContext.getOpPackageName());
|
||||
if (mSessionId != NO_SESSION) {
|
||||
mState = STATE_ACTIVE;
|
||||
}
|
||||
final AutofillClient client = getClientLocked();
|
||||
if (client != null) {
|
||||
client.autofillCallbackResetableStateAvailable();
|
||||
@@ -912,7 +973,9 @@ public final class AutofillManager {
|
||||
}
|
||||
|
||||
private void finishSessionLocked() {
|
||||
if (sVerbose) Log.v(TAG, "finishSessionLocked()");
|
||||
if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + mState);
|
||||
|
||||
if (!isActiveLocked()) return;
|
||||
|
||||
try {
|
||||
mService.finishSession(mSessionId, mContext.getUserId());
|
||||
@@ -920,12 +983,13 @@ public final class AutofillManager {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
mTrackedViews = null;
|
||||
mSessionId = NO_SESSION;
|
||||
resetSessionLocked();
|
||||
}
|
||||
|
||||
private void cancelSessionLocked() {
|
||||
if (sVerbose) Log.v(TAG, "cancelSessionLocked()");
|
||||
if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + mState);
|
||||
|
||||
if (!isActiveLocked()) return;
|
||||
|
||||
try {
|
||||
mService.cancelSession(mSessionId, mContext.getUserId());
|
||||
@@ -938,7 +1002,9 @@ public final class AutofillManager {
|
||||
|
||||
private void resetSessionLocked() {
|
||||
mSessionId = NO_SESSION;
|
||||
mState = STATE_UNKNOWN;
|
||||
mTrackedViews = null;
|
||||
mFillableIds = null;
|
||||
}
|
||||
|
||||
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
|
||||
@@ -947,7 +1013,6 @@ public final class AutofillManager {
|
||||
Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
|
||||
+ ", value=" + value + ", action=" + action + ", flags=" + flags);
|
||||
}
|
||||
|
||||
boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0;
|
||||
|
||||
try {
|
||||
@@ -958,6 +1023,7 @@ public final class AutofillManager {
|
||||
if (newId != mSessionId) {
|
||||
if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
|
||||
mSessionId = newId;
|
||||
mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
|
||||
final AutofillClient client = getClientLocked();
|
||||
if (client != null) {
|
||||
client.autofillCallbackResetableStateAvailable();
|
||||
@@ -1219,6 +1285,27 @@ public final class AutofillManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void setSaveUiState(int sessionId, boolean shown) {
|
||||
if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
|
||||
synchronized (mLock) {
|
||||
if (mSessionId != NO_SESSION) {
|
||||
// Race condition: app triggered a new session after the previous session was
|
||||
// finished but before server called setSaveUiState() - need to cancel the new
|
||||
// session to avoid further inconsistent behavior.
|
||||
Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown
|
||||
+ ") called on existing session " + mSessionId + "; cancelling it");
|
||||
cancelSessionLocked();
|
||||
}
|
||||
if (shown) {
|
||||
mSessionId = sessionId;
|
||||
mState = STATE_SHOWING_SAVE_UI;
|
||||
} else {
|
||||
mSessionId = NO_SESSION;
|
||||
mState = STATE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestHideFillUi(AutofillId id) {
|
||||
final View anchor = findView(id);
|
||||
if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
|
||||
@@ -1329,6 +1416,46 @@ public final class AutofillManager {
|
||||
return mService != null;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void onPendingSaveUi(int operation, IBinder token) {
|
||||
if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
|
||||
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mService.onPendingSaveUi(operation, token);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dump(String outerPrefix, PrintWriter pw) {
|
||||
pw.print(outerPrefix); pw.println("AutofillManager:");
|
||||
final String pfx = outerPrefix + " ";
|
||||
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
|
||||
pw.print(pfx); pw.print("state: "); pw.println(
|
||||
DebugUtils.flagsToString(AutofillManager.class, "STATE_", mState));
|
||||
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
|
||||
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
|
||||
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
|
||||
pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData);
|
||||
pw.print(pfx); pw.print("tracked views: ");
|
||||
if (mTrackedViews == null) {
|
||||
pw.println("null");
|
||||
} else {
|
||||
final String pfx2 = pfx + " ";
|
||||
pw.println();
|
||||
pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds);
|
||||
pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
|
||||
}
|
||||
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
|
||||
}
|
||||
|
||||
private boolean isActiveLocked() {
|
||||
return mState == STATE_ACTIVE;
|
||||
}
|
||||
|
||||
private void post(Runnable runnable) {
|
||||
final AutofillClient client = getClientLocked();
|
||||
if (client == null) {
|
||||
@@ -1668,12 +1795,12 @@ public final class AutofillManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startIntentSender(IntentSender intentSender) {
|
||||
public void startIntentSender(IntentSender intentSender, Intent intent) {
|
||||
final AutofillManager afm = mAfm.get();
|
||||
if (afm != null) {
|
||||
afm.post(() -> {
|
||||
try {
|
||||
afm.mContext.startIntentSender(intentSender, null, 0, 0, 0);
|
||||
afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
|
||||
}
|
||||
@@ -1691,5 +1818,13 @@ public final class AutofillManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSaveUiState(int sessionId, boolean shown) {
|
||||
final AutofillManager afm = mAfm.get();
|
||||
if (afm != null) {
|
||||
afm.post(() ->afm.setSaveUiState(sessionId, shown));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,5 @@ interface IAutoFillManager {
|
||||
void disableOwnedAutofillServices(int userId);
|
||||
boolean isServiceSupported(int userId);
|
||||
boolean isServiceEnabled(int userId, String packageName);
|
||||
void onPendingSaveUi(int operation, IBinder token);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,12 @@ oneway interface IAutoFillManagerClient {
|
||||
void notifyNoFillUi(int sessionId, in AutofillId id);
|
||||
|
||||
/**
|
||||
* Starts the provided intent sender
|
||||
* Starts the provided intent sender.
|
||||
*/
|
||||
void startIntentSender(in IntentSender intentSender);
|
||||
void startIntentSender(in IntentSender intentSender, in Intent intent);
|
||||
|
||||
/**
|
||||
* Sets the state of the Autofill Save UI for a given session.
|
||||
*/
|
||||
void setSaveUiState(int sessionId, boolean shown);
|
||||
}
|
||||
|
||||
@@ -654,6 +654,21 @@ public final class AutofillManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPendingSaveUi(int operation, IBinder token) {
|
||||
Preconditions.checkNotNull(token, "token");
|
||||
Preconditions.checkArgument(operation == AutofillManager.PENDING_UI_OPERATION_CANCEL
|
||||
|| operation == AutofillManager.PENDING_UI_OPERATION_RESTORE,
|
||||
"invalid operation: %d", operation);
|
||||
synchronized (mLock) {
|
||||
final AutofillManagerServiceImpl service = peekServiceForUserLocked(
|
||||
UserHandle.getCallingUserId());
|
||||
if (service != null) {
|
||||
service.onPendingSaveUi(operation, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||
|
||||
@@ -41,7 +41,6 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.service.autofill.AutofillService;
|
||||
@@ -52,10 +51,12 @@ import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.IAutoFillService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.autofill.IAutoFillManagerClient;
|
||||
|
||||
@@ -233,26 +234,6 @@ final class AutofillManagerServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app.
|
||||
*/
|
||||
void requestSaveForUserLocked(IBinder activityToken) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int numSessions = mSessions.size();
|
||||
for (int i = 0; i < numSessions; i++) {
|
||||
final Session session = mSessions.valueAt(i);
|
||||
if (session.getActivityTokenLocked().equals(activityToken)) {
|
||||
session.callSaveLocked();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
|
||||
}
|
||||
|
||||
boolean addClientLocked(IAutoFillManagerClient client) {
|
||||
if (mClients == null) {
|
||||
mClients = new RemoteCallbackList<>();
|
||||
@@ -290,6 +271,7 @@ final class AutofillManagerServiceImpl {
|
||||
if (!isEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
|
||||
|
||||
// Occasionally clean up abandoned sessions
|
||||
pruneAbandonedSessionsLocked();
|
||||
@@ -461,6 +443,25 @@ final class AutofillManagerServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
void onPendingSaveUi(int operation, @NonNull IBinder token) {
|
||||
if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
|
||||
synchronized (mLock) {
|
||||
final int sessionCount = mSessions.size();
|
||||
for (int i = sessionCount - 1; i >= 0; i--) {
|
||||
final Session session = mSessions.valueAt(i);
|
||||
if (session.isSaveUiPendingForToken(token)) {
|
||||
session.onPendingSaveUi(operation, token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sDebug) {
|
||||
Slog.d(TAG, "No pending Save UI for token " + token + " and operation "
|
||||
+ DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_",
|
||||
operation));
|
||||
}
|
||||
}
|
||||
|
||||
void destroyLocked() {
|
||||
if (sVerbose) Slog.v(TAG, "destroyLocked()");
|
||||
|
||||
@@ -622,8 +623,12 @@ final class AutofillManagerServiceImpl {
|
||||
}
|
||||
|
||||
void destroySessionsLocked() {
|
||||
if (mSessions.size() == 0) {
|
||||
mUi.destroyAll(AutofillManager.NO_SESSION, null, null);
|
||||
return;
|
||||
}
|
||||
while (mSessions.size() > 0) {
|
||||
mSessions.valueAt(0).removeSelfLocked();
|
||||
mSessions.valueAt(0).forceRemoveSelfLocked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ import com.android.internal.os.HandlerCaller;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.autofill.ui.AutoFillUI;
|
||||
import com.android.server.autofill.ui.PendingUi;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
@@ -164,10 +165,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
@GuardedBy("mLock")
|
||||
private boolean mDestroyed;
|
||||
|
||||
/** Whether the session is currently saving */
|
||||
/** Whether the session is currently saving. */
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsSaving;
|
||||
|
||||
/**
|
||||
* Helper used to handle state of Save UI when it must be hiding to show a custom description
|
||||
* link and later recovered.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private PendingUi mPendingSaveUi;
|
||||
|
||||
/**
|
||||
* Receiver of assist data from the app's {@link Activity}.
|
||||
@@ -701,7 +708,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
mHandlerCaller.getHandler().post(() -> {
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
mClient.startIntentSender(intentSender);
|
||||
mClient.startIntentSender(intentSender, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error launching auth intent", e);
|
||||
@@ -964,8 +971,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
|
||||
mService.setSaveShown(id);
|
||||
final IAutoFillManagerClient client = getClient();
|
||||
mPendingSaveUi = new PendingUi(mActivityToken);
|
||||
getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo,
|
||||
valueFinder, mPackageName, this);
|
||||
valueFinder, mPackageName, this, mPendingSaveUi, id, client);
|
||||
if (client != null) {
|
||||
try {
|
||||
client.setSaveUiState(id, true);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e);
|
||||
}
|
||||
}
|
||||
mIsSaving = true;
|
||||
return false;
|
||||
}
|
||||
@@ -1246,7 +1262,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
// Remove the UI if the ViewState has changed.
|
||||
if (mCurrentViewId != viewState.id) {
|
||||
hideFillUiIfOwnedByMe();
|
||||
mUi.hideFillUi(this);
|
||||
mCurrentViewId = viewState.id;
|
||||
}
|
||||
|
||||
@@ -1256,7 +1272,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
case ACTION_VIEW_EXITED:
|
||||
if (mCurrentViewId == viewState.id) {
|
||||
if (sVerbose) Slog.d(TAG, "Exiting view " + id);
|
||||
hideFillUiIfOwnedByMe();
|
||||
mUi.hideFillUi(this);
|
||||
mCurrentViewId = null;
|
||||
}
|
||||
break;
|
||||
@@ -1396,7 +1412,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
|
||||
// Make sure we are hiding the UI which will be shown
|
||||
// only if handling the current response requires it.
|
||||
hideAllUiIfOwnedByMe();
|
||||
mUi.hideAll(this);
|
||||
|
||||
final int requestId = newResponse.getRequestId();
|
||||
if (sVerbose) {
|
||||
@@ -1583,6 +1599,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
|
||||
pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
|
||||
pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
|
||||
pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
|
||||
for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
|
||||
pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
|
||||
entry.getValue().dump(prefix2, pw);
|
||||
@@ -1644,7 +1661,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
if (!ids.isEmpty()) {
|
||||
if (waitingDatasetAuth) {
|
||||
hideFillUiIfOwnedByMe();
|
||||
mUi.hideFillUi(this);
|
||||
}
|
||||
if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
|
||||
|
||||
@@ -1664,38 +1681,65 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up this session.
|
||||
*
|
||||
* <p>Typically called in 2 scenarios:
|
||||
*
|
||||
* <ul>
|
||||
* <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}.
|
||||
* <li>When the service hosting the session is finished (for example, because the user
|
||||
* disabled it).
|
||||
* </ul>
|
||||
*/
|
||||
RemoteFillService destroyLocked() {
|
||||
if (mDestroyed) {
|
||||
return null;
|
||||
}
|
||||
hideAllUiIfOwnedByMe();
|
||||
mUi.destroyAll(id, getClient(), this);
|
||||
mUi.clearCallback(this);
|
||||
mDestroyed = true;
|
||||
mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
|
||||
return mRemoteFillService;
|
||||
}
|
||||
|
||||
private void hideAllUiIfOwnedByMe() {
|
||||
mUi.hideAll(this);
|
||||
}
|
||||
|
||||
private void hideFillUiIfOwnedByMe() {
|
||||
mUi.hideFillUi(this);
|
||||
/**
|
||||
* Cleans up this session and remove it from the service always, even if it does have a pending
|
||||
* Save UI.
|
||||
*/
|
||||
void forceRemoveSelfLocked() {
|
||||
if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
|
||||
|
||||
mPendingSaveUi = null;
|
||||
removeSelfLocked();
|
||||
mUi.destroyAll(id, getClient(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe version of {@link #removeSelfLocked()}.
|
||||
*/
|
||||
private void removeSelf() {
|
||||
synchronized (mLock) {
|
||||
removeSelfLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up this session and remove it from the service, but but only if it does not have a
|
||||
* pending Save UI.
|
||||
*/
|
||||
void removeSelfLocked() {
|
||||
if (sVerbose) Slog.v(TAG, "removeSelfLocked()");
|
||||
if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
|
||||
if (mDestroyed) {
|
||||
Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: "
|
||||
+ id + " destroyed");
|
||||
return;
|
||||
}
|
||||
if (isSaveUiPending()) {
|
||||
Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
|
||||
return;
|
||||
}
|
||||
|
||||
final RemoteFillService remoteFillService = destroyLocked();
|
||||
mService.removeSessionLocked(id);
|
||||
if (remoteFillService != null) {
|
||||
@@ -1703,6 +1747,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
void onPendingSaveUi(int operation, @NonNull IBinder token) {
|
||||
getUiForShowing().onPendingSaveUi(operation, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this session is hiding the Save UI to handle a custom description link for
|
||||
* a specific {@code token} created by {@link PendingUi#PendingUi(IBinder)}.
|
||||
*/
|
||||
boolean isSaveUiPendingForToken(@NonNull IBinder token) {
|
||||
return isSaveUiPending() && token.equals(mPendingSaveUi.getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this session is hiding the Save UI to handle a custom description link.
|
||||
*/
|
||||
private boolean isSaveUiPending() {
|
||||
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
|
||||
}
|
||||
|
||||
private int getLastResponseIndexLocked() {
|
||||
// The response ids are monotonically increasing so
|
||||
// we just find the largest id which is the last. We
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.content.IntentSender;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.SaveInfo;
|
||||
@@ -33,6 +35,7 @@ import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.IAutoFillManagerClient;
|
||||
import android.view.autofill.IAutofillWindowPresenter;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -143,7 +146,6 @@ public final class AutoFillUI {
|
||||
if (callback != mCallback) {
|
||||
return;
|
||||
}
|
||||
hideSaveUiUiThread(callback);
|
||||
if (mFillUi != null) {
|
||||
mFillUi.setFilterText(filterText);
|
||||
}
|
||||
@@ -245,7 +247,8 @@ public final class AutoFillUI {
|
||||
*/
|
||||
public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info,
|
||||
@NonNull ValueFinder valueFinder, @NonNull String packageName,
|
||||
@NonNull AutoFillUiCallback callback) {
|
||||
@NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingUi,
|
||||
int sessionId, @Nullable IAutoFillManagerClient client) {
|
||||
if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
|
||||
int numIds = 0;
|
||||
numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
|
||||
@@ -260,21 +263,22 @@ public final class AutoFillUI {
|
||||
return;
|
||||
}
|
||||
hideAllUiThread(callback);
|
||||
mSaveUi = new SaveUi(mContext, providerLabel, info, valueFinder, mOverlayControl,
|
||||
new SaveUi.OnSaveListener() {
|
||||
mSaveUi = new SaveUi(mContext, pendingUi, providerLabel, info, valueFinder,
|
||||
mOverlayControl, client, new SaveUi.OnSaveListener() {
|
||||
@Override
|
||||
public void onSave() {
|
||||
log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
|
||||
hideSaveUiUiThread(callback);
|
||||
hideSaveUiUiThread(mCallback);
|
||||
if (mCallback != null) {
|
||||
mCallback.save();
|
||||
}
|
||||
destroySaveUiUiThread(sessionId, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(IntentSender listener) {
|
||||
log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
|
||||
hideSaveUiUiThread(callback);
|
||||
hideSaveUiUiThread(mCallback);
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.sendIntent(mContext, 0, null, null, null);
|
||||
@@ -286,6 +290,7 @@ public final class AutoFillUI {
|
||||
if (mCallback != null) {
|
||||
mCallback.cancelSave();
|
||||
}
|
||||
destroySaveUiUiThread(sessionId, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -303,6 +308,19 @@ public final class AutoFillUI {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an operation in the pending save UI, if any.
|
||||
*/
|
||||
public void onPendingSaveUi(int operation, @NonNull IBinder token) {
|
||||
mHandler.post(() -> {
|
||||
if (mSaveUi != null) {
|
||||
mSaveUi.onPendingUi(operation, token);
|
||||
} else {
|
||||
Slog.w(TAG, "onPendingSaveUi(" + operation + "): no save ui");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all UI affordances.
|
||||
*/
|
||||
@@ -310,6 +328,14 @@ public final class AutoFillUI {
|
||||
mHandler.post(() -> hideAllUiThread(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all UI affordances.
|
||||
*/
|
||||
public void destroyAll(int sessionId, @Nullable IAutoFillManagerClient client,
|
||||
@Nullable AutoFillUiCallback callback) {
|
||||
mHandler.post(() -> destroyAllUiThread(sessionId, client, callback));
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("Autofill UI");
|
||||
final String prefix = " ";
|
||||
@@ -343,11 +369,40 @@ public final class AutoFillUI {
|
||||
+ ", mCallback=" + mCallback);
|
||||
}
|
||||
if (mSaveUi != null && (callback == null || callback == mCallback)) {
|
||||
mSaveUi.destroy();
|
||||
mSaveUi = null;
|
||||
mSaveUi.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@android.annotation.UiThread
|
||||
private void destroySaveUiUiThread(int sessionId, @Nullable IAutoFillManagerClient client) {
|
||||
if (mSaveUi == null) {
|
||||
// Calling destroySaveUiUiThread() twice is normal - it usually happens when the
|
||||
// first call is made after the SaveUI is hidden and the second when the session is
|
||||
// finished.
|
||||
if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): already destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): id=" + sessionId);
|
||||
mSaveUi.destroy();
|
||||
mSaveUi = null;
|
||||
if (client != null) {
|
||||
try {
|
||||
if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
|
||||
client.setSaveUiState(sessionId, false);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error notifying client to set save UI state to hidden: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@android.annotation.UiThread
|
||||
private void destroyAllUiThread(int sessionId, @Nullable IAutoFillManagerClient client,
|
||||
@Nullable AutoFillUiCallback callback) {
|
||||
hideFillUiUiThread(callback);
|
||||
destroySaveUiUiThread(sessionId, client);
|
||||
}
|
||||
|
||||
@android.annotation.UiThread
|
||||
private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
|
||||
hideFillUiUiThread(callback);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 com.android.server.autofill.ui;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.IBinder;
|
||||
import android.util.DebugUtils;
|
||||
|
||||
/**
|
||||
* Helper class used to handle a pending Autofill affordance such as the Save UI.
|
||||
*
|
||||
* <p>This class is not thread safe.
|
||||
*/
|
||||
// NOTE: this class could be an interface implemented by Session, but that would make it harder
|
||||
// to move the Autofill UI logic to a different process.
|
||||
public final class PendingUi {
|
||||
|
||||
public static final int STATE_CREATED = 1;
|
||||
public static final int STATE_PENDING = 2;
|
||||
public static final int STATE_FINISHED = 4;
|
||||
|
||||
private final IBinder mToken;
|
||||
private int mState;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param token token used to identify this pending UI.
|
||||
*/
|
||||
public PendingUi(@NonNull IBinder token) {
|
||||
mToken = token;
|
||||
mState = STATE_CREATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token used to identify this pending UI.
|
||||
*/
|
||||
@NonNull
|
||||
public IBinder getToken() {
|
||||
return mToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current lifecycle state.
|
||||
*/
|
||||
public void setState(int state) {
|
||||
mState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current lifecycle state.
|
||||
*/
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given token matches the token used to identify this pending UI.
|
||||
*/
|
||||
public boolean matches(IBinder token) {
|
||||
return mToken.equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PendingUi: [token=" + mToken + ", state="
|
||||
+ DebugUtils.flagsToString(PendingUi.class, "STATE_", mState) + "]";
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,14 @@ import static com.android.server.autofill.Helper.sVerbose;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Dialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.service.autofill.CustomDescription;
|
||||
import android.service.autofill.SaveInfo;
|
||||
import android.service.autofill.ValueFinder;
|
||||
@@ -31,15 +36,17 @@ import android.text.Html;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.view.Gravity;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.IAutoFillManagerClient;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.UiThread;
|
||||
@@ -109,12 +116,15 @@ final class SaveUi {
|
||||
|
||||
private final CharSequence mTitle;
|
||||
private final CharSequence mSubTitle;
|
||||
private final PendingUi mPendingUi;
|
||||
|
||||
private boolean mDestroyed;
|
||||
|
||||
SaveUi(@NonNull Context context, @NonNull CharSequence providerLabel, @NonNull SaveInfo info,
|
||||
SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
|
||||
@NonNull CharSequence providerLabel, @NonNull SaveInfo info,
|
||||
@NonNull ValueFinder valueFinder, @NonNull OverlayControl overlayControl,
|
||||
@NonNull OnSaveListener listener) {
|
||||
@NonNull IAutoFillManagerClient client, @NonNull OnSaveListener listener) {
|
||||
mPendingUi= pendingUi;
|
||||
mListener = new OneTimeListener(listener);
|
||||
mOverlayControl = overlayControl;
|
||||
|
||||
@@ -171,8 +181,49 @@ final class SaveUi {
|
||||
|
||||
final RemoteViews presentation = customDescription.getPresentation(valueFinder);
|
||||
if (presentation != null) {
|
||||
final RemoteViews.OnClickHandler handler = new RemoteViews.OnClickHandler() {
|
||||
@Override
|
||||
public boolean onClickHandler(View view, PendingIntent pendingIntent,
|
||||
Intent intent) {
|
||||
// We need to hide the Save UI before launching the pending intent, and
|
||||
// restore back it once the activity is finished, and that's achieved by
|
||||
// adding a custom extra in the activity intent.
|
||||
if (pendingIntent != null) {
|
||||
if (intent == null) {
|
||||
Slog.w(TAG,
|
||||
"remote view on custom description does not have intent");
|
||||
return false;
|
||||
}
|
||||
if (!pendingIntent.isActivity()) {
|
||||
Slog.w(TAG, "ignoring custom description pending intent that's not "
|
||||
+ "for an activity: " + pendingIntent);
|
||||
return false;
|
||||
}
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG,
|
||||
"Intercepting custom description intent: " + intent);
|
||||
}
|
||||
final IBinder token = mPendingUi.getToken();
|
||||
intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
|
||||
try {
|
||||
client.startIntentSender(pendingIntent.getIntentSender(),
|
||||
intent);
|
||||
mPendingUi.setState(PendingUi.STATE_PENDING);
|
||||
if (sDebug) {
|
||||
Slog.d(TAG, "hiding UI until restored with token " + token);
|
||||
}
|
||||
hide();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "error triggering pending intent: " + intent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
final View customSubtitleView = presentation.apply(context, null);
|
||||
final View customSubtitleView = presentation.apply(context, null, handler);
|
||||
subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
|
||||
subtitleContainer.addView(customSubtitleView);
|
||||
subtitleContainer.setVisibility(View.VISIBLE);
|
||||
@@ -202,7 +253,7 @@ final class SaveUi {
|
||||
} else {
|
||||
noButton.setText(R.string.autofill_save_no);
|
||||
}
|
||||
View.OnClickListener cancelListener =
|
||||
final View.OnClickListener cancelListener =
|
||||
(v) -> mListener.onCancel(info.getNegativeActionListener());
|
||||
noButton.setOnClickListener(cancelListener);
|
||||
|
||||
@@ -212,6 +263,9 @@ final class SaveUi {
|
||||
mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
|
||||
mDialog.setContentView(view);
|
||||
|
||||
// Dialog can be dismissed when touched outside.
|
||||
mDialog.setOnDismissListener((d) -> mListener.onCancel(info.getNegativeActionListener()));
|
||||
|
||||
final Window window = mDialog.getWindow();
|
||||
window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
@@ -227,9 +281,50 @@ final class SaveUi {
|
||||
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
|
||||
params.windowAnimations = R.style.AutofillSaveAnimation;
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the pending UI, if any.
|
||||
*
|
||||
* @param operation how to update it.
|
||||
* @param token token associated with the pending UI - if it doesn't match the pending token,
|
||||
* the operation will be ignored.
|
||||
*/
|
||||
void onPendingUi(int operation, @NonNull IBinder token) {
|
||||
if (!mPendingUi.matches(token)) {
|
||||
Slog.w(TAG, "restore(" + operation + "): got token " + token + " instead of "
|
||||
+ mPendingUi.getToken());
|
||||
return;
|
||||
}
|
||||
switch (operation) {
|
||||
case AutofillManager.PENDING_UI_OPERATION_RESTORE:
|
||||
if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
|
||||
show();
|
||||
break;
|
||||
case AutofillManager.PENDING_UI_OPERATION_CANCEL:
|
||||
if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
|
||||
hide();
|
||||
break;
|
||||
default:
|
||||
Slog.w(TAG, "restore(): invalid operation " + operation);
|
||||
}
|
||||
mPendingUi.setState(PendingUi.STATE_FINISHED);
|
||||
}
|
||||
|
||||
private void show() {
|
||||
Slog.i(TAG, "Showing save dialog: " + mTitle);
|
||||
mDialog.show();
|
||||
mOverlayControl.hideOverlays();
|
||||
}
|
||||
|
||||
void hide() {
|
||||
if (sVerbose) Slog.v(TAG, "Hiding save dialog.");
|
||||
try {
|
||||
mDialog.hide();
|
||||
} finally {
|
||||
mOverlayControl.showOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
@@ -238,7 +333,6 @@ final class SaveUi {
|
||||
throwIfDestroyed();
|
||||
mListener.onDestroy();
|
||||
mHandler.removeCallbacksAndMessages(mListener);
|
||||
if (sVerbose) Slog.v(TAG, "destroy(): dismissing dialog");
|
||||
mDialog.dismiss();
|
||||
mDestroyed = true;
|
||||
} finally {
|
||||
@@ -260,6 +354,7 @@ final class SaveUi {
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix); pw.print("title: "); pw.println(mTitle);
|
||||
pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
|
||||
pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
|
||||
|
||||
final View view = mDialog.getWindow().getDecorView();
|
||||
final int[] loc = view.getLocationOnScreen();
|
||||
|
||||
Reference in New Issue
Block a user