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 
18 package com.android.settings.slices;
19 
20 import static android.content.ContentResolver.SCHEME_CONTENT;
21 import static android.content.pm.PackageManager.PERMISSION_DENIED;
22 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
23 import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
24 import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.anyString;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.doReturn;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.spy;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.when;
38 
39 import android.app.PendingIntent;
40 import android.app.slice.SliceManager;
41 import android.content.ContentResolver;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.res.Resources.Theme;
45 import android.net.Uri;
46 import android.os.StrictMode;
47 import android.provider.Settings;
48 import android.provider.SettingsSlicesContract;
49 import android.util.ArraySet;
50 import android.view.accessibility.AccessibilityManager;
51 
52 import androidx.slice.Slice;
53 import androidx.slice.SliceProvider;
54 import androidx.slice.widget.SliceLiveData;
55 
56 import com.android.settings.Utils;
57 import com.android.settings.testutils.DatabaseTestUtils;
58 import com.android.settings.testutils.FakeToggleController;
59 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
60 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
61 import com.android.settings.testutils.shadow.ShadowThreadUtils;
62 import com.android.settings.testutils.shadow.ShadowUserManager;
63 import com.android.settings.testutils.shadow.ShadowUtils;
64 import com.android.settings.wifi.slice.WifiScanWorker;
65 import com.android.settingslib.wifi.WifiTracker;
66 
67 import org.junit.After;
68 import org.junit.Before;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 import org.mockito.Mock;
72 import org.mockito.MockitoAnnotations;
73 import org.robolectric.Robolectric;
74 import org.robolectric.RobolectricTestRunner;
75 import org.robolectric.RuntimeEnvironment;
76 import org.robolectric.Shadows;
77 import org.robolectric.annotation.Config;
78 import org.robolectric.annotation.Implementation;
79 import org.robolectric.annotation.Implements;
80 import org.robolectric.annotation.Resetter;
81 import org.robolectric.shadow.api.Shadow;
82 import org.robolectric.shadows.ShadowAccessibilityManager;
83 import org.robolectric.shadows.ShadowBinder;
84 import org.robolectric.shadows.ShadowPackageManager;
85 
86 import java.util.ArrayList;
87 import java.util.Arrays;
88 import java.util.Collection;
89 import java.util.Collections;
90 import java.util.HashMap;
91 import java.util.HashSet;
92 import java.util.List;
93 import java.util.Set;
94 
95 /**
96  * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider)
97  */
98 @RunWith(RobolectricTestRunner.class)
99 @Config(shadows = {ShadowUserManager.class, ShadowUtils.class,
100         SlicesDatabaseAccessorTest.ShadowApplicationPackageManager.class,
101         ShadowBluetoothAdapter.class, ShadowLockPatternUtils.class,
102         SettingsSliceProviderTest.ShadowWifiScanWorker.class,
103         SettingsSliceProviderTest.ShadowTheme.class})
104 public class SettingsSliceProviderTest {
105 
106     private static final String KEY = "KEY";
107     private static final Uri INTENT_SLICE_URI =
108             new Uri.Builder().scheme(SCHEME_CONTENT)
109                     .authority(SettingsSliceProvider.SLICE_AUTHORITY)
110                     .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
111                     .appendPath(KEY)
112                     .build();
113     private static final Uri ACTION_SLICE_URI =
114             new Uri.Builder().scheme(SCHEME_CONTENT)
115                     .authority(SettingsSlicesContract.AUTHORITY)
116                     .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
117                     .appendPath(KEY)
118                     .build();
119 
120     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
121 
122     private Context mContext;
123     private SettingsSliceProvider mProvider;
124     private ShadowPackageManager mPackageManager;
125     @Mock
126     private SliceManager mManager;
127 
128     private static final List<Uri> SPECIAL_CASE_PLATFORM_URIS = Arrays.asList(
129             CustomSliceRegistry.WIFI_SLICE_URI,
130             CustomSliceRegistry.BLUETOOTH_URI,
131             CustomSliceRegistry.LOCATION_SLICE_URI
132     );
133 
134     private static final List<Uri> SPECIAL_CASE_OEM_URIS = Arrays.asList(
135             CustomSliceRegistry.ZEN_MODE_SLICE_URI,
136             CustomSliceRegistry.FLASHLIGHT_SLICE_URI,
137             CustomSliceRegistry.MOBILE_DATA_SLICE_URI,
138             CustomSliceRegistry.WIFI_CALLING_URI
139     );
140 
141     @Before
setUp()142     public void setUp() {
143         MockitoAnnotations.initMocks(this);
144         mContext = spy(RuntimeEnvironment.application);
145         // Register the fake a11y Service
146         ShadowAccessibilityManager shadowAccessibilityManager = Shadow.extract(
147                 RuntimeEnvironment.application.getSystemService(AccessibilityManager.class));
148         shadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>());
149 
150         mProvider = spy(new SettingsSliceProvider());
151         ShadowStrictMode.reset();
152         mProvider.mSliceWeakDataCache = new HashMap<>();
153         mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
154         when(mProvider.getContext()).thenReturn(mContext);
155 
156         SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
157 
158         doReturn(mManager).when(mContext).getSystemService(SliceManager.class);
159         when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList());
160 
161         mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
162 
163         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
164     }
165 
166     @After
cleanUp()167     public void cleanUp() {
168         ShadowThreadUtils.reset();
169         ShadowTheme.reset();
170         DatabaseTestUtils.clearDb(mContext);
171     }
172 
173     @Test
testInitialSliceReturned_emptySlice()174     public void testInitialSliceReturned_emptySlice() {
175         SliceTestUtils.insertSliceToDb(mContext, KEY);
176         Slice slice = mProvider.onBindSlice(INTENT_SLICE_URI);
177 
178         assertThat(slice.getUri()).isEqualTo(INTENT_SLICE_URI);
179         assertThat(slice.getItems()).isEmpty();
180     }
181 
182     @Test
testLoadSlice_returnsSliceFromAccessor()183     public void testLoadSlice_returnsSliceFromAccessor() {
184         SliceTestUtils.insertSliceToDb(mContext, KEY);
185 
186         mProvider.loadSlice(INTENT_SLICE_URI);
187         SliceData data = mProvider.mSliceWeakDataCache.get(INTENT_SLICE_URI);
188 
189         assertThat(data.getKey()).isEqualTo(KEY);
190         assertThat(data.getTitle()).isEqualTo(SliceTestUtils.FAKE_TITLE);
191     }
192 
193     @Test
loadSlice_registersIntentFilter()194     public void loadSlice_registersIntentFilter() {
195         SliceTestUtils.insertSliceToDb(mContext, KEY);
196 
197         mProvider.loadSlice(INTENT_SLICE_URI);
198 
199         verify(mProvider)
200                 .registerIntentToUri(eq(FakeToggleController.INTENT_FILTER), eq(INTENT_SLICE_URI));
201     }
202 
203     @Test
loadSlice_registersBackgroundListener()204     public void loadSlice_registersBackgroundListener() {
205         SliceTestUtils.insertSliceToDb(mContext, KEY);
206 
207         mProvider.loadSlice(INTENT_SLICE_URI);
208 
209         Robolectric.flushForegroundThreadScheduler();
210         Robolectric.flushBackgroundThreadScheduler();
211 
212         assertThat(mProvider.mPinnedWorkers.get(INTENT_SLICE_URI).getClass())
213                 .isEqualTo(FakeToggleController.TestWorker.class);
214     }
215 
216     @Test
testLoadSlice_cachedEntryRemovedOnBuild()217     public void testLoadSlice_cachedEntryRemovedOnBuild() {
218         SliceData data = getDummyData();
219         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
220         mProvider.onBindSlice(data.getUri());
221         SliceTestUtils.insertSliceToDb(mContext, data.getKey());
222 
223         SliceData cachedData = mProvider.mSliceWeakDataCache.get(data.getUri());
224 
225         assertThat(cachedData).isNull();
226     }
227 
228     @Test
onBindSlice_mainThread_shouldNotOverrideStrictMode()229     public void onBindSlice_mainThread_shouldNotOverrideStrictMode() {
230         ShadowThreadUtils.setIsMainThread(true);
231         final StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
232         SliceData data = getDummyData();
233         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
234         mProvider.onBindSlice(data.getUri());
235 
236         final StrictMode.ThreadPolicy newThreadPolicy = StrictMode.getThreadPolicy();
237 
238         assertThat(newThreadPolicy.toString()).isEqualTo(oldThreadPolicy.toString());
239     }
240 
241     @Test
242     @Config(shadows = ShadowStrictMode.class)
onBindSlice_backgroundThread_shouldOverrideStrictMode()243     public void onBindSlice_backgroundThread_shouldOverrideStrictMode() {
244         ShadowThreadUtils.setIsMainThread(false);
245 
246         SliceData data = getDummyData();
247         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
248         mProvider.onBindSlice(data.getUri());
249 
250         assertThat(ShadowStrictMode.isThreadPolicyOverridden()).isTrue();
251     }
252 
253     @Test
onBindSlice_requestsBlockedSlice_returnsNull()254     public void onBindSlice_requestsBlockedSlice_returnsNull() {
255         final String blockedKey = "blocked_key";
256         final Set<String> blockedSet = new ArraySet<>();
257         blockedSet.add(blockedKey);
258         doReturn(blockedSet).when(mProvider).getBlockedKeys();
259         final Uri blockedUri = new Uri.Builder()
260                 .scheme(ContentResolver.SCHEME_CONTENT)
261                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
262                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
263                 .appendPath(blockedKey)
264                 .build();
265 
266         final Slice slice = mProvider.onBindSlice(blockedUri);
267 
268         assertThat(slice).isNull();
269     }
270 
271     @Test
onBindSlice_nightModeChanged_shouldReloadTheme()272     public void onBindSlice_nightModeChanged_shouldReloadTheme() {
273         mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
274         final SliceData data = getDummyData();
275         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
276         mProvider.onBindSlice(data.getUri());
277 
278         mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
279         mProvider.onBindSlice(data.getUri());
280 
281         assertThat(ShadowTheme.isThemeRebased()).isTrue();
282     }
283 
284     @Test
onBindSlice_nightModeNotChanged_shouldNotReloadTheme()285     public void onBindSlice_nightModeNotChanged_shouldNotReloadTheme() {
286         mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
287         SliceData data = getDummyData();
288         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
289         mProvider.onBindSlice(data.getUri());
290 
291         mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
292         mProvider.onBindSlice(data.getUri());
293 
294         assertThat(ShadowTheme.isThemeRebased()).isFalse();
295     }
296 
297     @Test
getDescendantUris_fullActionUri_returnsSelf()298     public void getDescendantUris_fullActionUri_returnsSelf() {
299         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(ACTION_SLICE_URI);
300 
301         assertThat(descendants).containsExactly(ACTION_SLICE_URI);
302     }
303 
304     @Test
getDescendantUris_fullIntentUri_returnsSelf()305     public void getDescendantUris_fullIntentUri_returnsSelf() {
306 
307         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(ACTION_SLICE_URI);
308 
309         assertThat(descendants).containsExactly(ACTION_SLICE_URI);
310     }
311 
312     @Test
getDescendantUris_wrongPath_returnsEmpty()313     public void getDescendantUris_wrongPath_returnsEmpty() {
314         final Uri uri = new Uri.Builder()
315                 .scheme(SCHEME_CONTENT)
316                 .authority(SettingsSlicesContract.AUTHORITY)
317                 .appendPath("invalid_path")
318                 .build();
319 
320         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
321 
322         assertThat(descendants).isEmpty();
323     }
324 
325     @Test
getDescendantUris_invalidPath_returnsEmpty()326     public void getDescendantUris_invalidPath_returnsEmpty() {
327         final String key = "platform_key";
328         SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
329                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
330         final Uri uri = new Uri.Builder()
331                 .scheme(SCHEME_CONTENT)
332                 .authority(SettingsSlicesContract.AUTHORITY)
333                 .appendPath("invalid")
334                 .build();
335 
336         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
337         descendants.removeAll(SPECIAL_CASE_OEM_URIS);
338 
339         assertThat(descendants).isEmpty();
340     }
341 
342     @Test
getDescendantUris_platformSlice_doesNotReturnOEMSlice()343     public void getDescendantUris_platformSlice_doesNotReturnOEMSlice() {
344         SliceTestUtils.insertSliceToDb(mContext, "oem_key", false /* isPlatformSlice */,
345                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
346         final Uri uri = new Uri.Builder()
347                 .scheme(SCHEME_CONTENT)
348                 .authority(SettingsSlicesContract.AUTHORITY)
349                 .build();
350 
351         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
352         descendants.removeAll(SPECIAL_CASE_PLATFORM_URIS);
353 
354         assertThat(descendants).isEmpty();
355     }
356 
357     @Test
getDescendantUris_oemSlice_doesNotReturnPlatformSlice()358     public void getDescendantUris_oemSlice_doesNotReturnPlatformSlice() {
359         SliceTestUtils.insertSliceToDb(mContext, "platform_key", true /* isPlatformSlice */,
360                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
361         final Uri uri = new Uri.Builder()
362                 .scheme(SCHEME_CONTENT)
363                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
364                 .build();
365 
366         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
367         descendants.removeAll(SPECIAL_CASE_OEM_URIS);
368 
369         assertThat(descendants).isEmpty();
370     }
371 
372     @Test
getDescendantUris_oemSlice_returnsOEMUriDescendant()373     public void getDescendantUris_oemSlice_returnsOEMUriDescendant() {
374         final String key = "oem_key";
375         SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
376                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
377         final Uri uri = new Uri.Builder()
378                 .scheme(SCHEME_CONTENT)
379                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
380                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
381                 .build();
382         final Collection<Uri> expectedUris = new HashSet<>();
383         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
384         expectedUris.add(new Uri.Builder()
385                 .scheme(SCHEME_CONTENT)
386                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
387                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
388                 .appendPath(key)
389                 .build());
390 
391         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
392 
393         assertThat(descendants).containsExactlyElementsIn(expectedUris);
394     }
395 
396     @Test
getDescendantUris_oemSliceNoPath_returnsOEMUriDescendant()397     public void getDescendantUris_oemSliceNoPath_returnsOEMUriDescendant() {
398         final String key = "oem_key";
399         SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
400                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
401         final Uri uri = new Uri.Builder()
402                 .scheme(SCHEME_CONTENT)
403                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
404                 .build();
405         final Collection<Uri> expectedUris = new HashSet<>();
406         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
407         expectedUris.add(new Uri.Builder()
408                 .scheme(SCHEME_CONTENT)
409                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
410                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
411                 .appendPath(key)
412                 .build());
413 
414         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
415 
416         assertThat(descendants).containsExactlyElementsIn(expectedUris);
417     }
418 
419     @Test
getDescendantUris_oemSliceNoPath_notContainPrivateUri()420     public void getDescendantUris_oemSliceNoPath_notContainPrivateUri() {
421         final String key = "oem_key";
422         SliceTestUtils.insertSliceToDb(mContext, key, false /* isPlatformSlice */,
423                 null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
424         final Uri uri = new Uri.Builder()
425                 .scheme(SCHEME_CONTENT)
426                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
427                 .build();
428         final Uri expectedUri = new Uri.Builder()
429                 .scheme(SCHEME_CONTENT)
430                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
431                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
432                 .appendPath(key)
433                 .build();
434 
435         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
436 
437         assertThat(descendants).doesNotContain(expectedUri);
438     }
439 
440     @Test
getDescendantUris_platformSlice_returnsPlatformUriDescendant()441     public void getDescendantUris_platformSlice_returnsPlatformUriDescendant() {
442         final String key = "platform_key";
443         SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
444                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
445         final Uri uri = new Uri.Builder()
446                 .scheme(SCHEME_CONTENT)
447                 .authority(SettingsSlicesContract.AUTHORITY)
448                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
449                 .build();
450         final Collection<Uri> expectedUris = new HashSet<>();
451         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
452         expectedUris.add(new Uri.Builder()
453                 .scheme(SCHEME_CONTENT)
454                 .authority(SettingsSlicesContract.AUTHORITY)
455                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
456                 .appendPath(key)
457                 .build());
458 
459         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
460 
461         assertThat(descendants).containsExactlyElementsIn(expectedUris);
462     }
463 
464     @Test
getDescendantUris_platformSliceNoPath_returnsPlatformUriDescendant()465     public void getDescendantUris_platformSliceNoPath_returnsPlatformUriDescendant() {
466         final String key = "platform_key";
467         SliceTestUtils.insertSliceToDb(mContext, key, true /* isPlatformSlice */,
468                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
469         final Uri uri = new Uri.Builder()
470                 .scheme(SCHEME_CONTENT)
471                 .authority(SettingsSlicesContract.AUTHORITY)
472                 .build();
473         final Collection<Uri> expectedUris = new HashSet<>();
474         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
475         expectedUris.add(new Uri.Builder()
476                 .scheme(SCHEME_CONTENT)
477                 .authority(SettingsSlicesContract.AUTHORITY)
478                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
479                 .appendPath(key)
480                 .build());
481 
482         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
483 
484         assertThat(descendants).containsExactlyElementsIn(expectedUris);
485     }
486 
487     @Test
getDescendantUris_noAuthorityNorPath_returnsAllUris()488     public void getDescendantUris_noAuthorityNorPath_returnsAllUris() {
489         final String platformKey = "platform_key";
490         final String oemKey = "oemKey";
491         SliceTestUtils.insertSliceToDb(mContext, platformKey, true /* isPlatformSlice */,
492                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
493         SliceTestUtils.insertSliceToDb(mContext, oemKey, false /* isPlatformSlice */,
494                 null /* customizedUnavailableSliceSubtitle */, true /* isPublicSlice */);
495         final Uri uri = new Uri.Builder()
496                 .scheme(SCHEME_CONTENT)
497                 .build();
498         final Collection<Uri> expectedUris = new HashSet<>();
499         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
500         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
501         expectedUris.add(new Uri.Builder()
502                 .scheme(SCHEME_CONTENT)
503                 .authority(SettingsSlicesContract.AUTHORITY)
504                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
505                 .appendPath(platformKey)
506                 .build());
507         expectedUris.add(new Uri.Builder()
508                 .scheme(SCHEME_CONTENT)
509                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
510                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
511                 .appendPath(oemKey)
512                 .build());
513 
514         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
515 
516         assertThat(descendants).containsExactlyElementsIn(expectedUris);
517     }
518 
519     @Test
520     @Config(qualifiers = "mcc999")
getDescendantUris_privateSlicesNeeded_containsPrivateSliceUri()521     public void getDescendantUris_privateSlicesNeeded_containsPrivateSliceUri() {
522         final String privateKey = "test_private";
523         final Uri specialUri = Uri.parse("content://com.android.settings.slices/test");
524         doReturn(true).when(mProvider).isPrivateSlicesNeeded(specialUri);
525         SliceTestUtils.insertSliceToDb(mContext, privateKey /* key */, false /* isPlatformSlice */,
526                 null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
527         final Collection<Uri> expectedUris = new HashSet<>();
528         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
529         expectedUris.add(new Uri.Builder()
530                 .scheme(SCHEME_CONTENT)
531                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
532                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
533                 .appendPath(privateKey)
534                 .build());
535 
536         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(specialUri);
537 
538         assertThat(descendants).containsExactlyElementsIn(expectedUris);
539     }
540 
541     @Test
542     @Config(qualifiers = "mcc999")
getDescendantUris_privateSlicesNotNeeded_notContainPrivateSliceUri()543     public void getDescendantUris_privateSlicesNotNeeded_notContainPrivateSliceUri() {
544         final Uri specialUri = Uri.parse("content://com.android.settings.slices/test");
545         doReturn(false).when(mProvider).isPrivateSlicesNeeded(specialUri);
546         SliceTestUtils.insertSliceToDb(mContext,
547                 "test_private" /* key */, false /* isPlatformSlice */,
548                 null /* customizedUnavailableSliceSubtitle */, false /* isPublicSlice */);
549         final Uri expectedUri = new Uri.Builder()
550                 .scheme(SCHEME_CONTENT)
551                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
552                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
553                 .appendPath("test_private")
554                 .build();
555 
556         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(specialUri);
557 
558         assertThat(descendants).doesNotContain(expectedUri);
559     }
560 
561     @Test
onCreatePermissionRequest_returnsSettingIntent()562     public void onCreatePermissionRequest_returnsSettingIntent() {
563         final PendingIntent pendingIntent = mProvider.onCreatePermissionRequest(
564                 CustomSliceRegistry.FLASHLIGHT_SLICE_URI, "com.android.whaaaat");
565         final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS)
566                 .setPackage(Utils.SETTINGS_PACKAGE_NAME);
567         PendingIntent settingsPendingIntent =
568                 PendingIntent.getActivity(mContext, 0, settingsIntent, 0);
569 
570         assertThat(pendingIntent).isEqualTo(settingsPendingIntent);
571     }
572 
573     @Test
bindSlice_wifiSlice_returnsWifiSlice()574     public void bindSlice_wifiSlice_returnsWifiSlice() {
575         final Slice wifiSlice = mProvider.onBindSlice(CustomSliceRegistry.WIFI_SLICE_URI);
576 
577         assertThat(wifiSlice.getUri()).isEqualTo(CustomSliceRegistry.WIFI_SLICE_URI);
578     }
579 
580     @Test
bindSlice_flashlightSlice_returnsFlashlightSlice()581     public void bindSlice_flashlightSlice_returnsFlashlightSlice() {
582         Settings.Secure.putInt(
583                 mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
584 
585         final Slice flashlightSlice = mProvider.onBindSlice(
586                 CustomSliceRegistry.FLASHLIGHT_SLICE_URI);
587 
588         assertThat(flashlightSlice.getUri()).isEqualTo(CustomSliceRegistry.FLASHLIGHT_SLICE_URI);
589     }
590 
591     @Test
onSlicePinned_noIntentRegistered_specialCaseUri_doesNotCrash()592     public void onSlicePinned_noIntentRegistered_specialCaseUri_doesNotCrash() {
593         final Uri uri = new Uri.Builder()
594                 .scheme(ContentResolver.SCHEME_CONTENT)
595                 .authority(SettingsSlicesContract.AUTHORITY)
596                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
597                 .appendPath(SettingsSlicesContract.KEY_LOCATION)
598                 .build();
599 
600         mProvider.onSlicePinned(uri);
601     }
602 
603     @Test
onSlicePinned_backgroundWorker_started()604     public void onSlicePinned_backgroundWorker_started() {
605         mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
606 
607         verify(ShadowWifiScanWorker.getWifiTracker()).onStart();
608     }
609 
610     @Test
onSlicePinned_backgroundWorker_stopped()611     public void onSlicePinned_backgroundWorker_stopped() {
612         mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
613         mProvider.onSliceUnpinned(CustomSliceRegistry.WIFI_SLICE_URI);
614 
615         verify(ShadowWifiScanWorker.getWifiTracker()).onStop();
616     }
617 
618     @Test
shutdown_backgroundWorker_closed()619     public void shutdown_backgroundWorker_closed() {
620         mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
621         mProvider.shutdown();
622 
623         verify(ShadowWifiScanWorker.getWifiTracker()).onDestroy();
624     }
625 
626     @Test
627     @Config(qualifiers = "mcc998")
grantWhitelistedPackagePermissions_noWhitelist_shouldNotGrant()628     public void grantWhitelistedPackagePermissions_noWhitelist_shouldNotGrant() {
629         final List<Uri> uris = new ArrayList<>();
630         uris.add(Uri.parse("content://settings/slice"));
631 
632         SettingsSliceProvider.grantWhitelistedPackagePermissions(mContext, uris);
633 
634         verify(mManager, never()).grantSlicePermission(anyString(), any(Uri.class));
635     }
636 
637     @Test
638     @Config(qualifiers = "mcc999")
grantWhitelistedPackagePermissions_hasPackageWhitelist_shouldGrant()639     public void grantWhitelistedPackagePermissions_hasPackageWhitelist_shouldGrant() {
640         final List<Uri> uris = new ArrayList<>();
641         uris.add(Uri.parse("content://settings/slice"));
642 
643         SettingsSliceProvider.grantWhitelistedPackagePermissions(mContext, uris);
644 
645         verify(mManager)
646                 .grantSlicePermission("com.android.settings.slice_whitelist_package", uris.get(0));
647     }
648 
649     @Test
650     @Config(qualifiers = "mcc999")
isPrivateSlicesNeeded_incorrectUri_returnFalse()651     public void isPrivateSlicesNeeded_incorrectUri_returnFalse() {
652         final Uri uri = Uri.parse("content://com.android.settings.slices/test123");
653 
654         assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
655     }
656 
657     @Test
isPrivateSlicesNeeded_noUri_returnFalse()658     public void isPrivateSlicesNeeded_noUri_returnFalse() {
659         final Uri uri = Uri.parse("content://com.android.settings.slices/test");
660 
661         assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
662     }
663 
664     @Test
665     @Config(qualifiers = "mcc999")
isPrivateSlicesNeeded_correctUriWithPermissionAndIsSI_returnTrue()666     public void isPrivateSlicesNeeded_correctUriWithPermissionAndIsSI_returnTrue() {
667         final Uri uri = Uri.parse("content://com.android.settings.slices/test");
668         ShadowBinder.setCallingUid(123);
669         doReturn(PERMISSION_GRANTED)
670                 .when(mContext).checkPermission(anyString(), anyInt(), anyInt());
671         mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.intelligence"});
672 
673         assertThat(mProvider.isPrivateSlicesNeeded(uri)).isTrue();
674     }
675 
676     @Test
677     @Config(qualifiers = "mcc999")
isPrivateSlicesNeeded_correctUriWithPermissionNotSI_returnFalse()678     public void isPrivateSlicesNeeded_correctUriWithPermissionNotSI_returnFalse() {
679         final Uri uri = Uri.parse("content://com.android.settings.slices/test");
680         ShadowBinder.setCallingUid(123);
681         doReturn(PERMISSION_GRANTED)
682                 .when(mContext).checkPermission(anyString(), anyInt(), anyInt());
683         mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.test"});
684 
685         assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
686     }
687 
688     @Test
689     @Config(qualifiers = "mcc999")
isPrivateSlicesNeeded_correctUriNoPermission_returnFalse()690     public void isPrivateSlicesNeeded_correctUriNoPermission_returnFalse() {
691         final Uri uri = Uri.parse("content://com.android.settings.slices/test");
692         ShadowBinder.setCallingUid(123);
693         doReturn(PERMISSION_DENIED).when(mContext).checkPermission(anyString(), anyInt(), anyInt());
694         mPackageManager.setPackagesForUid(123, new String[]{"com.android.settings.intelligence"});
695 
696         assertThat(mProvider.isPrivateSlicesNeeded(uri)).isFalse();
697     }
698 
getDummyData()699     private static SliceData getDummyData() {
700         return new SliceData.Builder()
701                 .setKey(KEY)
702                 .setUri(URI)
703                 .setTitle(SliceTestUtils.FAKE_TITLE)
704                 .setSummary(SliceTestUtils.FAKE_SUMMARY)
705                 .setScreenTitle(SliceTestUtils.FAKE_SCREEN_TITLE)
706                 .setIcon(SliceTestUtils.FAKE_ICON)
707                 .setFragmentName(SliceTestUtils.FAKE_FRAGMENT_NAME)
708                 .setPreferenceControllerClassName(SliceTestUtils.FAKE_CONTROLLER_NAME)
709                 .build();
710     }
711 
712     @Implements(WifiScanWorker.class)
713     public static class ShadowWifiScanWorker {
714         private static WifiTracker mWifiTracker;
715 
716         @Implementation
onSlicePinned()717         protected void onSlicePinned() {
718             mWifiTracker = mock(WifiTracker.class);
719             mWifiTracker.onStart();
720         }
721 
722         @Implementation
onSliceUnpinned()723         protected void onSliceUnpinned() {
724             mWifiTracker.onStop();
725         }
726 
727         @Implementation
close()728         protected void close() {
729             mWifiTracker.onDestroy();
730         }
731 
getWifiTracker()732         static WifiTracker getWifiTracker() {
733             return mWifiTracker;
734         }
735     }
736 
737     @Implements(value = StrictMode.class)
738     public static class ShadowStrictMode {
739 
740         private static int sSetThreadPolicyCount;
741 
742         @Resetter
reset()743         public static void reset() {
744             sSetThreadPolicyCount = 0;
745         }
746 
747         @Implementation
setThreadPolicy(final StrictMode.ThreadPolicy policy)748         protected static void setThreadPolicy(final StrictMode.ThreadPolicy policy) {
749             sSetThreadPolicyCount++;
750         }
751 
isThreadPolicyOverridden()752         private static boolean isThreadPolicyOverridden() {
753             return sSetThreadPolicyCount != 0;
754         }
755     }
756 
757     @Implements(Theme.class)
758     public static class ShadowTheme {
759         private static boolean sThemeRebased;
760 
761         @Resetter
reset()762         public static void reset() {
763             sThemeRebased = false;
764         }
765 
766         @Implementation
rebase()767         public void rebase() {
768             sThemeRebased = true;
769         }
770 
isThemeRebased()771         static boolean isThemeRebased() {
772             return sThemeRebased;
773         }
774     }
775 }
776