Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d3e18b81 authored by Riley Jones's avatar Riley Jones
Browse files

Fix for FAB targets not matching current user on user switch

On user switch, waits for A11yManagerService to finish initializing user before showing the FAB.
This makes sure the FAB loads with the correct information.

Demonstration video: https://x20web.corp.google.com/users/jo/jonesriley/fabFootage/fabUserSwitchFix.mp4

Flag: NONE Internal change
Test: Switch between users with different FAB targets (>0 targets). On user switch, the FAB should display the correct targets.
Bug: 360249982
Change-Id: I434161d0add3e87b6d21a6590bdf44a2b113245c
parent 925d1a6f
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
@@ -1050,6 +1050,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}
+7 −0
Original line number Diff line number Diff line
@@ -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);
}
+35 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+30 −11
Original line number Diff line number Diff line
@@ -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));
        }
    }
}
+7 −2
Original line number Diff line number Diff line
@@ -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;
Loading