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

Commit 41373625 authored by Camden Bickel's avatar Camden Bickel Committed by Android (Google) Code Review
Browse files

Merge "Fix FAB reporting incorrect state" into main

parents f2f50c03 1c994971
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());