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

Commit 96fbf55e authored by jasonwshsu's avatar jasonwshsu
Browse files

Show accessibility floating menu after user unlock their phone

* Accessibiltiy floating menu needs to retrieve information from AccessibilityManagerService and it will be ready before receiving onUserUnlocked()
* When enter the lockscreen, destroy the accessibility floating menu; when exist the lockscreen, show the accessibility floating menu.

Bug: 184272107
Bug: 178285746
Test: atest AccessibilityFloatingMenuControllerTest
Change-Id: I052a57296fab2afa243bd6b696bdfdb4235599a3
parent f142b5a3
Loading
Loading
Loading
Loading
+58 −34
Original line number Diff line number Diff line
@@ -20,11 +20,12 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATIN

import android.content.Context;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;

import androidx.annotation.MainThread;

import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -37,41 +38,52 @@ import javax.inject.Inject;
@SysUISingleton
public class AccessibilityFloatingMenuController implements
        AccessibilityButtonModeObserver.ModeChangedListener,
        AccessibilityButtonTargetsObserver.TargetsChangedListener,
        AccessibilityManager.AccessibilityStateChangeListener {
        AccessibilityButtonTargetsObserver.TargetsChangedListener {

    private final Context mContext;
    private final AccessibilityManager mAccessibilityManager;
    private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
    private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;

    @VisibleForTesting
    IAccessibilityFloatingMenu mFloatingMenu;
    private int mBtnMode;
    private String mBtnTargets;
    private boolean mIsKeyguardVisible;
    private boolean mIsAccessibilityManagerServiceReady;

    @VisibleForTesting
    final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() {
        // Accessibility floating menu needs to retrieve information from
        // AccessibilityManagerService, and it would be ready before onUserUnlocked().
        @Override
        public void onUserUnlocked() {
            mIsAccessibilityManagerServiceReady = true;
            handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
        }

        // Keyguard state would be changed before AccessibilityManagerService is ready to retrieve,
        // need to wait until receive onUserUnlocked().
        @Override
        public void onKeyguardVisibilityChanged(boolean showing) {
            mIsKeyguardVisible = showing;
            if (mIsAccessibilityManagerServiceReady) {
                handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
            }
        }
    };

    @Inject
    public AccessibilityFloatingMenuController(Context context,
            AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
            AccessibilityButtonModeObserver accessibilityButtonModeObserver,
            KeyguardUpdateMonitor keyguardUpdateMonitor) {
        mContext = context;
        mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
        mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;

        mAccessibilityButtonModeObserver.addListener(this);
        mAccessibilityButtonTargetsObserver.addListener(this);
        mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
        mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();

        // Accessibility floating menu widget needs accessibility service to work, but system
        // accessibility might be unavailable during the phone get booted, hence it needs to wait
        // for accessibility manager callback to work.
        mAccessibilityManager.addAccessibilityStateChangeListener(this);
        if (mAccessibilityManager.isEnabled()) {
            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
            mAccessibilityManager.removeAccessibilityStateChangeListener(this);
        }
        init();
    }

    /**
@@ -82,7 +94,7 @@ public class AccessibilityFloatingMenuController implements
    @Override
    public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) {
        mBtnMode = mode;
        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
        handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
    }

    /**
@@ -94,27 +106,39 @@ public class AccessibilityFloatingMenuController implements
    @Override
    public void onAccessibilityButtonTargetsChanged(String targets) {
        mBtnTargets = targets;
        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
        handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
    }

    private void init() {
        mIsKeyguardVisible = false;
        mIsAccessibilityManagerServiceReady = false;
        mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
        mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
        registerContentObservers();
    }

    private void registerContentObservers() {
        mAccessibilityButtonModeObserver.addListener(this);
        mAccessibilityButtonTargetsObserver.addListener(this);
        mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
    }

    /**
     * Handles visibility of the accessibility floating menu when system accessibility state
     * changes.
     * If system accessibility become available onAccessibilityStateChanged(true), then we don't
     * need to listen to this listener anymore.
     * Handles the accessibility floating menu visibility with the given values.
     *
     * @param enabled Whether accessibility is enabled.
     * @param keyguardVisible the keyguard visibility status. Not show the
     *                        {@link AccessibilityFloatingMenu} when keyguard appears.
     * @param mode accessibility button mode {@link AccessibilityButtonMode}
     * @param targets accessibility button list; it should comes from
     *                {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
     */
    @Override
    public void onAccessibilityStateChanged(boolean enabled) {
        if (enabled) {
            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
        }

        mAccessibilityManager.removeAccessibilityStateChangeListener(this);
    private void handleFloatingMenuVisibility(boolean keyguardVisible,
            @AccessibilityButtonMode int mode, String targets) {
        if (keyguardVisible) {
            destroyFloatingMenu();
            return;
        }

    private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) {
        if (shouldShowFloatingMenu(mode, targets)) {
            showFloatingMenu();
        } else {
+4 −2
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Prefs;
@@ -263,9 +264,10 @@ public class DependencyProvider {
    @SysUISingleton
    public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController(
            Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
            AccessibilityButtonModeObserver accessibilityButtonModeObserver,
            KeyguardUpdateMonitor keyguardUpdateMonitor) {
        return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver,
                accessibilityButtonModeObserver);
                accessibilityButtonModeObserver, keyguardUpdateMonitor);
    }

    /** */
+62 −28
Original line number Diff line number Diff line
@@ -24,15 +24,15 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;

import androidx.test.filters.SmallTest;

import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -41,7 +41,8 @@ import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

@@ -56,11 +57,13 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private AccessibilityFloatingMenuController mController;
    private AccessibilityButtonTargetsObserver mTargetsObserver;
    private AccessibilityButtonModeObserver mModeObserver;
    @Mock
    private AccessibilityManager mMockA11yManager;
    @Captor
    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
    private KeyguardUpdateMonitorCallback mKeyguardCallback;

    @Test
    public void initController_registerListeners() {
@@ -70,37 +73,56 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
                any(AccessibilityButtonTargetsObserver.TargetsChangedListener.class));
        verify(mModeObserver).addListener(
                any(AccessibilityButtonModeObserver.ModeChangedListener.class));
        verify(mMockA11yManager).addAccessibilityStateChangeListener(any(
                AccessibilityManager.AccessibilityStateChangeListener.class));
        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
    }

    @Test
    public void initController_accessibilityManagerEnabled_showWidget() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
        Settings.Secure.putString(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
        when(mMockA11yManager.isEnabled()).thenReturn(true);

    public void onUserUnlocked_keyguardNotShow_showWidget() {
        enableAccessibilityFloatingMenuConfig();
        mController = setUpController();
        captureKeyguardUpdateMonitorCallback();
        mKeyguardCallback.onKeyguardVisibilityChanged(false);

        mKeyguardCallback.onUserUnlocked();

        assertThat(mController.mFloatingMenu).isNotNull();
        verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
    }

    @Test
    public void initController_accessibilityManagerDisabledThenCallbackToEnabled_showWidget() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
        Settings.Secure.putString(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
        when(mMockA11yManager.isEnabled()).thenReturn(false);
    public void onUserUnlocked_keyguardShowing_destroyWidget() {
        enableAccessibilityFloatingMenuConfig();
        mController = setUpController();
        captureKeyguardUpdateMonitorCallback();
        mKeyguardCallback.onKeyguardVisibilityChanged(true);

        mKeyguardCallback.onUserUnlocked();

        assertThat(mController.mFloatingMenu).isNull();
    }

    @Test
    public void onKeyguardVisibilityChanged_showing_destroyWidget() {
        enableAccessibilityFloatingMenuConfig();
        mController = setUpController();
        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContext);
        captureKeyguardUpdateMonitorCallback();
        mKeyguardCallback.onUserUnlocked();

        mKeyguardCallback.onKeyguardVisibilityChanged(true);

        assertThat(mController.mFloatingMenu).isNull();
    }

    @Test
    public void onKeyguardVisibilityChanged_notShow_showWidget() {
        enableAccessibilityFloatingMenuConfig();
        mController = setUpController();
        mController.onAccessibilityStateChanged(true);
        captureKeyguardUpdateMonitorCallback();
        mKeyguardCallback.onUserUnlocked();

        mKeyguardCallback.onKeyguardVisibilityChanged(false);

        assertThat(mController.mFloatingMenu).isNotNull();
        verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
    }

    @Test
@@ -126,7 +148,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
    }

    @Test
    public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_showWidget() {
    public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
        Settings.Secure.putString(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
        mController = setUpController();
@@ -170,7 +192,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
    }

    @Test
    public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_showWidget() {
    public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -182,7 +204,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
    }

    @Test
    public void onAccessibilityButtonTargetsChanged_buttonModeAndNoButtonTargets_destroyWidget() {
    public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -196,9 +218,21 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
    private AccessibilityFloatingMenuController setUpController() {
        mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
        mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
        mContext.addMockSystemService(AccessibilityManager.class, mMockA11yManager);
        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);

        return new AccessibilityFloatingMenuController(mContext, mTargetsObserver,
                mModeObserver);
                mModeObserver, mKeyguardUpdateMonitor);
    }

    private void enableAccessibilityFloatingMenuConfig() {
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
        Settings.Secure.putString(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
    }

    private void captureKeyguardUpdateMonitorCallback() {
        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
        mKeyguardCallback = mKeyguardCallbackCaptor.getValue();
    }
}