Merge "Autofill compatibility mode."

This commit is contained in:
TreeHugger Robot
2018-02-05 09:55:29 +00:00
committed by Android (Google) Code Review
32 changed files with 1208 additions and 245 deletions

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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> &lt;service android:name=".MyAutofillService"
* android:permission="android.permission.BIND_AUTOFILL_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.service.autofill.AutofillService" /&gt;
* &lt;/intent-filter&gt;
* &lt;meta-data android:name="android.autofillservice" android:resource="@xml/autofillservice" /&gt;
* &lt;/service&gt;</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> &lt;autofill-service xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;compatibility-package android:name="foo.bar.baz" android:maxLongVersionCode="1000000000"/&gt;
* &lt;/autofill-service&gt;</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";

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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));
}
}

View File

@@ -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++) {

View File

@@ -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);
}

View File

@@ -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");
}