• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.google.common.truth.Truth.assertThat;
22 import static org.mockito.ArgumentMatchers.eq;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.app.slice.SliceManager;
30 import android.content.ContentResolver;
31 import android.content.ContentValues;
32 import android.content.Context;
33 import android.database.sqlite.SQLiteDatabase;
34 import android.net.Uri;
35 import android.os.StrictMode;
36 import android.provider.SettingsSlicesContract;
37 import android.util.ArraySet;
38 
39 import com.android.settings.bluetooth.BluetoothSliceBuilder;
40 import com.android.settings.location.LocationSliceBuilder;
41 import com.android.settings.notification.ZenModeSliceBuilder;
42 import com.android.settings.testutils.DatabaseTestUtils;
43 import com.android.settings.testutils.FakeToggleController;
44 import com.android.settings.testutils.SettingsRobolectricTestRunner;
45 import com.android.settings.testutils.shadow.ShadowThreadUtils;
46 import com.android.settings.wifi.WifiSliceBuilder;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.robolectric.RuntimeEnvironment;
53 import org.robolectric.annotation.Config;
54 import org.robolectric.annotation.Implementation;
55 import org.robolectric.annotation.Implements;
56 import org.robolectric.annotation.Resetter;
57 
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 
66 import androidx.slice.Slice;
67 
68 /**
69  * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider)
70  */
71 @RunWith(SettingsRobolectricTestRunner.class)
72 @Config(shadows = ShadowThreadUtils.class)
73 public class SettingsSliceProviderTest {
74 
75     private static final String KEY = "KEY";
76     private static final String INTENT_PATH =
77             SettingsSlicesContract.PATH_SETTING_INTENT + "/" + KEY;
78     private static final String TITLE = "title";
79     private static final String SUMMARY = "summary";
80     private static final String SCREEN_TITLE = "screen title";
81     private static final String FRAGMENT_NAME = "fragment name";
82     private static final int ICON = 1234; // I declare a thumb war
83     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
84     private static final String PREF_CONTROLLER = FakeToggleController.class.getName();
85 
86     private Context mContext;
87     private SettingsSliceProvider mProvider;
88     private SQLiteDatabase mDb;
89     private SliceManager mManager;
90 
91     private static final List<Uri> SPECIAL_CASE_PLATFORM_URIS = Arrays.asList(
92             WifiSliceBuilder.WIFI_URI,
93             BluetoothSliceBuilder.BLUETOOTH_URI,
94             LocationSliceBuilder.LOCATION_URI
95     );
96 
97     private static final List<Uri> SPECIAL_CASE_OEM_URIS = Arrays.asList(
98             ZenModeSliceBuilder.ZEN_MODE_URI
99     );
100 
101     @Before
setUp()102     public void setUp() {
103         mContext = spy(RuntimeEnvironment.application);
104         mProvider = spy(new SettingsSliceProvider());
105         ShadowStrictMode.reset();
106         mProvider.mSliceWeakDataCache = new HashMap<>();
107         mProvider.mSliceDataCache = new HashMap<>();
108         mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
109         when(mProvider.getContext()).thenReturn(mContext);
110 
111         mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
112         SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
113         mManager = mock(SliceManager.class);
114         when(mContext.getSystemService(SliceManager.class)).thenReturn(mManager);
115         when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList());
116     }
117 
118     @After
cleanUp()119     public void cleanUp() {
120         ShadowThreadUtils.reset();
121         DatabaseTestUtils.clearDb(mContext);
122     }
123 
124     @Test
testInitialSliceReturned_emptySlice()125     public void testInitialSliceReturned_emptySlice() {
126         insertSpecialCase(KEY);
127         final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
128         Slice slice = mProvider.onBindSlice(uri);
129 
130         assertThat(slice.getUri()).isEqualTo(uri);
131         assertThat(slice.getItems()).isEmpty();
132     }
133 
134     @Test
testLoadSlice_returnsSliceFromAccessor()135     public void testLoadSlice_returnsSliceFromAccessor() {
136         insertSpecialCase(KEY);
137         final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
138 
139         mProvider.loadSlice(uri);
140         SliceData data = mProvider.mSliceWeakDataCache.get(uri);
141 
142         assertThat(data.getKey()).isEqualTo(KEY);
143         assertThat(data.getTitle()).isEqualTo(TITLE);
144     }
145 
146     @Test
loadSlice_registersIntentFilter()147     public void loadSlice_registersIntentFilter() {
148         insertSpecialCase(KEY);
149         final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
150 
151         mProvider.loadSlice(uri);
152 
153         verify(mProvider).registerIntentToUri(eq(FakeToggleController.INTENT_FILTER), eq(uri));
154     }
155 
156     @Test
testLoadSlice_doesNotCacheWithoutPin()157     public void testLoadSlice_doesNotCacheWithoutPin() {
158         insertSpecialCase(KEY);
159         final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
160 
161         mProvider.loadSlice(uri);
162         SliceData data = mProvider.mSliceDataCache.get(uri);
163 
164         assertThat(data).isNull();
165     }
166 
167     @Test
testLoadSlice_cachesWithPin()168     public void testLoadSlice_cachesWithPin() {
169         insertSpecialCase(KEY);
170         final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
171         when(mManager.getPinnedSlices()).thenReturn(Arrays.asList(uri));
172 
173         mProvider.loadSlice(uri);
174         SliceData data = mProvider.mSliceDataCache.get(uri);
175 
176         assertThat(data.getKey()).isEqualTo(KEY);
177         assertThat(data.getTitle()).isEqualTo(TITLE);
178     }
179 
180     @Test
testLoadSlice_cachedEntryRemovedOnBuild()181     public void testLoadSlice_cachedEntryRemovedOnBuild() {
182         SliceData data = getDummyData();
183         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
184         mProvider.onBindSlice(data.getUri());
185         insertSpecialCase(data.getKey());
186 
187         SliceData cachedData = mProvider.mSliceWeakDataCache.get(data.getUri());
188 
189         assertThat(cachedData).isNull();
190     }
191 
192     @Test
onBindSlice_mainThread_shouldNotOverrideStrictMode()193     public void onBindSlice_mainThread_shouldNotOverrideStrictMode() {
194         ShadowThreadUtils.setIsMainThread(true);
195         final StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
196         SliceData data = getDummyData();
197         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
198         mProvider.onBindSlice(data.getUri());
199 
200         final StrictMode.ThreadPolicy newThreadPolicy = StrictMode.getThreadPolicy();
201 
202         assertThat(newThreadPolicy.toString()).isEqualTo(oldThreadPolicy.toString());
203     }
204 
205     @Test
206     @Config(shadows = ShadowStrictMode.class)
onBindSlice_backgroundThread_shouldOverrideStrictMode()207     public void onBindSlice_backgroundThread_shouldOverrideStrictMode() {
208         ShadowThreadUtils.setIsMainThread(false);
209 
210         SliceData data = getDummyData();
211         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
212         mProvider.onBindSlice(data.getUri());
213 
214         assertThat(ShadowStrictMode.isThreadPolicyOverridden()).isTrue();
215     }
216 
217     @Test
onBindSlice_requestsBlockedSlice_retunsNull()218     public void onBindSlice_requestsBlockedSlice_retunsNull() {
219         final String blockedKey = "blocked_key";
220         final Set<String> blockedSet = new ArraySet<>();
221         blockedSet.add(blockedKey);
222         doReturn(blockedSet).when(mProvider).getBlockedKeys();
223         final Uri blockedUri = new Uri.Builder()
224                 .scheme(ContentResolver.SCHEME_CONTENT)
225                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
226                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
227                 .appendPath(blockedKey)
228                 .build();
229 
230         final Slice slice = mProvider.onBindSlice(blockedUri);
231 
232         assertThat(slice).isNull();
233     }
234 
235     @Test
testLoadSlice_cachedEntryRemovedOnUnpin()236     public void testLoadSlice_cachedEntryRemovedOnUnpin() {
237         SliceData data = getDummyData();
238         mProvider.mSliceDataCache.put(data.getUri(), data);
239         mProvider.onSliceUnpinned(data.getUri());
240         insertSpecialCase(data.getKey());
241 
242         SliceData cachedData = mProvider.mSliceWeakDataCache.get(data.getUri());
243 
244         assertThat(cachedData).isNull();
245     }
246 
247     @Test
getDescendantUris_fullActionUri_returnsSelf()248     public void getDescendantUris_fullActionUri_returnsSelf() {
249         final Uri uri = SliceBuilderUtils.getUri(
250                 SettingsSlicesContract.PATH_SETTING_ACTION + "/key", true);
251 
252         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
253 
254         assertThat(descendants).containsExactly(uri);
255     }
256 
257     @Test
getDescendantUris_fullIntentUri_returnsSelf()258     public void getDescendantUris_fullIntentUri_returnsSelf() {
259         final Uri uri = SliceBuilderUtils.getUri(
260                 SettingsSlicesContract.PATH_SETTING_ACTION + "/key", true);
261 
262         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
263 
264         assertThat(descendants).containsExactly(uri);
265     }
266 
267     @Test
getDescendantUris_wrongPath_returnsEmpty()268     public void getDescendantUris_wrongPath_returnsEmpty() {
269         final Uri uri = SliceBuilderUtils.getUri("invalid_path", true);
270 
271         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
272 
273         assertThat(descendants).isEmpty();
274     }
275 
276     @Test
getDescendantUris_invalidPath_returnsEmpty()277     public void getDescendantUris_invalidPath_returnsEmpty() {
278         final String key = "platform_key";
279         insertSpecialCase(key, true /* isPlatformSlice */);
280         final Uri uri = new Uri.Builder()
281                 .scheme(SCHEME_CONTENT)
282                 .authority(SettingsSlicesContract.AUTHORITY)
283                 .appendPath("invalid")
284                 .build();
285 
286         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
287         descendants.removeAll(SPECIAL_CASE_OEM_URIS);
288 
289         assertThat(descendants).isEmpty();
290     }
291 
292     @Test
getDescendantUris_platformSlice_doesNotReturnOEMSlice()293     public void getDescendantUris_platformSlice_doesNotReturnOEMSlice() {
294         insertSpecialCase("oem_key", false /* isPlatformSlice */);
295         final Uri uri = new Uri.Builder()
296                 .scheme(SCHEME_CONTENT)
297                 .authority(SettingsSlicesContract.AUTHORITY)
298                 .build();
299 
300         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
301         descendants.removeAll(SPECIAL_CASE_PLATFORM_URIS);
302 
303         assertThat(descendants).isEmpty();
304     }
305 
306     @Test
getDescendantUris_oemSlice_doesNotReturnPlatformSlice()307     public void getDescendantUris_oemSlice_doesNotReturnPlatformSlice() {
308         insertSpecialCase("platform_key", true /* isPlatformSlice */);
309         final Uri uri = new Uri.Builder()
310                 .scheme(SCHEME_CONTENT)
311                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
312                 .build();
313 
314         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
315         descendants.removeAll(SPECIAL_CASE_OEM_URIS);
316 
317         assertThat(descendants).isEmpty();
318     }
319 
320     @Test
getDescendantUris_oemSlice_returnsOEMUriDescendant()321     public void getDescendantUris_oemSlice_returnsOEMUriDescendant() {
322         final String key = "oem_key";
323         insertSpecialCase(key, false /* isPlatformSlice */);
324         final Uri uri = new Uri.Builder()
325                 .scheme(SCHEME_CONTENT)
326                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
327                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
328                 .build();
329         final Collection<Uri> expectedUris = new HashSet<>();
330         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
331         expectedUris.add(new Uri.Builder()
332                 .scheme(SCHEME_CONTENT)
333                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
334                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
335                 .appendPath(key)
336                 .build());
337 
338         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
339 
340         assertThat(descendants).containsExactlyElementsIn(expectedUris);
341     }
342 
343     @Test
getDescendantUris_oemSliceNoPath_returnsOEMUriDescendant()344     public void getDescendantUris_oemSliceNoPath_returnsOEMUriDescendant() {
345         final String key = "oem_key";
346         insertSpecialCase(key, false /* isPlatformSlice */);
347         final Uri uri = new Uri.Builder()
348                 .scheme(SCHEME_CONTENT)
349                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
350                 .build();
351         final Collection<Uri> expectedUris = new HashSet<>();
352         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
353         expectedUris.add(new Uri.Builder()
354                 .scheme(SCHEME_CONTENT)
355                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
356                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
357                 .appendPath(key)
358                 .build());
359 
360         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
361 
362         assertThat(descendants).containsExactlyElementsIn(expectedUris);
363     }
364 
365     @Test
getDescendantUris_platformSlice_returnsPlatformUriDescendant()366     public void getDescendantUris_platformSlice_returnsPlatformUriDescendant() {
367         final String key = "platform_key";
368         insertSpecialCase(key, true /* isPlatformSlice */);
369         final Uri uri = new Uri.Builder()
370                 .scheme(SCHEME_CONTENT)
371                 .authority(SettingsSlicesContract.AUTHORITY)
372                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
373                 .build();
374         final Collection<Uri> expectedUris = new HashSet<>();
375         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
376         expectedUris.add(new Uri.Builder()
377                 .scheme(SCHEME_CONTENT)
378                 .authority(SettingsSlicesContract.AUTHORITY)
379                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
380                 .appendPath(key)
381                 .build());
382 
383         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
384 
385         assertThat(descendants).containsExactlyElementsIn(expectedUris);
386     }
387 
388     @Test
getDescendantUris_platformSliceNoPath_returnsPlatformUriDescendant()389     public void getDescendantUris_platformSliceNoPath_returnsPlatformUriDescendant() {
390         final String key = "platform_key";
391         insertSpecialCase(key, true /* isPlatformSlice */);
392         final Uri uri = new Uri.Builder()
393                 .scheme(SCHEME_CONTENT)
394                 .authority(SettingsSlicesContract.AUTHORITY)
395                 .build();
396         final Collection<Uri> expectedUris = new HashSet<>();
397         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
398         expectedUris.add(new Uri.Builder()
399                 .scheme(SCHEME_CONTENT)
400                 .authority(SettingsSlicesContract.AUTHORITY)
401                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
402                 .appendPath(key)
403                 .build());
404 
405         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
406 
407         assertThat(descendants).containsExactlyElementsIn(expectedUris);
408     }
409 
410     @Test
getDescendantUris_noAuthorityNorPath_returnsAllUris()411     public void getDescendantUris_noAuthorityNorPath_returnsAllUris() {
412         final String platformKey = "platform_key";
413         final String oemKey = "oemKey";
414         insertSpecialCase(platformKey, true /* isPlatformSlice */);
415         insertSpecialCase(oemKey, false /* isPlatformSlice */);
416         final Uri uri = new Uri.Builder()
417                 .scheme(SCHEME_CONTENT)
418                 .build();
419         final Collection<Uri> expectedUris = new HashSet<>();
420         expectedUris.addAll(SPECIAL_CASE_PLATFORM_URIS);
421         expectedUris.addAll(SPECIAL_CASE_OEM_URIS);
422         expectedUris.add(new Uri.Builder()
423                 .scheme(SCHEME_CONTENT)
424                 .authority(SettingsSlicesContract.AUTHORITY)
425                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
426                 .appendPath(platformKey)
427                 .build());
428         expectedUris.add(new Uri.Builder()
429                 .scheme(SCHEME_CONTENT)
430                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
431                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
432                 .appendPath(oemKey)
433                 .build());
434 
435         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(uri);
436 
437         assertThat(descendants).containsExactlyElementsIn(expectedUris);
438     }
439 
440     @Test
bindSlice_wifiSlice_returnsWifiSlice()441     public void bindSlice_wifiSlice_returnsWifiSlice() {
442         final Slice wifiSlice = mProvider.onBindSlice(WifiSliceBuilder.WIFI_URI);
443 
444         assertThat(wifiSlice.getUri()).isEqualTo(WifiSliceBuilder.WIFI_URI);
445     }
446 
447     @Test
onSlicePinned_noIntentRegistered_specialCaseUri_doesNotCrash()448     public void onSlicePinned_noIntentRegistered_specialCaseUri_doesNotCrash() {
449         final Uri uri = new Uri.Builder()
450                 .scheme(ContentResolver.SCHEME_CONTENT)
451                 .authority(SettingsSlicesContract.AUTHORITY)
452                 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
453                 .appendPath(SettingsSlicesContract.KEY_LOCATION)
454                 .build();
455 
456         mProvider.onSlicePinned(uri);
457     }
458 
insertSpecialCase(String key)459     private void insertSpecialCase(String key) {
460         insertSpecialCase(key, true);
461     }
462 
insertSpecialCase(String key, boolean isPlatformSlice)463     private void insertSpecialCase(String key, boolean isPlatformSlice) {
464         ContentValues values = new ContentValues();
465         values.put(SlicesDatabaseHelper.IndexColumns.KEY, key);
466         values.put(SlicesDatabaseHelper.IndexColumns.TITLE, TITLE);
467         values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, "s");
468         values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, "s");
469         values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, 1234);
470         values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, "test");
471         values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, PREF_CONTROLLER);
472         values.put(SlicesDatabaseHelper.IndexColumns.PLATFORM_SLICE, isPlatformSlice);
473         values.put(SlicesDatabaseHelper.IndexColumns.SLICE_TYPE, SliceData.SliceType.INTENT);
474 
475         mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values);
476     }
477 
getDummyData()478     private static SliceData getDummyData() {
479         return new SliceData.Builder()
480                 .setKey(KEY)
481                 .setTitle(TITLE)
482                 .setSummary(SUMMARY)
483                 .setScreenTitle(SCREEN_TITLE)
484                 .setIcon(ICON)
485                 .setFragmentName(FRAGMENT_NAME)
486                 .setUri(URI)
487                 .setPreferenceControllerClassName(PREF_CONTROLLER)
488                 .build();
489     }
490 
491     @Implements(value = StrictMode.class, inheritImplementationMethods = true)
492     public static class ShadowStrictMode {
493 
494         private static int sSetThreadPolicyCount;
495 
496         @Resetter
reset()497         public static void reset() {
498             sSetThreadPolicyCount = 0;
499         }
500 
501         @Implementation
setThreadPolicy(final StrictMode.ThreadPolicy policy)502         public static void setThreadPolicy(final StrictMode.ThreadPolicy policy) {
503             sSetThreadPolicyCount++;
504         }
505 
isThreadPolicyOverridden()506         public static boolean isThreadPolicyOverridden() {
507             return sSetThreadPolicyCount != 0;
508         }
509     }
510 }