diff options
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | api/system-current.txt | 3 | ||||
| -rw-r--r-- | api/test-current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/app/IUiAutomationConnection.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/app/Instrumentation.java | 51 | ||||
| -rw-r--r-- | core/java/android/app/UiAutomation.java | 59 | ||||
| -rw-r--r-- | core/java/android/app/UiAutomationConnection.java | 9 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/IAccessibilityManager.aidl | 2 | ||||
| -rw-r--r-- | services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java | 58 |
9 files changed, 156 insertions, 34 deletions
diff --git a/api/current.txt b/api/current.txt index 943eae00c3a7..cf2ea6d99ec4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4642,6 +4642,7 @@ package android.app { method public android.content.Context getContext(); method public android.content.Context getTargetContext(); method public android.app.UiAutomation getUiAutomation(); + method public android.app.UiAutomation getUiAutomation(int); method public boolean invokeContextMenuAction(android.app.Activity, int, int); method public boolean invokeMenuActionSync(android.app.Activity, int, int); method public boolean isProfiling(); @@ -5538,6 +5539,7 @@ package android.app { public final class UiAutomation { method public void clearWindowAnimationFrameStats(); method public boolean clearWindowContentFrameStats(int); + method public void destroy(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); @@ -5554,6 +5556,7 @@ package android.app { method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public android.graphics.Bitmap takeScreenshot(); method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException; + field public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 1; // 0x1 field public static final int ROTATION_FREEZE_0 = 0; // 0x0 field public static final int ROTATION_FREEZE_180 = 2; // 0x2 field public static final int ROTATION_FREEZE_270 = 3; // 0x3 diff --git a/api/system-current.txt b/api/system-current.txt index 5d2c2e831d86..6a420e2f06ad 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4774,6 +4774,7 @@ package android.app { method public android.content.Context getContext(); method public android.content.Context getTargetContext(); method public android.app.UiAutomation getUiAutomation(); + method public android.app.UiAutomation getUiAutomation(int); method public boolean invokeContextMenuAction(android.app.Activity, int, int); method public boolean invokeMenuActionSync(android.app.Activity, int, int); method public boolean isProfiling(); @@ -5670,6 +5671,7 @@ package android.app { public final class UiAutomation { method public void clearWindowAnimationFrameStats(); method public boolean clearWindowContentFrameStats(int); + method public void destroy(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); @@ -5686,6 +5688,7 @@ package android.app { method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public android.graphics.Bitmap takeScreenshot(); method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException; + field public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 1; // 0x1 field public static final int ROTATION_FREEZE_0 = 0; // 0x0 field public static final int ROTATION_FREEZE_180 = 2; // 0x2 field public static final int ROTATION_FREEZE_270 = 3; // 0x3 diff --git a/api/test-current.txt b/api/test-current.txt index 4d8d07bd337e..22eeef43a1ae 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4642,6 +4642,7 @@ package android.app { method public android.content.Context getContext(); method public android.content.Context getTargetContext(); method public android.app.UiAutomation getUiAutomation(); + method public android.app.UiAutomation getUiAutomation(int); method public boolean invokeContextMenuAction(android.app.Activity, int, int); method public boolean invokeMenuActionSync(android.app.Activity, int, int); method public boolean isProfiling(); @@ -5538,6 +5539,7 @@ package android.app { public final class UiAutomation { method public void clearWindowAnimationFrameStats(); method public boolean clearWindowContentFrameStats(int); + method public void destroy(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); @@ -5556,6 +5558,7 @@ package android.app { method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public android.graphics.Bitmap takeScreenshot(); method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException; + field public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 1; // 0x1 field public static final int ROTATION_FREEZE_0 = 0; // 0x0 field public static final int ROTATION_FREEZE_180 = 2; // 0x2 field public static final int ROTATION_FREEZE_270 = 3; // 0x3 diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 2caec36911f1..7640e75ba153 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -33,7 +33,7 @@ import android.os.ParcelFileDescriptor; * {@hide} */ interface IUiAutomationConnection { - void connect(IAccessibilityServiceClient client); + void connect(IAccessibilityServiceClient client, int flags); void disconnect(); boolean injectInputEvent(in InputEvent event, boolean sync); boolean setRotation(int rotation); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 53974c1c5312..9a88f2c67697 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1837,16 +1837,59 @@ public class Instrumentation { * {@link Instrumentation} APIs. Using both APIs at the same time is not * a mistake by itself but a client has to be aware of the APIs limitations. * </p> - * @return The UI automation instance. + * @return The UI automation instance. If none exists, a new one is created with no flags set. * * @see UiAutomation */ public UiAutomation getUiAutomation() { if (mUiAutomationConnection != null) { if (mUiAutomation == null) { + return getUiAutomation(0); + } + return mUiAutomation; + } + return null; + } + + /** + * Gets the {@link UiAutomation} instance with flags set. + * <p> + * <strong>Note:</strong> Only one UiAutomation can be obtained. Calling this method + * twice with different flags will fail unless the UiAutomation obtained in the first call + * is released with {@link UiAutomation#destroy()}. + * </p> + * <p> + * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation} + * work across application boundaries while the APIs exposed by the instrumentation + * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will + * not allow you to inject the event in an app different from the instrumentation + * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)} + * will work regardless of the current application. + * </p> + * <p> + * A typical test case should be using either the {@link UiAutomation} or + * {@link Instrumentation} APIs. Using both APIs at the same time is not + * a mistake by itself but a client has to be aware of the APIs limitations. + * </p> + * + * @param flags The flags to be passed to the UiAutomation, for example + * {@link UiAutomation#FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES}. + * + * @return The UI automation instance. + * + * @see UiAutomation + */ + public UiAutomation getUiAutomation(int flags) { + if (mUiAutomationConnection != null) { + if ((mUiAutomation == null) || (mUiAutomation.isDestroyed())) { mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(), mUiAutomationConnection); - mUiAutomation.connect(); + mUiAutomation.connect(flags); + } else { + if (mUiAutomation.getFlags() != flags) { + throw new RuntimeException( + "Cannot get a UiAutomation with different flags from the existing one"); + } } return mUiAutomation; } @@ -1861,8 +1904,8 @@ public class Instrumentation { try { Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); } catch (RuntimeException e) { - Log.w(TAG, "Exception setting priority of instrumentation thread " - + Process.myTid(), e); + Log.w(TAG, "Exception setting priority of instrumentation thread " + + Process.myTid(), e); } if (mAutomaticPerformanceSnapshots) { startPerformanceSnapshot(); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index dce2e518d1e0..79d383c53cf6 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -105,6 +105,14 @@ public final class UiAutomation { /** Rotation constant: Freeze rotation to 270 degrees . */ public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270; + /** + * UiAutomation supresses accessibility services by default. This flag specifies that + * existing accessibility services should continue to run, and that new ones may start. + * This flag is set when obtaining the UiAutomation from + * {@link Instrumentation#getUiAutomation(int)}. + */ + public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 0x00000001; + private final Object mLock = new Object(); private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>(); @@ -123,6 +131,10 @@ public final class UiAutomation { private boolean mIsConnecting; + private boolean mIsDestroyed; + + private int mFlags; + /** * Listener for observing the {@link AccessibilityEvent} stream. */ @@ -182,11 +194,22 @@ public final class UiAutomation { } /** - * Connects this UiAutomation to the accessibility introspection APIs. + * Connects this UiAutomation to the accessibility introspection APIs with default flags. * * @hide */ public void connect() { + connect(0); + } + + /** + * Connects this UiAutomation to the accessibility introspection APIs. + * + * @param flags Any flags to apply to the automation as it gets connected + * + * @hide + */ + public void connect(int flags) { synchronized (mLock) { throwIfConnectedLocked(); if (mIsConnecting) { @@ -197,7 +220,8 @@ public final class UiAutomation { try { // Calling out without a lock held. - mUiAutomationConnection.connect(mClient); + mUiAutomationConnection.connect(mClient, flags); + mFlags = flags; } catch (RemoteException re) { throw new RuntimeException("Error while connecting UiAutomation", re); } @@ -227,6 +251,17 @@ public final class UiAutomation { } /** + * Get the flags used to connect the service. + * + * @return The flags used to connect + * + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** * Disconnects this UiAutomation from the accessibility introspection APIs. * * @hide @@ -263,6 +298,17 @@ public final class UiAutomation { } /** + * Reports if the object has been destroyed + * + * @return {code true} if the object has been destroyed. + * + * @hide + */ + public boolean isDestroyed() { + return mIsDestroyed; + } + + /** * Sets a callback for observing the stream of {@link AccessibilityEvent}s. * * @param listener The callback. @@ -274,6 +320,15 @@ public final class UiAutomation { } /** + * Destroy this UiAutomation. After calling this method, attempting to use the object will + * result in errors. + */ + public void destroy() { + disconnect(); + mIsDestroyed = true; + } + + /** * Performs a global action. Such an action can be performed at any moment * regardless of the current application or user location in that application. * For example going back, going home, opening recents, etc. diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index bd10267bc0f5..276f774a8f4e 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -77,7 +77,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { private int mOwningUid; - public void connect(IAccessibilityServiceClient client) { + public void connect(IAccessibilityServiceClient client, int flags) { if (client == null) { throw new IllegalArgumentException("Client cannot be null!"); } @@ -87,7 +87,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { throw new IllegalStateException("Already connected."); } mOwningUid = Binder.getCallingUid(); - registerUiTestAutomationServiceLocked(client); + registerUiTestAutomationServiceLocked(client, flags); storeRotationStateLocked(); } } @@ -322,7 +322,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } } - private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) { + private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, + int flags) { IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); AccessibilityServiceInfo info = new AccessibilityServiceInfo(); @@ -337,7 +338,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. - manager.registerUiTestAutomationService(mToken, client, info); + manager.registerUiTestAutomationService(mToken, client, info, flags); mClient = client; } catch (RemoteException re) { throw new IllegalStateException("Error while registering UiTestAutomationService.", re); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 9e7905759f2d..655c9b32c032 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -51,7 +51,7 @@ interface IAccessibilityManager { void removeAccessibilityInteractionConnection(IWindow windowToken); void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client, - in AccessibilityServiceInfo info); + in AccessibilityServiceInfo info, int flags); void unregisterUiTestAutomationService(IAccessibilityServiceClient client); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 03809c07ea10..2a8672dd099d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.UiAutomation; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -51,6 +52,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -258,10 +260,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { UserState userState = getCurrentUserStateLocked(); // We have to reload the installed services since some services may // have different attributes, resolve info (does not support equals), - // etc. Remove them then to force reload. Do it even if automation is - // running since when it goes away, we will have to reload as well. + // etc. Remove them then to force reload. userState.mInstalledServices.clear(); - if (userState.mUiAutomationService == null) { + if (!userState.isUiAutomationSuppressingOtherServices()) { if (readConfigurationForUserStateLocked(userState)) { onUserStateChangedLocked(userState); } @@ -296,7 +297,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, userState.mTouchExplorationGrantedServices, userId); // We will update when the automation service dies. - if (userState.mUiAutomationService == null) { + if (!userState.isUiAutomationSuppressingOtherServices()) { onUserStateChangedLocked(userState); } return; @@ -330,7 +331,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userState.mEnabledServices, userId); // We will update when the automation service dies. - if (userState.mUiAutomationService == null) { + if (!userState.isUiAutomationSuppressingOtherServices()) { onUserStateChangedLocked(userState); } } @@ -362,7 +363,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // We will update when the automation service dies. UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { + if (!userState.isUiAutomationSuppressingOtherServices()) { if (readConfigurationForUserStateLocked(userState)) { onUserStateChangedLocked(userState); } @@ -473,11 +474,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); - // The automation service is a fake one and should not be reported - // to clients as being enabled. The automation service is always the - // only active one, if it exists. + // The automation service can suppress other services. UserState userState = getUserStateLocked(resolvedUserId); - if (userState.mUiAutomationService != null) { + if (userState.isUiAutomationSuppressingOtherServices()) { return Collections.emptyList(); } @@ -490,7 +489,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int serviceCount = services.size(); for (int i = 0; i < serviceCount; i++) { Service service = services.get(i); - if ((service.mFeedbackType & feedbackTypeBit) != 0) { + // Don't report the UIAutomation (fake service) + if (!sFakeAccessibilityServiceComponentName.equals(service.mComponentName) + && (service.mFeedbackType & feedbackTypeBit) != 0) { result.add(service.mAccessibilityServiceInfo); } } @@ -621,7 +622,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient serviceClient, - AccessibilityServiceInfo accessibilityServiceInfo) { + AccessibilityServiceInfo accessibilityServiceInfo, + int flags) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); @@ -645,15 +647,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mUiAutomationServiceOwner = owner; userState.mUiAutomationServiceClient = serviceClient; - - // Set the temporary state. + userState.mUiAutomationFlags = flags; userState.mIsAccessibilityEnabled = true; - userState.mIsTouchExplorationEnabled = false; - userState.mIsEnhancedWebAccessibilityEnabled = false; - userState.mIsDisplayMagnificationEnabled = false; - userState.mIsAutoclickEnabled = false; userState.mInstalledServices.add(accessibilityServiceInfo); - userState.mEnabledServices.clear(); + if (userState.isUiAutomationSuppressingOtherServices()) { + // Set the temporary state. + userState.mIsTouchExplorationEnabled = false; + userState.mIsEnhancedWebAccessibilityEnabled = false; + userState.mIsDisplayMagnificationEnabled = false; + userState.mIsAutoclickEnabled = false; + userState.mEnabledServices.clear(); + } userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName); userState.mTouchExplorationGrantedServices.add(sFakeAccessibilityServiceComponentName); @@ -694,7 +698,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { UserState userState = getCurrentUserStateLocked(); // This is a nop if UI automation is enabled. - if (userState.mUiAutomationService != null) { + if (userState.isUiAutomationSuppressingOtherServices()) { return; } @@ -1027,6 +1031,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (!mTempComponentNameSet.equals(userState.mEnabledServices)) { userState.mEnabledServices.clear(); userState.mEnabledServices.addAll(mTempComponentNameSet); + if (userState.mUiAutomationService != null) { + userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName); + } mTempComponentNameSet.clear(); return true; } @@ -3981,6 +3988,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean mAccessibilityFocusOnlyInActiveWindow; private Service mUiAutomationService; + private int mUiAutomationFlags; private IAccessibilityServiceClient mUiAutomationServiceClient; private IBinder mUiAutomationServiceOwner; @@ -4044,6 +4052,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void destroyUiAutomationService() { mUiAutomationService = null; + mUiAutomationFlags = 0; mUiAutomationServiceClient = null; if (mUiAutomationServiceOwner != null) { mUiAutomationServiceOwner.unlinkToDeath( @@ -4051,6 +4060,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mUiAutomationServiceOwner = null; } } + + boolean isUiAutomationSuppressingOtherServices() { + return ((mUiAutomationService != null) && (mUiAutomationFlags + & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0); + } } private final class AccessibilityContentObserver extends ContentObserver { @@ -4130,8 +4144,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // we are checking for changes only the parent settings. UserState userState = getCurrentUserStateLocked(); - // We will update when the automation service dies. - if (userState.mUiAutomationService != null) { + // If the automation service is suppressing, we will update when it dies. + if (userState.isUiAutomationSuppressingOtherServices()) { return; } |