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.app.Activity; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.ContextWrapper; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.content.res.Resources.NotFoundException; 28 import android.database.ContentObserver; 29 import android.graphics.drawable.Drawable; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.Build.VERSION_CODES; 33 import android.os.Bundle; 34 import android.util.DisplayMetrics; 35 import android.util.Log; 36 import android.util.TypedValue; 37 import androidx.annotation.ColorInt; 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.VisibleForTesting; 41 import androidx.window.embedding.ActivityEmbeddingController; 42 import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; 43 import com.google.android.setupcompat.util.BuildCompatUtils; 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.EnumMap; 47 import java.util.List; 48 import java.util.Objects; 49 50 /** The helper reads and caches the partner configurations from SUW. */ 51 public class PartnerConfigHelper { 52 53 private static final String TAG = PartnerConfigHelper.class.getSimpleName(); 54 55 public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner"; 56 57 @VisibleForTesting public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig"; 58 59 @VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig"; 60 61 @VisibleForTesting 62 public static final String IS_SUW_DAY_NIGHT_ENABLED_METHOD = "isSuwDayNightEnabled"; 63 64 @VisibleForTesting 65 public static final String IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD = 66 "isExtendedPartnerConfigEnabled"; 67 68 @VisibleForTesting 69 public static final String IS_MATERIAL_YOU_STYLE_ENABLED_METHOD = "IsMaterialYouStyleEnabled"; 70 71 @VisibleForTesting 72 public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled"; 73 74 @VisibleForTesting 75 public static final String IS_FULL_DYNAMIC_COLOR_ENABLED_METHOD = "isFullDynamicColorEnabled"; 76 77 @VisibleForTesting 78 public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; 79 80 @VisibleForTesting 81 public static final String IS_FONT_WEIGHT_ENABLED_METHOD = "isFontWeightEnabled"; 82 83 @VisibleForTesting 84 public static final String IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD = 85 "isEmbeddedActivityOnePaneEnabled"; 86 87 @VisibleForTesting 88 public static final String IS_FORCE_TWO_PANE_ENABLED_METHOD = "isForceTwoPaneEnabled"; 89 90 @VisibleForTesting 91 public static final String IS_GLIF_EXPRESSIVE_ENABLED = "isGlifExpressiveEnabled"; 92 93 @VisibleForTesting 94 public static final String IS_ENHANCED_SETUP_DESIGN_METRICS_ENABLED = 95 "isEnhancedSetupDesignMetricsEnabled"; 96 97 /** The method name to get the if the keyboard focus enhancement enabled */ 98 @VisibleForTesting 99 public static final String IS_KEYBOARD_FOCUS_ENHANCEMENT_ENABLED_METHOD = 100 "isKeyboardFocusEnhancementEnabled"; 101 102 @VisibleForTesting 103 public static final String GET_SUW_DEFAULT_THEME_STRING_METHOD = "suwDefaultThemeString"; 104 105 @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; 106 @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; 107 @VisibleForTesting public static final String GLIF_EXPRESSIVE_RESOURCE_SUFFIX = "_expressive"; 108 109 @VisibleForTesting 110 public static final String EMBEDDED_ACTIVITY_RESOURCE_SUFFIX = "_embedded_activity"; 111 112 @VisibleForTesting public static Bundle suwDayNightEnabledBundle = null; 113 114 @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; 115 116 @VisibleForTesting public static Bundle applyMaterialYouConfigBundle = null; 117 118 @VisibleForTesting public static Bundle applyDynamicColorBundle = null; 119 @VisibleForTesting public static Bundle applyFullDynamicColorBundle = null; 120 121 @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; 122 123 @VisibleForTesting public static Bundle applyFontWeightBundle = null; 124 125 @VisibleForTesting public static Bundle applyEmbeddedActivityOnePaneBundle = null; 126 127 @VisibleForTesting public static Bundle suwDefaultThemeBundle = null; 128 129 @VisibleForTesting public static Bundle keyboardFocusEnhancementBundle = null; 130 131 private static PartnerConfigHelper instance = null; 132 133 @VisibleForTesting Bundle resultBundle = null; 134 135 @VisibleForTesting 136 final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class); 137 138 private static ContentObserver contentObserver; 139 140 private static int savedConfigUiMode; 141 142 private static boolean savedConfigEmbeddedActivityMode; 143 144 @VisibleForTesting static Bundle applyTransitionBundle = null; 145 146 @SuppressWarnings("NonFinalStaticField") 147 @VisibleForTesting 148 public static Bundle applyForceTwoPaneBundle = null; 149 150 @VisibleForTesting public static Bundle applyGlifExpressiveBundle = null; 151 152 @VisibleForTesting public static Bundle enableMetricsLoggingBundle = null; 153 154 @VisibleForTesting public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; 155 156 /** The method name to get if transition settings is set from client. */ 157 public static final String APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD = 158 "applyGlifThemeControlledTransition"; 159 160 /** 161 * When testing related to fake PartnerConfigHelper instance, should sync the following saved 162 * config with testing environment. 163 */ 164 @VisibleForTesting public static int savedScreenHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; 165 166 @VisibleForTesting public static int savedScreenWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED; 167 168 /** A string to be a suffix of resource name which is associating to force two pane feature. */ 169 @VisibleForTesting static final String FORCE_TWO_PANE_SUFFIX = "_two_pane"; 170 get(@onNull Context context)171 public static synchronized PartnerConfigHelper get(@NonNull Context context) { 172 if (!isValidInstance(context)) { 173 instance = new PartnerConfigHelper(context); 174 } 175 return instance; 176 } 177 isValidInstance(@onNull Context context)178 private static boolean isValidInstance(@NonNull Context context) { 179 Configuration currentConfig = context.getResources().getConfiguration(); 180 if (instance == null) { 181 savedConfigEmbeddedActivityMode = 182 isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); 183 savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 184 savedOrientation = currentConfig.orientation; 185 savedScreenWidth = currentConfig.screenWidthDp; 186 savedScreenHeight = currentConfig.screenHeightDp; 187 return false; 188 } else { 189 boolean uiModeChanged = 190 isSetupWizardDayNightEnabled(context) 191 && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; 192 boolean embeddedActivityModeChanged = 193 isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); 194 if (uiModeChanged 195 || embeddedActivityModeChanged != savedConfigEmbeddedActivityMode 196 || currentConfig.orientation != savedOrientation 197 || currentConfig.screenWidthDp != savedScreenWidth 198 || currentConfig.screenHeightDp != savedScreenHeight) { 199 savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 200 savedOrientation = currentConfig.orientation; 201 savedScreenHeight = currentConfig.screenHeightDp; 202 savedScreenWidth = currentConfig.screenWidthDp; 203 resetInstance(); 204 return false; 205 } 206 } 207 return true; 208 } 209 PartnerConfigHelper(Context context)210 private PartnerConfigHelper(Context context) { 211 getPartnerConfigBundle(context); 212 213 registerContentObserver(context); 214 } 215 216 /** 217 * Returns whether partner customized config values are available. This is true if setup wizard's 218 * content provider returns us a non-empty bundle, even if all the values are default, and none 219 * are customized by the overlay APK. 220 */ isAvailable()221 public boolean isAvailable() { 222 return resultBundle != null && !resultBundle.isEmpty(); 223 } 224 225 /** 226 * Returns whether the given {@code resourceConfig} are available. This is true if setup wizard's 227 * content provider returns us a non-empty bundle, and this result bundle includes the given 228 * {@code resourceConfig} even if all the values are default, and none are customized by the 229 * overlay APK. 230 */ isPartnerConfigAvailable(PartnerConfig resourceConfig)231 public boolean isPartnerConfigAvailable(PartnerConfig resourceConfig) { 232 return isAvailable() && resultBundle.containsKey(resourceConfig.getResourceName()); 233 } 234 235 /** 236 * Returns the color of given {@code resourceConfig}, or 0 if the given {@code resourceConfig} is 237 * not found. If the {@code ResourceType} of the given {@code resourceConfig} is not color, 238 * IllegalArgumentException will be thrown. 239 * 240 * @param context The context of client activity 241 * @param resourceConfig The {@link PartnerConfig} of target resource 242 */ 243 @ColorInt getColor(@onNull Context context, PartnerConfig resourceConfig)244 public int getColor(@NonNull Context context, PartnerConfig resourceConfig) { 245 if (resourceConfig.getResourceType() != ResourceType.COLOR) { 246 throw new IllegalArgumentException("Not a color resource"); 247 } 248 249 if (partnerResourceCache.containsKey(resourceConfig)) { 250 return (int) partnerResourceCache.get(resourceConfig); 251 } 252 253 int result = 0; 254 try { 255 ResourceEntry resourceEntry = 256 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 257 Resources resource = resourceEntry.getResources(); 258 int resId = resourceEntry.getResourceId(); 259 260 // for @null 261 TypedValue outValue = new TypedValue(); 262 resource.getValue(resId, outValue, true); 263 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 264 return result; 265 } 266 267 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 268 result = resource.getColor(resId, null); 269 } else { 270 result = resource.getColor(resId); 271 } 272 partnerResourceCache.put(resourceConfig, result); 273 } catch (NullPointerException exception) { 274 // fall through 275 } 276 return result; 277 } 278 279 /** 280 * Returns the {@code Drawable} of given {@code resourceConfig}, or {@code null} if the given 281 * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code 282 * resourceConfig} is not drawable, IllegalArgumentException will be thrown. 283 * 284 * @param context The context of client activity 285 * @param resourceConfig The {@code PartnerConfig} of target resource 286 */ 287 @Nullable getDrawable(@onNull Context context, PartnerConfig resourceConfig)288 public Drawable getDrawable(@NonNull Context context, PartnerConfig resourceConfig) { 289 if (resourceConfig.getResourceType() != ResourceType.DRAWABLE) { 290 throw new IllegalArgumentException("Not a drawable resource"); 291 } 292 293 if (partnerResourceCache.containsKey(resourceConfig)) { 294 return (Drawable) partnerResourceCache.get(resourceConfig); 295 } 296 297 Drawable result = null; 298 try { 299 ResourceEntry resourceEntry = 300 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 301 Resources resource = resourceEntry.getResources(); 302 int resId = resourceEntry.getResourceId(); 303 304 // for @null 305 TypedValue outValue = new TypedValue(); 306 resource.getValue(resId, outValue, true); 307 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 308 return result; 309 } 310 311 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 312 result = resource.getDrawable(resId, null); 313 } else { 314 result = resource.getDrawable(resId); 315 } 316 partnerResourceCache.put(resourceConfig, result); 317 } catch (NullPointerException | NotFoundException exception) { 318 // fall through 319 } 320 return result; 321 } 322 323 /** 324 * Returns the string of the given {@code resourceConfig}, or {@code null} if the given {@code 325 * resourceConfig} is not found. If the {@code ResourceType} of the given {@code resourceConfig} 326 * is not string, IllegalArgumentException will be thrown. 327 * 328 * @param context The context of client activity 329 * @param resourceConfig The {@code PartnerConfig} of target resource 330 */ 331 @Nullable getString(@onNull Context context, PartnerConfig resourceConfig)332 public String getString(@NonNull Context context, PartnerConfig resourceConfig) { 333 if (resourceConfig.getResourceType() != ResourceType.STRING) { 334 throw new IllegalArgumentException("Not a string resource"); 335 } 336 337 if (partnerResourceCache.containsKey(resourceConfig)) { 338 return (String) partnerResourceCache.get(resourceConfig); 339 } 340 341 String result = null; 342 try { 343 ResourceEntry resourceEntry = 344 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 345 Resources resource = resourceEntry.getResources(); 346 int resId = resourceEntry.getResourceId(); 347 348 result = resource.getString(resId); 349 partnerResourceCache.put(resourceConfig, result); 350 } catch (NullPointerException exception) { 351 // fall through 352 } 353 return result; 354 } 355 356 /** 357 * Returns the string array of the given {@code resourceConfig}, or {@code null} if the given 358 * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code 359 * resourceConfig} is not string, IllegalArgumentException will be thrown. 360 * 361 * @param context The context of client activity 362 * @param resourceConfig The {@code PartnerConfig} of target resource 363 */ 364 @NonNull getStringArray(@onNull Context context, PartnerConfig resourceConfig)365 public List<String> getStringArray(@NonNull Context context, PartnerConfig resourceConfig) { 366 if (resourceConfig.getResourceType() != ResourceType.STRING_ARRAY) { 367 throw new IllegalArgumentException("Not a string array resource"); 368 } 369 370 String[] result; 371 List<String> listResult = new ArrayList<>(); 372 373 try { 374 ResourceEntry resourceEntry = 375 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 376 Resources resource = resourceEntry.getResources(); 377 int resId = resourceEntry.getResourceId(); 378 379 result = resource.getStringArray(resId); 380 Collections.addAll(listResult, result); 381 } catch (NullPointerException exception) { 382 // fall through 383 } 384 385 return listResult; 386 } 387 388 /** 389 * Returns the boolean of given {@code resourceConfig}, or {@code defaultValue} if the given 390 * {@code resourceName} is not found. If the {@code ResourceType} of the given {@code 391 * resourceConfig} is not boolean, IllegalArgumentException will be thrown. 392 * 393 * @param context The context of client activity 394 * @param resourceConfig The {@code PartnerConfig} of target resource 395 * @param defaultValue The default value 396 */ getBoolean( @onNull Context context, PartnerConfig resourceConfig, boolean defaultValue)397 public boolean getBoolean( 398 @NonNull Context context, PartnerConfig resourceConfig, boolean defaultValue) { 399 if (resourceConfig.getResourceType() != ResourceType.BOOL) { 400 throw new IllegalArgumentException("Not a bool resource"); 401 } 402 403 if (partnerResourceCache.containsKey(resourceConfig)) { 404 return (boolean) partnerResourceCache.get(resourceConfig); 405 } 406 407 boolean result = defaultValue; 408 try { 409 ResourceEntry resourceEntry = 410 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 411 Resources resource = resourceEntry.getResources(); 412 int resId = resourceEntry.getResourceId(); 413 414 result = resource.getBoolean(resId); 415 partnerResourceCache.put(resourceConfig, result); 416 } catch (NullPointerException | NotFoundException exception) { 417 // fall through 418 } 419 return result; 420 } 421 422 /** 423 * Returns the dimension of given {@code resourceConfig}. The default return value is 0. 424 * 425 * @param context The context of client activity 426 * @param resourceConfig The {@code PartnerConfig} of target resource 427 */ getDimension(@onNull Context context, PartnerConfig resourceConfig)428 public float getDimension(@NonNull Context context, PartnerConfig resourceConfig) { 429 return getDimension(context, resourceConfig, 0); 430 } 431 432 /** 433 * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} is 434 * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 435 * resourceConfig} is not dimension, will throw IllegalArgumentException. 436 * 437 * @param context The context of client activity 438 * @param resourceConfig The {@code PartnerConfig} of target resource 439 * @param defaultValue The default value 440 */ getDimension( @onNull Context context, PartnerConfig resourceConfig, float defaultValue)441 public float getDimension( 442 @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { 443 if (resourceConfig.getResourceType() != ResourceType.DIMENSION) { 444 throw new IllegalArgumentException("Not a dimension resource"); 445 } 446 447 if (partnerResourceCache.containsKey(resourceConfig)) { 448 return getDimensionFromTypedValue( 449 context, (TypedValue) partnerResourceCache.get(resourceConfig)); 450 } 451 452 float result = defaultValue; 453 try { 454 ResourceEntry resourceEntry = 455 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 456 Resources resource = resourceEntry.getResources(); 457 int resId = resourceEntry.getResourceId(); 458 459 result = resource.getDimension(resId); 460 TypedValue value = getTypedValueFromResource(resource, resId, TypedValue.TYPE_DIMENSION); 461 partnerResourceCache.put(resourceConfig, value); 462 result = 463 getDimensionFromTypedValue( 464 context, (TypedValue) partnerResourceCache.get(resourceConfig)); 465 } catch (NullPointerException | NotFoundException exception) { 466 // fall through 467 } 468 return result; 469 } 470 471 /** 472 * Returns the float of given {@code resourceConfig}. The default return value is 0. 473 * 474 * @param context The context of client activity 475 * @param resourceConfig The {@code PartnerConfig} of target resource 476 */ getFraction(@onNull Context context, PartnerConfig resourceConfig)477 public float getFraction(@NonNull Context context, PartnerConfig resourceConfig) { 478 return getFraction(context, resourceConfig, 0.0f); 479 } 480 481 /** 482 * Returns the float of given {@code resourceConfig}. If the given {@code resourceConfig} not 483 * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 484 * resourceConfig} is not fraction, will throw IllegalArgumentException. 485 * 486 * @param context The context of client activity 487 * @param resourceConfig The {@code PartnerConfig} of target resource 488 * @param defaultValue The default value 489 */ getFraction( @onNull Context context, PartnerConfig resourceConfig, float defaultValue)490 public float getFraction( 491 @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { 492 if (resourceConfig.getResourceType() != ResourceType.FRACTION) { 493 throw new IllegalArgumentException("Not a fraction resource"); 494 } 495 496 if (partnerResourceCache.containsKey(resourceConfig)) { 497 return (float) partnerResourceCache.get(resourceConfig); 498 } 499 500 float result = defaultValue; 501 try { 502 ResourceEntry resourceEntry = 503 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 504 Resources resource = resourceEntry.getResources(); 505 int resId = resourceEntry.getResourceId(); 506 507 result = resource.getFraction(resId, 1, 1); 508 partnerResourceCache.put(resourceConfig, result); 509 } catch (NullPointerException | NotFoundException exception) { 510 // fall through 511 } 512 return result; 513 } 514 515 /** 516 * Returns the integer of given {@code resourceConfig}. If the given {@code resourceConfig} is not 517 * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code 518 * resourceConfig} is not dimension, will throw IllegalArgumentException. 519 * 520 * @param context The context of client activity 521 * @param resourceConfig The {@code PartnerConfig} of target resource 522 * @param defaultValue The default value 523 */ getInteger(@onNull Context context, PartnerConfig resourceConfig, int defaultValue)524 public int getInteger(@NonNull Context context, PartnerConfig resourceConfig, int defaultValue) { 525 if (resourceConfig.getResourceType() != ResourceType.INTEGER) { 526 throw new IllegalArgumentException("Not a integer resource"); 527 } 528 529 if (partnerResourceCache.containsKey(resourceConfig)) { 530 return (int) partnerResourceCache.get(resourceConfig); 531 } 532 533 int result = defaultValue; 534 try { 535 ResourceEntry resourceEntry = 536 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 537 Resources resource = resourceEntry.getResources(); 538 int resId = resourceEntry.getResourceId(); 539 540 result = resource.getInteger(resId); 541 partnerResourceCache.put(resourceConfig, result); 542 } catch (NullPointerException | NotFoundException exception) { 543 // fall through 544 } 545 return result; 546 } 547 548 /** 549 * Returns the {@link ResourceEntry} of given {@code resourceConfig}, or {@code null} if the given 550 * {@code resourceConfig} is not found. If the {@link ResourceType} of the given {@code 551 * resourceConfig} is not illustration, IllegalArgumentException will be thrown. 552 * 553 * @param context The context of client activity 554 * @param resourceConfig The {@link PartnerConfig} of target resource 555 */ 556 @Nullable getIllustrationResourceEntry( @onNull Context context, PartnerConfig resourceConfig)557 public ResourceEntry getIllustrationResourceEntry( 558 @NonNull Context context, PartnerConfig resourceConfig) { 559 if (resourceConfig.getResourceType() != ResourceType.ILLUSTRATION) { 560 throw new IllegalArgumentException("Not a illustration resource"); 561 } 562 563 if (partnerResourceCache.containsKey(resourceConfig)) { 564 return (ResourceEntry) partnerResourceCache.get(resourceConfig); 565 } 566 567 try { 568 ResourceEntry resourceEntry = 569 getResourceEntryFromKey(context, resourceConfig.getResourceName()); 570 571 Resources resource = resourceEntry.getResources(); 572 int resId = resourceEntry.getResourceId(); 573 574 // TODO: The illustration resource entry validation should validate is it a video 575 // resource or not? 576 // for @null 577 TypedValue outValue = new TypedValue(); 578 resource.getValue(resId, outValue, true); 579 if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { 580 return null; 581 } 582 583 partnerResourceCache.put(resourceConfig, resourceEntry); 584 return resourceEntry; 585 } catch (NullPointerException exception) { 586 // fall through 587 } 588 589 return null; 590 } 591 getPartnerConfigBundle(Context context)592 private void getPartnerConfigBundle(Context context) { 593 if (resultBundle == null || resultBundle.isEmpty()) { 594 try { 595 resultBundle = 596 context 597 .getContentResolver() 598 .call( 599 getContentUri(), 600 SUW_GET_PARTNER_CONFIG_METHOD, 601 /* arg= */ null, 602 /* extras= */ null); 603 partnerResourceCache.clear(); 604 Log.i( 605 TAG, "PartnerConfigsBundle=" + (resultBundle != null ? resultBundle.size() : "(null)")); 606 } catch (IllegalArgumentException | SecurityException exception) { 607 Log.w(TAG, "Fail to get config from suw provider"); 608 } 609 } 610 } 611 612 @Nullable 613 @VisibleForTesting getResourceEntryFromKey(Context context, String resourceName)614 ResourceEntry getResourceEntryFromKey(Context context, String resourceName) { 615 Bundle resourceEntryBundle = resultBundle.getBundle(resourceName); 616 Bundle fallbackBundle = resultBundle.getBundle(KEY_FALLBACK_CONFIG); 617 if (fallbackBundle != null) { 618 resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName)); 619 } 620 621 ResourceEntry resourceEntry = ResourceEntry.fromBundle(context, resourceEntryBundle); 622 623 if (BuildCompatUtils.isAtLeastU() && isActivityEmbedded(context)) { 624 resourceEntry = adjustEmbeddedActivityResourceEntryDefaultValue(context, resourceEntry); 625 } else if (BuildCompatUtils.isAtLeastV() && isGlifExpressiveEnabled(context)) { 626 resourceEntry = adjustGlifExpressiveResourceEntryDefaultValue(context, resourceEntry); 627 } else if (BuildCompatUtils.isAtLeastU() && isForceTwoPaneEnabled(context)) { 628 resourceEntry = adjustForceTwoPaneResourceEntryDefaultValue(context, resourceEntry); 629 } else if (BuildCompatUtils.isAtLeastT() && shouldApplyMaterialYouStyle(context)) { 630 resourceEntry = adjustMaterialYouResourceEntryDefaultValue(context, resourceEntry); 631 } 632 633 return adjustResourceEntryDayNightMode(context, resourceEntry); 634 } 635 636 @VisibleForTesting isActivityEmbedded(Context context)637 boolean isActivityEmbedded(Context context) { 638 Activity activity; 639 try { 640 activity = lookupActivityFromContext(context); 641 } catch (IllegalArgumentException e) { 642 Log.w(TAG, "Not a Activity instance in parent tree"); 643 return false; 644 } 645 646 return isEmbeddedActivityOnePaneEnabled(context) 647 && ActivityEmbeddingController.getInstance(activity).isActivityEmbedded(activity); 648 } 649 lookupActivityFromContext(Context context)650 public static Activity lookupActivityFromContext(Context context) { 651 if (context instanceof Activity) { 652 return (Activity) context; 653 } else if (context instanceof ContextWrapper) { 654 return lookupActivityFromContext(((ContextWrapper) context).getBaseContext()); 655 } else { 656 throw new IllegalArgumentException("Cannot find instance of Activity in parent tree"); 657 } 658 } 659 660 /** 661 * Force to day mode if setup wizard does not support day/night mode and current system is in 662 * night mode. 663 */ adjustResourceEntryDayNightMode( Context context, ResourceEntry resourceEntry)664 private static ResourceEntry adjustResourceEntryDayNightMode( 665 Context context, ResourceEntry resourceEntry) { 666 Resources resource = resourceEntry.getResources(); 667 Configuration configuration = resource.getConfiguration(); 668 if (!isSetupWizardDayNightEnabled(context) && Util.isNightMode(configuration)) { 669 if (resourceEntry == null) { 670 Log.w(TAG, "resourceEntry is null, skip to force day mode."); 671 return resourceEntry; 672 } 673 configuration.uiMode = 674 Configuration.UI_MODE_NIGHT_NO 675 | (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); 676 resource.updateConfiguration(configuration, resource.getDisplayMetrics()); 677 } 678 679 return resourceEntry; 680 } 681 682 // Check the MNStyle flag and replace the inputResourceEntry.resourceName & 683 // inputResourceEntry.resourceId after T, that means if using Gliv4 before S, will always use 684 // glifv3 resources. adjustMaterialYouResourceEntryDefaultValue( Context context, ResourceEntry inputResourceEntry)685 ResourceEntry adjustMaterialYouResourceEntryDefaultValue( 686 Context context, ResourceEntry inputResourceEntry) { 687 // If not overlay resource 688 try { 689 if (Objects.equals(inputResourceEntry.getPackageName(), SUW_PACKAGE_NAME)) { 690 String resourceTypeName = 691 inputResourceEntry 692 .getResources() 693 .getResourceTypeName(inputResourceEntry.getResourceId()); 694 // try to update resourceName & resourceId 695 String materialYouResourceName = 696 inputResourceEntry.getResourceName().concat(MATERIAL_YOU_RESOURCE_SUFFIX); 697 int materialYouResourceId = 698 inputResourceEntry 699 .getResources() 700 .getIdentifier( 701 materialYouResourceName, resourceTypeName, inputResourceEntry.getPackageName()); 702 if (materialYouResourceId != 0) { 703 Log.i(TAG, "use material you resource:" + materialYouResourceName); 704 return new ResourceEntry( 705 inputResourceEntry.getPackageName(), 706 materialYouResourceName, 707 materialYouResourceId, 708 inputResourceEntry.getResources()); 709 } 710 } 711 } catch (NotFoundException ex) { 712 // fall through 713 } 714 return inputResourceEntry; 715 } 716 717 // Check the embedded activity flag and replace the inputResourceEntry.resourceName & 718 // inputResourceEntry.resourceId, and try to find the embedded resource from the different 719 // package. adjustEmbeddedActivityResourceEntryDefaultValue( Context context, ResourceEntry inputResourceEntry)720 ResourceEntry adjustEmbeddedActivityResourceEntryDefaultValue( 721 Context context, ResourceEntry inputResourceEntry) { 722 // If not overlay resource 723 try { 724 String resourceTypeName = 725 inputResourceEntry.getResources().getResourceTypeName(inputResourceEntry.getResourceId()); 726 // For the first time to get embedded activity resource id, it may get from setup wizard 727 // package or Overlay package. 728 String embeddedActivityResourceName = 729 inputResourceEntry.getResourceName().concat(EMBEDDED_ACTIVITY_RESOURCE_SUFFIX); 730 int embeddedActivityResourceId = 731 inputResourceEntry 732 .getResources() 733 .getIdentifier( 734 embeddedActivityResourceName, 735 resourceTypeName, 736 inputResourceEntry.getPackageName()); 737 if (embeddedActivityResourceId != 0) { 738 Log.i(TAG, "use embedded activity resource:" + embeddedActivityResourceName); 739 return new ResourceEntry( 740 inputResourceEntry.getPackageName(), 741 embeddedActivityResourceName, 742 embeddedActivityResourceId, 743 inputResourceEntry.getResources()); 744 } else { 745 // If resource id is not available from the Overlay package, try to get it from setup wizard 746 // package. 747 PackageManager manager = context.getPackageManager(); 748 Resources resources = manager.getResourcesForApplication(SUW_PACKAGE_NAME); 749 embeddedActivityResourceId = 750 resources.getIdentifier( 751 embeddedActivityResourceName, resourceTypeName, SUW_PACKAGE_NAME); 752 if (embeddedActivityResourceId != 0) { 753 return new ResourceEntry( 754 SUW_PACKAGE_NAME, 755 embeddedActivityResourceName, 756 embeddedActivityResourceId, 757 resources); 758 } 759 } 760 } catch (NotFoundException | NameNotFoundException ex) { 761 // fall through 762 } 763 return inputResourceEntry; 764 } 765 766 // Retrieve {@code resourceEntry} with _two_pane suffix resource from the partner resource, 767 // otherwise fallback to origin partner resource if two pane resource not available. adjustForceTwoPaneResourceEntryDefaultValue( Context context, ResourceEntry resourceEntry)768 ResourceEntry adjustForceTwoPaneResourceEntryDefaultValue( 769 Context context, ResourceEntry resourceEntry) { 770 if (context == null) { 771 return resourceEntry; 772 } 773 774 try { 775 String resourceTypeName = 776 resourceEntry.getResources().getResourceTypeName(resourceEntry.getResourceId()); 777 String forceTwoPaneResourceName = 778 resourceEntry.getResourceName().concat(FORCE_TWO_PANE_SUFFIX); 779 int twoPaneResourceId = 780 resourceEntry 781 .getResources() 782 .getIdentifier( 783 forceTwoPaneResourceName, resourceTypeName, resourceEntry.getPackageName()); 784 if (twoPaneResourceId != Resources.ID_NULL) { 785 Log.i(TAG, "two pane resource=" + forceTwoPaneResourceName); 786 return new ResourceEntry( 787 resourceEntry.getPackageName(), 788 forceTwoPaneResourceName, 789 twoPaneResourceId, 790 resourceEntry.getResources()); 791 } else { 792 // If resource id is not available from the Overlay package, try to get it from setup wizard 793 // package. 794 PackageManager packageManager = context.getPackageManager(); 795 Resources resources = packageManager.getResourcesForApplication(SUW_PACKAGE_NAME); 796 twoPaneResourceId = 797 resources.getIdentifier(forceTwoPaneResourceName, resourceTypeName, SUW_PACKAGE_NAME); 798 if (twoPaneResourceId != 0) { 799 return new ResourceEntry( 800 SUW_PACKAGE_NAME, forceTwoPaneResourceName, twoPaneResourceId, resources); 801 } 802 } 803 } catch (NameNotFoundException | NotFoundException ignore) { 804 // fall through 805 } 806 return resourceEntry; 807 } 808 809 // Check the GlifExpressive flag and replace the inputResourceEntry.resourceName & 810 // inputResourceEntry.resourceId after V, that means if using GlifExpressive theme before V, will 811 // always use glifv4 resources. adjustGlifExpressiveResourceEntryDefaultValue( Context context, ResourceEntry inputResourceEntry)812 ResourceEntry adjustGlifExpressiveResourceEntryDefaultValue( 813 Context context, ResourceEntry inputResourceEntry) { 814 // If not overlay resource 815 try { 816 if (Objects.equals(inputResourceEntry.getPackageName(), SUW_PACKAGE_NAME)) { 817 String resourceTypeName = 818 inputResourceEntry 819 .getResources() 820 .getResourceTypeName(inputResourceEntry.getResourceId()); 821 // try to update resourceName & resourceId 822 String glifExpressiveResourceName = 823 inputResourceEntry.getResourceName().concat(GLIF_EXPRESSIVE_RESOURCE_SUFFIX); 824 int glifExpressiveResourceId = 825 inputResourceEntry 826 .getResources() 827 .getIdentifier( 828 glifExpressiveResourceName, 829 resourceTypeName, 830 inputResourceEntry.getPackageName()); 831 if (glifExpressiveResourceId != 0) { 832 Log.i(TAG, "use expressive resource:" + glifExpressiveResourceName); 833 return new ResourceEntry( 834 inputResourceEntry.getPackageName(), 835 glifExpressiveResourceName, 836 glifExpressiveResourceId, 837 inputResourceEntry.getResources()); 838 } 839 } 840 } catch (NotFoundException ex) { 841 // fall through 842 } 843 return inputResourceEntry; 844 } 845 846 @VisibleForTesting resetInstance()847 public static synchronized void resetInstance() { 848 instance = null; 849 suwDayNightEnabledBundle = null; 850 applyExtendedPartnerConfigBundle = null; 851 applyMaterialYouConfigBundle = null; 852 applyDynamicColorBundle = null; 853 applyFullDynamicColorBundle = null; 854 applyNeutralButtonStyleBundle = null; 855 applyEmbeddedActivityOnePaneBundle = null; 856 suwDefaultThemeBundle = null; 857 applyTransitionBundle = null; 858 applyForceTwoPaneBundle = null; 859 applyGlifExpressiveBundle = null; 860 keyboardFocusEnhancementBundle = null; 861 enableMetricsLoggingBundle = null; 862 } 863 864 /** 865 * Checks whether SetupWizard supports the DayNight theme during setup flow; if return false setup 866 * flow should force to light theme. 867 * 868 * <p>Returns true if the setupwizard is listening to system DayNight theme setting. 869 */ isSetupWizardDayNightEnabled(@onNull Context context)870 public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) { 871 if (suwDayNightEnabledBundle == null) { 872 try { 873 suwDayNightEnabledBundle = 874 context 875 .getContentResolver() 876 .call( 877 getContentUri(), 878 IS_SUW_DAY_NIGHT_ENABLED_METHOD, 879 /* arg= */ null, 880 /* extras= */ null); 881 } catch (IllegalArgumentException | SecurityException exception) { 882 Log.w(TAG, "SetupWizard DayNight supporting status unknown; return as false."); 883 suwDayNightEnabledBundle = null; 884 return false; 885 } 886 } 887 888 return (suwDayNightEnabledBundle != null 889 && suwDayNightEnabledBundle.getBoolean(IS_SUW_DAY_NIGHT_ENABLED_METHOD, false)); 890 } 891 892 /** Returns true if the SetupWizard supports the extended partner configs during setup flow. */ shouldApplyExtendedPartnerConfig(@onNull Context context)893 public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) { 894 if (applyExtendedPartnerConfigBundle == null) { 895 try { 896 applyExtendedPartnerConfigBundle = 897 context 898 .getContentResolver() 899 .call( 900 getContentUri(), 901 IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, 902 /* arg= */ null, 903 /* extras= */ null); 904 } catch (IllegalArgumentException | SecurityException exception) { 905 Log.w( 906 TAG, 907 "SetupWizard extended partner configs supporting status unknown; return as false."); 908 applyExtendedPartnerConfigBundle = null; 909 return false; 910 } 911 } 912 913 return (applyExtendedPartnerConfigBundle != null 914 && applyExtendedPartnerConfigBundle.getBoolean( 915 IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false)); 916 } 917 918 /** 919 * Returns true if the SetupWizard is flow enabled "Material You(Glifv4)" style, or the result of 920 * shouldApplyExtendedPartnerConfig() in SDK S as fallback. 921 */ shouldApplyMaterialYouStyle(@onNull Context context)922 public static boolean shouldApplyMaterialYouStyle(@NonNull Context context) { 923 if (applyMaterialYouConfigBundle == null || applyMaterialYouConfigBundle.isEmpty()) { 924 try { 925 applyMaterialYouConfigBundle = 926 context 927 .getContentResolver() 928 .call( 929 getContentUri(), 930 IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, 931 /* arg= */ null, 932 /* extras= */ null); 933 // The suw version did not support the flag yet, fallback to 934 // shouldApplyExtendedPartnerConfig() for SDK S. 935 if (applyMaterialYouConfigBundle != null 936 && applyMaterialYouConfigBundle.isEmpty() 937 && !BuildCompatUtils.isAtLeastT()) { 938 return shouldApplyExtendedPartnerConfig(context); 939 } 940 } catch (IllegalArgumentException | SecurityException exception) { 941 Log.w(TAG, "SetupWizard Material You configs supporting status unknown; return as false."); 942 applyMaterialYouConfigBundle = null; 943 return false; 944 } 945 } 946 947 return ((applyMaterialYouConfigBundle != null 948 && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, false)) 949 || isGlifExpressiveEnabled(context)); 950 } 951 952 /** 953 * Returns default glif theme name string from setupwizard, or if the setupwizard has not 954 * supported this api, return a null string. 955 */ 956 @Nullable getSuwDefaultThemeString(@onNull Context context)957 public static String getSuwDefaultThemeString(@NonNull Context context) { 958 if (suwDefaultThemeBundle == null || suwDefaultThemeBundle.isEmpty()) { 959 try { 960 suwDefaultThemeBundle = 961 context 962 .getContentResolver() 963 .call( 964 getContentUri(), 965 GET_SUW_DEFAULT_THEME_STRING_METHOD, 966 /* arg= */ null, 967 /* extras= */ null); 968 } catch (IllegalArgumentException | SecurityException exception) { 969 Log.w(TAG, "SetupWizard default theme status unknown; return as null."); 970 suwDefaultThemeBundle = null; 971 return null; 972 } 973 } 974 if (suwDefaultThemeBundle == null || suwDefaultThemeBundle.isEmpty()) { 975 return null; 976 } 977 return suwDefaultThemeBundle.getString(GET_SUW_DEFAULT_THEME_STRING_METHOD); 978 } 979 980 /** Returns true if the SetupWizard supports the dynamic color during setup flow. */ isSetupWizardDynamicColorEnabled(@onNull Context context)981 public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { 982 if (applyDynamicColorBundle == null) { 983 try { 984 applyDynamicColorBundle = 985 context 986 .getContentResolver() 987 .call( 988 getContentUri(), 989 IS_DYNAMIC_COLOR_ENABLED_METHOD, 990 /* arg= */ null, 991 /* extras= */ null); 992 } catch (IllegalArgumentException | SecurityException exception) { 993 Log.w(TAG, "SetupWizard dynamic color supporting status unknown; return as false."); 994 applyDynamicColorBundle = null; 995 return false; 996 } 997 } 998 999 return (applyDynamicColorBundle != null 1000 && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); 1001 } 1002 1003 /** Returns {@code true} if the SetupWizard supports the full dynamic color during setup flow. */ isSetupWizardFullDynamicColorEnabled(@onNull Context context)1004 public static boolean isSetupWizardFullDynamicColorEnabled(@NonNull Context context) { 1005 if (applyFullDynamicColorBundle == null) { 1006 try { 1007 applyFullDynamicColorBundle = 1008 context 1009 .getContentResolver() 1010 .call( 1011 getContentUri(), 1012 IS_FULL_DYNAMIC_COLOR_ENABLED_METHOD, 1013 /* arg= */ null, 1014 /* extras= */ null); 1015 } catch (IllegalArgumentException | SecurityException exception) { 1016 Log.w(TAG, "SetupWizard full dynamic color supporting status unknown; return as false."); 1017 applyFullDynamicColorBundle = null; 1018 return false; 1019 } 1020 } 1021 1022 return (applyFullDynamicColorBundle != null 1023 && applyFullDynamicColorBundle.getBoolean(IS_FULL_DYNAMIC_COLOR_ENABLED_METHOD, false)); 1024 } 1025 1026 /** Returns true if the SetupWizard supports the one-pane embedded activity during setup flow. */ isEmbeddedActivityOnePaneEnabled(@onNull Context context)1027 public static boolean isEmbeddedActivityOnePaneEnabled(@NonNull Context context) { 1028 if (applyEmbeddedActivityOnePaneBundle == null) { 1029 try { 1030 applyEmbeddedActivityOnePaneBundle = 1031 context 1032 .getContentResolver() 1033 .call( 1034 getContentUri(), 1035 IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, 1036 /* arg= */ null, 1037 /* extras= */ null); 1038 } catch (IllegalArgumentException | SecurityException exception) { 1039 Log.w( 1040 TAG, 1041 "SetupWizard one-pane support in embedded activity status unknown; return as false."); 1042 applyEmbeddedActivityOnePaneBundle = null; 1043 return false; 1044 } 1045 } 1046 1047 return (applyEmbeddedActivityOnePaneBundle != null 1048 && applyEmbeddedActivityOnePaneBundle.getBoolean( 1049 IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, false)); 1050 } 1051 1052 /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ isNeutralButtonStyleEnabled(@onNull Context context)1053 public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { 1054 if (applyNeutralButtonStyleBundle == null) { 1055 try { 1056 applyNeutralButtonStyleBundle = 1057 context 1058 .getContentResolver() 1059 .call( 1060 getContentUri(), 1061 IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, 1062 /* arg= */ null, 1063 /* extras= */ null); 1064 } catch (IllegalArgumentException | SecurityException exception) { 1065 Log.w(TAG, "Neutral button style supporting status unknown; return as false."); 1066 applyNeutralButtonStyleBundle = null; 1067 return false; 1068 } 1069 } 1070 1071 return (applyNeutralButtonStyleBundle != null 1072 && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); 1073 } 1074 1075 /** Returns true if the SetupWizard supports the font weight customization during setup flow. */ isFontWeightEnabled(@onNull Context context)1076 public static boolean isFontWeightEnabled(@NonNull Context context) { 1077 if (applyFontWeightBundle == null) { 1078 try { 1079 applyFontWeightBundle = 1080 context 1081 .getContentResolver() 1082 .call( 1083 getContentUri(), 1084 IS_FONT_WEIGHT_ENABLED_METHOD, 1085 /* arg= */ null, 1086 /* extras= */ null); 1087 } catch (IllegalArgumentException | SecurityException exception) { 1088 Log.w(TAG, "Font weight supporting status unknown; return as false."); 1089 applyFontWeightBundle = null; 1090 return false; 1091 } 1092 } 1093 1094 return (applyFontWeightBundle != null 1095 && applyFontWeightBundle.getBoolean(IS_FONT_WEIGHT_ENABLED_METHOD, true)); 1096 } 1097 1098 /** 1099 * Returns the system property to indicate the transition settings is set by Glif theme rather 1100 * than the client. 1101 */ isGlifThemeControlledTransitionApplied(@onNull Context context)1102 public static boolean isGlifThemeControlledTransitionApplied(@NonNull Context context) { 1103 if (applyTransitionBundle == null || applyTransitionBundle.isEmpty()) { 1104 try { 1105 applyTransitionBundle = 1106 context 1107 .getContentResolver() 1108 .call( 1109 getContentUri(), 1110 APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, 1111 /* arg= */ null, 1112 /* extras= */ null); 1113 } catch (IllegalArgumentException | SecurityException exception) { 1114 Log.w( 1115 TAG, 1116 "applyGlifThemeControlledTransition unknown; return applyGlifThemeControlledTransition" 1117 + " as default value"); 1118 } 1119 } 1120 if (applyTransitionBundle != null && !applyTransitionBundle.isEmpty()) { 1121 return applyTransitionBundle.getBoolean(APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, true); 1122 } 1123 return true; 1124 } 1125 1126 /** Returns a boolean indicate whether the force two pane feature enable or not. */ isForceTwoPaneEnabled(@onNull Context context)1127 public static boolean isForceTwoPaneEnabled(@NonNull Context context) { 1128 if (applyForceTwoPaneBundle == null || applyForceTwoPaneBundle.isEmpty()) { 1129 try { 1130 applyForceTwoPaneBundle = 1131 context 1132 .getContentResolver() 1133 .call( 1134 getContentUri(), 1135 IS_FORCE_TWO_PANE_ENABLED_METHOD, 1136 /* arg= */ null, 1137 /* extras= */ null); 1138 } catch (IllegalArgumentException | SecurityException exception) { 1139 Log.w(TAG, "isForceTwoPaneEnabled status is unknown; return as false."); 1140 } 1141 } 1142 if (applyForceTwoPaneBundle != null && !applyForceTwoPaneBundle.isEmpty()) { 1143 return applyForceTwoPaneBundle.getBoolean(IS_FORCE_TWO_PANE_ENABLED_METHOD, false); 1144 } 1145 return false; 1146 } 1147 1148 /** Returns whether the keyboard focus enhancement is enabled. */ isKeyboardFocusEnhancementEnabled(@onNull Context context)1149 public static boolean isKeyboardFocusEnhancementEnabled(@NonNull Context context) { 1150 if (keyboardFocusEnhancementBundle == null || keyboardFocusEnhancementBundle.isEmpty()) { 1151 try { 1152 keyboardFocusEnhancementBundle = 1153 context 1154 .getContentResolver() 1155 .call( 1156 getContentUri(), 1157 IS_KEYBOARD_FOCUS_ENHANCEMENT_ENABLED_METHOD, 1158 /* arg= */ null, 1159 /* extras= */ null); 1160 } catch (IllegalArgumentException | SecurityException exception) { 1161 Log.w(TAG, "SetupWizard keyboard focus enhancement status unknown; return as false."); 1162 keyboardFocusEnhancementBundle = null; 1163 return false; 1164 } 1165 } 1166 if (keyboardFocusEnhancementBundle == null || keyboardFocusEnhancementBundle.isEmpty()) { 1167 return false; 1168 } 1169 return keyboardFocusEnhancementBundle.getBoolean(IS_KEYBOARD_FOCUS_ENHANCEMENT_ENABLED_METHOD); 1170 } 1171 1172 /** 1173 * Returns true if the SetupWizard supports Glif Expressive style inside or outside setup flow. 1174 */ isGlifExpressiveEnabled(@onNull Context context)1175 public static boolean isGlifExpressiveEnabled(@NonNull Context context) { 1176 1177 if (applyGlifExpressiveBundle == null || applyGlifExpressiveBundle.isEmpty()) { 1178 try { 1179 applyGlifExpressiveBundle = 1180 context 1181 .getContentResolver() 1182 .call( 1183 getContentUri(), 1184 IS_GLIF_EXPRESSIVE_ENABLED, 1185 /* arg= */ null, 1186 /* extras= */ null); 1187 } catch (IllegalArgumentException | SecurityException exception) { 1188 Log.w(TAG, "isGlifExpressiveEnabled status is unknown; return as false."); 1189 } 1190 } 1191 if (applyGlifExpressiveBundle != null && !applyGlifExpressiveBundle.isEmpty()) { 1192 return applyGlifExpressiveBundle.getBoolean(IS_GLIF_EXPRESSIVE_ENABLED, false); 1193 } 1194 1195 return false; 1196 } 1197 1198 /** Returns true if the SetupWizard enable the UI component logging. */ isEnhancedSetupDesignMetricsEnabled(@onNull Context context)1199 public static boolean isEnhancedSetupDesignMetricsEnabled(@NonNull Context context) { 1200 if (enableMetricsLoggingBundle == null || enableMetricsLoggingBundle.isEmpty()) { 1201 try { 1202 enableMetricsLoggingBundle = 1203 context 1204 .getContentResolver() 1205 .call( 1206 getContentUri(), 1207 IS_ENHANCED_SETUP_DESIGN_METRICS_ENABLED, 1208 /* arg= */ null, 1209 /* extras= */ null); 1210 } catch (IllegalArgumentException | SecurityException exception) { 1211 Log.w(TAG, "Method " + IS_ENHANCED_SETUP_DESIGN_METRICS_ENABLED + " is unknown"); 1212 enableMetricsLoggingBundle = null; 1213 return false; 1214 } 1215 } 1216 1217 if (enableMetricsLoggingBundle != null && !enableMetricsLoggingBundle.isEmpty()) { 1218 return enableMetricsLoggingBundle.getBoolean(IS_ENHANCED_SETUP_DESIGN_METRICS_ENABLED, false); 1219 } 1220 1221 return false; 1222 } 1223 1224 @VisibleForTesting getContentUri()1225 static Uri getContentUri() { 1226 return new Uri.Builder() 1227 .scheme(ContentResolver.SCHEME_CONTENT) 1228 .authority(SUW_AUTHORITY) 1229 .build(); 1230 } 1231 getTypedValueFromResource(Resources resource, int resId, int type)1232 private static TypedValue getTypedValueFromResource(Resources resource, int resId, int type) { 1233 TypedValue value = new TypedValue(); 1234 resource.getValue(resId, value, true); 1235 if (value.type != type) { 1236 throw new NotFoundException( 1237 "Resource ID #0x" 1238 + Integer.toHexString(resId) 1239 + " type #0x" 1240 + Integer.toHexString(value.type) 1241 + " is not valid"); 1242 } 1243 return value; 1244 } 1245 getDimensionFromTypedValue(Context context, TypedValue value)1246 private static float getDimensionFromTypedValue(Context context, TypedValue value) { 1247 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 1248 return value.getDimension(displayMetrics); 1249 } 1250 registerContentObserver(Context context)1251 private static void registerContentObserver(Context context) { 1252 if (isSetupWizardDayNightEnabled(context)) { 1253 if (contentObserver != null) { 1254 unregisterContentObserver(context); 1255 } 1256 1257 Uri contentUri = getContentUri(); 1258 try { 1259 contentObserver = 1260 new ContentObserver(null) { 1261 @Override 1262 public void onChange(boolean selfChange) { 1263 super.onChange(selfChange); 1264 resetInstance(); 1265 } 1266 }; 1267 context 1268 .getContentResolver() 1269 .registerContentObserver(contentUri, /* notifyForDescendants= */ true, contentObserver); 1270 } catch (SecurityException | NullPointerException | IllegalArgumentException e) { 1271 Log.w(TAG, "Failed to register content observer for " + contentUri + ": " + e); 1272 } 1273 } 1274 } 1275 unregisterContentObserver(Context context)1276 private static void unregisterContentObserver(Context context) { 1277 try { 1278 context.getContentResolver().unregisterContentObserver(contentObserver); 1279 contentObserver = null; 1280 } catch (SecurityException | NullPointerException | IllegalArgumentException e) { 1281 Log.w(TAG, "Failed to unregister content observer: " + e); 1282 } 1283 } 1284 } 1285