1 /*
2  * Copyright (C) 2018 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 
17 package com.android.settings.slices;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.doReturn;
22 import static org.mockito.Mockito.spy;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.provider.Settings;
28 import android.provider.SettingsSlicesContract;
29 import android.util.Pair;
30 
31 import androidx.core.graphics.drawable.IconCompat;
32 import androidx.slice.Slice;
33 import androidx.slice.SliceMetadata;
34 import androidx.slice.SliceProvider;
35 import androidx.slice.core.SliceAction;
36 import androidx.slice.widget.SliceLiveData;
37 
38 import com.android.settings.R;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.testutils.FakeInvalidSliderController;
41 import com.android.settings.testutils.FakeSliderController;
42 import com.android.settings.testutils.FakeToggleController;
43 import com.android.settings.testutils.FakeUnavailablePreferenceController;
44 import com.android.settings.testutils.SliceTester;
45 import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.robolectric.RobolectricTestRunner;
52 import org.robolectric.RuntimeEnvironment;
53 import org.robolectric.annotation.Config;
54 
55 @RunWith(RobolectricTestRunner.class)
56 @Config(shadows = ShadowRestrictedLockUtilsInternal.class)
57 public class SliceBuilderUtilsTest {
58 
59     private static final String KEY = "KEY";
60     private static final String TITLE = "title";
61     private static final String SUMMARY = "summary";
62     private static final String SCREEN_TITLE = "screen title";
63     private static final String KEYWORDS = "a, b, c";
64     private static final String FRAGMENT_NAME = "fragment name";
65     private static final String RESTRICTION = "no_brightness";
66     private static final int ICON = R.drawable.ic_settings_accent;
67     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
68     private static final Class TOGGLE_CONTROLLER = FakeToggleController.class;
69     private static final Class SLIDER_CONTROLLER = FakeSliderController.class;
70     private static final Class INVALID_SLIDER_CONTROLLER = FakeInvalidSliderController.class;
71     private static final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class;
72 
73     private Context mContext;
74 
75     @Before
setUp()76     public void setUp() {
77         mContext = RuntimeEnvironment.application;
78         // Set-up specs for SliceMetadata.
79         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
80     }
81 
82     @After
tearDown()83     public void tearDown() {
84         ShadowRestrictedLockUtilsInternal.reset();
85     }
86 
87     @Test
buildIntentSlice_returnsMatchingSlice()88     public void buildIntentSlice_returnsMatchingSlice() {
89         final SliceData sliceData = getMockData(CONTEXT_CONTROLLER, SliceData.SliceType.INTENT);
90         final Slice slice = SliceBuilderUtils.buildSlice(mContext, sliceData);
91 
92         SliceTester.testSettingsIntentSlice(mContext, slice, sliceData);
93     }
94 
95     @Test
buildToggleSlice_returnsMatchingSlice()96     public void buildToggleSlice_returnsMatchingSlice() {
97         final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH);
98 
99         final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData);
100         SliceTester.testSettingsToggleSlice(mContext, slice, mockData);
101     }
102 
103     @Test
buildSliderSlice_returnsMatchingSlice()104     public void buildSliderSlice_returnsMatchingSlice() {
105         final SliceData data = getMockData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER);
106 
107         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
108         SliceTester.testSettingsSliderSlice(mContext, slice, data);
109     }
110 
111     @Test
buildToggleSlice_withUserRestriction_shouldReturnToggleSlice()112     public void buildToggleSlice_withUserRestriction_shouldReturnToggleSlice() {
113         final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
114                 RESTRICTION);
115 
116         final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData);
117 
118         SliceTester.testSettingsToggleSlice(mContext, slice, mockData);
119     }
120 
121     @Test
buildToggleSlice_withUserRestrictionAndRestricted_shouldReturnIntentSlice()122     public void buildToggleSlice_withUserRestrictionAndRestricted_shouldReturnIntentSlice() {
123         final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
124                 RESTRICTION);
125         ShadowRestrictedLockUtilsInternal.setRestricted(true);
126 
127         final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData);
128 
129         SliceTester.testSettingsIntentSlice(mContext, slice, mockData);
130     }
131 
132     @Test
testGetPreferenceController_buildsMatchingController()133     public void testGetPreferenceController_buildsMatchingController() {
134         final BasePreferenceController controller =
135                 SliceBuilderUtils.getPreferenceController(mContext, getMockData());
136 
137         assertThat(controller).isInstanceOf(FakeToggleController.class);
138     }
139 
140     @Test
testGetPreferenceController_contextOnly_buildsMatchingController()141     public void testGetPreferenceController_contextOnly_buildsMatchingController() {
142         final BasePreferenceController controller =
143                 SliceBuilderUtils.getPreferenceController(mContext,
144                         getMockData(CONTEXT_CONTROLLER, 0));
145 
146         assertThat(controller).isInstanceOf(FakeContextOnlyPreferenceController.class);
147     }
148 
149     @Test
getDynamicSummary_allowDynamicSummary_returnsControllerSummary()150     public void getDynamicSummary_allowDynamicSummary_returnsControllerSummary() {
151         final SliceData data = getMockData();
152         final FakePreferenceController controller = spy(
153                 new FakePreferenceController(mContext, KEY));
154         final String controllerSummary = "new_Summary";
155         doReturn(controllerSummary).when(controller).getSummary();
156 
157         final CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
158 
159         assertThat(summary).isEqualTo(controllerSummary);
160     }
161 
162     @Test
getDynamicSummary_allowDynamicSummary_nullSummary_returnsNull()163     public void getDynamicSummary_allowDynamicSummary_nullSummary_returnsNull() {
164         final SliceData data = getMockData();
165         final FakePreferenceController controller = spy(
166                 new FakePreferenceController(mContext, KEY));
167 
168         doReturn(null).when(controller).getSummary();
169 
170         final CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
171 
172         assertThat(summary).isNull();
173     }
174 
175     @Test
getDynamicSummary_noScreenTitle_returnsPrefControllerSummary()176     public void getDynamicSummary_noScreenTitle_returnsPrefControllerSummary() {
177         final SliceData data = getMockData("", "");
178         final FakePreferenceController controller = spy(
179                 new FakePreferenceController(mContext, KEY));
180         final String controllerSummary = "new_Summary";
181         doReturn(controllerSummary).when(controller).getSummary();
182 
183         final CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
184 
185         assertThat(summary).isEqualTo(controllerSummary);
186     }
187 
188     @Test
getDynamicSummary_screenTitleMatchesTitle_returnsPrefControllerSummary()189     public void getDynamicSummary_screenTitleMatchesTitle_returnsPrefControllerSummary() {
190         final SliceData data = getMockData("", TITLE);
191         final FakePreferenceController controller = spy(
192                 new FakePreferenceController(mContext, KEY));
193         final String controllerSummary = "new_Summary";
194         doReturn(controllerSummary).when(controller).getSummary();
195 
196         final CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
197 
198         assertThat(summary).isEqualTo(controllerSummary);
199     }
200 
201     @Test
getDynamicSummary_emptyScreenTitle_emptyControllerSummary_returnsEmptyString()202     public void getDynamicSummary_emptyScreenTitle_emptyControllerSummary_returnsEmptyString() {
203         final SliceData data = getMockData(null, null);
204         final FakePreferenceController controller = new FakePreferenceController(mContext, KEY);
205         final CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
206 
207         assertThat(summary).isNull();
208     }
209 
210     @Test
getDynamicSummary_screenTitleAndControllerPlaceholder_returnsSliceEmptyString()211     public void getDynamicSummary_screenTitleAndControllerPlaceholder_returnsSliceEmptyString() {
212         final String summaryPlaceholder = mContext.getString(R.string.summary_placeholder);
213         final SliceData data = getMockData(summaryPlaceholder, summaryPlaceholder);
214         final FakePreferenceController controller = spy(
215                 new FakePreferenceController(mContext, KEY));
216         doReturn(summaryPlaceholder).when(controller).getSummary();
217 
218         CharSequence summary = SliceBuilderUtils.getSubtitleText(mContext, controller, data);
219 
220         assertThat(summary).isEqualTo(summaryPlaceholder);
221     }
222 
223     @Test
getPathData_splitsIntentUri()224     public void getPathData_splitsIntentUri() {
225         final Uri uri = new Uri.Builder()
226                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
227                 .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
228                 .appendPath(KEY)
229                 .build();
230 
231         final Pair<Boolean, String> pathPair = SliceBuilderUtils.getPathData(uri);
232 
233         assertThat(pathPair.first).isTrue();
234         assertThat(pathPair.second).isEqualTo(KEY);
235     }
236 
237     @Test
getPathData_splitsActionUri()238     public void getPathData_splitsActionUri() {
239         final Uri uri = new Uri.Builder()
240                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
241                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
242                 .appendPath(KEY)
243                 .build();
244 
245         final Pair<Boolean, String> pathPair = SliceBuilderUtils.getPathData(uri);
246 
247         assertThat(pathPair.first).isFalse();
248         assertThat(pathPair.second).isEqualTo(KEY);
249     }
250 
251     @Test
getPathData_noKey_returnsNull()252     public void getPathData_noKey_returnsNull() {
253         final Uri uri = new Uri.Builder()
254                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
255                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
256                 .build();
257 
258         final Pair<Boolean, String> pathPair = SliceBuilderUtils.getPathData(uri);
259 
260         assertThat(pathPair).isNull();
261     }
262 
263     @Test
getPathData_extraArg_returnsNull()264     public void getPathData_extraArg_returnsNull() {
265         final Uri uri = new Uri.Builder()
266                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
267                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
268                 .appendPath(KEY)
269                 .appendPath(KEY)
270                 .build();
271 
272         final Pair<Boolean, String> pathPair = SliceBuilderUtils.getPathData(uri);
273 
274         assertThat(pathPair.first).isFalse();
275         assertThat(pathPair.second).isEqualTo(KEY + "/" + KEY);
276     }
277 
278     @Test
testUnsupportedSlice_validTitleSummary()279     public void testUnsupportedSlice_validTitleSummary() {
280         final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
281                 SliceData.SliceType.SWITCH);
282         Settings.Global.putInt(mContext.getContentResolver(),
283                 FakeUnavailablePreferenceController.AVAILABILITY_KEY,
284                 BasePreferenceController.UNSUPPORTED_ON_DEVICE);
285 
286         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
287 
288         assertThat(slice).isNull();
289     }
290 
291     @Test
testDisabledForUserSlice_validTitleSummary()292     public void testDisabledForUserSlice_validTitleSummary() {
293         final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
294                 SliceData.SliceType.SWITCH);
295         Settings.Global.putInt(mContext.getContentResolver(),
296                 FakeUnavailablePreferenceController.AVAILABILITY_KEY,
297                 BasePreferenceController.DISABLED_FOR_USER);
298 
299         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
300 
301         assertThat(slice).isNull();
302     }
303 
304     @Test
testDisabledDependentSettingSlice_validTitleSummary()305     public void testDisabledDependentSettingSlice_validTitleSummary() {
306         final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
307                 SliceData.SliceType.INTENT);
308         Settings.Global.putInt(mContext.getContentResolver(),
309                 FakeUnavailablePreferenceController.AVAILABILITY_KEY,
310                 BasePreferenceController.DISABLED_DEPENDENT_SETTING);
311 
312         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
313 
314         SliceTester.testSettingsUnavailableSlice(mContext, slice, data);
315     }
316 
317     @Test
testConditionallyUnavailableSlice_sliceShouldBeNull()318     public void testConditionallyUnavailableSlice_sliceShouldBeNull() {
319         final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
320                 SliceData.SliceType.SWITCH);
321         Settings.Global.putInt(mContext.getContentResolver(),
322                 FakeUnavailablePreferenceController.AVAILABILITY_KEY,
323                 BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
324 
325         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
326 
327         assertThat(slice).isNull();
328     }
329 
330     @Test
testContentIntent_includesUniqueData()331     public void testContentIntent_includesUniqueData() {
332         final SliceData sliceData = getMockData();
333         final Uri expectedUri = new Uri.Builder().appendPath(sliceData.getKey()).build();
334 
335         final Intent intent = SliceBuilderUtils.getContentIntent(mContext, sliceData);
336         final Uri intentData = intent.getData();
337 
338         assertThat(intentData).isEqualTo(expectedUri);
339     }
340 
341     @Test
buildIntentSlice_noIconPassed_returnsSliceWithIcon()342     public void buildIntentSlice_noIconPassed_returnsSliceWithIcon() {
343         final int expectedIconResource = IconCompat.createWithResource(mContext,
344                 R.drawable.ic_settings_accent).toIcon().getResId();
345         final SliceData sliceData = getMockData(CONTEXT_CONTROLLER, SliceData.SliceType.INTENT,
346                 0x0);
347 
348         final Slice slice = SliceBuilderUtils.buildSlice(mContext, sliceData);
349 
350         final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
351         final SliceAction primaryAction = metadata.getPrimaryAction();
352         final int actualIconResource = primaryAction.getIcon().toIcon().getResId();
353         assertThat(actualIconResource).isEqualTo(expectedIconResource);
354     }
355 
356     @Test
buildDisabledDependentSlice_noIconPassed_returnsSliceWithIcon()357     public void buildDisabledDependentSlice_noIconPassed_returnsSliceWithIcon() {
358         final int expectedIconResource = IconCompat.createWithResource(mContext,
359                 R.drawable.ic_settings_accent).toIcon().getResId();
360         final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
361                 SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE, 0 /* icon */,
362                 null /* unavailableSliceSubtitle */);
363         Settings.Global.putInt(mContext.getContentResolver(),
364                 FakeUnavailablePreferenceController.AVAILABILITY_KEY,
365                 BasePreferenceController.DISABLED_DEPENDENT_SETTING);
366 
367         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
368 
369         final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
370         final SliceAction primaryAction = metadata.getPrimaryAction();
371         final int actualIconResource = primaryAction.getIcon().toIcon().getResId();
372         assertThat(actualIconResource).isEqualTo(expectedIconResource);
373     }
374 
375     @Test
buildToggleSlice_noIconPassed_returnsSliceWithIcon()376     public void buildToggleSlice_noIconPassed_returnsSliceWithIcon() {
377         final int expectedIconResource = IconCompat.createWithResource(mContext,
378                 R.drawable.ic_settings_accent).toIcon().getResId();
379         final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
380                 0x0);
381 
382         final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData);
383 
384         final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
385         final SliceAction primaryAction = metadata.getPrimaryAction();
386         final int actualIconResource = primaryAction.getIcon().toIcon().getResId();
387         assertThat(actualIconResource).isEqualTo(expectedIconResource);
388     }
389 
390     @Test
buildSliderSlice_noIconPassed_returnsSliceWithIcon()391     public void buildSliderSlice_noIconPassed_returnsSliceWithIcon() {
392         final int expectedIconResource = IconCompat.createWithResource(mContext,
393                 R.drawable.ic_settings_accent).toIcon().getResId();
394         final SliceData data = getMockData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER, 0x0);
395 
396         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
397 
398         final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
399         final SliceAction primaryAction = metadata.getPrimaryAction();
400         final int actualIconResource = primaryAction.getIcon().toIcon().getResId();
401         assertThat(actualIconResource).isEqualTo(expectedIconResource);
402     }
403 
404     @Test
buildSliderSlice_invalidSlider_returnNull()405     public void buildSliderSlice_invalidSlider_returnNull() {
406         final SliceData data = getMockData(INVALID_SLIDER_CONTROLLER, SliceData.SliceType.SLIDER,
407                 0x0 /* icon */);
408 
409         assertThat(SliceBuilderUtils.buildSlice(mContext, data)).isNull();
410     }
411 
412     @Test
getSafeIcon_replacesEmptyIconWithSettingsIcon()413     public void getSafeIcon_replacesEmptyIconWithSettingsIcon() {
414         final int settingsIcon = R.drawable.ic_settings_accent;
415         final int zeroIcon = 0x0;
416         final SliceData data = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
417                 zeroIcon);
418 
419         final IconCompat actualIcon = SliceBuilderUtils.getSafeIcon(mContext, data);
420 
421         final int actualIconResource = actualIcon.toIcon().getResId();
422         assertThat(actualIconResource).isNotEqualTo(zeroIcon);
423         assertThat(actualIconResource).isEqualTo(settingsIcon);
424     }
425 
426     @Test
getSafeIcon_invalidResource_shouldFallbackToSettingsIcon()427     public void getSafeIcon_invalidResource_shouldFallbackToSettingsIcon() {
428         final int settingsIcon = R.drawable.ic_settings_accent;
429         final int badIcon = 0x12345678;
430         final SliceData data = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
431                 badIcon);
432 
433         final IconCompat actualIcon = SliceBuilderUtils.getSafeIcon(mContext, data);
434 
435         final int actualIconResource = actualIcon.toIcon().getResId();
436         assertThat(actualIconResource).isEqualTo(settingsIcon);
437     }
438 
getMockData()439     private SliceData getMockData() {
440         return getMockData(TOGGLE_CONTROLLER, SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE,
441                 ICON, null /* unavailableSliceSubtitle */);
442     }
443 
getMockData(Class prefController, int sliceType, int icon)444     private SliceData getMockData(Class prefController, int sliceType, int icon) {
445         return getMockData(prefController, SUMMARY, sliceType, SCREEN_TITLE,
446                 icon, null /* unavailableSliceSubtitle */);
447     }
448 
getMockData(String summary, String screenTitle)449     private SliceData getMockData(String summary, String screenTitle) {
450         return getMockData(TOGGLE_CONTROLLER, summary, SliceData.SliceType.SWITCH, screenTitle,
451                 ICON, null /* unavailableSliceSubtitle */);
452     }
453 
getMockData(Class prefController, int sliceType)454     private SliceData getMockData(Class prefController, int sliceType) {
455         return getMockData(prefController, SUMMARY, sliceType, SCREEN_TITLE, ICON,
456                 null /* unavailableSliceSubtitle */);
457     }
458 
getMockData(Class prefController, int sliceType, String userRestriction)459     private SliceData getMockData(Class prefController, int sliceType, String userRestriction) {
460         return getMockData(prefController, SUMMARY, sliceType, SCREEN_TITLE, ICON,
461                 null /* unavailableSliceSubtitle */, userRestriction);
462     }
463 
getMockData(Class prefController, String summary, int sliceType, String screenTitle, int icon, String unavailableSliceSubtitle)464     private SliceData getMockData(Class prefController, String summary, int sliceType,
465             String screenTitle, int icon, String unavailableSliceSubtitle) {
466         return getMockData(prefController, summary, sliceType, screenTitle, icon,
467                 unavailableSliceSubtitle, null /* userRestriction */);
468     }
469 
getMockData(Class prefController, String summary, int sliceType, String screenTitle, int icon, String unavailableSliceSubtitle, String userRestriction)470     private SliceData getMockData(Class prefController, String summary, int sliceType,
471             String screenTitle, int icon, String unavailableSliceSubtitle, String userRestriction) {
472         return new SliceData.Builder()
473                 .setKey(KEY)
474                 .setTitle(TITLE)
475                 .setSummary(summary)
476                 .setScreenTitle(screenTitle)
477                 .setKeywords(KEYWORDS)
478                 .setIcon(icon)
479                 .setFragmentName(FRAGMENT_NAME)
480                 .setUri(URI)
481                 .setPreferenceControllerClassName(prefController.getName())
482                 .setSliceType(sliceType)
483                 .setUnavailableSliceSubtitle(unavailableSliceSubtitle)
484                 .setUserRestriction(userRestriction)
485                 .build();
486     }
487 }
488