diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b91d35b604ca0..fa4c25ad460ab 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3603,12 +3603,6 @@ true - - - + + accessibility_display_inversion_enabled:inversion + + true diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 8835e5db50c06..54a928d78a892 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -454,11 +454,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D final Resources res = context.getResources(); final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); - final String extraTileList = res.getString( - com.android.internal.R.string.config_defaultExtraQuickSettingsTiles); tiles.addAll(Arrays.asList(defaultTileList.split(","))); - tiles.addAll(Arrays.asList(extraTileList.split(","))); if (Build.IS_DEBUGGABLE && GarbageMonitor.MemoryTile.ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) { tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java index c22463964a192..65d815053e47f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java @@ -88,4 +88,12 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl public int getCurrentUser() { return mUserId; } + + public String getKey() { + return mSettingName; + } + + public boolean isListening() { + return mListening; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 12ea1004038e8..f79abe4053d81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -15,16 +15,19 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.content.res.Resources; import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; -import android.provider.Settings.Secure; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.SecureSetting; +import com.android.systemui.qs.external.CustomTile; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -32,18 +35,24 @@ import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; +import java.util.ArrayList; +import java.util.Objects; + import javax.inject.Inject; /** * Manages which tiles should be automatically added to QS. */ public class AutoTileManager { + private static final String TAG = "AutoTileManager"; + public static final String HOTSPOT = "hotspot"; public static final String SAVER = "saver"; public static final String INVERSION = "inversion"; public static final String WORK = "work"; public static final String NIGHT = "night"; public static final String CAST = "cast"; + public static final String SETTING_SEPARATOR = ":"; protected final Context mContext; protected final QSTileHost mHost; @@ -54,6 +63,7 @@ public class AutoTileManager { private final ManagedProfileController mManagedProfileController; private final NightDisplayListener mNightDisplayListener; private final CastController mCastController; + private final ArrayList mAutoAddSettingList = new ArrayList<>(); @Inject public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host, @@ -78,21 +88,6 @@ public class AutoTileManager { if (!mAutoTracker.isAdded(SAVER)) { dataSaverController.addCallback(mDataSaverListener); } - if (!mAutoTracker.isAdded(INVERSION)) { - mColorsSetting = new SecureSetting(mContext, mHandler, - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - if (mAutoTracker.isAdded(INVERSION)) return; - if (value != 0) { - mHost.addTile(INVERSION); - mAutoTracker.setTileAdded(INVERSION); - mHandler.post(() -> mColorsSetting.setListening(false)); - } - } - }; - mColorsSetting.setListening(true); - } if (!mAutoTracker.isAdded(WORK)) { managedProfileController.addCallback(mProfileCallback); } @@ -103,12 +98,10 @@ public class AutoTileManager { if (!mAutoTracker.isAdded(CAST)) { castController.addCallback(mCastCallback); } + populateSettingsList(); } public void destroy() { - if (mColorsSetting != null) { - mColorsSetting.setListening(false); - } mAutoTracker.destroy(); mHotspotController.removeCallback(mHotspotCallback); mDataSaverController.removeCallback(mDataSaverListener); @@ -117,6 +110,42 @@ public class AutoTileManager { mNightDisplayListener.setCallback(null); } mCastController.removeCallback(mCastCallback); + int settingsN = mAutoAddSettingList.size(); + for (int i = 0; i < settingsN; i++) { + mAutoAddSettingList.get(i).setListening(false); + } + } + + /** + * Populates a list with the pairs setting:spec in the config resource. + *

+ * This will only create {@link AutoAddSetting} objects for those tiles that have not been + * auto-added before, and set the corresponding {@link ContentObserver} to listening. + */ + private void populateSettingsList() { + String [] autoAddList; + try { + autoAddList = mContext.getResources().getStringArray( + R.array.config_quickSettingsAutoAdd); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "Missing config resource"); + return; + } + // getStringArray returns @NotNull, so if we got here, autoAddList is not null + for (String tile : autoAddList) { + String[] split = tile.split(SETTING_SEPARATOR); + if (split.length == 2) { + String setting = split[0]; + String spec = split[1]; + if (!mAutoTracker.isAdded(spec)) { + AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec); + mAutoAddSettingList.add(s); + s.setListening(true); + } + } else { + Log.w(TAG, "Malformed item in array: " + tile); + } + } } public void unmarkTileAsAutoAdded(String tabSpec) { @@ -139,8 +168,6 @@ public class AutoTileManager { } }; - private SecureSetting mColorsSetting; - private final DataSaverController.Listener mDataSaverListener = new Listener() { @Override public void onDataSaverChanged(boolean isDataSaving) { @@ -213,4 +240,47 @@ public class AutoTileManager { } } }; + + @VisibleForTesting + protected SecureSetting getSecureSettingForKey(String key) { + for (SecureSetting s : mAutoAddSettingList) { + if (Objects.equals(key, s.getKey())) { + return s; + } + } + return null; + } + + /** + * Tracks tiles that should be auto added when a setting changes. + *

+ * When the setting changes to a value different from 0, if the tile has not been auto added + * before, it will be added and the listener will be stopped. + */ + private class AutoAddSetting extends SecureSetting { + private final String mSpec; + + AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) { + super(context, handler, setting); + mSpec = tileSpec; + } + + @Override + protected void handleValueChanged(int value, boolean observedChange) { + if (mAutoTracker.isAdded(mSpec)) { + // This should not be listening anymore + mHandler.post(() -> setListening(false)); + return; + } + if (value != 0) { + if (mSpec.startsWith(CustomTile.PREFIX)) { + mHost.addTile(CustomTile.getComponentFromSpec(mSpec)); + } else { + mHost.addTile(mSpec); + } + mAutoTracker.setTileAdded(mSpec); + mHandler.post(() -> setListening(false)); + } + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 966fa88653bff..11477395a7813 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -122,11 +122,6 @@ public class QSTileHostTest extends SysuiTestCase { mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger); setUpTileFactory(); - // Override this config so there are no unexpected tiles - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, - ""); - Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING, "", ActivityManager.getCurrentUser()); } @@ -209,34 +204,6 @@ public class QSTileHostTest extends SysuiTestCase { verify(mQSLogger).logTileAdded("spec1"); } - @Test - public void testDefaultAndExtra() { - mContext.getOrCreateTestableResources() - .addOverride(R.string.quick_settings_tiles_default, "spec1"); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, "spec2"); - mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default"); - assertEquals(2, mQSTileHost.getTiles().size()); - QSTile[] elements = mQSTileHost.getTiles().toArray(new QSTile[0]); - assertTrue(elements[0] instanceof TestTile1); - assertTrue(elements[1] instanceof TestTile2); - - verify(mQSLogger).logTileAdded("spec1"); - verify(mQSLogger).logTileAdded("spec2"); - } - - @Test - public void testExtraCustom() { - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, - CUSTOM_TILE_SPEC); - mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default"); - assertEquals(1, mQSTileHost.getTiles().size()); - assertEquals(mCustomTile, CollectionUtils.firstOrNull(mQSTileHost.getTiles())); - - verify(mQSLogger).logTileAdded(CUSTOM_TILE_SPEC); - } - @Test public void testNoRepeatedSpecs_addTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 67ad37b2d29a8..1a6921a1d136a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -20,19 +20,24 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.SecureSetting; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -52,6 +57,13 @@ import java.util.List; @SmallTest public class AutoTileManagerTest extends SysuiTestCase { + private static final String TEST_SETTING = "setting"; + private static final String TEST_SPEC = "spec"; + private static final String TEST_SETTING_COMPONENT = "setting_component"; + private static final String TEST_COMPONENT = "test_pkg/test_cls"; + private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")"; + private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR; + @Mock private QSTileHost mQsTileHost; @Mock private AutoAddTracker mAutoAddTracker; @Mock private CastController mCastController; @@ -61,7 +73,20 @@ public class AutoTileManagerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mAutoTileManager = new AutoTileManager(mContext, mAutoAddTracker, mQsTileHost, + + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsAutoAdd, + new String[] { + TEST_SETTING + SEPARATOR + TEST_SPEC, + TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC + } + ); + + mAutoTileManager = createAutoTileManager(); + } + + private AutoTileManager createAutoTileManager() { + return new AutoTileManager(mContext, mAutoAddTracker, mQsTileHost, Handler.createAsync(TestableLooper.get(this).getLooper()), mock(HotspotController.class), mock(DataSaverController.class), @@ -137,4 +162,72 @@ public class AutoTileManagerTest extends SysuiTestCase { mAutoTileManager.mCastCallback.onCastDevicesChanged(); verify(mQsTileHost, never()).addTile("cast"); } + + @Test + public void testSettingTileAdded_onChanged() { + changeValue(TEST_SETTING, 1); + waitForIdleSync(); + verify(mAutoAddTracker).setTileAdded(TEST_SPEC); + verify(mQsTileHost).addTile(TEST_SPEC); + } + + @Test + public void testSettingTileAddedComponent_onChanged() { + changeValue(TEST_SETTING_COMPONENT, 1); + waitForIdleSync(); + verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC); + verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)); + } + + @Test + public void testSettingTileAdded_onlyOnce() { + changeValue(TEST_SETTING, 1); + waitForIdleSync(); + TestableLooper.get(this).processAllMessages(); + changeValue(TEST_SETTING, 2); + waitForIdleSync(); + verify(mAutoAddTracker).setTileAdded(TEST_SPEC); + verify(mQsTileHost).addTile(TEST_SPEC); + } + + @Test + public void testSettingTileNotAdded_onChangedTo0() { + changeValue(TEST_SETTING, 0); + waitForIdleSync(); + verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC); + verify(mQsTileHost, never()).addTile(TEST_SPEC); + } + + @Test + public void testSettingTileNotAdded_ifPreviouslyAdded() { + when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true); + + changeValue(TEST_SETTING, 1); + waitForIdleSync(); + verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC); + verify(mQsTileHost, never()).addTile(TEST_SPEC); + } + + @Test + public void testEmptyArray_doesNotCrash() { + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsAutoAdd, new String[0]); + createAutoTileManager(); + } + + @Test + public void testMissingConfig_doesNotCrash() { + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsAutoAdd, null); + createAutoTileManager(); + } + + // Will only notify if it's listening + private void changeValue(String key, int value) { + SecureSetting s = mAutoTileManager.getSecureSettingForKey(key); + Settings.Secure.putInt(mContext.getContentResolver(), key, value); + if (s != null && s.isListening()) { + s.onChange(false); + } + } }