diff options
12 files changed, 709 insertions, 313 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index af3bf2aee049..42192356ed10 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -724,8 +724,9 @@ public class Activity extends ContextThemeWrapper public static final int FINISH_TASK_WITH_ACTIVITY = 2; static final String FRAGMENTS_TAG = "android:fragments"; - static final String AUTOFILL_RESET_NEEDED_TAG = "android:autofillResetNeeded"; + private static final String LAST_ACCESSIBILITY_ID = "android:lastAccessibilityId"; + private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded"; private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; @@ -773,6 +774,9 @@ public class Activity extends ContextThemeWrapper private SearchManager mSearchManager; private MenuInflater mMenuInflater; + /** The autofill manager. Always access via {@link #getAutofillManager()}. */ + @Nullable private AutofillManager mAutofillManager; + static final class NonConfigurationInstances { Object activity; HashMap<String, Object> children; @@ -853,6 +857,9 @@ public class Activity extends ContextThemeWrapper private boolean mAutoFillResetNeeded; + /** The last accessibility id that was returned from {@link #getNextAccessibilityId()} */ + private int mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID; + private AutofillPopupWindow mAutofillPopupWindow; private static native String getDlWarning(); @@ -930,6 +937,19 @@ public class Activity extends ContextThemeWrapper } /** + * (Create and) return the autofill manager + * + * @return The autofill manager + */ + @NonNull private AutofillManager getAutofillManager() { + if (mAutofillManager == null) { + mAutofillManager = getSystemService(AutofillManager.class); + } + + return mAutofillManager; + } + + /** * Called when the activity is starting. This is where most initialization * should go: calling {@link #setContentView(int)} to inflate the * activity's UI, using {@link #findViewById} to programmatically interact @@ -970,6 +990,13 @@ public class Activity extends ContextThemeWrapper } } if (savedInstanceState != null) { + mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false); + mLastAccessibilityId = savedInstanceState.getInt(LAST_ACCESSIBILITY_ID, View.NO_ID); + + if (mAutoFillResetNeeded) { + getAutofillManager().onCreate(savedInstanceState); + } + Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null); @@ -1058,12 +1085,6 @@ public class Activity extends ContextThemeWrapper * @see #onSaveInstanceState */ protected void onRestoreInstanceState(Bundle savedInstanceState) { - mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED_TAG, false); - - if (mAutoFillResetNeeded) { - getSystemService(AutofillManager.class).onRestoreInstanceState(savedInstanceState); - } - if (mWindow != null) { Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG); if (windowState != null) { @@ -1312,6 +1333,27 @@ public class Activity extends ContextThemeWrapper } /** + * Gets the next accessibility ID. + * + * <p>All IDs will be bigger than {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs returned + * will be unique. + * + * @return A ID that is unique in the activity + * + * {@hide} + */ + @Override + public int getNextAccessibilityId() { + if (mLastAccessibilityId == Integer.MAX_VALUE - 1) { + mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID; + } + + mLastAccessibilityId++; + + return mLastAccessibilityId; + } + + /** * Check whether this activity is running as part of a voice interaction with the user. * If true, it should perform its interaction with the user through the * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}. @@ -1505,13 +1547,15 @@ public class Activity extends ContextThemeWrapper */ protected void onSaveInstanceState(Bundle outState) { outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); + + outState.putInt(LAST_ACCESSIBILITY_ID, mLastAccessibilityId); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } if (mAutoFillResetNeeded) { - outState.putBoolean(AUTOFILL_RESET_NEEDED_TAG, mAutoFillResetNeeded); - getSystemService(AutofillManager.class).onSaveInstanceState(outState); + outState.putBoolean(AUTOFILL_RESET_NEEDED, true); + getAutofillManager().onSaveInstanceState(outState); } getApplication().dispatchActivitySaveInstanceState(this, outState); } @@ -1802,7 +1846,7 @@ public class Activity extends ContextThemeWrapper mTranslucentCallback = null; mCalled = true; if (isFinishing() && mAutoFillResetNeeded) { - getSystemService(AutofillManager.class).commit(); + getAutofillManager().commit(); } } @@ -3057,6 +3101,19 @@ public class Activity extends ContextThemeWrapper } /** + * Called before {@link #onAttachedToWindow}. + * + * @hide + */ + @CallSuper + public void onBeforeAttachedToWindow() { + if (mAutoFillResetNeeded) { + getAutofillManager().onAttachedToWindow( + getWindow().getDecorView().getRootView().getWindowToken()); + } + } + + /** * Called when the main window associated with the activity has been * attached to the window manager. * See {@link View#onAttachedToWindow() View.onAttachedToWindow()} @@ -7125,7 +7182,7 @@ public class Activity extends ContextThemeWrapper } } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) { Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; - getSystemService(AutofillManager.class).onAuthenticationResult(resultData); + getAutofillManager().onAuthenticationResult(resultData); } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 368c7b8106b3..88bade1691a6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -61,6 +61,7 @@ import android.provider.MediaStore; import android.util.AttributeSet; import android.view.Display; import android.view.DisplayAdjustments; +import android.view.View; import android.view.ViewDebug; import android.view.WindowManager; import android.view.textclassifier.TextClassificationManager; @@ -436,6 +437,29 @@ public abstract class Context { */ public abstract Context getApplicationContext(); + /** Non-activity related accessibility ids are unique in the app */ + private static int sLastAccessibilityId = View.NO_ID; + + /** + * Gets the next accessibility ID. + * + * <p>All IDs will be smaller or the same as {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs + * returned will be unique. + * + * @return A ID that is unique in the process + * + * {@hide} + */ + public int getNextAccessibilityId() { + if (sLastAccessibilityId == View.LAST_APP_ACCESSIBILITY_ID - 1) { + sLastAccessibilityId = View.NO_ID; + } + + sLastAccessibilityId++; + + return sLastAccessibilityId; + } + /** * Add a new {@link ComponentCallbacks} to the base application of the * Context, which will be called at the same times as the ComponentCallbacks diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index aae22c1bb750..c82b9ebb6b39 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -82,8 +82,7 @@ public abstract class AutofillService extends Service { // Internal extras /** @hide */ - public static final String EXTRA_ACTIVITY_TOKEN = - "android.service.autofill.extra.ACTIVITY_TOKEN"; + public static final String EXTRA_SESSION_ID = "android.service.autofill.extra.SESSION_ID"; // Handler messages. private static final int MSG_CONNECT = 1; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b4100123a5b8..a09db9c3972d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -797,6 +797,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int NO_ID = -1; /** + * Last ID that is given to Views that are no part of activities. + * + * {@hide} + */ + public static final int LAST_APP_ACCESSIBILITY_ID = Integer.MAX_VALUE / 2; + + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. */ @@ -1967,11 +1974,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private SparseArray<Object> mKeyedTags; /** - * The next available accessibility id. - */ - private static int sNextAccessibilityViewId; - - /** * The animation currently associated with this view. * @hide */ @@ -2013,10 +2015,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(resolveId = true) int mID = NO_ID; - /** - * The stable ID of this view for accessibility purposes. + /** The ID of this view for accessibility and autofill purposes. + * <ul> + * <li>== {@link #NO_ID}: ID has not been assigned yet + * <li>≤ {@link #LAST_APP_ACCESSIBILITY_ID}: View is not part of a activity. The ID is + * unique in the process. This might change + * over activity lifecycle events. + * <li>> {@link #LAST_APP_ACCESSIBILITY_ID}: View is part of a activity. The ID is + * unique in the activity. This stays the same + * over activity lifecycle events. */ - int mAccessibilityViewId = NO_ID; + private int mAccessibilityViewId = NO_ID; private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; @@ -8144,7 +8153,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public int getAccessibilityViewId() { if (mAccessibilityViewId == NO_ID) { - mAccessibilityViewId = sNextAccessibilityViewId++; + mAccessibilityViewId = mContext.getNextAccessibilityId(); } return mAccessibilityViewId; } @@ -17159,7 +17168,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @CallSuper protected Parcelable onSaveInstanceState() { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; - if (mStartActivityRequestWho != null || isAutofilled()) { + if (mStartActivityRequestWho != null || isAutofilled() + || mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) { BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE); if (mStartActivityRequestWho != null) { @@ -17170,8 +17180,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, state.mSavedData |= BaseSavedState.IS_AUTOFILLED; } + if (mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) { + state.mSavedData |= BaseSavedState.ACCESSIBILITY_ID; + } + state.mStartActivityRequestWhoSaved = mStartActivityRequestWho; state.mIsAutofilled = isAutofilled(); + state.mAccessibilityViewId = mAccessibilityViewId; return state; } return BaseSavedState.EMPTY_STATE; @@ -17249,6 +17264,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) { setAutofilled(baseState.mIsAutofilled); } + if ((baseState.mSavedData & BaseSavedState.ACCESSIBILITY_ID) != 0) { + mAccessibilityViewId = baseState.mAccessibilityViewId; + } } } @@ -24408,11 +24426,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static class BaseSavedState extends AbsSavedState { static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1; static final int IS_AUTOFILLED = 0b10; + static final int ACCESSIBILITY_ID = 0b100; // Flags that describe what data in this state is valid int mSavedData; String mStartActivityRequestWhoSaved; boolean mIsAutofilled; + int mAccessibilityViewId; /** * Constructor used when reading from a parcel. Reads the state of the superclass. @@ -24435,6 +24455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mSavedData = source.readInt(); mStartActivityRequestWhoSaved = source.readString(); mIsAutofilled = source.readBoolean(); + mAccessibilityViewId = source.readInt(); } /** @@ -24453,6 +24474,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.writeInt(mSavedData); out.writeString(mStartActivityRequestWhoSaved); out.writeBoolean(mIsAutofilled); + out.writeInt(mAccessibilityViewId); } public static final Parcelable.Creator<BaseSavedState> CREATOR diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 6dd8ecfa12e3..a432d30f86ca 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -480,6 +480,13 @@ public abstract class Window { public void onWindowFocusChanged(boolean hasFocus); /** + * @hide + */ + default void onBeforeAttachedToWindow() { + // empty + } + + /** * Called when the window has been attached to the window manager. * See {@link View#onAttachedToWindow() View.onAttachedToWindow()} * for more information. diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java index 02c8945d9fce..7018529cd52a 100644 --- a/core/java/android/view/WindowCallbackWrapper.java +++ b/core/java/android/view/WindowCallbackWrapper.java @@ -109,6 +109,11 @@ public class WindowCallbackWrapper implements Window.Callback { } @Override + public void onBeforeAttachedToWindow() { + mWrapped.onBeforeAttachedToWindow(); + } + + @Override public void onAttachedToWindow() { mWrapped.onAttachedToWindow(); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 07fad60e5b6a..d429d379876f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import android.view.View; import android.view.WindowManagerGlobal; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -48,6 +49,8 @@ import java.util.Objects; /** * App entry point to the AutoFill Framework. + * + * <p>It is safe to call into this from any thread. */ // TODO(b/33197203): improve this javadoc //TODO(b/33197203): restrict manager calls to activity @@ -92,6 +95,7 @@ public final class AutofillManager { */ public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS"; + static final String SESSION_ID_TAG = "android:sessionId"; static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; // Public flags start from the lowest bit @@ -108,17 +112,32 @@ public final class AutofillManager { private final MetricsLogger mMetricsLogger = new MetricsLogger(); + /** + * There is currently no session running. + * {@hide} + */ + public static final int NO_SESSION = Integer.MIN_VALUE; + private final IAutoFillManager mService; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") private IAutoFillManagerClient mServiceClient; + @GuardedBy("mLock") private AutofillCallback mCallback; - private Context mContext; + private final Context mContext; - private boolean mHasSession; + @GuardedBy("mLock") + private int mSessionId = NO_SESSION; + + @GuardedBy("mLock") private boolean mEnabled; /** If a view changes to this mapping the autofill operation was successful */ + @GuardedBy("mLock") @Nullable private ParcelableMap mLastAutofilledData; /** @hide */ @@ -172,8 +191,63 @@ public final class AutofillManager { * * {@hide} */ - public void onRestoreInstanceState(Bundle savedInstanceState) { - mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); + public void onCreate(Bundle savedInstanceState) { + synchronized (mLock) { + mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); + + if (mSessionId != NO_SESSION) { + Log.w(TAG, "New session was started before onCreate()"); + return; + } + + mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); + + if (mSessionId != NO_SESSION) { + ensureServiceClientAddedIfNeededLocked(); + + final AutofillClient client = getClientLocked(); + if (client != null) { + try { + final boolean sessionWasRestored = mService.restoreSession(mSessionId, + mContext.getActivityToken(), mServiceClient.asBinder()); + + if (!sessionWasRestored) { + Log.w(TAG, "Session " + mSessionId + " could not be restored"); + mSessionId = NO_SESSION; + } else { + if (DEBUG) { + Log.d(TAG, "session " + mSessionId + " was restored"); + } + + client.autofillCallbackResetableStateAvailable(); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not figure out if there was an autofill session", e); + } + } + } + } + } + + /** + * Set window future popup windows should be attached to. + * + * @param windowToken The window the popup windows should be attached to + * + * {@hide} + */ + public void onAttachedToWindow(@NonNull IBinder windowToken) { + synchronized (mLock) { + if (mSessionId == NO_SESSION) { + return; + } + + try { + mService.setWindow(mSessionId, windowToken); + } catch (RemoteException e) { + Log.e(TAG, "Could not attach window to session " + mSessionId); + } + } } /** @@ -184,8 +258,14 @@ public final class AutofillManager { * {@hide} */ public void onSaveInstanceState(Bundle outState) { - if (mLastAutofilledData != null) { - outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); + synchronized (mLock) { + if (mSessionId != NO_SESSION) { + outState.putInt(SESSION_ID_TAG, mSessionId); + } + + if (mLastAutofilledData != null) { + outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); + } } } @@ -198,8 +278,10 @@ public final class AutofillManager { * @return whether autofill is enabled for the current user. */ public boolean isEnabled() { - ensureServiceClientAddedIfNeeded(); - return mEnabled; + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); + return mEnabled; + } } /** @@ -212,16 +294,18 @@ public final class AutofillManager { * @param view view requesting the new autofill context. */ public void requestAutofill(@NonNull View view) { - ensureServiceClientAddedIfNeeded(); + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); - if (!mEnabled) { - return; - } + if (!mEnabled) { + return; + } - final AutofillId id = getAutofillId(view); - final AutofillValue value = view.getAutofillValue(); + final AutofillId id = getAutofillId(view); + final AutofillValue value = view.getAutofillValue(); - startSession(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST); + startSessionLocked(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST); + } } /** @@ -236,14 +320,16 @@ public final class AutofillManager { * @param bounds child boundaries, relative to the top window. */ public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) { - ensureServiceClientAddedIfNeeded(); + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); - if (!mEnabled) { - return; - } + if (!mEnabled) { + return; + } - final AutofillId id = getAutofillId(view, childId); - startSession(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST); + final AutofillId id = getAutofillId(view, childId); + startSessionLocked(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST); + } } @@ -253,24 +339,30 @@ public final class AutofillManager { * @param view {@link View} that was entered. */ public void notifyViewEntered(@NonNull View view) { - ensureServiceClientAddedIfNeeded(); + AutofillCallback callback = null; + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); - if (!mEnabled) { - if (mCallback != null) { - mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + final AutofillId id = getAutofillId(view); + final AutofillValue value = view.getAutofillValue(); + + if (mSessionId == NO_SESSION) { + // Starts new session. + startSessionLocked(id, view.getWindowToken(), null, value, 0); + } else { + // Update focus on existing session. + updateSessionLocked(id, null, value, FLAG_VIEW_ENTERED); + } } - return; } - final AutofillId id = getAutofillId(view); - final AutofillValue value = view.getAutofillValue(); - - if (!mHasSession) { - // Starts new session. - startSession(id, view.getWindowToken(), null, value, 0); - } else { - // Update focus on existing session. - updateSession(id, null, value, FLAG_VIEW_ENTERED); + if (callback != null) { + mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); } } @@ -280,13 +372,15 @@ public final class AutofillManager { * @param view {@link View} that was exited. */ public void notifyViewExited(@NonNull View view) { - ensureServiceClientAddedIfNeeded(); + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); - if (mEnabled && mHasSession) { - final AutofillId id = getAutofillId(view); + if (mEnabled && mSessionId != NO_SESSION) { + final AutofillId id = getAutofillId(view); - // Update focus on existing session. - updateSession(id, null, null, FLAG_VIEW_EXITED); + // Update focus on existing session. + updateSessionLocked(id, null, null, FLAG_VIEW_EXITED); + } } } @@ -298,23 +392,30 @@ public final class AutofillManager { * @param bounds child boundaries, relative to the top window. */ public void notifyViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) { - ensureServiceClientAddedIfNeeded(); + AutofillCallback callback = null; + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + final AutofillId id = getAutofillId(view, childId); - if (!mEnabled) { - if (mCallback != null) { - mCallback.onAutofillEvent(view, childId, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + if (mSessionId == NO_SESSION) { + // Starts new session. + startSessionLocked(id, view.getWindowToken(), bounds, null, 0); + } else { + // Update focus on existing session. + updateSessionLocked(id, bounds, null, FLAG_VIEW_ENTERED); + } } - return; } - final AutofillId id = getAutofillId(view, childId); - - if (!mHasSession) { - // Starts new session. - startSession(id, view.getWindowToken(), bounds, null, 0); - } else { - // Update focus on existing session. - updateSession(id, bounds, null, FLAG_VIEW_ENTERED); + if (callback != null) { + callback.onAutofillEvent(view, childId, + AutofillCallback.EVENT_INPUT_UNAVAILABLE); } } @@ -325,13 +426,15 @@ public final class AutofillManager { * @param childId id identifying the virtual child inside the view. */ public void notifyViewExited(@NonNull View view, int childId) { - ensureServiceClientAddedIfNeeded(); + synchronized (mLock) { + ensureServiceClientAddedIfNeededLocked(); - if (mEnabled && mHasSession) { - final AutofillId id = getAutofillId(view, childId); + if (mEnabled && mSessionId != NO_SESSION) { + final AutofillId id = getAutofillId(view, childId); - // Update focus on existing session. - updateSession(id, null, null, FLAG_VIEW_EXITED); + // Update focus on existing session. + updateSessionLocked(id, null, null, FLAG_VIEW_EXITED); + } } } @@ -345,40 +448,42 @@ public final class AutofillManager { boolean valueWasRead = false; AutofillValue value = null; - // If the session is gone some fields might still be highlighted, hence we have to remove - // the isAutofilled property even if no sessions are active. - if (mLastAutofilledData == null) { - view.setAutofilled(false); - } else { - id = getAutofillId(view); - if (mLastAutofilledData.containsKey(id)) { - value = view.getAutofillValue(); - valueWasRead = true; - - if (Objects.equals(mLastAutofilledData.get(id), value)) { - view.setAutofilled(true); + synchronized (mLock) { + // If the session is gone some fields might still be highlighted, hence we have to + // remove the isAutofilled property even if no sessions are active. + if (mLastAutofilledData == null) { + view.setAutofilled(false); + } else { + id = getAutofillId(view); + if (mLastAutofilledData.containsKey(id)) { + value = view.getAutofillValue(); + valueWasRead = true; + + if (Objects.equals(mLastAutofilledData.get(id), value)) { + view.setAutofilled(true); + } else { + view.setAutofilled(false); + mLastAutofilledData.remove(id); + } } else { view.setAutofilled(false); - mLastAutofilledData.remove(id); } - } else { - view.setAutofilled(false); } - } - if (!mEnabled || !mHasSession) { - return; - } + if (!mEnabled || mSessionId == NO_SESSION) { + return; + } - if (id == null) { - id = getAutofillId(view); - } + if (id == null) { + id = getAutofillId(view); + } - if (!valueWasRead) { - value = view.getAutofillValue(); - } + if (!valueWasRead) { + value = view.getAutofillValue(); + } - updateSession(id, null, value, FLAG_VALUE_CHANGED); + updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED); + } } @@ -390,12 +495,14 @@ public final class AutofillManager { * @param value new value of the child. */ public void notifyValueChanged(View view, int childId, AutofillValue value) { - if (!mEnabled || !mHasSession) { - return; - } + synchronized (mLock) { + if (!mEnabled || mSessionId == NO_SESSION) { + return; + } - final AutofillId id = getAutofillId(view, childId); - updateSession(id, null, value, FLAG_VALUE_CHANGED); + final AutofillId id = getAutofillId(view, childId); + updateSessionLocked(id, null, value, FLAG_VALUE_CHANGED); + } } /** @@ -405,11 +512,13 @@ public final class AutofillManager { * call this method after the form is submitted and another page is rendered. */ public void commit() { - if (!mEnabled && !mHasSession) { - return; - } + synchronized (mLock) { + if (!mEnabled && mSessionId == NO_SESSION) { + return; + } - finishSession(); + finishSessionLocked(); + } } /** @@ -419,14 +528,16 @@ public final class AutofillManager { * call this method if the user does not post the form but moves to another form in this page. */ public void cancel() { - if (!mEnabled && !mHasSession) { - return; - } + synchronized (mLock) { + if (!mEnabled && mSessionId == NO_SESSION) { + return; + } - cancelSession(); + cancelSessionLocked(); + } } - private AutofillClient getClient() { + private AutofillClient getClientLocked() { if (mContext instanceof AutofillClient) { return (AutofillClient) mContext; } @@ -442,21 +553,21 @@ public final class AutofillManager { if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data); - if (data == null) { - return; - } - final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); - final Bundle responseData = new Bundle(); - responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); - try { - mService.setAuthenticationResult(responseData, - mContext.getActivityToken(), mContext.getUserId()); - } catch (RemoteException e) { - Log.e(TAG, "Error delivering authentication result", e); + synchronized (mLock) { + if (mSessionId == NO_SESSION || data == null) { + return; + } + final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); + final Bundle responseData = new Bundle(); + responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); + try { + mService.setAuthenticationResult(responseData, mSessionId, mContext.getUserId()); + } catch (RemoteException e) { + Log.e(TAG, "Error delivering authentication result", e); + } } } - private static AutofillId getAutofillId(View view) { return new AutofillId(view.getAccessibilityViewId()); } @@ -465,71 +576,74 @@ public final class AutofillManager { return new AutofillId(parent.getAccessibilityViewId(), childId); } - private void startSession(@NonNull AutofillId id, @NonNull IBinder windowToken, + private void startSessionLocked(@NonNull AutofillId id, @NonNull IBinder windowToken, @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { if (DEBUG) { - Log.d(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value + Log.d(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value + ", flags=" + flags); } try { - mService.startSession(mContext.getActivityToken(), windowToken, + mSessionId = mService.startSession(mContext.getActivityToken(), windowToken, mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, mContext.getOpPackageName()); - AutofillClient client = getClient(); + AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } - mHasSession = true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - private void finishSession() { + private void finishSessionLocked() { if (DEBUG) { - Log.d(TAG, "finishSession()"); + Log.d(TAG, "finishSessionLocked()"); } - mHasSession = false; + try { - mService.finishSession(mContext.getActivityToken(), mContext.getUserId()); + mService.finishSession(mSessionId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + mSessionId = NO_SESSION; } - private void cancelSession() { + private void cancelSessionLocked() { if (DEBUG) { - Log.d(TAG, "cancelSession()"); + Log.d(TAG, "cancelSessionLocked()"); } - mHasSession = false; + try { - mService.cancelSession(mContext.getActivityToken(), mContext.getUserId()); + mService.cancelSession(mSessionId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + mSessionId = NO_SESSION; } - private void updateSession(AutofillId id, Rect bounds, AutofillValue value, int flags) { + private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) { if (DEBUG) { if (VERBOSE || (flags & FLAG_VIEW_EXITED) != 0) { - Log.d(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value - + ", flags=" + flags); + Log.d(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds + + ", value=" + value + ", flags=" + flags); } } try { - mService.updateSession(mContext.getActivityToken(), id, bounds, value, flags, - mContext.getUserId()); + mService.updateSession(mSessionId, id, bounds, value, flags, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - private void ensureServiceClientAddedIfNeeded() { - if (getClient() == null) { + private void ensureServiceClientAddedIfNeededLocked() { + if (getClientLocked() == null) { return; } + if (mServiceClient == null) { mServiceClient = new AutofillManagerClient(this); try { @@ -546,16 +660,18 @@ public final class AutofillManager { * @param callback callback to receive events. */ public void registerCallback(@Nullable AutofillCallback callback) { - if (callback == null) return; + synchronized (mLock) { + if (callback == null) return; - final boolean hadCallback = mCallback != null; - mCallback = callback; + final boolean hadCallback = mCallback != null; + mCallback = callback; - if (mHasSession && !hadCallback) { - try { - mService.setHasCallback(mContext.getActivityToken(), mContext.getUserId(), true); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (!hadCallback) { + try { + mService.setHasCallback(mSessionId, mContext.getUserId(), true); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } } @@ -566,13 +682,13 @@ public final class AutofillManager { * @param callback callback to stop receiving events. */ public void unregisterCallback(@Nullable AutofillCallback callback) { - if (callback == null || mCallback == null || callback != mCallback) return; + synchronized (mLock) { + if (callback == null || mCallback == null || callback != mCallback) return; - mCallback = null; + mCallback = null; - if (mHasSession) { try { - mService.setHasCallback(mContext.getActivityToken(), mContext.getUserId(), false); + mService.setHasCallback(mSessionId, mContext.getUserId(), false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -585,13 +701,21 @@ public final class AutofillManager { if (anchor == null) { return; } - if (getClient().autofillCallbackRequestShowFillUi(anchor, width, height, - anchorBounds, presenter) && mCallback != null) { + + AutofillCallback callback = null; + synchronized (mLock) { + if (getClientLocked().autofillCallbackRequestShowFillUi(anchor, width, height, + anchorBounds, presenter) && mCallback != null) { + callback = mCallback; + } + } + + if (callback != null) { if (id.isVirtual()) { - mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_SHOWN); } else { - mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); + callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); } } } @@ -605,10 +729,12 @@ public final class AutofillManager { private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { AutofillValue currentValue = view.getAutofillValue(); if (Objects.equals(currentValue, targetValue)) { - if (mLastAutofilledData == null) { - mLastAutofilledData = new ParcelableMap(1); + synchronized (mLock) { + if (mLastAutofilledData == null) { + mLastAutofilledData = new ParcelableMap(1); + } + mLastAutofilledData.put(getAutofillId(view), targetValue); } - mLastAutofilledData.put(getAutofillId(view), targetValue); view.setAutofilled(true); } } @@ -646,11 +772,13 @@ public final class AutofillManager { } valuesByParent.put(id.getVirtualChildId(), value); } else { - // Mark the view as to be autofilled with 'value' - if (mLastAutofilledData == null) { - mLastAutofilledData = new ParcelableMap(itemCount - i); + synchronized (mLock) { + // Mark the view as to be autofilled with 'value' + if (mLastAutofilledData == null) { + mLastAutofilledData = new ParcelableMap(itemCount - i); + } + mLastAutofilledData.put(id, value); } - mLastAutofilledData.put(id, value); view.autofill(value); @@ -679,26 +807,41 @@ public final class AutofillManager { } private void requestHideFillUi(IBinder windowToken, AutofillId id) { - if (getClient().autofillCallbackRequestHideFillUi() && mCallback != null) { - final View anchor = findAchorView(windowToken, id); + final View anchor = findAchorView(windowToken, id); + + AutofillCallback callback = null; + synchronized (mLock) { + if (getClientLocked().autofillCallbackRequestHideFillUi() && mCallback != null) { + callback = mCallback; + } + } + + if (callback != null) { if (id.isVirtual()) { - mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_HIDDEN); } else { - mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); + callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); } } } private void notifyNoFillUi(IBinder windowToken, AutofillId id) { - if (mCallback != null) { - final View anchor = findAchorView(windowToken, id); + final View anchor = findAchorView(windowToken, id); + + AutofillCallback callback; + synchronized (mLock) { + callback = mCallback; + } + + if (callback != null) { if (id.isVirtual()) { - mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_UNAVAILABLE); } else { - mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); } + } } @@ -787,7 +930,11 @@ public final class AutofillManager { public void setState(boolean enabled) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.mContext.getMainThreadHandler().post(() -> afm.mEnabled = enabled); + afm.mContext.getMainThreadHandler().post(() -> { + synchronized (afm.mLock) { + afm.mEnabled = enabled; + } + }); } } @@ -808,8 +955,8 @@ public final class AutofillManager { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { - if (afm.getClient() != null) { - afm.getClient().autofillCallbackAuthenticate(intent, fillInIntent); + if (afm.getClientLocked() != null) { + afm.getClientLocked().autofillCallbackAuthenticate(intent, fillInIntent); } }); } @@ -821,7 +968,7 @@ public final class AutofillManager { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { - if (afm.getClient() != null) { + if (afm.getClientLocked() != null) { afm.requestShowFillUi(windowToken, id, width, height, anchorBounds, presenter); } @@ -834,7 +981,7 @@ public final class AutofillManager { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { - if (afm.getClient() != null) { + if (afm.getClientLocked() != null) { afm.requestHideFillUi(windowToken, id); } }); @@ -846,7 +993,7 @@ public final class AutofillManager { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { - if (afm.getClient() != null) { + if (afm.getClientLocked() != null) { afm.notifyNoFillUi(windowToken, id); } }); diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 97210cc919a5..20d09ae13a60 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -30,14 +30,15 @@ import android.view.autofill.IAutoFillManagerClient; */ interface IAutoFillManager { boolean addClient(in IAutoFillManagerClient client, int userId); - oneway void startSession(in IBinder activityToken, IBinder windowToken, in IBinder appCallback, + int startSession(IBinder activityToken, IBinder windowToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, String packageName); - oneway void updateSession(in IBinder activityToken, in AutofillId id, in Rect bounds, + boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback); + void setWindow(int sessionId, in IBinder windowToken); + oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int flags, int userId); - oneway void finishSession(in IBinder activityToken, int userId); - oneway void cancelSession(in IBinder activityToken, int userId); - oneway void setAuthenticationResult(in Bundle data, - in IBinder activityToken, int userId); - oneway void setHasCallback(in IBinder activityToken, int userId, boolean hasIt); + void finishSession(int sessionId, int userId); + void cancelSession(int sessionId, int userId); + void setAuthenticationResult(in Bundle data, int sessionId, int userId); + oneway void setHasCallback(int sessionId, int userId, boolean hasIt); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index a8e16c96acfa..9760f8111ec3 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1474,6 +1474,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final Window.Callback cb = mWindow.getCallback(); if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { + cb.onBeforeAttachedToWindow(); cb.onAttachedToWindow(); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 2bcc260b1c70..89645f457a55 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -333,23 +333,23 @@ public final class AutofillManagerService extends SystemService { } @Override - public void setAuthenticationResult(Bundle data, IBinder activityToken, int userId) { + public void setAuthenticationResult(Bundle data, int sessionId, int userId) { synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - service.setAuthenticationResultLocked(data, activityToken); + service.setAuthenticationResultLocked(data, sessionId, getCallingUid()); } } @Override - public void setHasCallback(IBinder activityToken, int userId, boolean hasIt) { + public void setHasCallback(int sessionId, int userId, boolean hasIt) { synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - service.setHasCallback(activityToken, hasIt); + service.setHasCallback(sessionId, getCallingUid(), hasIt); } } @Override - public void startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback, + public int startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags, String packageName) { // TODO(b/33197203): make sure it's called by resumed / focused activity @@ -369,41 +369,73 @@ public final class AutofillManagerService extends SystemService { synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - service.startSessionLocked(activityToken, windowToken, appCallback, - autofillId, bounds, value, hasCallback, flags, packageName); + return service.startSessionLocked(activityToken, getCallingUid(), windowToken, + appCallback, autofillId, bounds, value, hasCallback, flags, packageName); } } @Override - public void updateSession(IBinder activityToken, AutofillId id, Rect bounds, + public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback) + throws RemoteException { + activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); + appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); + + synchronized (mLock) { + final AutofillManagerServiceImpl service = mServicesCache.get( + UserHandle.getCallingUserId()); + if (service != null) { + return service.restoreSession(sessionId, getCallingUid(), activityToken, + appCallback); + } + } + + return false; + } + + @Override + public void setWindow(int sessionId, IBinder windowToken) throws RemoteException { + windowToken = Preconditions.checkNotNull(windowToken, "windowToken"); + + synchronized (mLock) { + final AutofillManagerServiceImpl service = mServicesCache.get( + UserHandle.getCallingUserId()); + if (service != null) { + service.setWindow(sessionId, getCallingUid(), windowToken); + } + } + } + + @Override + public void updateSession(int sessionId, AutofillId id, Rect bounds, AutofillValue value, int flags, int userId) { synchronized (mLock) { final AutofillManagerServiceImpl service = mServicesCache.get( UserHandle.getCallingUserId()); if (service != null) { - service.updateSessionLocked(activityToken, id, bounds, value, flags); + service.updateSessionLocked(sessionId, getCallingUid(), id, bounds, value, + flags); } } } @Override - public void finishSession(IBinder activityToken, int userId) { + public void finishSession(int sessionId, int userId) { synchronized (mLock) { final AutofillManagerServiceImpl service = mServicesCache.get( UserHandle.getCallingUserId()); if (service != null) { - service.finishSessionLocked(activityToken); + service.finishSessionLocked(sessionId, getCallingUid()); } } } @Override - public void cancelSession(IBinder activityToken, int userId) { + public void cancelSession(int sessionId, int userId) { synchronized (mLock) { final AutofillManagerServiceImpl service = mServicesCache.get( UserHandle.getCallingUserId()); if (service != null) { - service.cancelSessionLocked(activityToken); + service.cancelSessionLocked(sessionId, getCallingUid()); } } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index c8a5780fedf1..94d534a3d7fe 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -16,10 +16,11 @@ package com.android.server.autofill; -import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN; +import static android.service.autofill.AutofillService.EXTRA_SESSION_ID; import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; import static android.view.autofill.AutofillManager.FLAG_START_SESSION; +import static android.view.autofill.AutofillManager.NO_SESSION; import static com.android.server.autofill.Helper.DEBUG; import static com.android.server.autofill.Helper.VERBOSE; @@ -48,11 +49,11 @@ import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; import android.service.autofill.IAutoFillService; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.LocalLog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; +import android.util.SparseArray; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; @@ -65,6 +66,7 @@ import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Random; /** * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the @@ -74,6 +76,7 @@ import java.util.ArrayList; final class AutofillManagerServiceImpl { private static final String TAG = "AutofillManagerServiceImpl"; + private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; static final int MSG_SERVICE_SAVE = 1; @@ -85,6 +88,8 @@ final class AutofillManagerServiceImpl { private RemoteCallbackList<IAutoFillManagerClient> mClients; private AutofillServiceInfo mInfo; + private static final Random sRandom = new Random(); + private final LocalLog mRequestsHistory; /** * Whether service was disabled for user due to {@link UserManager} restrictions. @@ -94,7 +99,7 @@ final class AutofillManagerServiceImpl { private final HandlerCaller.Callback mHandlerCallback = (msg) -> { switch (msg.what) { case MSG_SERVICE_SAVE: - handleSessionSave((IBinder) msg.obj); + handleSessionSave(msg.arg1); break; default: Slog.w(TAG, "invalid msg on handler: " + msg); @@ -113,7 +118,7 @@ final class AutofillManagerServiceImpl { // TODO(b/33197203): need to make sure service is bound while callback is pending and/or // use WeakReference @GuardedBy("mLock") - private final ArrayMap<IBinder, Session> mSessions = new ArrayMap<>(); + private final SparseArray<Session> mSessions = new SparseArray<>(); /** * Receiver of assist data from the app's {@link Activity}. @@ -137,12 +142,12 @@ final class AutofillManagerServiceImpl { return; } - final IBinder activityToken = receiverExtras.getBinder(EXTRA_ACTIVITY_TOKEN); + final int sessionId = receiverExtras.getInt(EXTRA_SESSION_ID); final Session session; synchronized (mLock) { - session = mSessions.get(activityToken); + session = mSessions.get(sessionId); if (session == null) { - Slog.w(TAG, "no server session for activityToken " + activityToken); + Slog.w(TAG, "no server session for " + sessionId); return; } // TODO(b/33197203): since service is fetching the data (to use for save later), @@ -244,13 +249,17 @@ final class AutofillManagerServiceImpl { if (!isEnabled()) { return; } - final Session session = mSessions.get(activityToken); - if (session == null) { - Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken); - 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; + } } - session.callSaveLocked(); + Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken); } boolean addClientLocked(IAutoFillManagerClient client) { @@ -261,61 +270,59 @@ final class AutofillManagerServiceImpl { return isEnabled(); } - void setAuthenticationResultLocked(Bundle data, IBinder activityToken) { + void setAuthenticationResultLocked(Bundle data, int sessionId, int uid) { if (!isEnabled()) { return; } - final Session session = mSessions.get(activityToken); - if (session != null) { + final Session session = mSessions.get(sessionId); + if (session != null && uid == session.uid) { session.setAuthenticationResultLocked(data); } } - void setHasCallback(IBinder activityToken, boolean hasIt) { + void setHasCallback(int sessionId, int uid, boolean hasIt) { if (!isEnabled()) { return; } - final Session session = mSessions.get(activityToken); - if (session != null) { + final Session session = mSessions.get(sessionId); + if (session != null && uid == session.uid) { session.setHasCallback(hasIt); } } - void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken, + int startSessionLocked(@NonNull IBinder activityToken, int uid, @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, int flags, @NonNull String packageName) { if (!isEnabled()) { - return; + return 0; } - final String historyItem = "s=" + mInfo.getServiceInfo().packageName - + " u=" + mUserId + " a=" + activityToken - + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback - + " f=" + flags; - mRequestsHistory.log(historyItem); - - // TODO(b/33197203): Handle scenario when user forced autofill after app was already - // autofilled. - final Session session = mSessions.get(activityToken); - if (session != null) { - // Already started... - return; + final Session newSession = createSessionByTokenLocked(activityToken, uid, windowToken, + appCallbackToken, hasCallback, flags, packageName); + if (newSession == null) { + return NO_SESSION; } - final Session newSession = createSessionByTokenLocked(activityToken, - windowToken, appCallbackToken, hasCallback, flags, packageName); + final String historyItem = + "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName + + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + + hasCallback + " f=" + flags; + mRequestsHistory.log(historyItem); + newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION); + + return newSession.id; } - void finishSessionLocked(IBinder activityToken) { + void finishSessionLocked(int sessionId, int uid) { if (!isEnabled()) { return; } - final Session session = mSessions.get(activityToken); - if (session == null) { - Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken); + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.w(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")"); return; } @@ -328,26 +335,39 @@ final class AutofillManagerServiceImpl { } } - void cancelSessionLocked(IBinder activityToken) { + void cancelSessionLocked(int sessionId, int uid) { if (!isEnabled()) { return; } - final Session session = mSessions.get(activityToken); - if (session == null) { - Slog.w(TAG, "cancelSessionLocked(): no session for " + activityToken); + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")"); return; } session.removeSelfLocked(); } - private Session createSessionByTokenLocked(@NonNull IBinder activityToken, + private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback, int flags, @NonNull String packageName) { + // use random ids so that one app cannot know that another app creates sessions + int sessionId; + int tries = 0; + do { + tries++; + if (tries > MAX_SESSION_ID_CREATE_TRIES) { + Log.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries"); + return null; + } + + sessionId = sRandom.nextInt(); + } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); + final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, - activityToken, windowToken, appCallbackToken, hasCallback, flags, + sessionId, uid, activityToken, windowToken, appCallbackToken, hasCallback, flags, mInfo.getServiceInfo().getComponentName(), packageName); - mSessions.put(activityToken, newSession); + mSessions.put(newSession.id, newSession); /* * TODO(b/33197203): apply security checks below: @@ -358,7 +378,7 @@ final class AutofillManagerServiceImpl { */ try { final Bundle receiverExtras = new Bundle(); - receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken); + receiverExtras.putInt(EXTRA_SESSION_ID, sessionId); final long identity = Binder.clearCallingIdentity(); try { if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, @@ -374,12 +394,51 @@ final class AutofillManagerServiceImpl { return newSession; } - void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect virtualBounds, + /** + * Restores a session after an activity was temporarily destroyed. + * + * @param sessionId The id of the session to restore + * @param uid UID of the process that tries to restore the session + * @param activityToken The new instance of the activity + * @param appCallback The callbacks to the activity + */ + boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, + @NonNull IBinder appCallback) { + final Session session = mSessions.get(sessionId); + + if (session == null || uid != session.uid) { + return false; + } else { + session.switchActivity(activityToken, appCallback); + return true; + } + } + + /** + * Set the window the UI should get attached to + * + * @param sessionId The id of the session to restore + * @param uid UID of the process that tries to restore the session + * @param windowToken The window the activity is now in + */ + boolean setWindow(int sessionId, int uid, @NonNull IBinder windowToken) { + final Session session = mSessions.get(sessionId); + + if (session == null || uid != session.uid) { + return false; + } else { + session.switchWindow(windowToken); + return true; + } + } + + void updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, AutofillValue value, int flags) { - final Session session = mSessions.get(activityToken); - if (session == null) { + final Session session = mSessions.get(sessionId); + if (session == null || session.uid != uid) { if (VERBOSE) { - Slog.v(TAG, "updateSessionLocked(): session gone for " + activityToken); + Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId + "(" + uid + + ")"); } return; } @@ -387,15 +446,15 @@ final class AutofillManagerServiceImpl { session.updateLocked(autofillId, virtualBounds, value, flags); } - void removeSessionLocked(IBinder activityToken) { - mSessions.remove(activityToken); + void removeSessionLocked(int sessionId) { + mSessions.remove(sessionId); } - private void handleSessionSave(IBinder activityToken) { + private void handleSessionSave(int sessionId) { synchronized (mLock) { - final Session session = mSessions.get(activityToken); + final Session session = mSessions.get(sessionId); if (session == null) { - Slog.w(TAG, "handleSessionSave(): already gone: " + activityToken); + Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId); return; } @@ -408,8 +467,9 @@ final class AutofillManagerServiceImpl { Slog.v(TAG, "destroyLocked()"); } - for (Session session : mSessions.values()) { - session.destroyLocked(); + final int numSessions = mSessions.size(); + for (int i = 0; i < numSessions; i++) { + mSessions.valueAt(i).destroyLocked(); } mSessions.clear(); } @@ -463,15 +523,16 @@ final class AutofillManagerServiceImpl { } void destroySessionsLocked() { - for (Session session : mSessions.values()) { - session.removeSelf(); + while (mSessions.size() > 0) { + mSessions.valueAt(0).removeSelf(); } } void listSessionsLocked(ArrayList<String> output) { - for (IBinder activityToken : mSessions.keySet()) { + final int numSessions = mSessions.size(); + for (int i = 0; i < numSessions; i++) { output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName() - : null) + ":" + activityToken); + : null) + ":" + mSessions.keyAt(i)); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c3cbf18135fc..3ef9cd52762d 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -38,12 +38,10 @@ import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; import android.metrics.LogMaker; -import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; -import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; @@ -90,14 +88,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static final String TAG = "AutofillSession"; private final AutofillManagerServiceImpl mService; - private final IBinder mActivityToken; - private final IBinder mWindowToken; private final HandlerCaller mHandlerCaller; private final Object mLock; private final AutoFillUI mUi; private final MetricsLogger mMetricsLogger = new MetricsLogger(); + /** Id of the session */ + public final int id; + + /** uid the session is for */ + public final int uid; + + @GuardedBy("mLock") + @NonNull private IBinder mActivityToken; + + @GuardedBy("mLock") + @NonNull private IBinder mWindowToken; + /** Package name of the app that is auto-filled */ @NonNull private final String mPackageName; @@ -110,7 +118,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @Nullable private AutofillId mCurrentViewId; - private final IAutoFillManagerClient mClient; + @GuardedBy("mLock") + private IAutoFillManagerClient mClient; @GuardedBy("mLock") RemoteFillService mRemoteFillService; @@ -156,9 +165,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, - @NonNull Object lock, @NonNull IBinder activityToken, + @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback, int flags, @NonNull ComponentName componentName, @NonNull String packageName) { + id = sessionId; + this.uid = uid; mService = service; mLock = lock; mUi = ui; @@ -169,21 +180,43 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mHasCallback = hasCallback; mPackageName = packageName; mFlags = flags; - mClient = IAutoFillManagerClient.Stub.asInterface(client); - try { - client.linkToDeath(() -> { - if (VERBOSE) { - Slog.v(TAG, "app binder died"); - } - removeSelf(); - }, 0); - } catch (RemoteException e) { - Slog.w(TAG, "linkToDeath() on mClient failed: " + e); + mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName); + } + + /** + * Gets the currently registered activity token + * + * @return The activity token + */ + public IBinder getActivityTokenLocked() { + return mActivityToken; + } + + /** + * Sets new window for this session. + * + * @param newWindow The window the Ui should be attached to. Can be {@code null} if no + * further UI is needed. + */ + void switchWindow(@NonNull IBinder newWindow) { + synchronized (mLock) { + mWindowToken = newWindow; } + } - mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName); + /** + * Sets new activity and client for this session. + * + * @param newActivity The token of the new activity + * @param newClient The client receiving autofill callbacks + */ + void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { + synchronized (mLock) { + mActivityToken = newActivity; + mClient = IAutoFillManagerClient.Stub.asInterface(newClient); + } } // FillServiceCallbacks @@ -301,7 +334,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void save() { mHandlerCaller.getHandler() - .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, mActivityToken) + .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, id, 0) .sendToTarget(); } @@ -337,10 +370,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void requestHideFillUi(AutofillId id) { - try { - mClient.requestHideFillUi(mWindowToken, id); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting to hide fill UI", e); + synchronized (mLock) { + try { + mClient.requestHideFillUi(mWindowToken, id); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting to hide fill UI", e); + } } } @@ -402,7 +437,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final SaveInfo saveInfo = response.getSaveInfo(); if (DEBUG) { - Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo); + Slog.d(TAG, + "showSaveLocked(): mResponses=" + mResponses + ", mViewStates=" + mViewStates); } /* @@ -664,17 +700,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private void notifyUnavailableToClient() { - if (mCurrentViewId == null) { - // TODO(b/33197203): temporary sanity check; should never happen - Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null"); - return; - } - if (!mHasCallback) return; - try { - mClient.notifyNoFillUi(mWindowToken, mCurrentViewId); - } catch (RemoteException e) { - Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken - + " id=" + mCurrentViewId, e); + synchronized (mLock) { + if (mCurrentViewId == null) { + // TODO(b/33197203): temporary sanity check; should never happen + Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null"); + return; + } + if (!mHasCallback) return; + try { + mClient.notifyNoFillUi(mWindowToken, mCurrentViewId); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken + + " id=" + mCurrentViewId, e); + } } } @@ -683,12 +721,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response); } - if (mCurrentViewId == null) { - // TODO(b/33197203): temporary sanity check; should never happen - Slog.w(TAG, "processResponseLocked(): mCurrentViewId is null"); - return; - } - if (mResponses == null) { mResponses = new ArrayList<>(4); } @@ -699,6 +731,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setViewStatesLocked(response, ViewState.STATE_FILLABLE); + if (mCurrentViewId == null) { + return; + } + if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null && response.getDatasets().size() == 1) { Slog.d(TAG, "autofilling manual request directly"); @@ -807,13 +843,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void startAuthentication(IntentSender intent, Intent fillInIntent) { try { - mClient.authenticate(intent, fillInIntent); + synchronized (mLock) { + mClient.authenticate(intent, fillInIntent); + } } catch (RemoteException e) { Slog.e(TAG, "Error launching auth intent", e); } } void dumpLocked(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("id: "); pw.println(id); + pw.print(prefix); pw.print("uid: "); pw.println(uid); pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags); pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses); @@ -912,6 +952,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "removeSelfLocked()"); } destroyLocked(); - mService.removeSessionLocked(mActivityToken); + mService.removeSessionLocked(id); } } |