From 1c898252cbaab6abecc51e34dd122a28e045ae17 Mon Sep 17 00:00:00 2001 From: Roy Chou Date: Mon, 25 Mar 2024 09:37:42 +0000 Subject: [PATCH] fix(magnification): make always on toggle unavailable when capabilities is window only As b/328787031, we make the MagnificationAlwaysOnPreferenceController observe the magnification capabilities then update the preference enabled state. Therefore, when changing the capabilities to window mode only, the preference will become unavailable. We use the bug-fix flag to verify the fix with rollout process. Bug: 328787031 Flag: ACONFIG com.android.settings.accessibility.hide_magnification_always_on_toggle_when_window_mode_only DEVELOPMENT Test: manually flip the flag atest MagnificationCapabilitiesTest atest ToggleScreenMagnificationPreferenceFragmentTest atest MagnificationAlwaysOnPreferenceControllerTest Change-Id: I1a25f80131d84ecdd927030e40a18ebb32b7862f --- .../accessibility/accessibility_flags.aconfig | 10 +++ res/values/strings.xml | 2 + ...ificationAlwaysOnPreferenceController.java | 78 +++++++++++++++++- .../MagnificationCapabilities.java | 24 ++++++ ...ScreenMagnificationPreferenceFragment.java | 2 + ...ationAlwaysOnPreferenceControllerTest.java | 80 +++++++++++++++++-- .../MagnificationCapabilitiesTest.java | 39 ++++++++- ...enMagnificationPreferenceFragmentTest.java | 15 ++++ 8 files changed, 242 insertions(+), 8 deletions(-) diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 2b843cd79b6..691999005cb 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -24,6 +24,16 @@ flag { bug: "301198830" } +flag { + name: "hide_magnification_always_on_toggle_when_window_mode_only" + namespace: "accessibility" + description: "Decides whether to hide the magnification always on setting when capabilities is window mode only." + bug: "328787031" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "remove_qs_tooltip_in_suw" namespace: "accessibility" diff --git a/res/values/strings.xml b/res/values/strings.xml index 56856b67c0a..49635fe39ea 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4737,6 +4737,8 @@ Keep on while switching apps Magnifier stays on and zooms out when you switch apps + + Unavailable while only magnifying part of the screen Joystick diff --git a/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceController.java b/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceController.java index f3d857580aa..4a37a416414 100644 --- a/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceController.java +++ b/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceController.java @@ -20,26 +20,73 @@ import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; import android.provider.Settings; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + import com.android.settings.R; +import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; /** * Controller that accesses and switches the preference status of the magnification always on * feature, where the magnifier will not deactivate on Activity transitions; it will only zoom out * to 100%. */ -public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceController { +public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceController + implements LifecycleObserver, OnResume, OnPause { private static final String TAG = MagnificationAlwaysOnPreferenceController.class.getSimpleName(); static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED; + private Preference mPreference; + + @VisibleForTesting + final ContentObserver mContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + updateState(mPreference); + } + }; + public MagnificationAlwaysOnPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } + @Override + public void onResume() { + if (Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) { + MagnificationCapabilities.registerObserver(mContext, mContentObserver); + } + } + + @Override + public void onPause() { + if (Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) { + MagnificationCapabilities.unregisterObserver(mContext, mContentObserver); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + updateState(mPreference); + } + @Override public int getAvailabilityStatus() { return AVAILABLE; @@ -62,4 +109,33 @@ public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceC public int getSliceHighlightMenuRes() { return R.string.menu_key_accessibility; } + + @Override + public CharSequence getSummary() { + if (!Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) { + return super.getSummary(); + } + + @StringRes int resId = mPreference.isEnabled() + ? R.string.accessibility_screen_magnification_always_on_summary + : R.string.accessibility_screen_magnification_always_on_unavailable_summary; + return mContext.getString(resId); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (!Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) { + return; + } + + if (preference == null) { + return; + } + @MagnificationMode int mode = + MagnificationCapabilities.getCapabilities(mContext); + preference.setEnabled( + mode == MagnificationMode.FULLSCREEN || mode == MagnificationMode.ALL); + refreshSummary(preference); + } } diff --git a/src/com/android/settings/accessibility/MagnificationCapabilities.java b/src/com/android/settings/accessibility/MagnificationCapabilities.java index 04a2992f011..eb2ba5356c9 100644 --- a/src/com/android/settings/accessibility/MagnificationCapabilities.java +++ b/src/com/android/settings/accessibility/MagnificationCapabilities.java @@ -18,6 +18,7 @@ package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; import android.provider.Settings; import androidx.annotation.IntDef; @@ -101,5 +102,28 @@ public final class MagnificationCapabilities { MagnificationMode.FULLSCREEN, contentResolver.getUserId()); } + /** + * Register an observer class that gets callbacks when magnification capabilities changes. + * + * @param context A {@link Context}. + * @param contentObserver The object that receives callbacks when changes occur. + */ + public static void registerObserver(Context context, ContentObserver contentObserver) { + context.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(KEY_CAPABILITY), + /* notifyForDescendants= */ false, + contentObserver); + } + + /** + * Unregisters a magnification capabilities change observer. + * + * @param context A {@link Context}. + * @param contentObserver The previously registered observer that is no longer needed. + */ + public static void unregisterObserver(Context context, ContentObserver contentObserver) { + context.getContentResolver().unregisterContentObserver(contentObserver); + } + private MagnificationCapabilities() {} } diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 6ef764edfaf..8e17fa5c207 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -266,6 +266,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends defaultValue ); } + private void addAlwaysOnSetting(PreferenceCategory generalCategory) { if (!isAlwaysOnSettingEnabled()) { return; @@ -282,6 +283,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController( getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY); + getSettingsLifecycle().addObserver(alwaysOnPreferenceController); alwaysOnPreferenceController.displayPreference(getPreferenceScreen()); addPreferenceController(alwaysOnPreferenceController); } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceControllerTest.java index 417c3d412aa..3e97cec0f4a 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationAlwaysOnPreferenceControllerTest.java @@ -17,6 +17,7 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import static com.google.common.truth.Truth.assertThat; @@ -25,6 +26,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import androidx.preference.PreferenceManager; @@ -33,31 +37,42 @@ import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowContentResolver; @RunWith(RobolectricTestRunner.class) public class MagnificationAlwaysOnPreferenceControllerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String KEY_ALWAYS_ON = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED; - private final Context mContext = ApplicationProvider.getApplicationContext(); - private final SwitchPreference mSwitchPreference = spy(new SwitchPreference(mContext)); - private final MagnificationAlwaysOnPreferenceController mController = - new MagnificationAlwaysOnPreferenceController(mContext, - MagnificationAlwaysOnPreferenceController.PREF_KEY); + private Context mContext; + private ShadowContentResolver mShadowContentResolver; + private SwitchPreference mSwitchPreference; + private MagnificationAlwaysOnPreferenceController mController; @Before public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mShadowContentResolver = Shadow.extract(mContext.getContentResolver()); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + mSwitchPreference = spy(new SwitchPreference(mContext)); mSwitchPreference.setKey(MagnificationAlwaysOnPreferenceController.PREF_KEY); screen.addPreference(mSwitchPreference); - mController.displayPreference(screen); + mController = new MagnificationAlwaysOnPreferenceController(mContext, + MagnificationAlwaysOnPreferenceController.PREF_KEY); + mController.displayPreference(screen); mController.updateState(mSwitchPreference); + reset(mSwitchPreference); } @@ -80,4 +95,57 @@ public class MagnificationAlwaysOnPreferenceControllerTest { assertThat(mController.isChecked()).isFalse(); assertThat(mSwitchPreference.isChecked()).isFalse(); } + + @Test + @EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY) + public void onResume_flagOn_verifyRegisterCapabilityObserver() { + mController.onResume(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY))) + .hasSize(1); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY) + public void onPause_flagOn_verifyUnregisterCapabilityObserver() { + mController.onResume(); + mController.onPause(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY))) + .isEmpty(); + } + + @Test + @DisableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY) + public void updateState_windowModeOnlyAndFlagOff_preferenceIsAvailable() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW); + + mController.updateState(mSwitchPreference); + assertThat(mSwitchPreference.isEnabled()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY) + public void updateState_windowModeOnlyAndFlagOn_preferenceBecomesUnavailable() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW); + + mController.updateState(mSwitchPreference); + assertThat(mSwitchPreference.isEnabled()).isFalse(); + } + + @Test + public void updateState_fullscreenModeOnly_preferenceIsAvailable() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.FULLSCREEN); + + mController.updateState(mSwitchPreference); + assertThat(mSwitchPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_switchMode_preferenceIsAvailable() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.ALL); + + mController.updateState(mSwitchPreference); + assertThat(mSwitchPreference.isEnabled()).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationCapabilitiesTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationCapabilitiesTest.java index b0d03564c75..754b1a7f38e 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationCapabilitiesTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationCapabilitiesTest.java @@ -18,7 +18,14 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + import android.content.Context; +import android.database.ContentObserver; import androidx.test.core.app.ApplicationProvider; @@ -42,7 +49,6 @@ public final class MagnificationCapabilitiesTest { final int windowCapabilities = MagnificationCapabilities.getCapabilities(mContext); assertThat(windowCapabilities).isEqualTo( MagnificationCapabilities.MagnificationMode.WINDOW); - } @Test @@ -63,4 +69,35 @@ public final class MagnificationCapabilitiesTest { assertThat(windowCapabilities).isEqualTo( MagnificationCapabilities.MagnificationMode.FULLSCREEN); } + + @Test + public void registerObserver_triggeredWhenCapabilitiesChanged() { + MagnificationCapabilities.setCapabilities(mContext, + MagnificationCapabilities.MagnificationMode.FULLSCREEN); + + ContentObserver contentObserver = + spy(new ContentObserver(/* handler= */ null) {}); + + MagnificationCapabilities.registerObserver(mContext, contentObserver); + MagnificationCapabilities.setCapabilities(mContext, + MagnificationCapabilities.MagnificationMode.WINDOW); + + verify(contentObserver).onChange(/* selfChange= */ anyBoolean(), /* uri= */ any()); + } + + @Test + public void unregisterObserver_neverTriggeredWhenCapabilitiesChanged() { + MagnificationCapabilities.setCapabilities(mContext, + MagnificationCapabilities.MagnificationMode.FULLSCREEN); + + ContentObserver contentObserver = + spy(new ContentObserver(/* handler= */ null) {}); + + MagnificationCapabilities.registerObserver(mContext, contentObserver); + MagnificationCapabilities.unregisterObserver(mContext, contentObserver); + MagnificationCapabilities.setCapabilities(mContext, + MagnificationCapabilities.MagnificationMode.WINDOW); + + verify(contentObserver, never()).onChange(/* selfChange= */ anyBoolean(), /* uri= */ any()); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java index 1d85705a9e7..cc1c72ea10e 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java @@ -816,6 +816,21 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { assertThat(lifecycleObservers).comparingElementsUsing(instanceOf).contains(true); } + @Test + public void onCreateView_addTheAlwaysOnControllerToLifeCycleObserver() { + Correspondence instanceOf = Correspondence.transforming( + observer -> (observer instanceof MagnificationAlwaysOnPreferenceController), + "contains MagnificationAlwaysOnPreferenceController"); + + ToggleScreenMagnificationPreferenceFragment fragment = mFragController.create( + R.id.main_content, /* bundle= */ null).start().resume().get(); + + List lifecycleObservers = ReflectionHelpers.getField( + fragment.getSettingsLifecycle(), "mObservers"); + assertThat(lifecycleObservers).isNotNull(); + assertThat(lifecycleObservers).comparingElementsUsing(instanceOf).contains(true); + } + @Test public void onCreateDialog_setDialogDelegate_invokeDialogDelegate() { ToggleScreenMagnificationPreferenceFragment fragment =