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.google.android.setupcompat.partnerconfig; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.content.res.Resources.NotFoundException; 24 import android.database.ContentObserver; 25 import android.graphics.drawable.Drawable; 26 import android.net.Uri; 27 import android.os.Build; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Bundle; 30 import android.util.DisplayMetrics; 31 import android.util.Log; 32 import android.util.TypedValue; 33 import androidx.annotation.ColorInt; 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.annotation.VisibleForTesting; 37 import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.EnumMap; 41 import java.util.List; 42 43 /** The helper reads and caches the partner configurations from SUW. */ 44 public class PartnerConfigHelper { 45 46 private static final String TAG = PartnerConfigHelper.class.getSimpleName(); 47 48 @VisibleForTesting 49 public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner"; 50 51 @VisibleForTesting public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig"; 52 53 @VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig"; 54 55 @VisibleForTesting 56 public static final String IS_SUW_DAY_NIGHT_ENABLED_METHOD = "isSuwDayNightEnabled"; 57 58 @VisibleForTesting 59 public static final String IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD = 60 "isExtendedPartnerConfigEnabled"; 61 62 @VisibleForTesting 63 public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled"; 64 65 @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; 66 67 @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; 68 69 @VisibleForTesting public static Bundle applyDynamicColorBundle = null; 70 71 private static PartnerConfigHelper instance = null; 72 73 @VisibleForTesting Bundle resultBundle = null; 74 75 @VisibleForTesting 76 final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class); 77 78 private static ContentObserver contentObserver; 79 80 private static int savedConfigUiMode; 81 82 private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; 83 get(@onNull Context context)84 public static synchronized PartnerConfigHelper get(@NonNull Context context) { 85 if (!isValidInstance(context)) { 86 instance = new PartnerConfigHelper(context); 87 } 88 return instance; 89 } 90 isValidInstance(@onNull Context context)91 private static boolean isValidInstance(@NonNull Context context) { 92 Configuration currentConfig = context.getResources().getConfiguration(); 93 if (instance == null) { 94 savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 95 savedOrientation = currentConfig.orientation; 96 return false; 97 } else { 98 if (isSetupWizardDayNightEnabled(context) 99 && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) { 100 savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 101 resetInstance(); 102 return false; 103 } else if (currentConfig.orientation != savedOrientation) { 104 savedOrientation = currentConfig.orientation; 105 resetInstance(); 106 return false; 107 } 108 } 109 return true; 110 } 111 PartnerConfigHelper(Context context)112 private PartnerConfigHelper(Context context) { 113 getPartnerConfigBundle(context); 114 115 registerContentObserver(context); 116 } 117 118 /** 119 * Returns whether partner customized config values are available. This is true if setup wizard's 120 * content provider returns us a non-empty bundle, even if all the values are default, and none 121 * are customized by the overlay APK. 122 */ isAvailable()123 public boolean isAvailable() { 124 return resultBundle != null && !resultBundle.isEmpty(); 125 } 126 127 /** 128 * Returns whether the given {@code resourceConfig} are available. This is true if setup wizard's 129 * content provider returns us a non-empty bundle, and this result bundle includes the given 130 * {@code resourceConfig} even if all the values are default, and none are customized by the 131 * overlay APK. 132 */ isPartnerConfigAvailable(PartnerConfig resourceConfig)133 public boolean isPartnerConfigAvailable(PartnerConfig resourceConfig) { 134 return isAvailable() && resultBundle.containsKey(resourceConfig.getResourceName()); 135 } 136 137 /** 138 * Returns the color of given {@code resourceConfig}, or 0 if the given {@code resourceConfig} is 139 * not found. If the {@code ResourceType} of the given {@code resourceConfig} is not color, 140 * IllegalArgumentException will be thrown. 141 * 142 * @param context The context of client activity 143 * @param resourceConfig The {@link PartnerConfig} of target resource 144 */ 145 @ColorInt getColor(@onNull Context context, PartnerConfig resourceConfig)146 public int getColor(@NonNull Context context, PartnerConfig resourceConfig) { 147 if (resourceConfig.getResourceType() != ResourceType.COLOR) { 148 throw new IllegalArgumentException("Not a color resource"); 149 } 150 151 if (partnerResourceCache.containsKey(resourceConfig)) { 152 return (int) partnerResourceCache.get(resourceConfig); 153 } 154 155 int result = 0; 156 try { 157 ResourceEntry resourceEntry = 158 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 159 Resources resource = resourceEntry.getResources(); 160 int resId = resourceEntry.getResourceId(); 161 162 // for @null 163 TypedValue outValue = new TypedValue(); 164 resource.getValue(resId, outValue, true); 165 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 166 return result; 167 } 168 169 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 170 result = resource.getColor(resId, null); 171 } else { 172 result = resource.getColor(resId); 173 } 174 partnerResourceCache.put(resourceConfig, result); 175 } catch (NullPointerException exception) { 176 // fall through 177 } 178 return result; 179 } 180 181 /** 182 * Returns the {@code Drawable} of given {@code resourceConfig}, or {@code null} if the given 183 * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code 184 * resourceConfig} is not drawable, IllegalArgumentException will be thrown. 185 * 186 * @param context The context of client activity 187 * @param resourceConfig The {@code PartnerConfig} of target resource 188 */ 189 @Nullable getDrawable(@onNull Context context, PartnerConfig resourceConfig)190 public Drawable getDrawable(@NonNull Context context, PartnerConfig resourceConfig) { 191 if (resourceConfig.getResourceType() != ResourceType.DRAWABLE) { 192 throw new IllegalArgumentException("Not a drawable resource"); 193 } 194 195 if (partnerResourceCache.containsKey(resourceConfig)) { 196 return (Drawable) partnerResourceCache.get(resourceConfig); 197 } 198 199 Drawable result = null; 200 try { 201 ResourceEntry resourceEntry = 202 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 203 Resources resource = resourceEntry.getResources(); 204 int resId = resourceEntry.getResourceId(); 205 206 // for @null 207 TypedValue outValue = new TypedValue(); 208 resource.getValue(resId, outValue, true); 209 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 210 return result; 211 } 212 213 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 214 result = resource.getDrawable(resId, null); 215 } else { 216 result = resource.getDrawable(resId); 217 } 218 partnerResourceCache.put(resourceConfig, result); 219 } catch (NullPointerException | NotFoundException exception) { 220 // fall through 221 } 222 return result; 223 } 224 225 /** 226 * Returns the string of the given {@code resourceConfig}, or {@code null} if the given {@code 227 * resourceConfig} is not found. If the {@code ResourceType} of the given {@code resourceConfig} 228 * is not string, IllegalArgumentException will be thrown. 229 * 230 * @param context The context of client activity 231 * @param resourceConfig The {@code PartnerConfig} of target resource 232 */ 233 @Nullable getString(@onNull Context context, PartnerConfig resourceConfig)234 public String getString(@NonNull Context context, PartnerConfig resourceConfig) { 235 if (resourceConfig.getResourceType() != ResourceType.STRING) { 236 throw new IllegalArgumentException("Not a string resource"); 237 } 238 239 if (partnerResourceCache.containsKey(resourceConfig)) { 240 return (String) partnerResourceCache.get(resourceConfig); 241 } 242 243 String result = null; 244 try { 245 ResourceEntry resourceEntry = 246 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 247 Resources resource = resourceEntry.getResources(); 248 int resId = resourceEntry.getResourceId(); 249 250 result = resource.getString(resId); 251 partnerResourceCache.put(resourceConfig, result); 252 } catch (NullPointerException exception) { 253 // fall through 254 } 255 return result; 256 } 257 258 /** 259 * Returns the string array of the given {@code resourceConfig}, or {@code null} if the given 260 * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code 261 * resourceConfig} is not string, IllegalArgumentException will be thrown. 262 * 263 * @param context The context of client activity 264 * @param resourceConfig The {@code PartnerConfig} of target resource 265 */ 266 @NonNull getStringArray(@onNull Context context, PartnerConfig resourceConfig)267 public List<String> getStringArray(@NonNull Context context, PartnerConfig resourceConfig) { 268 if (resourceConfig.getResourceType() != ResourceType.STRING_ARRAY) { 269 throw new IllegalArgumentException("Not a string array resource"); 270 } 271 272 String[] result; 273 List<String> listResult = new ArrayList<>(); 274 275 try { 276 ResourceEntry resourceEntry = 277 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 278 Resources resource = resourceEntry.getResources(); 279 int resId = resourceEntry.getResourceId(); 280 281 result = resource.getStringArray(resId); 282 Collections.addAll(listResult, result); 283 } catch (NullPointerException exception) { 284 // fall through 285 } 286 287 return listResult; 288 } 289 290 /** 291 * Returns the boolean of given {@code resourceConfig}, or {@code defaultValue} if the given 292 * {@code resourceName} is not found. If the {@code ResourceType} of the given {@code 293 * resourceConfig} is not boolean, IllegalArgumentException will be thrown. 294 * 295 * @param context The context of client activity 296 * @param resourceConfig The {@code PartnerConfig} of target resource 297 * @param defaultValue The default value 298 */ getBoolean( @onNull Context context, PartnerConfig resourceConfig, boolean defaultValue)299 public boolean getBoolean( 300 @NonNull Context context, PartnerConfig resourceConfig, boolean defaultValue) { 301 if (resourceConfig.getResourceType() != ResourceType.BOOL) { 302 throw new IllegalArgumentException("Not a bool resource"); 303 } 304 305 if (partnerResourceCache.containsKey(resourceConfig)) { 306 return (boolean) partnerResourceCache.get(resourceConfig); 307 } 308 309 boolean result = defaultValue; 310 try { 311 ResourceEntry resourceEntry = 312 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 313 Resources resource = resourceEntry.getResources(); 314 int resId = resourceEntry.getResourceId(); 315 316 result = resource.getBoolean(resId); 317 partnerResourceCache.put(resourceConfig, result); 318 } catch (NullPointerException exception) { 319 // fall through 320 } 321 return result; 322 } 323 324 /** 325 * Returns the dimension of given {@code resourceConfig}. The default return value is 0. 326 * 327 * @param context The context of client activity 328 * @param resourceConfig The {@code PartnerConfig} of target resource 329 */ getDimension(@onNull Context context, PartnerConfig resourceConfig)330 public float getDimension(@NonNull Context context, PartnerConfig resourceConfig) { 331 return getDimension(context, resourceConfig, 0); 332 } 333 334 /** 335 * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} is 336 * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 337 * resourceConfig} is not dimension, will throw IllegalArgumentException. 338 * 339 * @param context The context of client activity 340 * @param resourceConfig The {@code PartnerConfig} of target resource 341 * @param defaultValue The default value 342 */ getDimension( @onNull Context context, PartnerConfig resourceConfig, float defaultValue)343 public float getDimension( 344 @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { 345 if (resourceConfig.getResourceType() != ResourceType.DIMENSION) { 346 throw new IllegalArgumentException("Not a dimension resource"); 347 } 348 349 if (partnerResourceCache.containsKey(resourceConfig)) { 350 return getDimensionFromTypedValue( 351 context, (TypedValue) partnerResourceCache.get(resourceConfig)); 352 } 353 354 float result = defaultValue; 355 try { 356 ResourceEntry resourceEntry = 357 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 358 Resources resource = resourceEntry.getResources(); 359 int resId = resourceEntry.getResourceId(); 360 361 result = resource.getDimension(resId); 362 TypedValue value = getTypedValueFromResource(resource, resId, TypedValue.TYPE_DIMENSION); 363 partnerResourceCache.put(resourceConfig, value); 364 result = 365 getDimensionFromTypedValue( 366 context, (TypedValue) partnerResourceCache.get(resourceConfig)); 367 } catch (NullPointerException exception) { 368 // fall through 369 } 370 return result; 371 } 372 373 /** 374 * Returns the float of given {@code resourceConfig}. The default return value is 0. 375 * 376 * @param context The context of client activity 377 * @param resourceConfig The {@code PartnerConfig} of target resource 378 */ getFraction(@onNull Context context, PartnerConfig resourceConfig)379 public float getFraction(@NonNull Context context, PartnerConfig resourceConfig) { 380 return getFraction(context, resourceConfig, 0.0f); 381 } 382 383 /** 384 * Returns the float of given {@code resourceConfig}. If the given {@code resourceConfig} not 385 * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 386 * resourceConfig} is not fraction, will throw IllegalArgumentException. 387 * 388 * @param context The context of client activity 389 * @param resourceConfig The {@code PartnerConfig} of target resource 390 * @param defaultValue The default value 391 */ getFraction( @onNull Context context, PartnerConfig resourceConfig, float defaultValue)392 public float getFraction( 393 @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { 394 if (resourceConfig.getResourceType() != ResourceType.FRACTION) { 395 throw new IllegalArgumentException("Not a fraction resource"); 396 } 397 398 if (partnerResourceCache.containsKey(resourceConfig)) { 399 return (float) partnerResourceCache.get(resourceConfig); 400 } 401 402 float result = defaultValue; 403 try { 404 ResourceEntry resourceEntry = 405 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 406 Resources resource = resourceEntry.getResources(); 407 int resId = resourceEntry.getResourceId(); 408 409 result = resource.getFraction(resId, 1, 1); 410 partnerResourceCache.put(resourceConfig, result); 411 } catch (NullPointerException exception) { 412 // fall through 413 } 414 return result; 415 } 416 417 /** 418 * Returns the integer of given {@code resourceConfig}. If the given {@code resourceConfig} is not 419 * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 420 * resourceConfig} is not dimension, will throw IllegalArgumentException. 421 * 422 * @param context The context of client activity 423 * @param resourceConfig The {@code PartnerConfig} of target resource 424 * @param defaultValue The default value 425 */ getInteger(@onNull Context context, PartnerConfig resourceConfig, int defaultValue)426 public int getInteger(@NonNull Context context, PartnerConfig resourceConfig, int defaultValue) { 427 if (resourceConfig.getResourceType() != ResourceType.INTEGER) { 428 throw new IllegalArgumentException("Not a integer resource"); 429 } 430 431 if (partnerResourceCache.containsKey(resourceConfig)) { 432 return (int) partnerResourceCache.get(resourceConfig); 433 } 434 435 int result = defaultValue; 436 try { 437 ResourceEntry resourceEntry = 438 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 439 Resources resource = resourceEntry.getResources(); 440 int resId = resourceEntry.getResourceId(); 441 442 result = resource.getInteger(resId); 443 partnerResourceCache.put(resourceConfig, result); 444 } catch (NullPointerException exception) { 445 // fall through 446 } 447 return result; 448 } 449 450 /** 451 * Returns the {@link ResourceEntry} of given {@code resourceConfig}, or {@code null} if the given 452 * {@code resourceConfig} is not found. If the {@link ResourceType} of the given {@code 453 * resourceConfig} is not illustration, IllegalArgumentException will be thrown. 454 * 455 * @param context The context of client activity 456 * @param resourceConfig The {@link PartnerConfig} of target resource 457 */ 458 @Nullable getIllustrationResourceEntry( @onNull Context context, PartnerConfig resourceConfig)459 public ResourceEntry getIllustrationResourceEntry( 460 @NonNull Context context, PartnerConfig resourceConfig) { 461 if (resourceConfig.getResourceType() != ResourceType.ILLUSTRATION) { 462 throw new IllegalArgumentException("Not a illustration resource"); 463 } 464 465 if (partnerResourceCache.containsKey(resourceConfig)) { 466 return (ResourceEntry) partnerResourceCache.get(resourceConfig); 467 } 468 469 try { 470 ResourceEntry resourceEntry = 471 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 472 473 Resources resource = resourceEntry.getResources(); 474 int resId = resourceEntry.getResourceId(); 475 476 // TODO: The illustration resource entry validation should validate is it a video 477 // resource or not? 478 // for @null 479 TypedValue outValue = new TypedValue(); 480 resource.getValue(resId, outValue, true); 481 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 482 return null; 483 } 484 485 partnerResourceCache.put(resourceConfig, resourceEntry); 486 return resourceEntry; 487 } catch (NullPointerException exception) { 488 // fall through 489 } 490 491 return null; 492 } 493 getPartnerConfigBundle(Context context)494 private void getPartnerConfigBundle(Context context) { 495 if (resultBundle == null || resultBundle.isEmpty()) { 496 try { 497 resultBundle = 498 context 499 .getContentResolver() 500 .call( 501 getContentUri(), 502 SUW_GET_PARTNER_CONFIG_METHOD, 503 /* arg= */ null, 504 /* extras= */ null); 505 partnerResourceCache.clear(); 506 } catch (IllegalArgumentException | SecurityException exception) { 507 Log.w(TAG, "Fail to get config from suw provider"); 508 } 509 } 510 } 511 512 @Nullable 513 @VisibleForTesting getResourceEntryFromKey(Context context, String resourceName)514 ResourceEntry getResourceEntryFromKey(Context context, String resourceName) { 515 Bundle resourceEntryBundle = resultBundle.getBundle(resourceName); 516 Bundle fallbackBundle = resultBundle.getBundle(KEY_FALLBACK_CONFIG); 517 if (fallbackBundle != null) { 518 resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName)); 519 } 520 521 return adjustResourceEntryDayNightMode( 522 context, ResourceEntry.fromBundle(context, resourceEntryBundle)); 523 } 524 525 /** 526 * Force to day mode if setup wizard does not support day/night mode and current system is in 527 * night mode. 528 */ adjustResourceEntryDayNightMode( Context context, ResourceEntry resourceEntry)529 private static ResourceEntry adjustResourceEntryDayNightMode( 530 Context context, ResourceEntry resourceEntry) { 531 Resources resource = resourceEntry.getResources(); 532 Configuration configuration = resource.getConfiguration(); 533 if (!isSetupWizardDayNightEnabled(context) && Util.isNightMode(configuration)) { 534 if (resourceEntry == null) { 535 Log.w(TAG, "resourceEntry is null, skip to force day mode."); 536 return resourceEntry; 537 } 538 configuration.uiMode = 539 Configuration.UI_MODE_NIGHT_NO 540 | (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); 541 resource.updateConfiguration(configuration, resource.getDisplayMetrics()); 542 } 543 544 return resourceEntry; 545 } 546 547 @VisibleForTesting resetInstance()548 public static synchronized void resetInstance() { 549 instance = null; 550 suwDayNightEnabledBundle = null; 551 applyExtendedPartnerConfigBundle = null; 552 applyDynamicColorBundle = null; 553 } 554 555 /** 556 * Checks whether SetupWizard supports the DayNight theme during setup flow; if return false setup 557 * flow should force to light theme. 558 * 559 * <p>Returns true if the setupwizard is listening to system DayNight theme setting. 560 */ isSetupWizardDayNightEnabled(@onNull Context context)561 public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) { 562 if (suwDayNightEnabledBundle == null) { 563 try { 564 suwDayNightEnabledBundle = 565 context 566 .getContentResolver() 567 .call( 568 getContentUri(), 569 IS_SUW_DAY_NIGHT_ENABLED_METHOD, 570 /* arg= */ null, 571 /* extras= */ null); 572 } catch (IllegalArgumentException | SecurityException exception) { 573 Log.w(TAG, "SetupWizard DayNight supporting status unknown; return as false."); 574 suwDayNightEnabledBundle = null; 575 return false; 576 } 577 } 578 579 return (suwDayNightEnabledBundle != null 580 && suwDayNightEnabledBundle.getBoolean(IS_SUW_DAY_NIGHT_ENABLED_METHOD, false)); 581 } 582 583 /** Returns true if the SetupWizard supports the extended partner configs during setup flow. */ shouldApplyExtendedPartnerConfig(@onNull Context context)584 public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) { 585 if (applyExtendedPartnerConfigBundle == null) { 586 try { 587 applyExtendedPartnerConfigBundle = 588 context 589 .getContentResolver() 590 .call( 591 getContentUri(), 592 IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, 593 /* arg= */ null, 594 /* extras= */ null); 595 } catch (IllegalArgumentException | SecurityException exception) { 596 Log.w( 597 TAG, 598 "SetupWizard extended partner configs supporting status unknown; return as false."); 599 applyExtendedPartnerConfigBundle = null; 600 return false; 601 } 602 } 603 604 return (applyExtendedPartnerConfigBundle != null 605 && applyExtendedPartnerConfigBundle.getBoolean( 606 IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false)); 607 } 608 609 /** Returns true if the SetupWizard supports the dynamic color during setup flow. */ isSetupWizardDynamicColorEnabled(@onNull Context context)610 public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { 611 if (applyDynamicColorBundle == null) { 612 try { 613 applyDynamicColorBundle = 614 context 615 .getContentResolver() 616 .call( 617 getContentUri(), 618 IS_DYNAMIC_COLOR_ENABLED_METHOD, 619 /* arg= */ null, 620 /* extras= */ null); 621 } catch (IllegalArgumentException | SecurityException exception) { 622 Log.w(TAG, "SetupWizard dynamic color supporting status unknown; return as false."); 623 applyDynamicColorBundle = null; 624 return false; 625 } 626 } 627 628 return (applyDynamicColorBundle != null 629 && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); 630 } 631 632 @VisibleForTesting getContentUri()633 static Uri getContentUri() { 634 return new Uri.Builder() 635 .scheme(ContentResolver.SCHEME_CONTENT) 636 .authority(SUW_AUTHORITY) 637 .build(); 638 } 639 getTypedValueFromResource(Resources resource, int resId, int type)640 private static TypedValue getTypedValueFromResource(Resources resource, int resId, int type) { 641 TypedValue value = new TypedValue(); 642 resource.getValue(resId, value, true); 643 if (value.type != type) { 644 throw new NotFoundException( 645 "Resource ID #0x" 646 + Integer.toHexString(resId) 647 + " type #0x" 648 + Integer.toHexString(value.type) 649 + " is not valid"); 650 } 651 return value; 652 } 653 getDimensionFromTypedValue(Context context, TypedValue value)654 private static float getDimensionFromTypedValue(Context context, TypedValue value) { 655 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 656 return value.getDimension(displayMetrics); 657 } 658 registerContentObserver(Context context)659 private static void registerContentObserver(Context context) { 660 if (isSetupWizardDayNightEnabled(context)) { 661 if (contentObserver != null) { 662 unregisterContentObserver(context); 663 } 664 665 Uri contentUri = getContentUri(); 666 try { 667 contentObserver = 668 new ContentObserver(null) { 669 @Override 670 public void onChange(boolean selfChange) { 671 super.onChange(selfChange); 672 resetInstance(); 673 } 674 }; 675 context 676 .getContentResolver() 677 .registerContentObserver(contentUri, /* notifyForDescendants= */ true, contentObserver); 678 } catch (SecurityException | NullPointerException | IllegalArgumentException e) { 679 Log.w(TAG, "Failed to register content observer for " + contentUri + ": " + e); 680 } 681 } 682 } 683 unregisterContentObserver(Context context)684 private static void unregisterContentObserver(Context context) { 685 try { 686 context.getContentResolver().unregisterContentObserver(contentObserver); 687 contentObserver = null; 688 } catch (SecurityException | NullPointerException | IllegalArgumentException e) { 689 Log.w(TAG, "Failed to unregister content observer: " + e); 690 } 691 } 692 } 693