diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java index e66e7ce37b3..a8d47d62542 100644 --- a/src/com/android/settings/display/ThemePreferenceController.java +++ b/src/com/android/settings/display/ThemePreferenceController.java @@ -15,15 +15,19 @@ package com.android.settings.display; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_THEME; -import android.app.AlertDialog; -import android.app.UiModeManager; import android.content.Context; -import android.content.DialogInterface.OnClickListener; +import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; - import android.text.TextUtils; + import com.android.settings.R; import com.android.settings.core.PreferenceController; import com.android.settings.core.instrumentation.MetricsFeatureProvider; @@ -31,17 +35,27 @@ import com.android.settings.overlay.FeatureFactory; import libcore.util.Objects; +import java.util.List; + public class ThemePreferenceController extends PreferenceController implements Preference.OnPreferenceChangeListener { private static final String KEY_THEME = "theme"; - private final UiModeManager mUiModeManager; private final MetricsFeatureProvider mMetricsFeatureProvider; + private final OverlayManager mOverlayService; + private final PackageManager mPackageManager; public ThemePreferenceController(Context context) { + this(context, ServiceManager.getService(Context.OVERLAY_SERVICE) != null + ? new OverlayManager() : null); + } + + @VisibleForTesting + ThemePreferenceController(Context context, OverlayManager overlayManager) { super(context); - mUiModeManager = context.getSystemService(UiModeManager.class); + mOverlayService = overlayManager; + mPackageManager = context.getPackageManager(); mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } @@ -61,12 +75,18 @@ public class ThemePreferenceController extends PreferenceController implements @Override public void updateState(Preference preference) { ListPreference pref = (ListPreference) preference; - String[] options = getAvailableThemes(); - for (int i = 0; i < options.length; i++) { - options[i] = nullToDefault(options[i]); + String[] pkgs = getAvailableThemes(); + CharSequence[] labels = new CharSequence[pkgs.length]; + for (int i = 0; i < pkgs.length; i++) { + try { + labels[i] = mPackageManager.getApplicationInfo(pkgs[i], 0) + .loadLabel(mPackageManager); + } catch (NameNotFoundException e) { + labels[i] = pkgs[i]; + } } - pref.setEntries(options); - pref.setEntryValues(options); + pref.setEntries(labels); + pref.setEntryValues(pkgs); String theme = getCurrentTheme(); if (TextUtils.isEmpty(theme)) { theme = mContext.getString(R.string.default_theme); @@ -77,49 +97,76 @@ public class ThemePreferenceController extends PreferenceController implements @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - if (Objects.equal(newValue, mUiModeManager.getTheme())) { + String current = getTheme(); + if (Objects.equal(newValue, current)) { return true; } - // TODO: STOPSHIP Don't require reboot and remove this prompt. - OnClickListener onConfirm = (d, i) -> { - mUiModeManager.setTheme(defaultToNull((String) newValue)); - ((ListPreference) preference).setValue((String) newValue); - }; - new AlertDialog.Builder(mContext) - .setTitle(R.string.change_theme_reboot) - .setPositiveButton(com.android.internal.R.string.global_action_restart, onConfirm) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return false; + try { + mOverlayService.setEnabledExclusive((String) newValue, true, UserHandle.myUserId()); + } catch (RemoteException e) { + return false; + } + return true; + } + + private String getTheme() { + try { + List infos = mOverlayService.getOverlayInfosForTarget("android", + UserHandle.myUserId()); + for (int i = 0, size = infos.size(); i < size; i++) { + if (infos.get(i).isEnabled()) { + return infos.get(i).packageName; + } + } + } catch (RemoteException e) { + } + return null; } @Override public boolean isAvailable() { + if (mOverlayService == null) return false; String[] themes = getAvailableThemes(); return themes != null && themes.length > 1; } + @VisibleForTesting String getCurrentTheme() { - return mUiModeManager.getTheme(); + return getTheme(); } @VisibleForTesting String[] getAvailableThemes() { - return mUiModeManager.getAvailableThemes(); + try { + List infos = mOverlayService.getOverlayInfosForTarget("android", + UserHandle.myUserId()); + String[] pkgs = new String[infos.size()]; + for (int i = 0, size = infos.size(); i < size; i++) { + pkgs[i] = infos.get(i).packageName; + } + return pkgs; + } catch (RemoteException e) { + } + return new String[0]; } - private String nullToDefault(String input) { - if (TextUtils.isEmpty(input)) { - return mContext.getString(R.string.default_theme); - } - return input; - } + public static class OverlayManager { + private final IOverlayManager mService; - private String defaultToNull(String input) { - if (mContext.getString(R.string.default_theme).equals(input)) { - return null; + public OverlayManager() { + mService = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); + } + + public void setEnabledExclusive(String pkg, boolean enabled, int userId) + throws RemoteException { + mService.setEnabledExclusive(pkg, enabled, userId); + } + + public List getOverlayInfosForTarget(String target, int userId) + throws RemoteException { + return mService.getOverlayInfosForTarget(target, userId); } - return input; } } diff --git a/tests/robotests/src/android/content/om/IOverlayManager.java b/tests/robotests/src/android/content/om/IOverlayManager.java new file mode 100644 index 00000000000..d4f6d10b9f7 --- /dev/null +++ b/tests/robotests/src/android/content/om/IOverlayManager.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.content.om; + +import android.os.IBinder; + +public class IOverlayManager { + + public static class Stub { + public static IOverlayManager asInterface(IBinder b) { + return null; + } + } +} diff --git a/tests/robotests/src/android/content/om/OverlayInfo.java b/tests/robotests/src/android/content/om/OverlayInfo.java new file mode 100644 index 00000000000..cfd3adc0505 --- /dev/null +++ b/tests/robotests/src/android/content/om/OverlayInfo.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.content.om; + +public class OverlayInfo { +} diff --git a/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java index fe3e7fd51c4..2c0f4a7c2fe 100644 --- a/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/ThemePreferenceControllerTest.java @@ -17,10 +17,15 @@ package com.android.settings.display; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.support.v7.preference.ListPreference; import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.display.ThemePreferenceController.OverlayManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,9 +33,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -40,24 +49,19 @@ public class ThemePreferenceControllerTest { private ListPreference mPreference; @Mock private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private ApplicationInfo mApplicationInfo; private ThemePreferenceController mController; @Before - public void setUp() { + public void setUp() throws NameNotFoundException { MockitoAnnotations.initMocks(this); - mController = spy(new ThemePreferenceController(mContext)); - } - - @Test - public void updateState_nullTheme_shouldSetSummaryToDefault() { - final String[] themes = {"Theme1", "Theme2"}; - doReturn(null).when(mController).getCurrentTheme(); - doReturn(themes).when(mController).getAvailableThemes(); - - mController.updateState(mPreference); - - verify(mPreference).setSummary(mContext.getString(R.string.default_theme)); + when(mPackageManager.getApplicationInfo(any(), anyInt())).thenReturn(mApplicationInfo); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + mController = spy(new ThemePreferenceController(mContext, mock(OverlayManager.class))); } @Test diff --git a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java index 231787e5e12..3137d59da65 100644 --- a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java @@ -14,95 +14,115 @@ * limitations under the License. */ -package com.android.settings.core; - -import static junit.framework.TestCase.assertNotNull; +package com.android.settings.display; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.UiModeManager; -import android.content.Context; import android.content.ContextWrapper; +import android.content.om.OverlayInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.v7.preference.ListPreference; -import com.android.settings.R; import com.android.settings.display.ThemePreferenceController; +import com.android.settings.display.ThemePreferenceController.OverlayManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + @SmallTest @RunWith(AndroidJUnit4.class) public class ThemePreferenceControllerTest { - private UiModeManager mMockUiModeManager; + private OverlayManager mMockOverlayManager; private ContextWrapper mContext; private ThemePreferenceController mPreferenceController; + private PackageManager mMockPackageManager; @Before public void setup() { - mMockUiModeManager = mock(UiModeManager.class); - mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) { + mMockOverlayManager = mock(OverlayManager.class); + mMockPackageManager = mock(PackageManager.class); + mContext = new ContextWrapper(InstrumentationRegistry.getContext()) { @Override - public Object getSystemService(String name) { - if (Context.UI_MODE_SERVICE.equals(name)) { - return mMockUiModeManager; - } - return super.getSystemService(name); + public PackageManager getPackageManager() { + return mMockPackageManager; } }; - mPreferenceController = new ThemePreferenceController(mContext); + mPreferenceController = new ThemePreferenceController(mContext, mMockOverlayManager); } @Test - public void testUpdateState() { - when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[] { - null, - "Theme1", - "Theme2", + public void testUpdateState() throws Exception { + OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android", + "", OverlayInfo.STATE_ENABLED, 0); + OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android", + "", 0, 0); + when(mMockPackageManager.getApplicationInfo(any(), anyInt())).thenAnswer(inv -> { + ApplicationInfo info = mock(ApplicationInfo.class); + if ("com.android.Theme1".equals(inv.getArguments()[0])) { + when(info.loadLabel(any())).thenReturn("Theme1"); + } else { + when(info.loadLabel(any())).thenReturn("Theme2"); + } + return info; }); - when(mMockUiModeManager.getTheme()).thenReturn("Theme1"); + when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn( + list(info1, info2)); ListPreference pref = mock(ListPreference.class); mPreferenceController.updateState(pref); ArgumentCaptor arg = ArgumentCaptor.forClass(String[].class); verify(pref).setEntries(arg.capture()); - String[] entries = arg.getValue(); - assertEquals(3, entries.length); - assertNotNull(entries[0]); - assertEquals("Theme1", entries[1]); - assertEquals("Theme2", entries[2]); + + CharSequence[] entries = arg.getValue(); + assertEquals(2, entries.length); + assertEquals("Theme1", entries[0]); + assertEquals("Theme2", entries[1]); verify(pref).setEntryValues(arg.capture()); - String[] entryValues = arg.getValue(); - assertEquals(3, entryValues.length); - assertNotNull(entryValues[0]); - assertEquals("Theme1", entryValues[1]); - assertEquals("Theme2", entryValues[2]); + CharSequence[] entryValues = arg.getValue(); + assertEquals("com.android.Theme1", entryValues[0]); + assertEquals("com.android.Theme2", entryValues[1]); - verify(pref).setValue(eq("Theme1")); + verify(pref).setValue(eq("com.android.Theme1")); } @Test - public void testAvailable_false() { - when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[1]); + public void testAvailable_false() throws Exception { + when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())) + .thenReturn(list(new OverlayInfo("", "", "", 0, 0))); assertFalse(mPreferenceController.isAvailable()); } @Test - public void testAvailable_true() { - when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[2]); + public void testAvailable_true() throws Exception { + when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())) + .thenReturn(list(new OverlayInfo("", "", "", 0, 0), + new OverlayInfo("", "", "", 0, 0))); assertTrue(mPreferenceController.isAvailable()); } + + private ArrayList list(OverlayInfo... infos) { + ArrayList list = new ArrayList<>(); + for (int i = 0; i < infos.length; i++) { + list.add(infos[i]); + } + return list; + } }