From acf98a6a4afead8c8fd983030504c432eca9cb7d Mon Sep 17 00:00:00 2001 From: Roy Chou Date: Thu, 11 Apr 2024 07:33:52 +0000 Subject: [PATCH 1/6] feat(onefingerpan): hide the one finger pan settings when window mode only Like MagnificationAlwaysOn toggle behavior, when the magnification capability is window-mode only we'll hide the OneFingerPan toggle too. Also do a small refactoring in MagnificationOneFingerPanningPreferenceControllerTest Bug: 333821725 Flag: ACONFIG com.android.server.accessibility.enable_magnification_one_finger_panning_gesture TEAMFOOD Test: manually atest MagnificationOneFingerPanningPreferenceControllerTest atest ToggleScreenMagnificationPreferenceFragmentTest Change-Id: I8684b5bae5cbfc5b75fc4c14d2e9173b17d0fb02 --- res/values/strings.xml | 2 + ...nOneFingerPanningPreferenceController.java | 60 ++++++- ...ScreenMagnificationPreferenceFragment.java | 3 +- ...FingerPanningPreferenceControllerTest.java | 155 ++++++++++-------- ...enMagnificationPreferenceFragmentTest.java | 74 ++++++++- 5 files changed, 216 insertions(+), 78 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index efbc430be9a..854609278d3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4816,6 +4816,8 @@ Move the magnification area by dragging two fingers. + + Unavailable while only magnifying part of the screen Magnify with shortcut diff --git a/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java b/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java index a2ce948994d..4eb5090dce6 100644 --- a/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java +++ b/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java @@ -21,27 +21,43 @@ import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import android.content.Context; import android.content.res.Resources; +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.VisibleForTesting; +import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.preference.TwoStatePreference; -import com.android.server.accessibility.Flags; 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; public class MagnificationOneFingerPanningPreferenceController - extends TogglePreferenceController { + extends TogglePreferenceController implements LifecycleObserver, OnResume, OnPause { static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; - @Nullable private TwoStatePreference mSwitchPreference; @VisibleForTesting final boolean mDefaultValue; + @VisibleForTesting + final ContentObserver mContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + updateState(mSwitchPreference); + } + }; + public MagnificationOneFingerPanningPreferenceController(Context context) { super(context, PREF_KEY); boolean defaultValue; @@ -54,10 +70,19 @@ public class MagnificationOneFingerPanningPreferenceController mDefaultValue = defaultValue; } + @Override + public void onResume() { + MagnificationCapabilities.registerObserver(mContext, mContentObserver); + } + + @Override + public void onPause() { + MagnificationCapabilities.unregisterObserver(mContext, mContentObserver); + } + @Override public int getAvailabilityStatus() { - return (Flags.enableMagnificationOneFingerPanningGesture()) - ? AVAILABLE : DISABLED_FOR_USER; + return AVAILABLE; } @Override @@ -73,14 +98,17 @@ public class MagnificationOneFingerPanningPreferenceController var toReturn = Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY, (isChecked ? ON : OFF)); - if (mSwitchPreference != null) { - refreshSummary(mSwitchPreference); - } + refreshSummary(mSwitchPreference); return toReturn; } @Override public CharSequence getSummary() { + if (!mSwitchPreference.isEnabled()) { + return mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_unavailable); + } + return (isChecked()) ? mContext.getString( R.string.accessibility_magnification_one_finger_panning_summary_on) @@ -97,6 +125,20 @@ public class MagnificationOneFingerPanningPreferenceController public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mSwitchPreference = screen.findPreference(getPreferenceKey()); - refreshSummary(mSwitchPreference); + updateState(mSwitchPreference); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + 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/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index d162272fdb6..e965a0d1cb8 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -202,7 +202,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); generalCategory.addPreference(mSettingsPreference); - addOneFingerPanningSetting(generalCategory); final MagnificationModePreferenceController magnificationModePreferenceController = new MagnificationModePreferenceController(getContext(), MagnificationModePreferenceController.PREF_KEY); @@ -212,6 +211,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends addPreferenceController(magnificationModePreferenceController); addFollowTypingSetting(generalCategory); + addOneFingerPanningSetting(generalCategory); addAlwaysOnSetting(generalCategory); addJoystickSetting(generalCategory); } @@ -302,6 +302,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends var oneFingerPanningPreferenceController = new MagnificationOneFingerPanningPreferenceController(getContext()); + getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController); oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen()); addPreferenceController(oneFingerPanningPreferenceController); } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java index 4501d273bcd..bfc8313d142 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java @@ -18,19 +18,15 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; -import static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER; +import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import androidx.preference.PreferenceManager; @@ -38,131 +34,137 @@ import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; -import com.android.server.accessibility.Flags; import com.android.settings.R; -import org.junit.After; 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 MagnificationOneFingerPanningPreferenceControllerTest { private static final String ONE_FINGER_PANNING_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - private final Context mContext = ApplicationProvider.getApplicationContext(); + private ShadowContentResolver mShadowContentResolver; private final SwitchPreference mSwitchPreference = spy(new SwitchPreference(mContext)); private final MagnificationOneFingerPanningPreferenceController mController = new MagnificationOneFingerPanningPreferenceController(mContext); - private PreferenceScreen mScreen; - @Before public void setUp() { + mShadowContentResolver = Shadow.extract(mContext.getContentResolver()); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); - mScreen = preferenceManager.createPreferenceScreen(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); mSwitchPreference.setKey(MagnificationOneFingerPanningPreferenceController.PREF_KEY); - mScreen.addPreference(mSwitchPreference); - mController.displayPreference(mScreen); - } - - @After - public void cleanup() { - // Can't use resetToDefaults as it NPE with - // "Cannot invoke "android.content.IContentProvider.call" - Settings.Secure.putInt( - mContext.getContentResolver(), - MagnificationOneFingerPanningPreferenceController.PREF_KEY, - (mController.mDefaultValue) ? ON : OFF); + screen.addPreference(mSwitchPreference); + mController.displayPreference(screen); } @Test - public void displayPreference_defaultState_correctSummarySet() { - assertThat(mSwitchPreference.getSummary()) - .isEqualTo(mContext.getString( - R.string.accessibility_magnification_one_finger_panning_summary_off)); + public void onResume_verifyRegisterCapabilityObserver() { + mController.onResume(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY))) + .hasSize(1); } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) - public void getAvailabilityStatus_flagDisabled_disabled() { - int status = mController.getAvailabilityStatus(); - - assertThat(status).isEqualTo(DISABLED_FOR_USER); + public void onPause_verifyUnregisterCapabilityObserver() { + mController.onResume(); + mController.onPause(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY))) + .isEmpty(); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) - public void getAvailabilityStatus_featureFlagEnabled_enabled() { - int status = mController.getAvailabilityStatus(); + public void updateState_windowModeOnly_preferenceIsUnavailable() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW); + mController.updateState(mSwitchPreference); - assertThat(status).isEqualTo(AVAILABLE); + 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(); } @Test public void isChecked_defaultState_returnFalse() { + mController.updateState(mSwitchPreference); + assertThat(mController.isChecked()).isFalse(); assertThat(mSwitchPreference.isChecked()).isFalse(); } @Test - public void isChecked_settingsEnabled_returnTrue() { + public void isChecked_settingsOn_returnTrue() { Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, ON); + mController.updateState(mSwitchPreference); assertThat(mController.isChecked()).isTrue(); } @Test - public void isChecked_settingsDisabled_returnTrue() { + public void isChecked_settingsOff_returnFalse() { Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, OFF); + mController.updateState(mSwitchPreference); assertThat(mController.isChecked()).isFalse(); } @Test - public void setChecked_enabled_enabledSummarySet() { - mController.setChecked(true); - - assertThat(mSwitchPreference.getSummary()).isEqualTo(enabledSummary()); - assertThat(mController.isChecked()).isTrue(); - } - - @Test - public void setChecked_disabled_disabledSummarySet() { - mController.setChecked(false); - - assertThat(mController.isChecked()).isFalse(); - assertThat(mSwitchPreference.getSummary()).isEqualTo(disabledSummary()); - } - - @Test - public void getSummary_disable_disableSummaryTextUsed() { + public void getSummary_switchModeAndSettingsOff_disabledSummaryTextUsed() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.ALL); Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, OFF); - var summary = mController.getSummary(); + mController.updateState(mSwitchPreference); - assertThat(summary).isEqualTo(disabledSummary()); + assertThat(mController.getSummary()).isEqualTo(disabledSummary()); } @Test - public void getSummary_enable_enabledSummaryTextUsed() { + public void getSummary_switchModeAndSettingsOn_enabledSummaryTextUsed() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.ALL); Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, ON); - var summary = mController.getSummary(); + mController.updateState(mSwitchPreference); - assertThat(summary).isEqualTo(enabledSummary()); + assertThat(mController.getSummary()).isEqualTo(enabledSummary()); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) - public void performClick_switchDefaultState_shouldReturnTrue() { + public void getSummary_windowModeOnly_unavailableSummaryTextUsed() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW); + + mController.updateState(mSwitchPreference); + + assertThat(mController.getSummary()).isEqualTo(unavailableSummary()); + } + + @Test + public void performClick_defaultSettings_toggleOn() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.ALL); + mController.updateState(mSwitchPreference); + reset(mSwitchPreference); + mSwitchPreference.performClick(); verify(mSwitchPreference).setChecked(true); @@ -170,6 +172,20 @@ public class MagnificationOneFingerPanningPreferenceControllerTest { assertThat(mSwitchPreference.isChecked()).isTrue(); } + @Test + public void performClick_settingsOn_toggleOff() { + MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.ALL); + Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, ON); + mController.updateState(mSwitchPreference); + reset(mSwitchPreference); + + mSwitchPreference.performClick(); + + verify(mSwitchPreference).setChecked(false); + assertThat(mController.isChecked()).isFalse(); + assertThat(mSwitchPreference.isChecked()).isFalse(); + } + private String enabledSummary() { return mContext.getString( R.string.accessibility_magnification_one_finger_panning_summary_on); @@ -179,4 +195,9 @@ public class MagnificationOneFingerPanningPreferenceControllerTest { return mContext.getString( R.string.accessibility_magnification_one_finger_panning_summary_off); } + + private String unavailableSummary() { + return mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_unavailable); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java index cc1c72ea10e..ab2e9d1a121 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java @@ -122,6 +122,8 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { private static final String KEY_FOLLOW_TYPING = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED; + private static final String KEY_SINGLE_FINGER_PANNING = + Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; private static final String KEY_ALWAYS_ON = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED; private static final String KEY_JOYSTICK = @@ -215,6 +217,43 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { assertThat(switchPreference.isChecked()).isFalse(); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) + public void onResume_defaultStateForOneFingerPan_switchPreferenceShouldReturnFalse() { + mFragController.create(R.id.main_content, /* bundle= */ null).start().resume(); + + final TwoStatePreference switchPreference = mFragController.get().findPreference( + MagnificationOneFingerPanningPreferenceController.PREF_KEY); + assertThat(switchPreference).isNotNull(); + assertThat(switchPreference.isChecked()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) + public void onResume_enableOneFingerPan_switchPreferenceShouldReturnTrue() { + setKeyOneFingerPanEnabled(true); + + mFragController.create(R.id.main_content, /* bundle= */ null).start().resume(); + + final TwoStatePreference switchPreference = mFragController.get().findPreference( + MagnificationOneFingerPanningPreferenceController.PREF_KEY); + assertThat(switchPreference).isNotNull(); + assertThat(switchPreference.isChecked()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) + public void onResume_disableOneFingerPan_switchPreferenceShouldReturnFalse() { + setKeyOneFingerPanEnabled(false); + + mFragController.create(R.id.main_content, /* bundle= */ null).start().resume(); + + final TwoStatePreference switchPreference = mFragController.get().findPreference( + MagnificationOneFingerPanningPreferenceController.PREF_KEY); + assertThat(switchPreference).isNotNull(); + assertThat(switchPreference.isChecked()).isFalse(); + } + @Test public void onResume_defaultStateForAlwaysOn_switchPreferenceShouldReturnTrue() { setAlwaysOnSupported(true); @@ -777,6 +816,16 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { assertThat(mFragController.get().mSettingsPreference).isNull(); } + @Test + @DisableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) + public void onCreateView_oneFingerPanNotSupported_settingsPreferenceIsNull() { + mFragController.create(R.id.main_content, /* bundle= */ null).start().resume(); + + final TwoStatePreference switchPreference = mFragController.get().findPreference( + MagnificationOneFingerPanningPreferenceController.PREF_KEY); + assertThat(switchPreference).isNull(); + } + @Test public void onCreateView_alwaysOnNotSupported_settingsPreferenceIsNull() { setAlwaysOnSupported(false); @@ -817,7 +866,25 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { } @Test - public void onCreateView_addTheAlwaysOnControllerToLifeCycleObserver() { + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE) + public void onCreateView_oneFingerPanSupported_addControllerToLifeCycleObserver() { + Correspondence instanceOf = Correspondence.transforming( + observer -> (observer instanceof MagnificationOneFingerPanningPreferenceController), + "contains MagnificationOneFingerPanningPreferenceController"); + + 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 onCreateView_alwaysOnSupported_addControllerToLifeCycleObserver() { + setAlwaysOnSupported(true); + Correspondence instanceOf = Correspondence.transforming( observer -> (observer instanceof MagnificationAlwaysOnPreferenceController), "contains MagnificationAlwaysOnPreferenceController"); @@ -984,6 +1051,11 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { enabled ? ON : OFF); } + private void setKeyOneFingerPanEnabled(boolean enabled) { + Settings.Secure.putInt(mContext.getContentResolver(), KEY_SINGLE_FINGER_PANNING, + enabled ? ON : OFF); + } + private void setAlwaysOnSupported(boolean supported) { ShadowDeviceConfig.setProperty( DeviceConfig.NAMESPACE_WINDOW_MANAGER, From 9ff8ddeed13a2aa8fcabd2fda2c2a882021a9a84 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Tue, 9 Apr 2024 18:03:15 +0000 Subject: [PATCH 2/6] Allow dynamic injecting preference into specific preferenceCategory Bug: 333547416 Test: robotest Change-Id: I443ec8c16afc6db334a6f16d857f69f293803979 --- aconfig/settings_flag_declarations.aconfig | 7 +++++ .../settings/dashboard/DashboardFragment.java | 29 ++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/aconfig/settings_flag_declarations.aconfig b/aconfig/settings_flag_declarations.aconfig index 814f7e1af0f..ac59f1b30c3 100644 --- a/aconfig/settings_flag_declarations.aconfig +++ b/aconfig/settings_flag_declarations.aconfig @@ -28,3 +28,10 @@ flag { description: "Feature flag to enable new settings homepage UX." bug: "321612737" } + +flag { + name: "dynamic_injection_category" + namespace: "android_settings" + description: "Feature flag to enable injection into PreferenceCategory." + bug: "333547416" +} diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 09730767d09..6df80f1c2ac 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -46,6 +46,7 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.core.CategoryMixin.CategoryHandler; import com.android.settings.core.CategoryMixin.CategoryListener; import com.android.settings.core.PreferenceControllerListHelper; +import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.core.AbstractPreferenceController; @@ -543,13 +544,23 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, pref, tile, key, mPlaceholderPreferenceController.getOrder()); - if (tile.hasGroupKey() && mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) { - final Preference group = screen.findPreference(tile.getGroupKey()); - if (group instanceof PreferenceCategory) { + if (Flags.dynamicInjectionCategory()) { + Preference group = screen.findPreference(tile.getGroupKey()); + if (tile.hasGroupKey() && group instanceof PreferenceCategory) { ((PreferenceCategory) group).addPreference(pref); + } else { + screen.addPreference(pref); } } else { - screen.addPreference(pref); + if (tile.hasGroupKey() + && mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) { + Preference group = screen.findPreference(tile.getGroupKey()); + if (group instanceof PreferenceCategory) { + ((PreferenceCategory) group).addPreference(pref); + } + } else { + screen.addPreference(pref); + } } registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); @@ -564,9 +575,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment for (Map.Entry> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); - final Preference preference = screen.findPreference(key); - if (preference != null) { - screen.removePreference(preference); + if (Flags.dynamicInjectionCategory()) { + screen.removePreferenceRecursively(key); + } else { + Preference preference = screen.findPreference(key); + if (preference != null) { + screen.removePreference(preference); + } } unregisterDynamicDataObservers(entry.getValue()); } From 7676217e4d82d5e4b57095aade5daa1feb83695d Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Wed, 10 Apr 2024 10:30:57 -0700 Subject: [PATCH 3/6] Call noteAppRestrictionChanged when toggling restrictions/exemptions This is to log the reasons for restriction level change and force stop Bug: 333882527 Test: statsd_testdrive 863 Toggle battery restrictions manually for an app and verify logging of reason atest BatteryOptimizeUtilsTest Change-Id: Ifcc99efc1b6acc5a992f7d952967210b07319f2e --- .../appinfo/AppButtonsPreferenceController.java | 5 +++++ .../android/settings/fuelgauge/BatteryOptimizeUtils.java | 4 ++-- src/com/android/settings/fuelgauge/BatteryUtils.java | 9 +++++++++ src/com/android/settings/fuelgauge/HighPowerDetail.java | 4 ++-- .../settings/spa/app/appinfo/PackageInfoPresenter.kt | 8 ++++++++ .../settings/fuelgauge/BatteryOptimizeUtilsTest.java | 8 ++++---- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java index 277f54b7464..41ce1fa35d9 100644 --- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java @@ -558,6 +558,11 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp ActivityManager am = (ActivityManager) mActivity.getSystemService( Context.ACTIVITY_SERVICE); Log.d(TAG, "Stopping package " + pkgName); + if (android.app.Flags.appRestrictionsApi()) { + am.noteAppRestrictionEnabled(pkgName, mAppEntry.info.uid, + ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true, + ActivityManager.RESTRICTION_REASON_USER, "settings", 0L); + } am.forceStopPackage(pkgName); int userId = UserHandle.getUserId(mAppEntry.info.uid); mState.invalidatePackage(pkgName, userId); diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 001876c394b..62e28fe6988 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -345,9 +345,9 @@ public class BatteryOptimizeUtils { try { batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); if (allowListed) { - powerAllowlistBackend.addApp(packageName); + powerAllowlistBackend.addApp(packageName, uid); } else { - powerAllowlistBackend.removeApp(packageName); + powerAllowlistBackend.removeApp(packageName, uid); } } catch (Exception e) { // Error cases, set standby mode as -1 for logging. diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 767f2c01881..b53bf477104 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -15,6 +15,7 @@ */ package com.android.settings.fuelgauge; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.Intent; @@ -386,6 +387,14 @@ public class BatteryUtils { // Control whether app could run in the background if it is pre O app mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode); } + // Notify system of reason for change + if (isForceAppStandbyEnabled(uid, packageName) != (mode == AppOpsManager.MODE_IGNORED)) { + mContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled( + packageName, uid, ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, + mode == AppOpsManager.MODE_IGNORED, + ActivityManager.RESTRICTION_REASON_USER, + "settings", 0); + } // Control whether app could run jobs in the background mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode); diff --git a/src/com/android/settings/fuelgauge/HighPowerDetail.java b/src/com/android/settings/fuelgauge/HighPowerDetail.java index b2585a6d23f..ee9efaef87a 100644 --- a/src/com/android/settings/fuelgauge/HighPowerDetail.java +++ b/src/com/android/settings/fuelgauge/HighPowerDetail.java @@ -142,9 +142,9 @@ public class HighPowerDetail extends InstrumentedDialogFragment if (newValue) { mBatteryUtils.setForceAppStandby( mPackageUid, mPackageName, AppOpsManager.MODE_ALLOWED); - mBackend.addApp(mPackageName); + mBackend.addApp(mPackageName, mPackageUid); } else { - mBackend.removeApp(mPackageName); + mBackend.removeApp(mPackageName, mPackageUid); } } } diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt index 81abae5a3e0..d5ce3af8e89 100644 --- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt +++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt @@ -16,6 +16,7 @@ package com.android.settings.spa.app.appinfo +import android.app.ActivityManager import android.app.settings.SettingsEnums import android.content.Context import android.content.Intent @@ -154,6 +155,13 @@ class PackageInfoPresenter( logAction(SettingsEnums.ACTION_APP_FORCE_STOP) coroutineScope.launch(Dispatchers.Default) { Log.d(TAG, "Stopping package $packageName") + if (android.app.Flags.appRestrictionsApi()) { + val uid = userPackageManager.getPackageUid(packageName, 0) + context.activityManager.noteAppRestrictionEnabled( + packageName, uid, + ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true, + ActivityManager.RESTRICTION_REASON_USER, "settings", 0) + } context.activityManager.forceStopPackageAsUser(packageName, userId) } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java index 6085b9a3ce4..9686709c013 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java @@ -206,8 +206,8 @@ public class BatteryOptimizeUtilsTest { TimeUnit.SECONDS.sleep(1); verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt()); - verify(mMockBackend, never()).addApp(anyString()); - verify(mMockBackend, never()).removeApp(anyString()); + verify(mMockBackend, never()).addApp(anyString(), anyInt()); + verify(mMockBackend, never()).removeApp(anyString(), anyInt()); verifyNoInteractions(mObserver); } @@ -358,9 +358,9 @@ public class BatteryOptimizeUtilsTest { private void verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed) { verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, appStandbyMode); if (allowListed) { - verify(mMockBackend).addApp(PACKAGE_NAME); + verify(mMockBackend).addApp(PACKAGE_NAME, UID); } else { - verify(mMockBackend).removeApp(PACKAGE_NAME); + verify(mMockBackend).removeApp(PACKAGE_NAME, UID); } } } From 5d82f6a648352e29ecb9d32dcb16527923d6bba7 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Wed, 17 Apr 2024 16:26:52 +0800 Subject: [PATCH 4/6] Remove the package name restriction of ACTION_UNINSTALL_PACKAGE PackageInstaller has protected the intent action by setting "android:priority=1". Test: manual Fix: 332228634 Change-Id: If0794e5957366d8b26acd0362b59c6c9076a0c4f --- .../ToggleAccessibilityServicePreferenceFragment.java | 3 +-- .../applications/appinfo/AppButtonsPreferenceController.java | 2 -- .../applications/appinfo/AppInfoDashboardFragment.java | 1 - .../settings/applications/manageapplications/CloneBackend.java | 3 --- src/com/android/settings/spa/app/AppUtil.kt | 2 -- 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java index 0e50a5c90d5..72e1ad8eb93 100644 --- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java @@ -443,8 +443,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends final ApplicationInfo appInfo = a11yServiceInfo.getResolveInfo().serviceInfo.applicationInfo; final Uri packageUri = Uri.parse("package:" + appInfo.packageName); - final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri) - .setPackage(getString(R.string.config_package_installer_package_name)); + final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); return uninstallIntent; } diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java index 277f54b7464..f508caf2885 100644 --- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java @@ -539,8 +539,6 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp // Create new intent to launch Uninstaller activity Uri packageUri = Uri.parse("package:" + packageName); Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); - uninstallIntent.setPackage(mContext.getString( - R.string.config_package_installer_package_name)); uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); mMetricsFeatureProvider.action(mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP); diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index afb33ae48d9..90d733e6fe1 100644 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -642,7 +642,6 @@ public class AppInfoDashboardFragment extends DashboardFragment // Create new intent to launch Uninstaller activity final Uri packageURI = Uri.parse("package:" + packageName); final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); - uninstallIntent.setPackage(getString(R.string.config_package_installer_package_name)); uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); mMetricsFeatureProvider.action( getContext(), SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP); diff --git a/src/com/android/settings/applications/manageapplications/CloneBackend.java b/src/com/android/settings/applications/manageapplications/CloneBackend.java index 06b4a997e17..406c9304386 100644 --- a/src/com/android/settings/applications/manageapplications/CloneBackend.java +++ b/src/com/android/settings/applications/manageapplications/CloneBackend.java @@ -34,7 +34,6 @@ import android.util.Log; import androidx.fragment.app.FragmentActivity; -import com.android.settings.R; import com.android.settings.Utils; import java.util.HashSet; @@ -79,8 +78,6 @@ public class CloneBackend { // Create new intent to launch Uninstaller activity. Uri packageUri = Uri.parse("package:" + packageName); Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); - uninstallIntent.setPackage(mContext.getString( - R.string.config_package_installer_package_name)); uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); uninstallIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(mCloneUserId)); // Trigger uninstall as clone user. diff --git a/src/com/android/settings/spa/app/AppUtil.kt b/src/com/android/settings/spa/app/AppUtil.kt index 2b30c6f0808..64da61380f5 100644 --- a/src/com/android/settings/spa/app/AppUtil.kt +++ b/src/com/android/settings/spa/app/AppUtil.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.UserHandle -import com.android.settings.R /** * Based on PackageManagerService design, and it looks like the suggested replacement in the @@ -37,7 +36,6 @@ fun Context.startUninstallActivity( val packageUri = Uri.parse("package:$packageName") val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply { - setPackage(getString(R.string.config_package_installer_package_name)) putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, forAllUsers) } startActivityAsUser(intent, userHandle) From f4462ac5c224db867b23f59638ae0bc93d6eff6f Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Tue, 16 Apr 2024 18:36:52 +0000 Subject: [PATCH 5/6] When erasing an eSIM, verify the device screen PIN lock if one is set. SIM PIN lock existence isn't checked anymore. Bug: 329202869 Test: manual - see b/329202869#comment41 Change-Id: I2c4d2b7e224973c4afa75573e05afe7607d2ae6b --- .../DeleteSimProfilePreferenceController.kt | 4 +- .../network/telephony/MobileNetworkUtils.java | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt index b7ee41dd50e..312d4468bfd 100644 --- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt +++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt @@ -26,7 +26,7 @@ import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.network.SubscriptionUtil -import com.android.settings.wifi.dpp.WifiDppUtils +import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle /** This controls a preference allowing the user to delete the profile for an eSIM. */ @@ -65,7 +65,7 @@ class DeleteSimProfilePreferenceController(context: Context, preferenceKey: Stri override fun handlePreferenceTreeClick(preference: Preference): Boolean { if (preference.key != preferenceKey) return false - WifiDppUtils.showLockScreen(mContext) { deleteSim() } + MobileNetworkUtils.showLockScreen(mContext) { deleteSim() } return true } diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java index 4430642e3cf..ea549ae7c94 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java +++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java @@ -31,6 +31,7 @@ import static com.android.settings.network.telephony.TelephonyConstants.Telephon import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO; import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA; +import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -41,13 +42,18 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.hardware.biometrics.BiometricPrompt; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; import android.os.PersistableBundle; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telecom.PhoneAccountHandle; @@ -68,6 +74,7 @@ import android.text.TextUtils; import android.util.Log; import android.view.Gravity; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -1050,4 +1057,53 @@ public class MobileNetworkUtils { .launch(); } + /** + * Shows authentication screen to confirm credentials (pin/pattern/password) for the current + * user of the device. + * + *

Similar to WifiDppUtils.showLockScreen(), but doesn't check for the existence of + * SIM PIN lock, only screen PIN lock. + * + * @param context The {@code Context} used to get {@link KeyguardManager} service + * @param onSuccess The {@code Runnable} which will be executed if the user does not setup + * device security or if lock screen is unlocked + */ + public static void showLockScreen(@NonNull Context context, @NonNull Runnable onSuccess) { + final KeyguardManager keyguardManager = + context.getSystemService(KeyguardManager.class); + + if (keyguardManager.isDeviceSecure()) { + final BiometricPrompt.AuthenticationCallback authenticationCallback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded( + BiometricPrompt.AuthenticationResult result) { + onSuccess.run(); + } + + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + // Do nothing + } + }; + + final int userId = UserHandle.myUserId(); + final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(context) + .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title)) + .setDeviceCredentialAllowed(true) + .setTextForDeviceCredential( + /* title= */ null, + Utils.getConfirmCredentialStringForUser( + context, userId, Utils.getCredentialType(context, userId)), + /* description= */ null) + .build(); + final Handler handler = new Handler(Looper.getMainLooper()); + biometricPrompt.authenticate( + new CancellationSignal(), + handler::post, + authenticationCallback); + } else { + onSuccess.run(); + } + } } From 660986353ea6821cc12f4da15c3b59ce602a3019 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 16 Apr 2024 09:26:22 +0800 Subject: [PATCH 6/6] Fix NetworkScanRequest maxSearchTime Which should read from resource R.integer.config_network_scan_helper_max_search_time_sec Fix: 334814332 Test: manual - with network scan Change-Id: I4afefe6470d6ef4bf325e83a0decce41b321e201 --- .../network/telephony/scan/NetworkScanRepository.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt index 2067b8c3be5..dfa79cbc869 100644 --- a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt +++ b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt @@ -25,8 +25,8 @@ import android.telephony.RadioAccessSpecifier import android.telephony.TelephonyManager import android.telephony.TelephonyScanManager import android.util.Log -import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner +import com.android.settings.R import com.android.settings.network.telephony.CellInfoUtil import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle @@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn -class NetworkScanRepository(context: Context, subId: Int) { +class NetworkScanRepository(private val context: Context, subId: Int) { sealed interface NetworkScanResult data class NetworkScanCellInfos(val cellInfos: List) : NetworkScanResult @@ -105,7 +105,7 @@ class NetworkScanRepository(context: Context, subId: Int) { NetworkScanRequest.SCAN_TYPE_ONE_SHOT, radioAccessSpecifiers, NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC, // one shot, not used - MAX_SEARCH_TIME_SEC, + context.resources.getInteger(R.integer.config_network_scan_helper_max_search_time_sec), true, INCREMENTAL_RESULTS_PERIODICITY_SEC, null, @@ -158,10 +158,6 @@ class NetworkScanRepository(context: Context, subId: Int) { companion object { private const val TAG = "NetworkScanRepository" - @VisibleForTesting - val MAX_SEARCH_TIME_SEC = 300 - - @VisibleForTesting - val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3 + private const val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3 } }