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