1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.customization.model.theme;
17 
18 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
19 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
20 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
21 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
22 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
23 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
24 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
25 import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
26 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
27 import static com.android.customization.model.ResourceConstants.THEME_SETTING;
28 
29 import static junit.framework.Assert.assertFalse;
30 import static junit.framework.Assert.assertTrue;
31 import static junit.framework.TestCase.assertEquals;
32 
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.spy;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.provider.Settings;
39 
40 import androidx.annotation.Nullable;
41 import androidx.fragment.app.FragmentActivity;
42 
43 import com.android.customization.model.CustomizationManager.Callback;
44 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
45 import com.android.customization.model.theme.custom.CustomTheme;
46 import com.android.customization.module.ThemesUserEventLogger;
47 import com.android.customization.testutils.OverlayManagerMocks;
48 
49 import org.json.JSONObject;
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 import org.robolectric.Robolectric;
57 import org.robolectric.RobolectricTestRunner;
58 
59 @RunWith(RobolectricTestRunner.class)
60 public class ThemeManagerTest {
61 
62     @Mock OverlayManagerCompat mMockOm;
63     @Mock ThemesUserEventLogger mThemesUserEventLogger;
64     @Mock ThemeBundleProvider mThemeBundleProvider;
65     private OverlayManagerMocks mMockOmHelper;
66     private ThemeManager mThemeManager;
67     private FragmentActivity mActivity;
68 
69     @Before
setUp()70     public void setUp() {
71         MockitoAnnotations.initMocks(this);
72         FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).get();
73         mActivity = spy(activity);
74         mMockOmHelper = new OverlayManagerMocks();
75         mMockOmHelper.setUpMock(mMockOm);
76         mThemeManager = new ThemeManager(mThemeBundleProvider, activity,
77                 mMockOm, mThemesUserEventLogger);
78     }
79 
80     @After
cleanUp()81     public void cleanUp() {
82         mMockOmHelper.clearOverlays();
83     }
84 
85     @Test
apply_WithDefaultTheme_StoresEmptyJsonString()86     public void apply_WithDefaultTheme_StoresEmptyJsonString() {
87         mMockOmHelper.addOverlay("test.package.name_color", ANDROID_PACKAGE,
88                 OVERLAY_CATEGORY_COLOR, true, 0);
89         mMockOmHelper.addOverlay("test.package.name_font", ANDROID_PACKAGE,
90                 OVERLAY_CATEGORY_FONT, true, 0);
91         mMockOmHelper.addOverlay("test.package.name_shape", ANDROID_PACKAGE,
92                 OVERLAY_CATEGORY_SHAPE, true, 0);
93         mMockOmHelper.addOverlay("test.package.name_icon", ANDROID_PACKAGE,
94                 OVERLAY_CATEGORY_ICON_ANDROID, true, 0);
95         mMockOmHelper.addOverlay("test.package.name_settings", SETTINGS_PACKAGE,
96                 OVERLAY_CATEGORY_ICON_SETTINGS, true, 0);
97         mMockOmHelper.addOverlay("test.package.name_sysui", SYSUI_PACKAGE,
98                 OVERLAY_CATEGORY_ICON_SYSUI, true, 0);
99         mMockOmHelper.addOverlay("test.package.name_themepicker", mActivity.getPackageName(),
100                 OVERLAY_CATEGORY_ICON_SYSUI, true, 0);
101 
102         ThemeBundle defaultTheme = new ThemeBundle.Builder().asDefault().build(mActivity);
103 
104         applyTheme(defaultTheme);
105 
106         assertEquals("Secure Setting should be empty JSON string after applying default theme",
107                 new JSONObject().toString(),
108                 Settings.Secure.getString(mActivity.getContentResolver(), THEME_SETTING));
109     }
110 
111     @Test
apply_WithOverlayTheme_StoresSerializedPackagesWithTimestamp()112     public void apply_WithOverlayTheme_StoresSerializedPackagesWithTimestamp() {
113         ThemeBundle theme = getOverlayTheme();
114         final String serializedPackagesWithTimestamp = theme.getSerializedPackagesWithTimestamp();
115 
116         theme = spy(theme);
117         // Makes it return the fixed serializedPackagesWithTimestamp to test. Since we will get
118         // fresh time every time, it's hard to compare for testing.
119         when(theme.getSerializedPackagesWithTimestamp())
120                 .thenReturn(serializedPackagesWithTimestamp);
121 
122         applyTheme(theme);
123 
124         assertEquals("Secure Setting should be the overlay packages after applying theme",
125                 serializedPackagesWithTimestamp,
126                 Settings.Secure.getString(mActivity.getContentResolver(), THEME_SETTING));
127     }
128 
129     @Test
isAvailable_ThemeBundleProviderAndOverlayManagerAreAvailable_ReturnsTrue()130     public void isAvailable_ThemeBundleProviderAndOverlayManagerAreAvailable_ReturnsTrue() {
131         when(mThemeBundleProvider.isAvailable()).thenReturn(true);
132         when(mMockOm.isAvailable()).thenReturn(true);
133 
134         assertTrue(mThemeManager.isAvailable());
135     }
136 
137     @Test
isAvailable_ThemeBundleProviderOrOverlayManagerIsAvailable_ReturnsFalse()138     public void isAvailable_ThemeBundleProviderOrOverlayManagerIsAvailable_ReturnsFalse() {
139         when(mThemeBundleProvider.isAvailable()).thenReturn(false);
140         when(mMockOm.isAvailable()).thenReturn(true);
141         assertFalse(mThemeManager.isAvailable());
142 
143         when(mThemeBundleProvider.isAvailable()).thenReturn(true);
144         when(mMockOm.isAvailable()).thenReturn(false);
145         assertFalse(mThemeManager.isAvailable());
146     }
147 
148     @Test
fetchOptions_ThemeBundleProviderFetches()149     public void fetchOptions_ThemeBundleProviderFetches() {
150         OptionsFetchedListener listener = mock(OptionsFetchedListener.class);
151 
152         mThemeManager.fetchOptions(listener, false);
153 
154         verify(mThemeBundleProvider).fetch(listener, false);
155     }
156 
157     @Test
removeCustomTheme_ThemeBundleProviderRemovesCustomTheme()158     public void removeCustomTheme_ThemeBundleProviderRemovesCustomTheme() {
159         CustomTheme customTheme = mock(CustomTheme.class);
160         mThemeManager.removeCustomTheme(customTheme);
161 
162         verify(mThemeBundleProvider).removeCustomTheme(customTheme);
163     }
164 
165     @Test
findThemeByPackages_ThemeBundleProviderFindsEquivalent()166     public void findThemeByPackages_ThemeBundleProviderFindsEquivalent() {
167         CustomTheme theme = mock(CustomTheme.class);
168         mThemeManager.findThemeByPackages(theme);
169 
170         verify(mThemeBundleProvider).findEquivalent(theme);
171     }
172 
173     @Test
storeEmptyTheme_SettingsSecureStoresEmptyTheme()174     public void storeEmptyTheme_SettingsSecureStoresEmptyTheme() {
175         mThemeManager.storeEmptyTheme();
176 
177         assertEquals(
178                 new JSONObject().toString(),
179                 Settings.Secure.getString(mActivity.getContentResolver(), THEME_SETTING));
180     }
181 
182     @Test
getStoredOverlays_GetsFromSettingsSecureWithExpectedName()183     public void getStoredOverlays_GetsFromSettingsSecureWithExpectedName() {
184         ThemeBundle theme = getOverlayTheme();
185 
186         applyTheme(theme);
187 
188         assertEquals(
189                 Settings.Secure.getString(mActivity.getContentResolver(), THEME_SETTING),
190                 mThemeManager.getStoredOverlays());
191     }
192 
getOverlayTheme()193     private ThemeBundle getOverlayTheme() {
194         final String bundleColorPackage = "test.package.name_color";
195         final String bundleFontPackage = "test.package.name_font";
196         final String otherPackage = "other.package.name_font";
197 
198         mMockOmHelper.addOverlay(bundleColorPackage, ANDROID_PACKAGE,
199                 OVERLAY_CATEGORY_COLOR, false, 0);
200         mMockOmHelper.addOverlay(bundleFontPackage, ANDROID_PACKAGE,
201                 OVERLAY_CATEGORY_FONT, false, 0);
202         mMockOmHelper.addOverlay(otherPackage, ANDROID_PACKAGE,
203                 OVERLAY_CATEGORY_FONT, false, 0);
204 
205         return new ThemeBundle.Builder()
206                 .addOverlayPackage(OVERLAY_CATEGORY_COLOR, bundleColorPackage)
207                 .addOverlayPackage(OVERLAY_CATEGORY_FONT, bundleFontPackage)
208                 .build(mActivity);
209     }
210 
applyTheme(ThemeBundle theme)211     private void applyTheme(ThemeBundle theme) {
212         mThemeManager.apply(theme, new Callback() {
213             @Override
214             public void onSuccess() {
215             }
216 
217             @Override
218             public void onError(@Nullable Throwable throwable) {
219             }
220         });
221     }
222 }
223