script-astra/Android/Sdk/sources/android-35/android/view/autofill/AutofillClientController.java

500 lines
18 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2021 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 android.view.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.Application;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Dumpable;
import android.util.Log;
import android.util.Slog;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A controller to manage the autofill requests for the {@link Activity}.
*
* @hide
*/
public final class AutofillClientController implements AutofillManager.AutofillClient, Dumpable {
private static final String TAG = "AutofillClientController";
private static final String LOG_TAG = "autofill_client";
public static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
public static final String LAST_AUTOFILL_ID = "android:lastAutofillId";
public static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded";
public static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
public static final String DUMPABLE_NAME = "AutofillManager";
/** The last autofill id that was returned from {@link #getNextAutofillId()} */
public int mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
@NonNull
private final Activity mActivity;
/** The autofill manager. Always access via {@link #getAutofillManager()}. */
@Nullable
private AutofillManager mAutofillManager;
/** The autofill dropdown fill ui. */
@Nullable
private AutofillPopupWindow mAutofillPopupWindow;
private boolean mAutoFillResetNeeded;
private boolean mAutoFillIgnoreFirstResumePause;
/**
* AutofillClientController constructor.
*/
public AutofillClientController(Activity activity) {
mActivity = activity;
}
private AutofillManager getAutofillManager() {
if (mAutofillManager == null) {
mAutofillManager = mActivity.getSystemService(AutofillManager.class);
}
return mAutofillManager;
}
// ------------------ Called for Activity events ------------------
/**
* Called when the Activity is attached.
*/
public void onActivityAttached(Application application) {
mActivity.setAutofillOptions(application.getAutofillOptions());
}
/**
* Called when the {@link Activity#onCreate(Bundle)} is called.
*/
public void onActivityCreated(@NonNull Bundle savedInstanceState) {
mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false);
mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, View.LAST_APP_AUTOFILL_ID);
if (mAutoFillResetNeeded) {
getAutofillManager().onCreate(savedInstanceState);
}
}
/**
* Called when the {@link Activity#onStart()} is called.
*/
public void onActivityStarted() {
if (mAutoFillResetNeeded) {
getAutofillManager().onVisibleForAutofill();
}
}
/**
* Called when the {@link Activity#onResume()} is called.
*/
public void onActivityResumed() {
enableAutofillCompatibilityIfNeeded();
if (mAutoFillResetNeeded) {
if (!mAutoFillIgnoreFirstResumePause) {
View focus = mActivity.getCurrentFocus();
if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
// TODO(b/148815880): Bring up keyboard if resumed from inline authentication.
// TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest#
// testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial
// window visibility after recreation is INVISIBLE in onResume() and next frame
// ViewRootImpl.performTraversals() changes window visibility to VISIBLE.
// So we cannot call View.notifyEnterOrExited() which will do nothing
// when View.isVisibleToUser() is false.
getAutofillManager().notifyViewEntered(focus);
}
}
}
}
/**
* Called when the Activity is performing resume.
*/
public void onActivityPerformResume(boolean followedByPause) {
if (mAutoFillResetNeeded) {
// When Activity is destroyed in paused state, and relaunch activity, there will be
// extra onResume and onPause event, ignore the first onResume and onPause.
// see ActivityThread.handleRelaunchActivity()
mAutoFillIgnoreFirstResumePause = followedByPause;
if (mAutoFillIgnoreFirstResumePause && DEBUG) {
Slog.v(TAG, "autofill will ignore first pause when relaunching " + this);
}
}
}
/**
* Called when the {@link Activity#onPause()} is called.
*/
public void onActivityPaused() {
if (mAutoFillResetNeeded) {
if (!mAutoFillIgnoreFirstResumePause) {
if (DEBUG) Log.v(TAG, "autofill notifyViewExited " + this);
View focus = mActivity.getCurrentFocus();
if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
getAutofillManager().notifyViewExited(focus);
}
} else {
// reset after first pause()
if (DEBUG) Log.v(TAG, "autofill got first pause " + this);
mAutoFillIgnoreFirstResumePause = false;
}
}
}
/**
* Called when the {@link Activity#onStop()} is called.
*/
public void onActivityStopped(Intent intent, boolean changingConfigurations) {
if (mAutoFillResetNeeded) {
// If stopped without changing the configurations, the response should expire.
getAutofillManager().onInvisibleForAutofill(!changingConfigurations);
} else if (intent != null
&& intent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)
&& intent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {
restoreAutofillSaveUi(intent);
}
}
/**
* Called when the {@link Activity#onDestroy()} is called.
*/
public void onActivityDestroyed() {
if (mActivity.isFinishing() && mAutoFillResetNeeded) {
getAutofillManager().onActivityFinishing();
}
}
/**
* Called when the {@link Activity#onSaveInstanceState(Bundle)} is called.
*/
public void onSaveInstanceState(Bundle outState) {
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
}
/**
* Called when the {@link Activity#finish()} is called.
*/
public void onActivityFinish(Intent intent) {
// Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
// be restored now.
if (intent != null && intent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
restoreAutofillSaveUi(intent);
}
}
/**
* Called when the {@link Activity#onBackPressed()} is called.
*/
public void onActivityBackPressed(Intent intent) {
// Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
// be restored now.
if (intent != null && intent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
restoreAutofillSaveUi(intent);
}
}
/**
* Called when the Activity is dispatching the result.
*/
public void onDispatchActivityResult(int requestCode, int resultCode, Intent data) {
Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
getAutofillManager().onAuthenticationResult(requestCode, resultData,
mActivity.getCurrentFocus());
}
/**
* Called when the {@link Activity#startActivity(Intent, Bundle)} is called.
*/
public void onStartActivity(Intent startIntent, Intent cachedIntent) {
if (cachedIntent != null
&& cachedIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)
&& cachedIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {
if (TextUtils.equals(mActivity.getPackageName(),
startIntent.resolveActivity(mActivity.getPackageManager()).getPackageName())) {
// Apply Autofill restore mechanism on the started activity by startActivity()
final IBinder token =
cachedIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
// Remove restore ability from current activity
cachedIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
cachedIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);
// Put restore token
startIntent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
startIntent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true);
}
}
}
/**
* Restore the autofill save ui.
*/
public void restoreAutofillSaveUi(Intent intent) {
final IBinder token =
intent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
// Make only restore Autofill once
intent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
intent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);
getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_RESTORE,
token);
}
/**
* Enable autofill compatibility mode for the Activity if the compatibility mode is enabled
* for the package.
*/
public void enableAutofillCompatibilityIfNeeded() {
if (mActivity.isAutofillCompatibilityEnabled()) {
final AutofillManager afm = mActivity.getSystemService(AutofillManager.class);
if (afm != null) {
afm.enableCompatibilityMode();
}
}
}
@Override
public String getDumpableName() {
return DUMPABLE_NAME;
}
@Override
public void dump(PrintWriter writer, String[] args) {
final String prefix = "";
final AutofillManager afm = getAutofillManager();
if (afm != null) {
afm.dump(prefix, writer);
writer.print(prefix); writer.print("Autofill Compat Mode: ");
writer.println(mActivity.isAutofillCompatibilityEnabled());
} else {
writer.print(prefix); writer.println("No AutofillManager");
}
}
/**
* Returns the next autofill ID that is unique in the activity
*
* <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned
* will be unique.
*/
public int getNextAutofillId() {
if (mLastAutofillId == Integer.MAX_VALUE - 1) {
mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
}
mLastAutofillId++;
return mLastAutofillId;
}
// ------------------ AutofillClient implementation ------------------
@Override
public AutofillId autofillClientGetNextAutofillId() {
return new AutofillId(getNextAutofillId());
}
@Override
public boolean autofillClientIsCompatibilityModeEnabled() {
return mActivity.isAutofillCompatibilityEnabled();
}
@Override
public boolean autofillClientIsVisibleForAutofill() {
return mActivity.isVisibleForAutofill();
}
@Override
public ComponentName autofillClientGetComponentName() {
return mActivity.getComponentName();
}
@Override
public IBinder autofillClientGetActivityToken() {
return mActivity.getActivityToken();
}
@Override
public boolean[] autofillClientGetViewVisibility(AutofillId[] autofillIds) {
final int autofillIdCount = autofillIds.length;
final boolean[] visible = new boolean[autofillIdCount];
for (int i = 0; i < autofillIdCount; i++) {
final AutofillId autofillId = autofillIds[i];
if (autofillId == null) {
visible[i] = false;
continue;
}
final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
if (view != null) {
if (!autofillId.isVirtualInt()) {
visible[i] = view.isVisibleToUser();
} else {
visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
}
}
}
if (android.view.autofill.Helper.sVerbose) {
Log.v(TAG, "autofillClientGetViewVisibility(): " + Arrays.toString(visible));
}
return visible;
}
@Override
public View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId) {
final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance()
.getRootViews(mActivity.getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
final View rootView = roots.get(rootNum).getView();
if (rootView != null && rootView.getAccessibilityWindowId() == windowId) {
final View view = rootView.findViewByAccessibilityIdTraversal(viewId);
if (view != null) {
return view;
}
}
}
return null;
}
@Override
public View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
if (autofillId == null) return null;
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
final View rootView = roots.get(rootNum).getView();
if (rootView != null) {
final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId());
if (view != null) {
return view;
}
}
}
return null;
}
@Override
public View[] autofillClientFindViewsByAutofillIdTraversal(AutofillId[] autofillIds) {
final View[] views = new View[autofillIds.length];
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
final View rootView = roots.get(rootNum).getView();
if (rootView != null) {
final int viewCount = autofillIds.length;
for (int viewNum = 0; viewNum < viewCount; viewNum++) {
if (autofillIds[viewNum] != null && views[viewNum] == null) {
views[viewNum] = rootView.findViewByAutofillIdTraversal(
autofillIds[viewNum].getViewId());
}
}
}
}
return views;
}
@Override
public boolean autofillClientIsFillUiShowing() {
return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
}
@Override
public boolean autofillClientRequestHideFillUi() {
if (mAutofillPopupWindow == null) {
return false;
}
mAutofillPopupWindow.dismiss();
mAutofillPopupWindow = null;
return true;
}
@Override
public boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width,
int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
final boolean wasShowing;
if (mAutofillPopupWindow == null) {
wasShowing = false;
mAutofillPopupWindow = new AutofillPopupWindow(presenter);
} else {
wasShowing = mAutofillPopupWindow.isShowing();
}
mAutofillPopupWindow.update(anchor, 0, 0, width, height, anchorBounds);
return !wasShowing && mAutofillPopupWindow.isShowing();
}
@Override
public void autofillClientDispatchUnhandledKey(View anchor, KeyEvent keyEvent) {
ViewRootImpl rootImpl = anchor.getViewRootImpl();
if (rootImpl != null) {
// don't care if anchorView is current focus, for example a custom view may only receive
// touchEvent, not focusable but can still trigger autofill window. The Key handling
// might be inside parent of the custom view.
rootImpl.dispatchKeyFromAutofill(keyEvent);
}
}
@Override
public boolean isDisablingEnterExitEventForAutofill() {
return mAutoFillIgnoreFirstResumePause || !mActivity.isResumed();
}
@Override
public void autofillClientResetableStateAvailable() {
mAutoFillResetNeeded = true;
}
@Override
public void autofillClientRunOnUiThread(Runnable action) {
mActivity.runOnUiThread(action);
}
@Override
public void autofillClientAuthenticate(int authenticationId, IntentSender intent,
Intent fillInIntent, boolean authenticateInline) {
try {
ActivityOptions activityOptions = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mActivity.startIntentSenderForResult(intent, AUTO_FILL_AUTH_WHO_PREFIX,
authenticationId, fillInIntent, 0, 0, activityOptions.toBundle());
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "authenticate() failed for intent:" + intent, e);
}
}
}