diff options
7 files changed, 193 insertions, 13 deletions
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index a4cea3364998..ab29df357268 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1051,6 +1051,52 @@ public final class AccessibilityManager { } /** + * Registers callback for when user initialization has completed. + * Does nothing if the same callback is already registered. + * + * @param callback The callback to be registered + * @hide + */ + public void registerUserInitializationCompleteCallback( + @NonNull IUserInitializationCompleteCallback callback) { + IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.registerUserInitializationCompleteCallback(callback); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while registering userInitializationCompleteCallback. ", re); + } + } + + /** + * Unregisters callback for when user initialization has completed. + * + * @param callback The callback to be unregistered + * @hide + */ + public void unregisterUserInitializationCompleteCallback( + @NonNull IUserInitializationCompleteCallback callback) { + IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.unregisterUserInitializationCompleteCallback(callback); + } catch (RemoteException re) { + Log.e(LOG_TAG, + "Error while unregistering userInitializationCompleteCallback. ", re); + } + } + + /** * Whether the current accessibility request comes from an * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool} * property set to true. diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 72a1fe424906..bf79a2c6c6ea 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -29,6 +29,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.AccessibilityWindowAttributes; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IUserInitializationCompleteCallback; import android.view.InputEvent; import android.view.IWindow; import android.view.MagnificationSpec; @@ -192,4 +193,10 @@ interface IAccessibilityManager { @EnforcePermission("MANAGE_ACCESSIBILITY") Bundle getA11yFeatureToTileMap(int userId); + + @RequiresNoPermission + void registerUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback); + + @RequiresNoPermission + void unregisterUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback); } diff --git a/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl new file mode 100644 index 000000000000..fe6c8e25dd00 --- /dev/null +++ b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +/** + * A callback for when a new user finishes initializing + * NOTE: Must remain a oneway interface, as it is called from system_server while holding a lock. + * oneway allows it to return immediately and not hold the lock for longer than is necessary. + * @hide + */ + +oneway interface IUserInitializationCompleteCallback { + + /** + * Called when a user initialization completes. + * + * @param userId the id of the initialized user + */ + @RequiresNoPermission + void onUserInitializationComplete(int userId); +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java index e4b7b7e69c61..275147e6694c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java @@ -21,11 +21,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import android.content.Context; import android.hardware.display.DisplayManager; +import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; import android.view.Display; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IUserInitializationCompleteCallback; import androidx.annotation.MainThread; @@ -68,6 +70,9 @@ public class AccessibilityFloatingMenuController implements private int mBtnMode; private String mBtnTargets; private boolean mIsKeyguardVisible; + private boolean mIsUserInInitialization; + @VisibleForTesting + Handler mHandler; @VisibleForTesting final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() { @@ -86,18 +91,14 @@ public class AccessibilityFloatingMenuController implements @Override public void onUserSwitching(int userId) { destroyFloatingMenu(); - } - - @Override - public void onUserSwitchComplete(int userId) { - mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0); - mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); - mBtnTargets = - mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets(); - handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets); + mIsUserInInitialization = true; } }; + @VisibleForTesting + final UserInitializationCompleteCallback mUserInitializationCompleteCallback = + new UserInitializationCompleteCallback(); + @Inject public AccessibilityFloatingMenuController(Context context, WindowManager windowManager, @@ -109,7 +110,8 @@ public class AccessibilityFloatingMenuController implements KeyguardUpdateMonitor keyguardUpdateMonitor, SecureSettings secureSettings, DisplayTracker displayTracker, - NavigationModeController navigationModeController) { + NavigationModeController navigationModeController, + Handler handler) { mContext = context; mWindowManager = windowManager; mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; @@ -121,6 +123,7 @@ public class AccessibilityFloatingMenuController implements mSecureSettings = secureSettings; mDisplayTracker = displayTracker; mNavigationModeController = navigationModeController; + mHandler = handler; mIsKeyguardVisible = false; } @@ -159,6 +162,8 @@ public class AccessibilityFloatingMenuController implements mAccessibilityButtonModeObserver.addListener(this); mAccessibilityButtonTargetsObserver.addListener(this); mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); + mAccessibilityManager.registerUserInitializationCompleteCallback( + mUserInitializationCompleteCallback); } /** @@ -172,7 +177,7 @@ public class AccessibilityFloatingMenuController implements */ private void handleFloatingMenuVisibility(boolean keyguardVisible, @AccessibilityButtonMode int mode, String targets) { - if (keyguardVisible) { + if (keyguardVisible || mIsUserInInitialization) { destroyFloatingMenu(); return; } @@ -210,4 +215,18 @@ public class AccessibilityFloatingMenuController implements mFloatingMenu.hide(); mFloatingMenu = null; } + + class UserInitializationCompleteCallback + extends IUserInitializationCompleteCallback.Stub { + @Override + public void onUserInitializationComplete(int userId) { + mIsUserInInitialization = false; + mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0); + mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); + mBtnTargets = + mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets(); + mHandler.post( + () -> handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets)); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 113a8c05ee66..5e37d4cd1faf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.hardware.display.DisplayManager; +import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; import android.testing.TestableLooper; @@ -80,6 +81,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private AccessibilityManager mAccessibilityManager; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private AccessibilityFloatingMenuController mController; + private TestableLooper mTestableLooper; @Mock private AccessibilityButtonTargetsObserver mTargetsObserver; @Mock @@ -108,6 +110,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture, /* isViewCaptureEnabled= */ false); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); + mTestableLooper = TestableLooper.get(this); when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(), @@ -231,7 +234,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mKeyguardCallback.onKeyguardVisibilityChanged(false); mKeyguardCallback.onUserSwitching(fakeUserId); - mKeyguardCallback.onUserSwitchComplete(fakeUserId); + mController.mUserInitializationCompleteCallback.onUserInitializationComplete(1); + mTestableLooper.processAllMessages(); assertThat(mController.mFloatingMenu).isNotNull(); } @@ -346,7 +350,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { new AccessibilityFloatingMenuController(mContextWrapper, windowManager, viewCaptureAwareWindowManager, displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, mSecureSettings, - displayTracker, mNavigationModeController); + displayTracker, mNavigationModeController, new Handler( + mTestableLooper.getLooper())); controller.init(); return controller; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7cbb97e56b01..f1a8b5a96080 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -163,6 +163,7 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IUserInitializationCompleteCallback; import android.view.inputmethod.EditorInfo; import com.android.internal.R; @@ -366,6 +367,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables = new ArrayList<>(); + @VisibleForTesting + final HashSet<IUserInitializationCompleteCallback> + mUserInitializationCompleteCallbacks = + new HashSet<IUserInitializationCompleteCallback>(); + @GuardedBy("mLock") private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -2034,6 +2040,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub obtainMessage(AccessibilityManagerService::announceNewUserIfNeeded, this), WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS); } + + for (IUserInitializationCompleteCallback callback + : mUserInitializationCompleteCallbacks) { + try { + callback.onUserInitializationComplete(mCurrentUserId); + } catch (RemoteException re) { + Log.e("AccessibilityManagerService", + "Error while dispatching userInitializationComplete callback: ", + re); + } + } } } @@ -6251,6 +6268,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + @RequiresNoPermission + public void registerUserInitializationCompleteCallback( + IUserInitializationCompleteCallback callback) { + synchronized (mLock) { + mUserInitializationCompleteCallbacks.add(callback); + } + } + + @Override + @RequiresNoPermission + public void unregisterUserInitializationCompleteCallback( + IUserInitializationCompleteCallback callback) { + synchronized (mLock) { + mUserInitializationCompleteCallbacks.remove(callback); + } + } + + @Override @EnforcePermission(INJECT_EVENTS) public void injectInputEventToInputFilter(InputEvent event) { injectInputEventToInputFilter_enforcePermission(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index c8cbbb5957ec..2e6c93cb92aa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -102,6 +102,7 @@ import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityWindowAttributes; import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IUserInitializationCompleteCallback; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; @@ -137,6 +138,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.mockito.internal.util.reflection.FieldReader; import org.mockito.internal.util.reflection.FieldSetter; import org.mockito.stubbing.Answer; @@ -209,6 +211,7 @@ public class AccessibilityManagerServiceTest { @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; @Mock private ProxyManager mProxyManager; @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + @Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback; @Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor; private IAccessibilityManager mA11yManagerServiceOnDevice; private AccessibilityServiceConnection mAccessibilityServiceConnection; @@ -2042,6 +2045,36 @@ public class AccessibilityManagerServiceTest { .isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); } + @Test + public void registerUserInitializationCompleteCallback_isRegistered() { + mA11yms.mUserInitializationCompleteCallbacks.clear(); + + mA11yms.registerUserInitializationCompleteCallback(mUserInitializationCompleteCallback); + + assertThat(mA11yms.mUserInitializationCompleteCallbacks).containsExactly( + mUserInitializationCompleteCallback); + } + + @Test + public void unregisterUserInitializationCompleteCallback_isUnregistered() { + mA11yms.mUserInitializationCompleteCallbacks.clear(); + mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback); + + mA11yms.unregisterUserInitializationCompleteCallback(mUserInitializationCompleteCallback); + + assertThat(mA11yms.mUserInitializationCompleteCallbacks).isEmpty(); + } + + @Test + public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException { + mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback); + + mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID); + + verify(mUserInitializationCompleteCallback).onUserInitializationComplete( + UserHandle.MIN_SECONDARY_USER_ID); + } + private Set<String> readStringsFromSetting(String setting) { final Set<String> result = new ArraySet<>(); mA11yms.readColonDelimitedSettingToSet( |