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