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
This commit is contained in:
jasonwshsu
2021-05-13 18:48:11 +08:00
parent f142b5a368
commit 96fbf55efd
3 changed files with 125 additions and 65 deletions

View File

@@ -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);
private void handleFloatingMenuVisibility(boolean keyguardVisible,
@AccessibilityButtonMode int mode, String targets) {
if (keyguardVisible) {
destroyFloatingMenu();
return;
}
mAccessibilityManager.removeAccessibilityStateChangeListener(this);
}
private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) {
if (shouldShowFloatingMenu(mode, targets)) {
showFloatingMenu();
} else {

View File

@@ -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);
}
/** */

View File

@@ -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();
mController.onAccessibilityStateChanged(true);
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();
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();
}
}