summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Instrumentation.java3
-rw-r--r--core/java/android/app/UiAutomation.java132
-rw-r--r--core/java/android/app/UiAutomationConnection.java11
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/tests/coretests/src/android/app/UiAutomationTest.java144
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java122
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java38
8 files changed, 432 insertions, 27 deletions
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c131ce574d2c..e31486f18dbf 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2354,8 +2354,7 @@ public class Instrumentation {
return mUiAutomation;
}
if (mustCreateNewAutomation) {
- mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
- mUiAutomationConnection);
+ mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
} else {
mUiAutomation.disconnect();
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 658e08444006..247d5bc77ffb 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -30,6 +32,7 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -45,6 +48,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Log;
@@ -69,8 +73,10 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.inputmethod.EditorInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import libcore.io.IoUtils;
@@ -202,6 +208,8 @@ public final class UiAutomation {
private final IUiAutomationConnection mUiAutomationConnection;
+ private final int mDisplayId;
+
private HandlerThread mRemoteCallbackThread;
private IAccessibilityServiceClient mClient;
@@ -261,24 +269,49 @@ public final class UiAutomation {
/**
* Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided context main looper and perform requests for privileged
+ * operations on the provided connection, and filtering display-related features to the display
+ * associated with the context (or the user running the test, on devices that
+ * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}).
+ *
+ * @param context the context associated with the automation
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Context context, IUiAutomationConnection connection) {
+ this(getDisplayId(context), context.getMainLooper(), connection);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
* layer on the thread of the provided looper and perform requests for privileged
* operations on the provided connection.
*
* @param looper The looper on which to execute accessibility callbacks.
* @param connection The connection for performing privileged operations.
*
+ * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead
+ *
* @hide
*/
+ @Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public UiAutomation(Looper looper, IUiAutomationConnection connection) {
- if (looper == null) {
- throw new IllegalArgumentException("Looper cannot be null!");
- }
- if (connection == null) {
- throw new IllegalArgumentException("Connection cannot be null!");
- }
+ this(DEFAULT_DISPLAY, looper, connection);
+ Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY");
+ }
+
+ private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) {
+ Preconditions.checkArgument(looper != null, "Looper cannot be null!");
+ Preconditions.checkArgument(connection != null, "Connection cannot be null!");
+
mLocalCallbackHandler = new Handler(looper);
mUiAutomationConnection = connection;
+ mDisplayId = displayId;
+
+ Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier()
+ + " on display " + mDisplayId);
}
/**
@@ -719,8 +752,14 @@ public final class UiAutomation {
}
/**
- * Gets the windows on the screen of the default display. This method returns only the windows
- * that a sighted user can interact with, as opposed to all windows.
+ * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the
+ * {@link android.view.Display#DEFAULT_DISPLAY default display).
+ *
+ * <p>
+ * This method returns only the windows that a sighted user can interact with, as opposed to
+ * all windows.
+
+ * <p>
* For example, if there is a modal dialog shown and the user cannot touch
* anything behind it, then only the modal window will be reported
* (assuming it is the top one). For convenience the returned windows
@@ -730,21 +769,23 @@ public final class UiAutomation {
* <strong>Note:</strong> In order to access the windows you have to opt-in
* to retrieve the interactive windows by setting the
* {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
- * </p>
*
* @return The windows if there are windows such, otherwise an empty list.
* @throws IllegalStateException If the connection to the accessibility subsystem is not
* established.
*/
public List<AccessibilityWindowInfo> getWindows() {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId);
+ }
final int connectionId;
synchronized (mLock) {
throwIfNotConnectedLocked();
connectionId = mConnectionId;
}
// Calling out without a lock held.
- return AccessibilityInteractionClient.getInstance()
- .getWindows(connectionId);
+ return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId,
+ mDisplayId);
}
/**
@@ -1112,8 +1153,10 @@ public final class UiAutomation {
* @return The screenshot bitmap on success, null otherwise.
*/
public Bitmap takeScreenshot() {
- Display display = DisplayManagerGlobal.getInstance()
- .getRealDisplay(Display.DEFAULT_DISPLAY);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId);
+ }
+ Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId);
Point displaySize = new Point();
display.getRealSize(displaySize);
@@ -1126,10 +1169,12 @@ public final class UiAutomation {
screenShot = mUiAutomationConnection.takeScreenshot(
new Rect(0, 0, displaySize.x, displaySize.y));
if (screenShot == null) {
+ Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+ + mDisplayId);
return null;
}
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while taking screenshot!", re);
+ Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re);
return null;
}
@@ -1509,6 +1554,14 @@ public final class UiAutomation {
return executeShellCommandInternal(command, true /* includeStderr */);
}
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
private ParcelFileDescriptor[] executeShellCommandInternal(
String command, boolean includeStderr) {
warnIfBetterCommand(command);
@@ -1564,6 +1617,7 @@ public final class UiAutomation {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
stringBuilder.append("[id=").append(mConnectionId);
+ stringBuilder.append(", displayId=").append(mDisplayId);
stringBuilder.append(", flags=").append(mFlags);
stringBuilder.append("]");
return stringBuilder.toString();
@@ -1601,6 +1655,55 @@ public final class UiAutomation {
return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0;
}
+ /**
+ * Gets the display id associated with the UiAutomation context.
+ *
+ * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call
+ * another one.
+ */
+ private static int getDisplayId(Context context) {
+ Preconditions.checkArgument(context != null, "Context cannot be null!");
+
+ UserManager userManager = context.getSystemService(UserManager.class);
+ // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple
+ // users, the display is only set on devices that support that
+ if (!userManager.isVisibleBackgroundUsersSupported()) {
+ return DEFAULT_DISPLAY;
+ }
+
+ int displayId = context.getDisplayId();
+ if (displayId == Display.INVALID_DISPLAY) {
+ // Shouldn't happen, but we better handle it
+ Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's"
+ + " running in the display assigned to the user");
+ return getMainDisplayIdAssignedToUser(context, userManager);
+ }
+
+ if (displayId != DEFAULT_DISPLAY) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")");
+ }
+ // Context is explicitly setting the display, so we respect that...
+ return displayId;
+ }
+ // ...otherwise, we need to get the display the test's user is running on
+ int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")");
+ }
+ return userDisplayId;
+ }
+
+ private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) {
+ if (!userManager.isUserVisible()) {
+ // Should also not happen, but ...
+ Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using "
+ + "DEFAULT_DISPLAY");
+ return DEFAULT_DISPLAY;
+ }
+ return userManager.getMainDisplayIdAssignedToUser();
+ }
+
private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
@@ -1621,6 +1724,7 @@ public final class UiAutomation {
if (DEBUG) {
Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
+ windowToken + ", user=" + Process.myUserHandle()
+ + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId
+ ", mGenerationId=" + mGenerationId
+ ", UiAutomation.mGenerationId="
+ UiAutomation.this.mGenerationId);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e800e38cca..d96a9d104ec2 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Bitmap;
@@ -117,7 +118,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
throw new IllegalStateException("Already connected.");
}
mOwningUid = Binder.getCallingUid();
- registerUiTestAutomationServiceLocked(client, flags);
+ registerUiTestAutomationServiceLocked(client,
+ Binder.getCallingUserHandle().getIdentifier(), flags);
storeRotationStateLocked();
}
}
@@ -553,7 +555,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
- int flags) {
+ @UserIdInt int userId, int flags) {
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -571,10 +573,11 @@ 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, flags);
+ manager.registerUiTestAutomationService(mToken, client, info, userId, flags);
mClient = client;
} catch (RemoteException re) {
- throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+ throw new IllegalStateException("Error while registering UiTestAutomationService for "
+ + "user " + userId + ".", re);
}
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1fac142df481..390503b80bad 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -62,7 +62,7 @@ interface IAccessibilityManager {
in IAccessibilityInteractionConnection connection);
void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
- in AccessibilityServiceInfo info, int flags);
+ in AccessibilityServiceInfo info, int userId, int flags);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java
new file mode 100644
index 000000000000..3ac5057bcf24
--- /dev/null
+++ b/core/tests/coretests/src/android/app/UiAutomationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UiAutomationTest {
+
+ private static final int SECONDARY_DISPLAY_ID = 42;
+ private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108;
+
+ private final Looper mLooper = Looper.getMainLooper();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private IUiAutomationConnection mConnection;
+
+ @Before
+ public void setFixtures() {
+ when(mContext.getMainLooper()).thenReturn(mLooper);
+ mockSystemService(UserManager.class, mUserManager);
+
+ // Set default expectations
+ mockVisibleBackgroundUsersSupported(/* supported= */ false);
+ mockUserVisibility(/* visible= */ true);
+ // make sure it's not used, unless explicitly mocked
+ mockDisplayAssignedToUser(INVALID_DISPLAY);
+ mockContextDisplay(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testContextConstructor_nullContext() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new UiAutomation((Context) null, mConnection));
+ }
+
+ @Test
+ public void testContextConstructor_nullConnection() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new UiAutomation(mContext, (IUiAutomationConnection) null));
+ }
+
+ @Test
+ public void testGetDisplay_contextWithSecondaryDisplayId() {
+ mockContextDisplay(SECONDARY_DISPLAY_ID);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplay_contextWithInvalidDisplayId() {
+ mockContextDisplay(INVALID_DISPLAY);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplay_visibleBgUsers() {
+ mockVisibleBackgroundUsersSupported(/* supported= */ true);
+ mockContextDisplay(SECONDARY_DISPLAY_ID);
+ // Should be using display from context, not from user
+ mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() {
+ mockVisibleBackgroundUsersSupported(/* supported= */ true);
+ mockContextDisplay(INVALID_DISPLAY);
+ mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER);
+ }
+
+ private <T> void mockSystemService(Class<T> svcClass, T svc) {
+ String svcName = svcClass.getName();
+ when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName);
+ when(mContext.getSystemService(svcName)).thenReturn(svc);
+ }
+
+ private void mockVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockContextDisplay(int displayId) {
+ when(mContext.getDisplayId()).thenReturn(displayId);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+ }
+
+ private void mockUserVisibility(boolean visible) {
+ when(mUserManager.isUserVisible()).thenReturn(visible);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 51325e72204d..0bdb0c80d219 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -112,6 +112,7 @@ import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.IWindow;
import android.view.InputDevice;
@@ -160,6 +161,7 @@ import com.android.server.accessibility.magnification.WindowMagnificationManager
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.settingslib.RestrictedLockUtils;
@@ -301,7 +303,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
new ArrayList<>();
- private int mCurrentUserId = UserHandle.USER_SYSTEM;
+ @GuardedBy("mLock")
+ private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation:
+ // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user
+ // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId
+ // is set as UserHandle.USER_CURRENT.
+ @GuardedBy("mLock")
+ private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT;
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ // purposes - in the long term, the whole service should be refactored so it handles "visible"
+ // users, not current user. Notice that because this is temporary, it's not trying to optimize
+ // performance / utilization (for example, it's not using an IntArray)
+ @GuardedBy("mLock")
+ @Nullable // only set when device supports visible background users
+ private final SparseBooleanArray mVisibleBgUserIds;
//TODO: Remove this hack
private boolean mInitialized;
@@ -316,6 +334,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
private final FlashNotificationsController mFlashNotificationsController;
+ private final UserManagerInternal mUmi;
private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
@@ -445,6 +464,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mHasInputFilter = true;
}
mFlashNotificationsController = new FlashNotificationsController(mContext);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+ // TODO(b/255426725): not used on tests
+ mVisibleBgUserIds = null;
+
init();
}
@@ -477,6 +500,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled()) {
+ mVisibleBgUserIds = new SparseBooleanArray();
+ mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
+ } else {
+ mVisibleBgUserIds = null;
+ }
+
init();
}
@@ -493,6 +525,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mCurrentUserId;
}
+ @GuardedBy("mLock")
+ @Override
+ public SparseBooleanArray getVisibleUserIdsLocked() {
+ return mVisibleBgUserIds;
+ }
+
@Override
public boolean isAccessibilityButtonShown() {
return mIsAccessibilityButtonShown;
@@ -1362,6 +1400,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void registerUiTestAutomationService(IBinder owner,
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo,
+ int userId,
int flags) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
@@ -1374,6 +1413,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
synchronized (mLock) {
+ changeCurrentUserForTestAutomationIfNeededLocked(userId);
mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
mSecurityPolicy, this, getTraceManager(), mWindowManagerService,
@@ -1390,7 +1430,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
synchronized (mLock) {
mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+ restoreCurrentUserAfterTestAutomationIfNeededLocked();
+ }
+ }
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ @GuardedBy("mLock")
+ private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) {
+ if (mVisibleBgUserIds == null) {
+ Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring "
+ + "because device doesn't support visible background users", userId);
+ return;
+ }
+ if (!mVisibleBgUserIds.get(userId)) {
+ Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible "
+ + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds);
+ return;
+ }
+ if (mCurrentUserId == userId) {
+ Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is "
+ + "already %d", mCurrentUserId);
+ return;
+ }
+ Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes",
+ mCurrentUserId, userId);
+ mRealCurrentUserId = mCurrentUserId;
+ switchUser(userId);
+ }
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ @GuardedBy("mLock")
+ private void restoreCurrentUserAfterTestAutomationIfNeededLocked() {
+ if (mVisibleBgUserIds == null) {
+ Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring "
+ + "because device doesn't support visible background users");
+ return;
}
+ Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes",
+ mRealCurrentUserId, mCurrentUserId);
+ int currentUserId = mRealCurrentUserId;
+ mRealCurrentUserId = UserHandle.USER_CURRENT;
+ switchUser(currentUserId);
}
@Override
@@ -2291,8 +2371,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void updateServicesLocked(AccessibilityUserState userState) {
Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
- boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
- .isUserUnlockingOrUnlocked(userState.mUserId);
+ boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2593,6 +2672,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible);
+ }
+ synchronized (mLock) {
+ if (visible) {
+ mVisibleBgUserIds.put(userId, visible);
+ } else {
+ mVisibleBgUserIds.delete(userId);
+ }
+ }
+ }
+
/**
* Called when any property of the user state has changed.
*
@@ -4025,7 +4117,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
pw.println();
pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+ if (mRealCurrentUserId != UserHandle.USER_CURRENT
+ && mCurrentUserId != mRealCurrentUserId) {
+ pw.append(" (set for UiAutomation purposes; \"real\" current user is ")
+ .append(String.valueOf(mRealCurrentUserId)).append(")");
+ }
pw.println();
+ if (mVisibleBgUserIds != null) {
+ pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
+ pw.println();
+ }
pw.append("hasWindowMagnificationConnection=").append(
String.valueOf(getWindowMagnificationMgr().isConnected()));
pw.println();
@@ -4052,6 +4153,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
pw.println();
mProxyManager.dump(fd, pw, args);
+ mA11yDisplayListener.dump(fd, pw, args);
}
}
@@ -4437,6 +4539,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
/* do nothing */
}
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Accessibility Display Listener:");
+ pw.println(" SystemUI uid: " + mSystemUiUid);
+ int size = mDisplaysList.size();
+ pw.printf(" %d valid display%s: ", size, (size == 1 ? "" : "s"));
+ for (int i = 0; i < size; i++) {
+ pw.print(mDisplaysList.get(i).getDisplayId());
+ if (i < size - 1) {
+ pw.print(", ");
+ }
+ }
+ pw.println();
+ }
+
private boolean isValidDisplay(@Nullable Display display) {
if (display == null || display.getType() == Display.TYPE_OVERLAY) {
return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c37ea501bbc9..88656239e59b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -39,6 +39,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.InputMethodInfo;
@@ -88,6 +89,12 @@ public class AccessibilitySecurityPolicy {
*/
int getCurrentUserIdLocked();
// TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
+
+ // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
+ /**
+ * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
+ */
+ @Nullable SparseBooleanArray getVisibleUserIdsLocked();
}
private final Context mContext;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index baed181ebd43..a8a536590004 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -51,6 +51,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
+import com.android.server.utils.Slogf;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -59,6 +60,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
@@ -67,6 +69,7 @@ import java.util.List;
public class AccessibilityWindowManager {
private static final String LOG_TAG = "AccessibilityWindowManager";
private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = false;
private static int sNextWindowId;
@@ -209,6 +212,9 @@ public class AccessibilityWindowManager {
* Constructor for DisplayWindowsObserver.
*/
DisplayWindowsObserver(int displayId) {
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId);
+ }
mDisplayId = displayId;
}
@@ -430,12 +436,27 @@ public class AccessibilityWindowManager {
synchronized (mLock) {
updateWindowsByWindowAttributesLocked(windows);
if (DEBUG) {
- Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
- Slog.i(LOG_TAG, "Windows changed: " + windows);
+ Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+ + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+ mAccessibilityUserManager.getCurrentUserIdLocked(),
+ mAccessibilityUserManager.getVisibleUserIdsLocked());
+ if (VERBOSE) {
+ Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows);
+ } else {
+ List<String> windowsInfo = windows.stream()
+ .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}")
+ .collect(Collectors.toList());
+ Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
+ }
}
if (shouldUpdateWindowsLocked(forceSend, windows)) {
mTopFocusedDisplayId = topFocusedDisplayId;
mTopFocusedWindowToken = topFocusedWindowToken;
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+ + "display %d and token %s",
+ topFocusedDisplayId, topFocusedWindowToken);
+ }
cacheWindows(windows);
// Lets the policy update the focused and active windows.
updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
@@ -443,6 +464,11 @@ public class AccessibilityWindowManager {
// Someone may be waiting for the windows - advertise it.
mLock.notifyAll();
}
+ else if (DEBUG) {
+ Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+ + "display %d and token %s",
+ topFocusedDisplayId, topFocusedWindowToken);
+ }
}
}
@@ -472,6 +498,12 @@ public class AccessibilityWindowManager {
}
final int windowCount = windows.size();
+ if (VERBOSE) {
+ Slogf.v(LOG_TAG,
+ "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, "
+ + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId,
+ windowCount, mCachedWindowInfos.size(), windows.size());
+ }
// We computed the windows and if they changed notify the client.
if (mCachedWindowInfos.size() != windowCount) {
// Different size means something changed.
@@ -1274,7 +1306,7 @@ public class AccessibilityWindowManager {
*/
@Nullable
public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
- if (DEBUG) {
+ if (VERBOSE) {
Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
}
RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);