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 }