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

Commit 1c994971 authored by Camden Bickel's avatar Camden Bickel
Browse files

Fix FAB reporting incorrect state

This CL also cleans up the flag
floating_menu_narrow_target_content_observer

Confirmed on my DUT that this CL fixes both of b/331740049 and b/376473165.

Bug: b/376473165
Test: atest MenuViewTest
Flag: com.android.systemui.floating_menu_notify_targets_changed_on_strict_diff
Change-Id: I73ff30a8920b681262f215c8426c26e6e4aa72d2
parent 553d7405
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -125,3 +125,13 @@ flag {
    description: "Update hearing device icon in floating menu according to the connection status."
    bug: "357882387"
}

flag {
    name: "floating_menu_notify_targets_changed_on_strict_diff"
    namespace: "accessibility"
    description: "Only notify listeners that the list of accessibility targets has changed if the lists are not identical."
    bug: "376473165"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+128 −6
Original line number Diff line number Diff line
@@ -18,17 +18,25 @@ package com.android.systemui.accessibility.floatingmenu;

import static android.app.UiModeManager.MODE_NIGHT_YES;

import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.app.UiModeManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -37,6 +45,8 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.settingslib.bluetooth.HearingAidDeviceManager;
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
@@ -54,6 +64,9 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.ArrayList;
import java.util.List;

/** Tests for {@link MenuView}. */
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -63,17 +76,24 @@ public class MenuViewTest extends SysuiTestCase {
    private int mNightMode;
    private UiModeManager mUiModeManager;
    private MenuView mMenuView;
    private MenuView mMenuViewSpy;
    private String mLastPosition;
    private MenuViewAppearance mStubMenuViewAppearance;
    private MenuViewModel mMenuViewModel;
    private final List<String> mShortcutTargets = new ArrayList<>();

    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Mock
    private AccessibilityManager mAccessibilityManager;

    @Mock
    private HearingAidDeviceManager mHearingAidDeviceManager;

    @Mock
    private MenuView.OnTargetFeaturesChangeListener mOnTargetFeaturesChangeListener;

    private SysuiTestableContext mSpyContext;

    @Before
@@ -91,22 +111,38 @@ public class MenuViewTest extends SysuiTestCase {
        mSpyContext = spy(mContext);
        doNothing().when(mSpyContext).startActivity(any());

        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
        mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
        doReturn(mShortcutTargets)
                .when(mAccessibilityManager)
                .getAccessibilityShortcutTargets(anyInt());

        final SecureSettings secureSettings = TestUtils.mockSecureSettings(mContext);
        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                secureSettings, mHearingAidDeviceManager);
        mMenuViewModel =
                new MenuViewModel(
                    mContext, mAccessibilityManager, secureSettings, mHearingAidDeviceManager);
        final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
        mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
        mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
                secureSettings));
        mMenuView =
                new MenuView(mSpyContext, mMenuViewModel, mStubMenuViewAppearance, secureSettings);
        mMenuView.setOnTargetFeaturesChangeListener(mOnTargetFeaturesChangeListener);
        mLastPosition = Prefs.getString(mSpyContext,
                Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);

        mMenuViewSpy =
                spy(
                        new MenuView(
                                mSpyContext,
                                mMenuViewModel,
                                mStubMenuViewAppearance,
                                secureSettings));
    }

    @Test
    public void onConfigurationChanged_updateViewModel() {
        mMenuView.onConfigurationChanged(/* newConfig= */ null);
        mMenuViewSpy.onConfigurationChanged(/* newConfig= */ null);

        verify(mMenuView).loadLayoutResources();
        verify(mMenuViewSpy).loadLayoutResources();
    }

    @Test
@@ -179,6 +215,75 @@ public class MenuViewTest extends SysuiTestCase {
        assertThat(radiiAnimator.isStarted()).isTrue();
    }

    @Test
    @DisableFlags(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF)
    public void onTargetFeaturesChanged_listenerCalled_flagDisabled() {
        // Call show() to start observing the target features change listener.
        mMenuView.show();

        // The target features change listener should be called when the observer is added.
        verify(mOnTargetFeaturesChangeListener, times(1)).onChange(any());

        // When the target features list changes, the listener should be called.
        mMenuViewModel.onTargetFeaturesChanged(
                List.of(
                        new TestAccessibilityTarget(mContext, 123),
                        new TestAccessibilityTarget(mContext, 456)));
        verify(mOnTargetFeaturesChangeListener, times(2)).onChange(any());

        // Double check that when the target features list changes, the listener should be called.
        List<AccessibilityTarget> newFeaturesList =
                List.of(
                        new TestAccessibilityTarget(mContext, 123),
                        new TestAccessibilityTarget(mContext, 789),
                        new TestAccessibilityTarget(mContext, 456));
        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());

        // When the target features list doesn't change, the listener will still be called.
        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
        verify(mOnTargetFeaturesChangeListener, times(4)).onChange(any());
    }

    @Test
    @EnableFlags(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF)
    public void onTargetFeaturesChanged_listenerCalled_flagEnabled() {
        // Call show() to start observing the target features change listener.
        mMenuView.show();

        // The target features change listener should be called when the observer is added.
        verify(mOnTargetFeaturesChangeListener, times(1)).onChange(any());

        // When the target features list changes, the listener should be called.
        mMenuViewModel.onTargetFeaturesChanged(
                List.of(
                        new TestAccessibilityTarget(mContext, 123),
                        new TestAccessibilityTarget(mContext, 456)));
        verify(mOnTargetFeaturesChangeListener, times(2)).onChange(any());

        // Double check that when the target features list changes, the listener should be called.
        List<AccessibilityTarget> newFeaturesList =
                List.of(
                        new TestAccessibilityTarget(mContext, 123),
                        new TestAccessibilityTarget(mContext, 789),
                        new TestAccessibilityTarget(mContext, 456));
        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());

        // When the target features list doesn't change, the listener should not be called again.
        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());

        // When the target features list changes order (but the UIDs of the targets don't change),
        // the listener should be called.
        mMenuViewModel.onTargetFeaturesChanged(
                List.of(
                        new TestAccessibilityTarget(mContext, 789),
                        new TestAccessibilityTarget(mContext, 123),
                        new TestAccessibilityTarget(mContext, 456)));
        verify(mOnTargetFeaturesChangeListener, times(4)).onChange(any());
    }

    private InstantInsetLayerDrawable getMenuViewInsetLayer() {
        return (InstantInsetLayerDrawable) mMenuView.getBackground();
    }
@@ -196,6 +301,23 @@ public class MenuViewTest extends SysuiTestCase {
        return radiiAnimator;
    }

    /** Simplified AccessibilityTarget for testing MenuView. */
    private static class TestAccessibilityTarget extends AccessibilityTarget {
        TestAccessibilityTarget(Context context, int uid) {
            // Set fields unused by tests to defaults that allow test compilation.
            super(
                    context,
                    ShortcutConstants.UserShortcutType.SOFTWARE,
                    0,
                    false,
                    MAGNIFICATION_COMPONENT_NAME.flattenToString(),
                    uid,
                    null,
                    null,
                    null);
        }
    }

    @After
    public void tearDown() throws Exception {
        mUiModeManager.setNightMode(mNightMode);
+3 −3
Original line number Diff line number Diff line
@@ -266,7 +266,7 @@ class MenuInfoRepository {
                mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
                UserHandle.USER_CURRENT);
        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
            mSecureSettings.registerContentObserverForUserSync(
                    mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
                    /* notifyForDescendants */ false,
@@ -287,7 +287,7 @@ class MenuInfoRepository {
                UserHandle.USER_CURRENT);
        mContext.registerComponentCallbacks(mComponentCallbacks);

        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
            mAccessibilityManager.addAccessibilityServicesStateChangeListener(
                    mA11yServicesStateChangeListener);
        }
@@ -317,7 +317,7 @@ class MenuInfoRepository {
        mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
        mContext.unregisterComponentCallbacks(mComponentCallbacks);

        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
            mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
                    mA11yServicesStateChangeListener);
        }
+24 −1
Original line number Diff line number Diff line
@@ -284,13 +284,36 @@ class MenuView extends FrameLayout implements
        onEdgeChanged();
        onPositionChanged();

        if (mFeaturesChangeListener != null) {
        boolean shouldSendFeatureChangeNotification =
                com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()
                    ? !areFeatureListsIdentical(targetFeatures, newTargetFeatures)
                    : true;
        if (mFeaturesChangeListener != null && shouldSendFeatureChangeNotification) {
            mFeaturesChangeListener.onChange(newTargetFeatures);
        }

        mMenuAnimationController.fadeOutIfEnabled();
    }

    /**
     * Returns true if the given feature lists are identical lists, i.e. the same list of {@link
     * AccessibilityTarget} (equality checked via UID) in the same order.
     */
    private boolean areFeatureListsIdentical(
            List<AccessibilityTarget> currentFeatures, List<AccessibilityTarget> newFeatures) {
        if (currentFeatures.size() != newFeatures.size()) {
            return false;
        }

        for (int i = 0; i < currentFeatures.size(); i++) {
            if (currentFeatures.get(i).getUid() != newFeatures.get(i).getUid()) {
                return false;
            }
        }

        return true;
    }

    private void onMenuFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) {
        mMenuAnimationController.updateOpacityWith(fadeEffectInfo.isFadeEffectEnabled(),
                fadeEffectInfo.getOpacity());