Merge "Autofill compatibility mode."
This commit is contained in:
committed by
Android (Google) Code Review
commit
1bb9f29909
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.accessibilityservice;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -43,6 +44,8 @@ import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -346,6 +349,19 @@ public class AccessibilityServiceInfo implements Parcelable {
|
||||
*/
|
||||
public String[] packageNames;
|
||||
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = true, prefix = { "FEEDBACK_" }, value = {
|
||||
FEEDBACK_AUDIBLE,
|
||||
FEEDBACK_GENERIC,
|
||||
FEEDBACK_HAPTIC,
|
||||
FEEDBACK_SPOKEN,
|
||||
FEEDBACK_VISUAL,
|
||||
FEEDBACK_BRAILLE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FeedbackType {}
|
||||
|
||||
/**
|
||||
* The feedback type an {@link AccessibilityService} provides.
|
||||
* <p>
|
||||
@@ -358,6 +374,7 @@ public class AccessibilityServiceInfo implements Parcelable {
|
||||
* @see #FEEDBACK_VISUAL
|
||||
* @see #FEEDBACK_BRAILLE
|
||||
*/
|
||||
@FeedbackType
|
||||
public int feedbackType;
|
||||
|
||||
/**
|
||||
@@ -818,7 +835,8 @@ public class AccessibilityServiceInfo implements Parcelable {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
|
||||
private static void appendFeedbackTypes(StringBuilder stringBuilder,
|
||||
@FeedbackType int feedbackTypes) {
|
||||
stringBuilder.append("feedbackTypes:");
|
||||
stringBuilder.append("[");
|
||||
while (feedbackTypes != 0) {
|
||||
|
||||
@@ -113,6 +113,7 @@ import android.view.Window.WindowControllerCallback;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillManager.AutofillClient;
|
||||
import android.view.autofill.AutofillPopupWindow;
|
||||
@@ -125,7 +126,6 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.app.ToolbarActionBar;
|
||||
import com.android.internal.app.WindowDecorActionBar;
|
||||
import com.android.internal.policy.DecorView;
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
|
||||
import dalvik.system.VMRuntime;
|
||||
@@ -5961,12 +5961,16 @@ public class Activity extends ContextThemeWrapper
|
||||
*
|
||||
* @return Returns the complete component name for this activity
|
||||
*/
|
||||
@Override
|
||||
public ComponentName getComponentName()
|
||||
{
|
||||
public ComponentName getComponentName() {
|
||||
return mComponent;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final ComponentName autofillClientGetComponentName() {
|
||||
return getComponentName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link SharedPreferences} object for accessing preferences
|
||||
* that are private to this activity. This simply calls the underlying
|
||||
@@ -6262,7 +6266,6 @@ public class Activity extends ContextThemeWrapper
|
||||
*
|
||||
* @param action the action to run on the UI thread
|
||||
*/
|
||||
@Override
|
||||
public final void runOnUiThread(Runnable action) {
|
||||
if (Thread.currentThread() != mUiThread) {
|
||||
mHandler.post(action);
|
||||
@@ -6271,6 +6274,12 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final void autofillClientRunOnUiThread(Runnable action) {
|
||||
runOnUiThread(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard implementation of
|
||||
* {@link android.view.LayoutInflater.Factory#onCreateView} used when
|
||||
@@ -7076,6 +7085,18 @@ public class Activity extends ContextThemeWrapper
|
||||
mCurrentConfig = config;
|
||||
|
||||
mWindow.setColorMode(info.colorMode);
|
||||
|
||||
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
|
||||
enableAutofillCompatibilityIfNeeded();
|
||||
}
|
||||
|
||||
private void enableAutofillCompatibilityIfNeeded() {
|
||||
if (isAutofillCompatibilityEnabled()) {
|
||||
final AutofillManager afm = getSystemService(AutofillManager.class);
|
||||
if (afm != null) {
|
||||
afm.enableCompatibilityMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -7572,7 +7593,7 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
final public void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
|
||||
public final void autofillClientAuthenticate(int authenticationId, IntentSender intent,
|
||||
Intent fillInIntent) {
|
||||
try {
|
||||
startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
|
||||
@@ -7584,13 +7605,13 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
final public void autofillCallbackResetableStateAvailable() {
|
||||
public final void autofillClientResetableStateAvailable() {
|
||||
mAutoFillResetNeeded = true;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
|
||||
public final boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width,
|
||||
int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
|
||||
final boolean wasShowing;
|
||||
|
||||
@@ -7607,7 +7628,7 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
final public boolean autofillCallbackRequestHideFillUi() {
|
||||
public final boolean autofillClientRequestHideFillUi() {
|
||||
if (mAutofillPopupWindow == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -7618,8 +7639,16 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) {
|
||||
final View[] views = new View[viewIds.length];
|
||||
public final boolean autofillClientIsFillUiShowing() {
|
||||
return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@NonNull
|
||||
public final View[] autofillClientFindViewsByAutofillIdTraversal(
|
||||
@NonNull AutofillId[] autofillId) {
|
||||
final View[] views = new View[autofillId.length];
|
||||
final ArrayList<ViewRootImpl> roots =
|
||||
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
|
||||
|
||||
@@ -7627,10 +7656,11 @@ public class Activity extends ContextThemeWrapper
|
||||
final View rootView = roots.get(rootNum).getView();
|
||||
|
||||
if (rootView != null) {
|
||||
for (int viewNum = 0; viewNum < viewIds.length; viewNum++) {
|
||||
final int viewCount = autofillId.length;
|
||||
for (int viewNum = 0; viewNum < viewCount; viewNum++) {
|
||||
if (views[viewNum] == null) {
|
||||
views[viewNum] = rootView.findViewByAutofillIdTraversal(
|
||||
viewIds[viewNum]);
|
||||
autofillId[viewNum].getViewId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7641,14 +7671,15 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@Nullable public View findViewByAutofillIdTraversal(int viewId) {
|
||||
@Nullable
|
||||
public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
|
||||
final ArrayList<ViewRootImpl> roots =
|
||||
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
|
||||
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
|
||||
final View rootView = roots.get(rootNum).getView();
|
||||
|
||||
if (rootView != null) {
|
||||
final View view = rootView.findViewByAutofillIdTraversal(viewId);
|
||||
final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId());
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
@@ -7660,50 +7691,62 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) {
|
||||
final boolean[] isVisible = new boolean[viewIds.length];
|
||||
final View views[] = findViewsByAutofillIdTraversal(viewIds);
|
||||
|
||||
for (int i = 0; i < viewIds.length; i++) {
|
||||
View view = views[i];
|
||||
if (view == null) {
|
||||
isVisible[i] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
isVisible[i] = true;
|
||||
|
||||
// Check if the view is visible by checking all parents
|
||||
while (true) {
|
||||
if (view instanceof DecorView && view.getViewRootImpl() == view.getParent()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (view.getVisibility() != View.VISIBLE) {
|
||||
isVisible[i] = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (view.getParent() instanceof View) {
|
||||
view = (View) view.getParent();
|
||||
public final @NonNull boolean[] autofillClientGetViewVisibility(
|
||||
@NonNull 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];
|
||||
final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
|
||||
if (view != null) {
|
||||
if (!autofillId.isVirtual()) {
|
||||
visible[i] = view.isVisibleToUser();
|
||||
} else {
|
||||
break;
|
||||
visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return visible;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
/** @hide */
|
||||
public final @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId,
|
||||
int windowId) {
|
||||
final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance()
|
||||
.getRootViews(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;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public boolean isVisibleForAutofill() {
|
||||
public final @Nullable IBinder autofillClientGetActivityToken() {
|
||||
return getActivityToken();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final boolean autofillClientIsVisibleForAutofill() {
|
||||
return !mStopped;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public boolean isDisablingEnterExitEventForAutofill() {
|
||||
public final boolean autofillIsCompatibilityModeEnabled() {
|
||||
return isAutofillCompatibilityEnabled();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final boolean isDisablingEnterExitEventForAutofill() {
|
||||
return mAutoFillIgnoreFirstResumePause || !mResumed;
|
||||
}
|
||||
|
||||
|
||||
@@ -637,6 +637,8 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
/** Initial values for {@link Profiler}. */
|
||||
ProfilerInfo initProfilerInfo;
|
||||
|
||||
boolean autofillCompatibilityEnabled;
|
||||
|
||||
public String toString() {
|
||||
return "AppBindData{appInfo=" + appInfo + "}";
|
||||
}
|
||||
@@ -863,7 +865,7 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
boolean enableBinderTracking, boolean trackAllocation,
|
||||
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
|
||||
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
|
||||
String buildSerial) {
|
||||
String buildSerial, boolean autofillCompatibilityEnabled) {
|
||||
|
||||
if (services != null) {
|
||||
// Setup the service cache in the ServiceManager
|
||||
@@ -889,6 +891,7 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
data.compatInfo = compatInfo;
|
||||
data.initProfilerInfo = profilerInfo;
|
||||
data.buildSerial = buildSerial;
|
||||
data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
|
||||
sendMessage(H.BIND_APPLICATION, data);
|
||||
}
|
||||
|
||||
@@ -5840,6 +5843,10 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
// If the app is being launched for full backup or restore, bring it up in
|
||||
// a restricted environment with the base application class.
|
||||
app = data.info.makeApplication(data.restrictedBackupMode, null);
|
||||
|
||||
// Propagate autofill compat state
|
||||
app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
|
||||
|
||||
mInitialApplication = app;
|
||||
|
||||
// don't bring up providers in restricted mode; they may depend on the
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.autofill.AutofillManager;
|
||||
|
||||
/**
|
||||
* Base class for maintaining global application state. You can provide your own
|
||||
@@ -299,4 +300,37 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public AutofillManager.AutofillClient getAutofillClient() {
|
||||
final AutofillManager.AutofillClient client = super.getAutofillClient();
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
// Okay, ppl use the application context when they should not. This breaks
|
||||
// autofill among other things. We pick the focused activity since autofill
|
||||
// interacts only with the currently focused activity and we need the fill
|
||||
// client only if a call comes from the focused activity. Sigh...
|
||||
final ActivityThread activityThread = ActivityThread.currentActivityThread();
|
||||
if (activityThread == null) {
|
||||
return null;
|
||||
}
|
||||
final int activityCount = activityThread.mActivities.size();
|
||||
for (int i = 0; i < activityCount; i++) {
|
||||
final ActivityThread.ActivityClientRecord record =
|
||||
activityThread.mActivities.valueAt(i);
|
||||
if (record == null) {
|
||||
continue;
|
||||
}
|
||||
final Activity activity = record.activity;
|
||||
if (activity == null) {
|
||||
continue;
|
||||
}
|
||||
if (activity.getWindow().getDecorView().hasFocus()) {
|
||||
return record.activity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,7 @@ class ContextImpl extends Context {
|
||||
private @Nullable String mSplitName = null;
|
||||
|
||||
private AutofillClient mAutofillClient = null;
|
||||
private boolean mIsAutofillCompatEnabled;
|
||||
|
||||
private final Object mSync = new Object();
|
||||
|
||||
@@ -2255,6 +2256,18 @@ class ContextImpl extends Context {
|
||||
mAutofillClient = client;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public boolean isAutofillCompatibilityEnabled() {
|
||||
return mIsAutofillCompatEnabled;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) {
|
||||
mIsAutofillCompatEnabled = autofillCompatEnabled;
|
||||
}
|
||||
|
||||
static ContextImpl createSystemContext(ActivityThread mainThread) {
|
||||
LoadedApk packageInfo = new LoadedApk(mainThread);
|
||||
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
|
||||
|
||||
@@ -67,7 +67,7 @@ oneway interface IApplicationThread {
|
||||
int debugMode, boolean enableBinderTracking, boolean trackAllocation,
|
||||
boolean restrictedBackupMode, boolean persistent, in Configuration config,
|
||||
in CompatibilityInfo compatInfo, in Map services,
|
||||
in Bundle coreSettings, in String buildSerial);
|
||||
in Bundle coreSettings, in String buildSerial, boolean isAutofillCompatEnabled);
|
||||
void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
|
||||
void scheduleExit();
|
||||
void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
@@ -500,9 +501,8 @@ public class AssistStructure implements Parcelable {
|
||||
ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
|
||||
if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
|
||||
if (forAutoFill) {
|
||||
final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0
|
||||
? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
|
||||
view.onProvideAutofillStructure(builder, autofillFlags);
|
||||
final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags);
|
||||
view.onProvideAutofillStructure(builder, viewFlags);
|
||||
} else {
|
||||
// This is a secure window, so it doesn't want a screenshot, and that
|
||||
// means we should also not copy out its view hierarchy for Assist
|
||||
@@ -512,9 +512,8 @@ public class AssistStructure implements Parcelable {
|
||||
}
|
||||
}
|
||||
if (forAutoFill) {
|
||||
final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0
|
||||
? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
|
||||
view.dispatchProvideAutofillStructure(builder, autofillFlags);
|
||||
final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags);
|
||||
view.dispatchProvideAutofillStructure(builder, viewFlags);
|
||||
} else {
|
||||
view.dispatchProvideStructure(builder);
|
||||
}
|
||||
@@ -532,6 +531,12 @@ public class AssistStructure implements Parcelable {
|
||||
mRoot = new ViewNode(reader, 0);
|
||||
}
|
||||
|
||||
int resolveViewAutofillFlags(Context context, int fillRequestFlags) {
|
||||
return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0
|
||||
|| context.isAutofillCompatibilityEnabled()
|
||||
? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
|
||||
}
|
||||
|
||||
void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
|
||||
out.writeInt(mX);
|
||||
out.writeInt(mY);
|
||||
|
||||
@@ -4924,6 +4924,19 @@ public abstract class Context {
|
||||
public void setAutofillClient(AutofillClient client) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean isAutofillCompatibilityEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the Context is using system resources,
|
||||
* which are non-runtime-overlay-themable and may show inconsistent UI.
|
||||
|
||||
@@ -994,4 +994,20 @@ public class ContextWrapper extends Context {
|
||||
public void setAutofillClient(AutofillClient client) {
|
||||
mBase.setAutofillClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public boolean isAutofillCompatibilityEnabled() {
|
||||
return mBase.isAutofillCompatibilityEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) {
|
||||
mBase.setAutofillCompatibilityEnabled(autofillCompatEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11332,6 +11332,15 @@ public final class Settings {
|
||||
public static final String CHAINED_BATTERY_ATTRIBUTION_ENABLED =
|
||||
"chained_battery_attribution_enabled";
|
||||
|
||||
/**
|
||||
* The packages whitelisted to be run in autofill compatibility mode.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final String AUTOFILL_COMPAT_ALLOWED_PACKAGES =
|
||||
"autofill_compat_allowed_packages";
|
||||
|
||||
/**
|
||||
* Settings to backup. This is here so that it's in the same place as the settings
|
||||
* keys and easy to update.
|
||||
@@ -11874,8 +11883,6 @@ public final class Settings {
|
||||
*/
|
||||
public static final String MULTI_SIM_SMS_PROMPT = "multi_sim_sms_prompt";
|
||||
|
||||
|
||||
|
||||
/** User preferred subscriptions setting.
|
||||
* This holds the details of the user selected subscription from the card and
|
||||
* the activation status. Each settings string have the comma separated values
|
||||
|
||||
@@ -490,8 +490,45 @@ import com.android.internal.os.SomeArgs;
|
||||
* strong suspicious that it could. For example, if an activity has four or more fields and one of
|
||||
* them is a list, chances are that these are address fields (like address, city, state, and
|
||||
* zip code).
|
||||
*
|
||||
* <a name="CompatibilityMode"></a>
|
||||
* <h3>Compatibility mode</h3>
|
||||
*
|
||||
* <p>Apps that use standard Android widgets support autofill out-of-the-box and need to do
|
||||
* very little to improve their user experience (annotating autofillable views and providing
|
||||
* autofill hints). However, some apps do their own rendering and the rendered content may
|
||||
* contain semantic structure that needs to be surfaced to the autofill framework. The platform
|
||||
* exposes APIs to achieve this, however it could take some time until these apps implement
|
||||
* autofill support.
|
||||
*
|
||||
* <p>To enable autofill for such apps the platform provides a compatibility mode in which the
|
||||
* platform would fall back to the accessibility APIs to generate the state reported to autofill
|
||||
* services and fill data. This mode needs to be explicitly requested for a given package up
|
||||
* to a specified max version code allowing clean migration path when the target app begins to
|
||||
* support autofill natively. Note that enabling compatibility may degrade performance for the
|
||||
* target package and should be used with caution. The platform supports whitelisting which packages
|
||||
* can be targeted in compatibility mode to ensure this mode is used only when needed and as long
|
||||
* as needed.
|
||||
*
|
||||
* <p>You can request compatibility mode for packages of interest in the meta-data resource
|
||||
* associated with your service. Below is a sample service declaration:
|
||||
*
|
||||
* <pre> <service android:name=".MyAutofillService"
|
||||
* android:permission="android.permission.BIND_AUTOFILL_SERVICE">
|
||||
* <intent-filter>
|
||||
* <action android:name="android.service.autofill.AutofillService" />
|
||||
* </intent-filter>
|
||||
* <meta-data android:name="android.autofillservice" android:resource="@xml/autofillservice" />
|
||||
* </service></pre>
|
||||
*
|
||||
* <P>In the XML file you can specify one or more packages for which to enable compatibility
|
||||
* mode. Below is a sample meta-data declaration:
|
||||
*
|
||||
* <pre> <autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
* <compatibility-package android:name="foo.bar.baz" android:maxLongVersionCode="1000000000"/>
|
||||
* </autofill-service></pre>
|
||||
*/
|
||||
// TODO(b/70407264): add code snippets above???
|
||||
// TODO(b/70407264): add code snippets to field classification ???
|
||||
public abstract class AutofillService extends Service {
|
||||
private static final String TAG = "AutofillService";
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AppGlobals;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.res.Resources;
|
||||
@@ -27,6 +28,8 @@ import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
@@ -35,11 +38,13 @@ import com.android.internal.R;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link ServiceInfo} and meta-data about an {@link AutofillService}.
|
||||
@@ -49,6 +54,9 @@ import java.io.IOException;
|
||||
public final class AutofillServiceInfo {
|
||||
private static final String TAG = "AutofillServiceInfo";
|
||||
|
||||
private static final String TAG_AUTOFILL_SERVICE = "autofill-service";
|
||||
private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package";
|
||||
|
||||
private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
try {
|
||||
@@ -70,29 +78,15 @@ public final class AutofillServiceInfo {
|
||||
@Nullable
|
||||
private final String mSettingsActivity;
|
||||
|
||||
public AutofillServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
this(pm, getServiceInfoOrThrow(comp, userHandle));
|
||||
}
|
||||
|
||||
public AutofillServiceInfo(PackageManager pm, ServiceInfo si) {
|
||||
mServiceInfo = si;
|
||||
final TypedArray metaDataArray = getMetaDataArray(pm, si);
|
||||
if (metaDataArray != null) {
|
||||
mSettingsActivity = metaDataArray
|
||||
.getString(R.styleable.AutofillService_settingsActivity);
|
||||
metaDataArray.recycle();
|
||||
} else {
|
||||
mSettingsActivity = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the meta-data as a {@link TypedArray}, or {@code null} if not provided,
|
||||
* or throws if invalid.
|
||||
*/
|
||||
@Nullable
|
||||
private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) {
|
||||
private final Map<String, Long> mCompatibilityPackages;
|
||||
|
||||
public AutofillServiceInfo(Context context, ComponentName comp, int userHandle)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
this(context, getServiceInfoOrThrow(comp, userHandle));
|
||||
}
|
||||
|
||||
public AutofillServiceInfo(Context context, ServiceInfo si) {
|
||||
// Check for permissions.
|
||||
if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) {
|
||||
if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) {
|
||||
@@ -111,45 +105,115 @@ public final class AutofillServiceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
mServiceInfo = si;
|
||||
|
||||
// Get the AutoFill metadata, if declared.
|
||||
XmlResourceParser parser = si.loadXmlMetaData(pm, AutofillService.SERVICE_META_DATA);
|
||||
final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(),
|
||||
AutofillService.SERVICE_META_DATA);
|
||||
if (parser == null) {
|
||||
return null;
|
||||
mSettingsActivity = null;
|
||||
mCompatibilityPackages = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse service info and get the <autofill-service> tag as an AttributeSet.
|
||||
AttributeSet attrs;
|
||||
String settingsActivity = null;
|
||||
Map<String, Long> compatibilityPackages = null;
|
||||
|
||||
try {
|
||||
// Move the XML parser to the first tag.
|
||||
try {
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||
&& type != XmlPullParser.START_TAG) {
|
||||
final Resources resources = context.getPackageManager().getResourcesForApplication(
|
||||
si.applicationInfo);
|
||||
|
||||
int type = 0;
|
||||
while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
|
||||
type = parser.next();
|
||||
}
|
||||
|
||||
if (TAG_AUTOFILL_SERVICE.equals(parser.getName())) {
|
||||
final AttributeSet allAttributes = Xml.asAttributeSet(parser);
|
||||
TypedArray afsAttributes = null;
|
||||
try {
|
||||
afsAttributes = resources.obtainAttributes(allAttributes,
|
||||
com.android.internal.R.styleable.AutofillService);
|
||||
settingsActivity = afsAttributes.getString(
|
||||
R.styleable.AutofillService_settingsActivity);
|
||||
} finally {
|
||||
if (afsAttributes != null) {
|
||||
afsAttributes.recycle();
|
||||
}
|
||||
}
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
Log.e(TAG, "Error parsing auto fill service meta-data", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!"autofill-service".equals(parser.getName())) {
|
||||
compatibilityPackages = parseCompatibilityPackages(parser, resources);
|
||||
} else {
|
||||
Log.e(TAG, "Meta-data does not start with autofill-service tag");
|
||||
return null;
|
||||
}
|
||||
attrs = Xml.asAttributeSet(parser);
|
||||
|
||||
// Get resources required to read the AttributeSet.
|
||||
Resources res;
|
||||
try {
|
||||
res = pm.getResourcesForApplication(si.applicationInfo);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Error getting application resources", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return res.obtainAttributes(attrs, R.styleable.AutofillService);
|
||||
} finally {
|
||||
parser.close();
|
||||
} catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
|
||||
Log.e(TAG, "Error parsing auto fill service meta-data", e);
|
||||
}
|
||||
|
||||
mSettingsActivity = settingsActivity;
|
||||
mCompatibilityPackages = compatibilityPackages;
|
||||
}
|
||||
|
||||
private Map<String, Long> parseCompatibilityPackages(XmlPullParser parser, Resources resources)
|
||||
throws IOException, XmlPullParserException {
|
||||
Map<String, Long> compatibilityPackages = null;
|
||||
|
||||
final int outerDepth = parser.getDepth();
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
|
||||
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TAG_COMPATIBILITY_PACKAGE.equals(parser.getName())) {
|
||||
TypedArray cpAttributes = null;
|
||||
try {
|
||||
final AttributeSet allAttributes = Xml.asAttributeSet(parser);
|
||||
|
||||
cpAttributes = resources.obtainAttributes(allAttributes,
|
||||
R.styleable.AutofillService_CompatibilityPackage);
|
||||
|
||||
final String name = cpAttributes.getString(
|
||||
R.styleable.AutofillService_CompatibilityPackage_name);
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
Log.e(TAG, "Invalid compatibility package:" + name);
|
||||
break;
|
||||
}
|
||||
|
||||
final String maxVersionCodeStr = cpAttributes.getString(
|
||||
R.styleable.AutofillService_CompatibilityPackage_maxLongVersionCode);
|
||||
final Long maxVersionCode;
|
||||
if (maxVersionCodeStr != null) {
|
||||
try {
|
||||
maxVersionCode = Long.parseLong(maxVersionCodeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, "Invalid compatibility max version code:"
|
||||
+ maxVersionCodeStr);
|
||||
break;
|
||||
}
|
||||
if (maxVersionCode < 0) {
|
||||
Log.e(TAG, "Invalid compatibility max version code:"
|
||||
+ maxVersionCode);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
maxVersionCode = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (compatibilityPackages == null) {
|
||||
compatibilityPackages = new ArrayMap<>();
|
||||
}
|
||||
compatibilityPackages.put(name, maxVersionCode);
|
||||
} finally {
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
if (cpAttributes != null) {
|
||||
cpAttributes.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compatibilityPackages;
|
||||
}
|
||||
|
||||
public ServiceInfo getServiceInfo() {
|
||||
@@ -161,8 +225,26 @@ public final class AutofillServiceInfo {
|
||||
return mSettingsActivity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public boolean isCompatibilityModeRequested(String packageName, long versionCode) {
|
||||
if (mCompatibilityPackages == null) {
|
||||
return false;
|
||||
}
|
||||
final Long maxVersionCode = mCompatibilityPackages.get(packageName);
|
||||
if (maxVersionCode == null) {
|
||||
return false;
|
||||
}
|
||||
return versionCode <= maxVersionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mServiceInfo == null ? "null" : mServiceInfo.toString();
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append(getClass().getSimpleName());
|
||||
builder.append("[").append(mServiceInfo);
|
||||
builder.append(", settings:").append(mSettingsActivity);
|
||||
builder.append(", hasCompatPckgs:").append(mCompatibilityPackages != null
|
||||
&& !mCompatibilityPackages.isEmpty()).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
@@ -7940,12 +7941,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* optimal implementation providing this data.
|
||||
*/
|
||||
public void onProvideVirtualStructure(ViewStructure structure) {
|
||||
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
|
||||
onProvideVirtualStructureCompat(structure, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback implementation to populate a ViewStructure from accessibility state.
|
||||
*
|
||||
* @param structure The structure to populate.
|
||||
* @param forAutofill Whether the structure is needed for autofill.
|
||||
*/
|
||||
private void onProvideVirtualStructureCompat(ViewStructure structure, boolean forAutofill) {
|
||||
final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
|
||||
if (provider != null) {
|
||||
AccessibilityNodeInfo info = createAccessibilityNodeInfo();
|
||||
final AccessibilityNodeInfo info = createAccessibilityNodeInfo();
|
||||
structure.setChildCount(1);
|
||||
ViewStructure root = structure.newChild(0);
|
||||
populateVirtualStructure(root, provider, info);
|
||||
final ViewStructure root = structure.newChild(0);
|
||||
populateVirtualStructure(root, provider, info, forAutofill);
|
||||
info.recycle();
|
||||
}
|
||||
}
|
||||
@@ -7974,6 +7985,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* <li>Call {@link android.view.autofill.AutofillManager#notifyViewEntered(View, int, Rect)}
|
||||
* and/or {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)}
|
||||
* when the focused virtual child changed.
|
||||
* <li>Override {@link #isVisibleToUserForAutofill(int)} to allow the platform to query
|
||||
* whether a given virtual view is visible to the user in order to support triggering
|
||||
* save when all views of interest go away.
|
||||
* <li>Call
|
||||
* {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
|
||||
* when the value of a virtual child changed.
|
||||
@@ -8009,6 +8023,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
|
||||
*/
|
||||
public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
|
||||
if (mContext.isAutofillCompatibilityEnabled()) {
|
||||
onProvideVirtualStructureCompat(structure, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8086,6 +8103,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* @attr ref android.R.styleable#Theme_autofilledHighlight
|
||||
*/
|
||||
public void autofill(@NonNull @SuppressWarnings("unused") SparseArray<AutofillValue> values) {
|
||||
if (!mContext.isAutofillCompatibilityEnabled()) {
|
||||
return;
|
||||
}
|
||||
final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
|
||||
if (provider == null) {
|
||||
return;
|
||||
}
|
||||
final int valueCount = values.size();
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
final AutofillValue value = values.valueAt(i);
|
||||
if (value.isText()) {
|
||||
final int virtualId = values.keyAt(i);
|
||||
final CharSequence text = value.getTextValue();
|
||||
final Bundle arguments = new Bundle();
|
||||
arguments.putCharSequence(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
|
||||
provider.performAction(virtualId, AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8339,7 +8375,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
|
||||
private void populateVirtualStructure(ViewStructure structure,
|
||||
AccessibilityNodeProvider provider, AccessibilityNodeInfo info) {
|
||||
AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
|
||||
boolean forAutofill) {
|
||||
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
|
||||
null, null, null);
|
||||
Rect rect = structure.getTempRect();
|
||||
@@ -8374,21 +8411,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
if (info.isContextClickable()) {
|
||||
structure.setContextClickable(true);
|
||||
}
|
||||
if (forAutofill) {
|
||||
structure.setAutofillId(new AutofillId(getAutofillId(),
|
||||
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
|
||||
}
|
||||
CharSequence cname = info.getClassName();
|
||||
structure.setClassName(cname != null ? cname.toString() : null);
|
||||
structure.setContentDescription(info.getContentDescription());
|
||||
if ((info.getText() != null || info.getError() != null)) {
|
||||
structure.setText(info.getText(), info.getTextSelectionStart(),
|
||||
info.getTextSelectionEnd());
|
||||
if (forAutofill) {
|
||||
if (info.isEditable()) {
|
||||
structure.setDataIsSensitive(true);
|
||||
structure.setAutofillType(AUTOFILL_TYPE_TEXT);
|
||||
final AutofillValue autofillValue = AutofillValue.forText(structure.getText());
|
||||
structure.setAutofillValue(autofillValue);
|
||||
if (info.isPassword()) {
|
||||
structure.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
}
|
||||
} else {
|
||||
structure.setDataIsSensitive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
final int NCHILDREN = info.getChildCount();
|
||||
if (NCHILDREN > 0) {
|
||||
structure.setChildCount(NCHILDREN);
|
||||
for (int i=0; i<NCHILDREN; i++) {
|
||||
if (AccessibilityNodeInfo.getVirtualDescendantId(info.getChildNodeIds().get(i))
|
||||
== AccessibilityNodeProvider.HOST_VIEW_ID) {
|
||||
Log.e(VIEW_LOG_TAG, "Virtual view pointing to its host. Ignoring");
|
||||
continue;
|
||||
}
|
||||
AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
|
||||
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
|
||||
ViewStructure child = structure.newChild(i);
|
||||
populateVirtualStructure(child, provider, cinfo);
|
||||
populateVirtualStructure(child, provider, cinfo, forAutofill);
|
||||
cinfo.recycle();
|
||||
}
|
||||
}
|
||||
@@ -8716,6 +8775,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return findViewByPredicateInsideOut(view, mMatchLabelForPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes whether this virtual autofill view is visible to the user.
|
||||
*
|
||||
* @return Whether the view is visible on the screen.
|
||||
*/
|
||||
public boolean isVisibleToUserForAutofill(int virtualId) {
|
||||
if (mContext.isAutofillCompatibilityEnabled()) {
|
||||
final AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
|
||||
if (provider != null) {
|
||||
final AccessibilityNodeInfo node = provider.createAccessibilityNodeInfo(virtualId);
|
||||
if (node != null) {
|
||||
return node.isVisibleToUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes whether this view is visible to the user. Such a view is
|
||||
* attached, visible, all its predecessors are visible, it is not clipped
|
||||
@@ -8725,7 +8802,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
protected boolean isVisibleToUser() {
|
||||
public boolean isVisibleToUser() {
|
||||
return isVisibleToUser(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -3555,13 +3555,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
public void dispatchProvideAutofillStructure(ViewStructure structure,
|
||||
@AutofillFlags int flags) {
|
||||
super.dispatchProvideAutofillStructure(structure, flags);
|
||||
|
||||
if (structure.getChildCount() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLaidOut()) {
|
||||
Log.v(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring "
|
||||
+ mChildrenCount + " children of " + getAutofillId());
|
||||
if (Helper.sVerbose) {
|
||||
Log.v(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring "
|
||||
+ mChildrenCount + " children of " + getAutofillId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5120,6 +5123,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
// manually assembling the hierarchy, update the ancestor default-focus chain.
|
||||
setDefaultFocus(child);
|
||||
}
|
||||
|
||||
touchAccessibilityNodeProviderIfNeeded(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* We may need to touch the provider to bring up the a11y layer. In a11y mode
|
||||
* clients inspect the screen or the user touches it which triggers bringing up
|
||||
* of the a11y infrastructure while in autofill mode we want the infra up and
|
||||
* running from the beginning since we watch for a11y events to drive autofill.
|
||||
*/
|
||||
private void touchAccessibilityNodeProviderIfNeeded(View child) {
|
||||
if (mContext.isAutofillCompatibilityEnabled()) {
|
||||
child.getAccessibilityNodeProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private void addInArray(View child, int index) {
|
||||
|
||||
@@ -71,6 +71,7 @@ import android.os.Trace;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.LongArray;
|
||||
import android.util.MergedConfiguration;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
@@ -114,6 +115,8 @@ import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
@@ -2569,6 +2572,10 @@ public final class ViewRootImpl implements ViewParent,
|
||||
~WindowManager.LayoutParams
|
||||
.SOFT_INPUT_IS_FORWARD_NAVIGATION;
|
||||
mHasHadWindowFocus = true;
|
||||
|
||||
// Refocusing a window that has a focused view should fire a
|
||||
// focus event for the view since the global focused view changed.
|
||||
fireAccessibilityFocusEventIfHasFocusedNode();
|
||||
} else {
|
||||
if (mPointerCapture) {
|
||||
handlePointerCaptureChanged(false);
|
||||
@@ -2578,6 +2585,86 @@ public final class ViewRootImpl implements ViewParent,
|
||||
mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
|
||||
}
|
||||
|
||||
private void fireAccessibilityFocusEventIfHasFocusedNode() {
|
||||
if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
|
||||
return;
|
||||
}
|
||||
final View focusedView = mView.findFocus();
|
||||
if (focusedView == null) {
|
||||
return;
|
||||
}
|
||||
final AccessibilityNodeProvider provider = focusedView.getAccessibilityNodeProvider();
|
||||
if (provider == null) {
|
||||
focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
} else {
|
||||
final AccessibilityNodeInfo focusedNode = findFocusedVirtualNode(provider);
|
||||
if (focusedNode != null) {
|
||||
final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(
|
||||
focusedNode.getSourceNodeId());
|
||||
// This is a best effort since clearing and setting the focus via the
|
||||
// provider APIs could have side effects. We don't have a provider API
|
||||
// similar to that on View to ask a given event to be fired.
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(
|
||||
AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
event.setSource(focusedView, virtualId);
|
||||
event.setPackageName(focusedNode.getPackageName());
|
||||
event.setChecked(focusedNode.isChecked());
|
||||
event.setContentDescription(focusedNode.getContentDescription());
|
||||
event.setPassword(focusedNode.isPassword());
|
||||
event.getText().add(focusedNode.getText());
|
||||
event.setEnabled(focusedNode.isEnabled());
|
||||
focusedView.getParent().requestSendAccessibilityEvent(focusedView, event);
|
||||
focusedNode.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AccessibilityNodeInfo findFocusedVirtualNode(AccessibilityNodeProvider provider) {
|
||||
AccessibilityNodeInfo focusedNode = provider.findFocus(
|
||||
AccessibilityNodeInfo.FOCUS_INPUT);
|
||||
if (focusedNode != null) {
|
||||
return focusedNode;
|
||||
}
|
||||
|
||||
if (!mContext.isAutofillCompatibilityEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Unfortunately some provider implementations don't properly
|
||||
// implement AccessibilityNodeProvider#findFocus
|
||||
AccessibilityNodeInfo current = provider.createAccessibilityNodeInfo(
|
||||
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||
if (current.isFocused()) {
|
||||
return current;
|
||||
}
|
||||
|
||||
final Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
|
||||
fringe.offer(current);
|
||||
|
||||
while (!fringe.isEmpty()) {
|
||||
current = fringe.poll();
|
||||
final LongArray childNodeIds = current.getChildNodeIds();
|
||||
if (childNodeIds== null || childNodeIds.size() <= 0) {
|
||||
continue;
|
||||
}
|
||||
final int childCount = childNodeIds.size();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(
|
||||
childNodeIds.get(i));
|
||||
final AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(virtualId);
|
||||
if (child != null) {
|
||||
if (child.isFocused()) {
|
||||
return child;
|
||||
}
|
||||
fringe.offer(child);
|
||||
}
|
||||
}
|
||||
current.recycle();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
|
||||
Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
|
||||
try {
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
|
||||
package android.view.accessibility;
|
||||
|
||||
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SdkConstant;
|
||||
@@ -44,7 +43,7 @@ import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.IWindow;
|
||||
import android.view.View;
|
||||
|
||||
import android.view.accessibility.AccessibilityEvent.EventType;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IntPair;
|
||||
|
||||
@@ -52,6 +51,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
|
||||
|
||||
/**
|
||||
* System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
|
||||
* and provides facilities for querying the accessibility state of the system.
|
||||
@@ -132,6 +133,8 @@ public final class AccessibilityManager {
|
||||
|
||||
boolean mIsHighTextContrastEnabled;
|
||||
|
||||
AccessibilityPolicy mAccessibilityPolicy;
|
||||
|
||||
private final ArrayMap<AccessibilityStateChangeListener, Handler>
|
||||
mAccessibilityStateChangeListeners = new ArrayMap<>();
|
||||
|
||||
@@ -215,6 +218,60 @@ public final class AccessibilityManager {
|
||||
void onHighTextContrastStateChanged(boolean enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Policy to inject behavior into the accessibility manager.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface AccessibilityPolicy {
|
||||
/**
|
||||
* Checks whether accessibility is enabled.
|
||||
*
|
||||
* @param accessibilityEnabled Whether the accessibility layer is enabled.
|
||||
* @return whether accessibility is enabled.
|
||||
*/
|
||||
boolean isEnabled(boolean accessibilityEnabled);
|
||||
|
||||
/**
|
||||
* Notifies the policy for an accessibility event.
|
||||
*
|
||||
* @param event The event.
|
||||
* @param accessibilityEnabled Whether the accessibility layer is enabled.
|
||||
* @param relevantEventTypes The events relevant events.
|
||||
* @return The event to dispatch or null.
|
||||
*/
|
||||
@Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event,
|
||||
boolean accessibilityEnabled, @EventType int relevantEventTypes);
|
||||
|
||||
/**
|
||||
* Gets the list of relevant events.
|
||||
*
|
||||
* @param relevantEventTypes The relevant events.
|
||||
* @return The relevant events to report.
|
||||
*/
|
||||
@EventType int getRelevantEventTypes(@EventType int relevantEventTypes);
|
||||
|
||||
/**
|
||||
* Gets the list of installed services to report.
|
||||
*
|
||||
* @param installedService The installed services.
|
||||
* @return The services to report.
|
||||
*/
|
||||
@NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
|
||||
@Nullable List<AccessibilityServiceInfo> installedService);
|
||||
|
||||
/**
|
||||
* Gets the list of enabled accessibility services.
|
||||
*
|
||||
* @param feedbackTypeFlags The feedback type to query for.
|
||||
* @param enabledService The enabled services.
|
||||
* @return The services to report.
|
||||
*/
|
||||
@Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
|
||||
@FeedbackType int feedbackTypeFlags,
|
||||
@Nullable List<AccessibilityServiceInfo> enabledService);
|
||||
}
|
||||
|
||||
private final IAccessibilityManagerClient.Stub mClient =
|
||||
new IAccessibilityManagerClient.Stub() {
|
||||
@Override
|
||||
@@ -341,11 +398,8 @@ public final class AccessibilityManager {
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
synchronized (mLock) {
|
||||
IAccessibilityManager service = getServiceLocked();
|
||||
if (service == null) {
|
||||
return false;
|
||||
}
|
||||
return mIsEnabled;
|
||||
return mIsEnabled || (mAccessibilityPolicy != null
|
||||
&& mAccessibilityPolicy.isEnabled(mIsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,12 +455,23 @@ public final class AccessibilityManager {
|
||||
public void sendAccessibilityEvent(AccessibilityEvent event) {
|
||||
final IAccessibilityManager service;
|
||||
final int userId;
|
||||
final AccessibilityEvent dispatchedEvent;
|
||||
synchronized (mLock) {
|
||||
service = getServiceLocked();
|
||||
if (service == null) {
|
||||
return;
|
||||
}
|
||||
if (!mIsEnabled) {
|
||||
event.setEventTime(SystemClock.uptimeMillis());
|
||||
if (mAccessibilityPolicy != null) {
|
||||
dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event,
|
||||
mIsEnabled, mRelevantEventTypes);
|
||||
if (dispatchedEvent == null) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dispatchedEvent = event;
|
||||
}
|
||||
if (!isEnabled()) {
|
||||
Looper myLooper = Looper.myLooper();
|
||||
if (myLooper == Looper.getMainLooper()) {
|
||||
throw new IllegalStateException(
|
||||
@@ -420,9 +485,9 @@ public final class AccessibilityManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((event.getEventType() & mRelevantEventTypes) == 0) {
|
||||
if ((dispatchedEvent.getEventType() & mRelevantEventTypes) == 0) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
|
||||
Log.i(LOG_TAG, "Not dispatching irrelevant event: " + dispatchedEvent
|
||||
+ " that is not among "
|
||||
+ AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
|
||||
}
|
||||
@@ -431,23 +496,25 @@ public final class AccessibilityManager {
|
||||
userId = mUserId;
|
||||
}
|
||||
try {
|
||||
event.setEventTime(SystemClock.uptimeMillis());
|
||||
// it is possible that this manager is in the same process as the service but
|
||||
// client using it is called through Binder from another process. Example: MMS
|
||||
// app adds a SMS notification and the NotificationManagerService calls this method
|
||||
long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
service.sendAccessibilityEvent(event, userId);
|
||||
service.sendAccessibilityEvent(dispatchedEvent, userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, event + " sent");
|
||||
Log.i(LOG_TAG, dispatchedEvent + " sent");
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Log.e(LOG_TAG, "Error during sending " + event + " ", re);
|
||||
Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re);
|
||||
} finally {
|
||||
event.recycle();
|
||||
if (event != dispatchedEvent) {
|
||||
event.recycle();
|
||||
}
|
||||
dispatchedEvent.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +529,7 @@ public final class AccessibilityManager {
|
||||
if (service == null) {
|
||||
return;
|
||||
}
|
||||
if (!mIsEnabled) {
|
||||
if (!isEnabled()) {
|
||||
Looper myLooper = Looper.myLooper();
|
||||
if (myLooper == Looper.getMainLooper()) {
|
||||
throw new IllegalStateException(
|
||||
@@ -532,6 +599,9 @@ public final class AccessibilityManager {
|
||||
} catch (RemoteException re) {
|
||||
Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
|
||||
}
|
||||
if (mAccessibilityPolicy != null) {
|
||||
services = mAccessibilityPolicy.getInstalledAccessibilityServiceList(services);
|
||||
}
|
||||
if (services != null) {
|
||||
return Collections.unmodifiableList(services);
|
||||
} else {
|
||||
@@ -574,6 +644,10 @@ public final class AccessibilityManager {
|
||||
} catch (RemoteException re) {
|
||||
Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
|
||||
}
|
||||
if (mAccessibilityPolicy != null) {
|
||||
services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
|
||||
feedbackTypeFlags, services);
|
||||
}
|
||||
if (services != null) {
|
||||
return Collections.unmodifiableList(services);
|
||||
} else {
|
||||
@@ -782,6 +856,19 @@ public final class AccessibilityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AccessibilityPolicy} controlling this manager.
|
||||
*
|
||||
* @param policy The policy.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setAccessibilityPolicy(@Nullable AccessibilityPolicy policy) {
|
||||
synchronized (mLock) {
|
||||
mAccessibilityPolicy = policy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the accessibility volume stream is active.
|
||||
*
|
||||
@@ -834,7 +921,7 @@ public final class AccessibilityManager {
|
||||
final boolean highTextContrastEnabled =
|
||||
(stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
|
||||
|
||||
final boolean wasEnabled = mIsEnabled;
|
||||
final boolean wasEnabled = isEnabled();
|
||||
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
|
||||
final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
|
||||
|
||||
@@ -843,7 +930,7 @@ public final class AccessibilityManager {
|
||||
mIsTouchExplorationEnabled = touchExplorationEnabled;
|
||||
mIsHighTextContrastEnabled = highTextContrastEnabled;
|
||||
|
||||
if (wasEnabled != enabled) {
|
||||
if (wasEnabled != isEnabled()) {
|
||||
notifyAccessibilityStateChanged();
|
||||
}
|
||||
|
||||
@@ -1052,16 +1139,15 @@ public final class AccessibilityManager {
|
||||
if (mAccessibilityStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
isEnabled = mIsEnabled;
|
||||
isEnabled = isEnabled();
|
||||
listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
|
||||
}
|
||||
|
||||
int numListeners = listeners.size();
|
||||
final int numListeners = listeners.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
final AccessibilityStateChangeListener listener =
|
||||
mAccessibilityStateChangeListeners.keyAt(i);
|
||||
mAccessibilityStateChangeListeners.valueAt(i)
|
||||
.post(() -> listener.onAccessibilityStateChanged(isEnabled));
|
||||
final AccessibilityStateChangeListener listener = listeners.keyAt(i);
|
||||
listeners.valueAt(i).post(() ->
|
||||
listener.onAccessibilityStateChanged(isEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1079,12 +1165,11 @@ public final class AccessibilityManager {
|
||||
listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
|
||||
}
|
||||
|
||||
int numListeners = listeners.size();
|
||||
final int numListeners = listeners.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
final TouchExplorationStateChangeListener listener =
|
||||
mTouchExplorationStateChangeListeners.keyAt(i);
|
||||
mTouchExplorationStateChangeListeners.valueAt(i)
|
||||
.post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
|
||||
final TouchExplorationStateChangeListener listener = listeners.keyAt(i);
|
||||
listeners.valueAt(i).post(() ->
|
||||
listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1102,12 +1187,11 @@ public final class AccessibilityManager {
|
||||
listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
|
||||
}
|
||||
|
||||
int numListeners = listeners.size();
|
||||
final int numListeners = listeners.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
final HighTextContrastChangeListener listener =
|
||||
mHighTextContrastStateChangeListeners.keyAt(i);
|
||||
mHighTextContrastStateChangeListeners.valueAt(i)
|
||||
.post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
|
||||
final HighTextContrastChangeListener listener = listeners.keyAt(i);
|
||||
listeners.valueAt(i).post(() ->
|
||||
listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
package android.view.autofill;
|
||||
|
||||
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
|
||||
import static android.view.autofill.Helper.sDebug;
|
||||
import static android.view.autofill.Helper.sVerbose;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -28,6 +25,8 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Rect;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Bundle;
|
||||
@@ -41,13 +40,22 @@ import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Choreographer;
|
||||
import android.view.View;
|
||||
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.accessibility.AccessibilityWindowInfo;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import sun.misc.Cleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -58,8 +66,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
|
||||
import static android.view.autofill.Helper.sDebug;
|
||||
import static android.view.autofill.Helper.sVerbose;
|
||||
|
||||
// TODO: use java.lang.ref.Cleaner once Android supports Java 9
|
||||
import sun.misc.Cleaner;
|
||||
|
||||
/**
|
||||
* The {@link AutofillManager} provides ways for apps and custom views to integrate with the
|
||||
@@ -178,7 +189,6 @@ public final class AutofillManager {
|
||||
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;
|
||||
/** @hide */ public static final int ACTION_VIEW_EXITED = 3;
|
||||
@@ -355,6 +365,10 @@ public final class AutofillManager {
|
||||
@GuardedBy("mLock")
|
||||
private boolean mSaveOnFinish;
|
||||
|
||||
/** If compatibility mode is enabled - this is a bridge to interact with a11y */
|
||||
@GuardedBy("mLock")
|
||||
private CompatibilityBridge mCompatibilityBridge;
|
||||
|
||||
/** @hide */
|
||||
public interface AutofillClient {
|
||||
/**
|
||||
@@ -364,13 +378,13 @@ public final class AutofillManager {
|
||||
* @param intent The authentication intent.
|
||||
* @param fillInIntent The authentication fill-in intent.
|
||||
*/
|
||||
void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
|
||||
void autofillClientAuthenticate(int authenticationId, IntentSender intent,
|
||||
Intent fillInIntent);
|
||||
|
||||
/**
|
||||
* Tells the client this manager has state to be reset.
|
||||
*/
|
||||
void autofillCallbackResetableStateAvailable();
|
||||
void autofillClientResetableStateAvailable();
|
||||
|
||||
/**
|
||||
* Request showing the autofill UI.
|
||||
@@ -382,7 +396,7 @@ public final class AutofillManager {
|
||||
* @param presenter The presenter that controls the fill UI window.
|
||||
* @return Whether the UI was shown.
|
||||
*/
|
||||
boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
|
||||
boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height,
|
||||
@Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
|
||||
|
||||
/**
|
||||
@@ -390,21 +404,28 @@ public final class AutofillManager {
|
||||
*
|
||||
* @return Whether the UI was hidden.
|
||||
*/
|
||||
boolean autofillCallbackRequestHideFillUi();
|
||||
boolean autofillClientRequestHideFillUi();
|
||||
|
||||
/**
|
||||
* Gets whether the fill UI is currenlty being shown.
|
||||
*
|
||||
* @return Whether the fill UI is currently being shown
|
||||
*/
|
||||
boolean autofillClientIsFillUiShowing();
|
||||
|
||||
/**
|
||||
* Checks if views are currently attached and visible.
|
||||
*
|
||||
* @return And array with {@code true} iff the view is attached or visible
|
||||
*/
|
||||
@NonNull boolean[] getViewVisibility(@NonNull int[] viewId);
|
||||
@NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds);
|
||||
|
||||
/**
|
||||
* Checks is the client is currently visible as understood by autofill.
|
||||
*
|
||||
* @return {@code true} if the client is currently visible
|
||||
*/
|
||||
boolean isVisibleForAutofill();
|
||||
boolean autofillClientIsVisibleForAutofill();
|
||||
|
||||
/**
|
||||
* Client might disable enter/exit event e.g. when activity is paused.
|
||||
@@ -414,30 +435,51 @@ public final class AutofillManager {
|
||||
/**
|
||||
* Finds views by traversing the hierarchies of the client.
|
||||
*
|
||||
* @param viewIds The autofill ids of the views to find
|
||||
* @param autofillIds The autofill ids of the views to find
|
||||
*
|
||||
* @return And array containing the views (empty if no views found).
|
||||
*/
|
||||
@NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
|
||||
@NonNull View[] autofillClientFindViewsByAutofillIdTraversal(
|
||||
@NonNull AutofillId[] autofillIds);
|
||||
|
||||
/**
|
||||
* Finds a view by traversing the hierarchies of the client.
|
||||
*
|
||||
* @param viewId The autofill id of the views to find
|
||||
* @param autofillId The autofill id of the views to find
|
||||
*
|
||||
* @return The view, or {@code null} if not found
|
||||
*/
|
||||
@Nullable View findViewByAutofillIdTraversal(int viewId);
|
||||
@Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId);
|
||||
|
||||
/**
|
||||
* Finds a view by a11y id in a given client window.
|
||||
*
|
||||
* @param viewId The accessibility id of the views to find
|
||||
* @param windowId The accessibility window id where to search
|
||||
*
|
||||
* @return The view, or {@code null} if not found
|
||||
*/
|
||||
@Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
|
||||
|
||||
/**
|
||||
* Runs the specified action on the UI thread.
|
||||
*/
|
||||
void runOnUiThread(Runnable action);
|
||||
void autofillClientRunOnUiThread(Runnable action);
|
||||
|
||||
/**
|
||||
* Gets the complete component name of this client.
|
||||
*/
|
||||
ComponentName getComponentName();
|
||||
ComponentName autofillClientGetComponentName();
|
||||
|
||||
/**
|
||||
* Gets the activity token
|
||||
*/
|
||||
@Nullable IBinder autofillClientGetActivityToken();
|
||||
|
||||
/**
|
||||
* @return Whether compatibility mode is enabled.
|
||||
*/
|
||||
boolean autofillIsCompatibilityModeEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,6 +490,19 @@ public final class AutofillManager {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void enableCompatibilityMode() {
|
||||
synchronized (mLock) {
|
||||
// The accessibility manager is a singleton so we may need to plug
|
||||
// different bridge based on which activity is currently focused
|
||||
// in the current process. Since compat would be rarely used, just
|
||||
// create and register a new instance every time.
|
||||
mCompatibilityBridge = new CompatibilityBridge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore state after activity lifecycle
|
||||
*
|
||||
@@ -477,7 +532,8 @@ public final class AutofillManager {
|
||||
if (client != null) {
|
||||
try {
|
||||
final boolean sessionWasRestored = mService.restoreSession(mSessionId,
|
||||
mContext.getActivityToken(), mServiceClient.asBinder());
|
||||
client.autofillClientGetActivityToken(),
|
||||
mServiceClient.asBinder());
|
||||
|
||||
if (!sessionWasRestored) {
|
||||
Log.w(TAG, "Session " + mSessionId + " could not be restored");
|
||||
@@ -488,7 +544,7 @@ public final class AutofillManager {
|
||||
Log.d(TAG, "session " + mSessionId + " was restored");
|
||||
}
|
||||
|
||||
client.autofillCallbackResetableStateAvailable();
|
||||
client.autofillClientResetableStateAvailable();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not figure out if there was an autofill session", e);
|
||||
@@ -501,22 +557,29 @@ public final class AutofillManager {
|
||||
/**
|
||||
* Called once the client becomes visible.
|
||||
*
|
||||
* @see AutofillClient#isVisibleForAutofill()
|
||||
* @see AutofillClient#autofillClientIsVisibleForAutofill()
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
public void onVisibleForAutofill() {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled && isActiveLocked() && mTrackedViews != null) {
|
||||
mTrackedViews.onVisibleForAutofillLocked();
|
||||
// This gets called when the client just got visible at which point the visibility
|
||||
// of the tracked views may not have been computed (due to a pending layout, etc).
|
||||
// While generally we have no way to know when the UI has settled. We will evaluate
|
||||
// the tracked views state at the end of next frame to guarantee that everything
|
||||
// that may need to be laid out is laid out.
|
||||
Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
|
||||
synchronized (mLock) {
|
||||
if (mEnabled && isActiveLocked() && mTrackedViews != null) {
|
||||
mTrackedViews.onVisibleForAutofillChangedLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once the client becomes invisible.
|
||||
*
|
||||
* @see AutofillClient#isVisibleForAutofill()
|
||||
* @see AutofillClient#autofillClientIsVisibleForAutofill()
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
@@ -550,6 +613,15 @@ public final class AutofillManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean isCompatibilityModeEnabled() {
|
||||
synchronized (mLock) {
|
||||
return mCompatibilityBridge != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether autofill is enabled for the current user.
|
||||
*
|
||||
@@ -653,7 +725,7 @@ public final class AutofillManager {
|
||||
|
||||
private boolean isClientVisibleForAutofillLocked() {
|
||||
final AutofillClient client = getClient();
|
||||
return client != null && client.isVisibleForAutofill();
|
||||
return client != null && client.autofillClientIsVisibleForAutofill();
|
||||
}
|
||||
|
||||
private boolean isClientDisablingEnterExitEvent() {
|
||||
@@ -1322,13 +1394,13 @@ public final class AutofillManager {
|
||||
final AutofillClient client = getClient();
|
||||
if (client == null) return; // NOTE: getClient() already logd it..
|
||||
|
||||
mSessionId = mService.startSession(mContext.getActivityToken(),
|
||||
mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
|
||||
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
|
||||
mCallback != null, flags, client.getComponentName());
|
||||
mCallback != null, flags, client.autofillClientGetComponentName());
|
||||
if (mSessionId != NO_SESSION) {
|
||||
mState = STATE_ACTIVE;
|
||||
}
|
||||
client.autofillCallbackResetableStateAvailable();
|
||||
client.autofillClientResetableStateAvailable();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
@@ -1383,14 +1455,16 @@ public final class AutofillManager {
|
||||
final AutofillClient client = getClient();
|
||||
if (client == null) return; // NOTE: getClient() already logd it..
|
||||
|
||||
final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
|
||||
final int newId = mService.updateOrRestartSession(
|
||||
client.autofillClientGetActivityToken(),
|
||||
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
|
||||
mCallback != null, flags, client.getComponentName(), mSessionId, action);
|
||||
mCallback != null, flags, client.autofillClientGetComponentName(),
|
||||
mSessionId, action);
|
||||
if (newId != mSessionId) {
|
||||
if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
|
||||
mSessionId = newId;
|
||||
mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
|
||||
client.autofillCallbackResetableStateAvailable();
|
||||
client.autofillClientResetableStateAvailable();
|
||||
}
|
||||
} else {
|
||||
mService.updateSession(mSessionId, id, bounds, value, action, flags,
|
||||
@@ -1489,7 +1563,7 @@ public final class AutofillManager {
|
||||
AutofillClient client = getClient();
|
||||
|
||||
if (client != null) {
|
||||
if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
|
||||
if (client.autofillClientRequestShowFillUi(anchor, width, height,
|
||||
anchorBounds, presenter) && mCallback != null) {
|
||||
callback = mCallback;
|
||||
}
|
||||
@@ -1516,7 +1590,7 @@ public final class AutofillManager {
|
||||
// clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
|
||||
// before onAuthenticationResult()
|
||||
mOnInvisibleCalled = false;
|
||||
client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
|
||||
client.autofillClientAuthenticate(authenticationId, intent, fillInIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1587,7 +1661,8 @@ public final class AutofillManager {
|
||||
final int itemCount = ids.size();
|
||||
int numApplied = 0;
|
||||
ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
|
||||
final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
|
||||
final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
|
||||
Helper.toArray(ids));
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
final AutofillId id = ids.get(i);
|
||||
@@ -1758,7 +1833,7 @@ public final class AutofillManager {
|
||||
// service being uninstalled and the UI being dismissed.
|
||||
AutofillClient client = getClient();
|
||||
if (client != null) {
|
||||
if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
|
||||
if (client.autofillClientRequestHideFillUi() && mCallback != null) {
|
||||
callback = mCallback;
|
||||
}
|
||||
}
|
||||
@@ -1806,35 +1881,6 @@ public final class AutofillManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of viewIds from a List of {@link AutofillId}.
|
||||
*
|
||||
* @param autofillIds The autofill ids to convert
|
||||
*
|
||||
* @return The array of viewIds.
|
||||
*/
|
||||
// TODO: move to Helper as static method
|
||||
@NonNull private int[] getViewIds(@NonNull AutofillId[] autofillIds) {
|
||||
final int numIds = autofillIds.length;
|
||||
final int[] viewIds = new int[numIds];
|
||||
for (int i = 0; i < numIds; i++) {
|
||||
viewIds[i] = autofillIds[i].getViewId();
|
||||
}
|
||||
|
||||
return viewIds;
|
||||
}
|
||||
|
||||
// TODO: move to Helper as static method
|
||||
@NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
|
||||
final int numIds = autofillIds.size();
|
||||
final int[] viewIds = new int[numIds];
|
||||
for (int i = 0; i < numIds; i++) {
|
||||
viewIds[i] = autofillIds.get(i).getViewId();
|
||||
}
|
||||
|
||||
return viewIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a single view by its id.
|
||||
*
|
||||
@@ -1844,12 +1890,10 @@ public final class AutofillManager {
|
||||
*/
|
||||
private View findView(@NonNull AutofillId autofillId) {
|
||||
final AutofillClient client = getClient();
|
||||
|
||||
if (client == null) {
|
||||
return null;
|
||||
if (client != null) {
|
||||
return client.autofillClientFindViewByAutofillIdTraversal(autofillId);
|
||||
}
|
||||
|
||||
return client.findViewByAutofillIdTraversal(autofillId.getViewId());
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -1895,6 +1939,7 @@ public final class AutofillManager {
|
||||
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
|
||||
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
|
||||
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
|
||||
pw.print(pfx); pw.print("compat mode enabled: "); pw.println(isCompatibilityModeEnabled());
|
||||
pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
|
||||
pw.print(" verbose: "); pw.println(sVerbose);
|
||||
}
|
||||
@@ -1934,7 +1979,218 @@ public final class AutofillManager {
|
||||
if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
|
||||
return;
|
||||
}
|
||||
client.runOnUiThread(runnable);
|
||||
client.autofillClientRunOnUiThread(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the accessibility based compatibility.
|
||||
*/
|
||||
private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy {
|
||||
@GuardedBy("mLock")
|
||||
private final Rect mFocusedBounds = new Rect();
|
||||
@GuardedBy("mLock")
|
||||
private final Rect mTempBounds = new Rect();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
@GuardedBy("mLock")
|
||||
private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
|
||||
|
||||
// Need to report a fake service in case a11y clients check the service list
|
||||
@NonNull
|
||||
@GuardedBy("mLock")
|
||||
AccessibilityServiceInfo mCompatServiceInfo;
|
||||
|
||||
CompatibilityBridge() {
|
||||
final AccessibilityManager am = AccessibilityManager.getInstance(mContext);
|
||||
am.setAccessibilityPolicy(this);
|
||||
}
|
||||
|
||||
private AccessibilityServiceInfo getCompatServiceInfo() {
|
||||
synchronized (mLock) {
|
||||
if (mCompatServiceInfo != null) {
|
||||
return mCompatServiceInfo;
|
||||
}
|
||||
final Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("android",
|
||||
"com.android.server.autofill.AutofillCompatAccessibilityService"));
|
||||
final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(
|
||||
intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
|
||||
try {
|
||||
mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
Log.e(TAG, "Cannot find compat autofill service:" + intent);
|
||||
throw new IllegalStateException("Cannot find compat autofill service");
|
||||
}
|
||||
return mCompatServiceInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(boolean accessibilityEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelevantEventTypes(int relevantEventTypes) {
|
||||
return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED
|
||||
| AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
|
||||
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
|
||||
List<AccessibilityServiceInfo> installedServices) {
|
||||
if (installedServices == null) {
|
||||
installedServices = new ArrayList<>();
|
||||
}
|
||||
installedServices.add(getCompatServiceInfo());
|
||||
return installedServices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
|
||||
int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) {
|
||||
if (enabledService == null) {
|
||||
enabledService = new ArrayList<>();
|
||||
}
|
||||
enabledService.add(getCompatServiceInfo());
|
||||
return enabledService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event,
|
||||
boolean accessibilityEnabled, int relevantEventTypes) {
|
||||
switch (event.getEventType()) {
|
||||
case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
|
||||
synchronized (mLock) {
|
||||
if (mFocusedWindowId == event.getWindowId()
|
||||
&& mFocusedNodeId == event.getSourceNodeId()) {
|
||||
return event;
|
||||
}
|
||||
if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
|
||||
&& mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) {
|
||||
notifyViewExited(mFocusedWindowId, mFocusedNodeId);
|
||||
mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
|
||||
mFocusedBounds.set(0, 0, 0, 0);
|
||||
}
|
||||
final int windowId = event.getWindowId();
|
||||
final long nodeId = event.getSourceNodeId();
|
||||
if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) {
|
||||
mFocusedWindowId = windowId;
|
||||
mFocusedNodeId = nodeId;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: {
|
||||
synchronized (mLock) {
|
||||
if (mFocusedWindowId == event.getWindowId()
|
||||
&& mFocusedNodeId == event.getSourceNodeId()) {
|
||||
notifyValueChanged(event.getWindowId(), event.getSourceNodeId());
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
|
||||
final AutofillClient client = getClient();
|
||||
if (client != null) {
|
||||
synchronized (mLock) {
|
||||
if (client.autofillClientIsFillUiShowing()) {
|
||||
notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds);
|
||||
}
|
||||
updateTrackedViewsLocked();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return accessibilityEnabled ? event : null;
|
||||
}
|
||||
|
||||
private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) {
|
||||
final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
|
||||
if (!isVirtualNode(virtualId)) {
|
||||
return false;
|
||||
}
|
||||
final View view = findViewByAccessibilityId(windowId, nodeId);
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
if (!node.isEditable()) {
|
||||
return false;
|
||||
}
|
||||
final Rect newBounds = mTempBounds;
|
||||
node.getBoundsInScreen(newBounds);
|
||||
if (newBounds.equals(focusedBounds)) {
|
||||
return false;
|
||||
}
|
||||
focusedBounds.set(newBounds);
|
||||
AutofillManager.this.notifyViewEntered(view, virtualId, newBounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void notifyViewExited(int windowId, long nodeId) {
|
||||
final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
|
||||
if (!isVirtualNode(virtualId)) {
|
||||
return;
|
||||
}
|
||||
final View view = findViewByAccessibilityId(windowId, nodeId);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
AutofillManager.this.notifyViewExited(view, virtualId);
|
||||
}
|
||||
|
||||
private void notifyValueChanged(int windowId, long nodeId) {
|
||||
final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId);
|
||||
if (!isVirtualNode(virtualId)) {
|
||||
return;
|
||||
}
|
||||
final View view = findViewByAccessibilityId(windowId, nodeId);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
AutofillManager.this.notifyValueChanged(view, virtualId,
|
||||
AutofillValue.forText(node.getText()));
|
||||
}
|
||||
|
||||
private void updateTrackedViewsLocked() {
|
||||
if (mTrackedViews != null) {
|
||||
mTrackedViews.onVisibleForAutofillChangedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private View findViewByAccessibilityId(int windowId, long nodeId) {
|
||||
final AutofillClient client = getClient();
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId);
|
||||
return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId);
|
||||
}
|
||||
|
||||
private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) {
|
||||
final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
return provider.createAccessibilityNodeInfo(virtualId);
|
||||
}
|
||||
|
||||
private boolean isVirtualNode(int nodeId) {
|
||||
return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
&& nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2013,11 +2269,11 @@ public final class AutofillManager {
|
||||
*/
|
||||
TrackedViews(@Nullable AutofillId[] trackedIds) {
|
||||
final AutofillClient client = getClient();
|
||||
if (trackedIds != null && client != null) {
|
||||
if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
|
||||
final boolean[] isVisible;
|
||||
|
||||
if (client.isVisibleForAutofill()) {
|
||||
isVisible = client.getViewVisibility(getViewIds(trackedIds));
|
||||
if (client.autofillClientIsVisibleForAutofill()) {
|
||||
isVisible = client.autofillClientGetViewVisibility(trackedIds);
|
||||
} else {
|
||||
// All false
|
||||
isVisible = new boolean[trackedIds.length];
|
||||
@@ -2054,7 +2310,7 @@ public final class AutofillManager {
|
||||
*/
|
||||
void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) {
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible="
|
||||
Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible="
|
||||
+ isVisible);
|
||||
}
|
||||
|
||||
@@ -2083,9 +2339,9 @@ public final class AutofillManager {
|
||||
/**
|
||||
* Called once the client becomes visible.
|
||||
*
|
||||
* @see AutofillClient#isVisibleForAutofill()
|
||||
* @see AutofillClient#autofillClientIsVisibleForAutofill()
|
||||
*/
|
||||
void onVisibleForAutofillLocked() {
|
||||
void onVisibleForAutofillChangedLocked() {
|
||||
// The visibility of the views might have changed while the client was not be visible,
|
||||
// hence update the visibility state for all views.
|
||||
AutofillClient client = getClient();
|
||||
@@ -2095,8 +2351,8 @@ public final class AutofillManager {
|
||||
if (mInvisibleTrackedIds != null) {
|
||||
final ArrayList<AutofillId> orderedInvisibleIds =
|
||||
new ArrayList<>(mInvisibleTrackedIds);
|
||||
final boolean[] isVisible = client.getViewVisibility(
|
||||
getViewIds(orderedInvisibleIds));
|
||||
final boolean[] isVisible = client.autofillClientGetViewVisibility(
|
||||
Helper.toArray(orderedInvisibleIds));
|
||||
|
||||
final int numInvisibleTrackedIds = orderedInvisibleIds.size();
|
||||
for (int i = 0; i < numInvisibleTrackedIds; i++) {
|
||||
@@ -2116,8 +2372,8 @@ public final class AutofillManager {
|
||||
if (mVisibleTrackedIds != null) {
|
||||
final ArrayList<AutofillId> orderedVisibleIds =
|
||||
new ArrayList<>(mVisibleTrackedIds);
|
||||
final boolean[] isVisible = client.getViewVisibility(
|
||||
getViewIds(orderedVisibleIds));
|
||||
final boolean[] isVisible = client.autofillClientGetViewVisibility(
|
||||
Helper.toArray(orderedVisibleIds));
|
||||
|
||||
final int numVisibleTrackedIds = orderedVisibleIds.size();
|
||||
for (int i = 0; i < numVisibleTrackedIds; i++) {
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package android.view.autofill;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
|
||||
/**
|
||||
* Autofill Manager local system service interface.
|
||||
*
|
||||
@@ -26,4 +29,14 @@ public abstract class AutofillManagerInternal {
|
||||
* Notifies the manager that the back key was pressed.
|
||||
*/
|
||||
public abstract void onBackKeyPressed();
|
||||
|
||||
/**
|
||||
* Gets whether compatibility mode is enabled for a package
|
||||
*
|
||||
* @param packageName The package for which to query.
|
||||
* @param versionCode The package version code.
|
||||
* @param userId The user id for which to query.
|
||||
*/
|
||||
public abstract boolean isCompatibilityModeRequested(@NonNull String packageName,
|
||||
long versionCode, @UserIdInt int userId);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package android.view.autofill;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/** @hide */
|
||||
public final class Helper {
|
||||
|
||||
@@ -59,6 +61,20 @@ public final class Helper {
|
||||
builder.append(" ]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convers a collaction of {@link AutofillId AutofillIds} to an array.
|
||||
* @param collection The collection.
|
||||
* @return The array.
|
||||
*/
|
||||
public static @NonNull AutofillId[] toArray(Collection<AutofillId> collection) {
|
||||
if (collection == null) {
|
||||
return new AutofillId[0];
|
||||
}
|
||||
final AutofillId[] array = new AutofillId[collection.size()];
|
||||
collection.toArray(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
private Helper() {
|
||||
throw new UnsupportedOperationException("contains static members only");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user