diff options
Diffstat (limited to 'partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java')
-rw-r--r-- | partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java | 318 |
1 files changed, 297 insertions, 21 deletions
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 7b9f65b..2ca8876 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -1,11 +1,11 @@ /* - * Copyright 2018 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -18,22 +18,27 @@ package com.google.android.setupcompat.partnerconfig; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; +import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.TypedValue; import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; +import java.util.ArrayList; +import java.util.Collections; import java.util.EnumMap; +import java.util.List; /** The helper reads and caches the partner configurations from SUW. */ public class PartnerConfigHelper { @@ -47,6 +52,22 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig"; + @VisibleForTesting + public static final String IS_SUW_DAY_NIGHT_ENABLED_METHOD = "isSuwDayNightEnabled"; + + @VisibleForTesting + public static final String IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD = + "isExtendedPartnerConfigEnabled"; + + @VisibleForTesting + public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled"; + + @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; + + @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; + + @VisibleForTesting public static Bundle applyDynamicColorBundle = null; + private static PartnerConfigHelper instance = null; @VisibleForTesting Bundle resultBundle = null; @@ -54,15 +75,44 @@ public class PartnerConfigHelper { @VisibleForTesting final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class); + private static ContentObserver contentObserver; + + private static int savedConfigUiMode; + + private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + public static synchronized PartnerConfigHelper get(@NonNull Context context) { - if (instance == null) { + if (!isValidInstance(context)) { instance = new PartnerConfigHelper(context); } return instance; } + private static boolean isValidInstance(@NonNull Context context) { + Configuration currentConfig = context.getResources().getConfiguration(); + if (instance == null) { + savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + savedOrientation = currentConfig.orientation; + return false; + } else { + if (isSetupWizardDayNightEnabled(context) + && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) { + savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + resetInstance(); + return false; + } else if (currentConfig.orientation != savedOrientation) { + savedOrientation = currentConfig.orientation; + resetInstance(); + return false; + } + } + return true; + } + private PartnerConfigHelper(Context context) { getPartnerConfigBundle(context); + + registerContentObserver(context); } /** @@ -75,12 +125,22 @@ public class PartnerConfigHelper { } /** + * Returns whether the given {@code resourceConfig} are available. This is true if setup wizard's + * content provider returns us a non-empty bundle, and this result bundle includes the given + * {@code resourceConfig} even if all the values are default, and none are customized by the + * overlay APK. + */ + public boolean isPartnerConfigAvailable(PartnerConfig resourceConfig) { + return isAvailable() && resultBundle.containsKey(resourceConfig.getResourceName()); + } + + /** * Returns the color of given {@code resourceConfig}, or 0 if the given {@code resourceConfig} is * not found. If the {@code ResourceType} of the given {@code resourceConfig} is not color, * IllegalArgumentException will be thrown. * * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource + * @param resourceConfig The {@link PartnerConfig} of target resource */ @ColorInt public int getColor(@NonNull Context context, PartnerConfig resourceConfig) { @@ -99,6 +159,13 @@ public class PartnerConfigHelper { Resources resource = resourceEntry.getResources(); int resId = resourceEntry.getResourceId(); + // for @null + TypedValue outValue = new TypedValue(); + resource.getValue(resId, outValue, true); + if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { + return result; + } + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { result = resource.getColor(resId, null); } else { @@ -189,6 +256,38 @@ public class PartnerConfigHelper { } /** + * Returns the string array of the given {@code resourceConfig}, or {@code null} if the given + * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code + * resourceConfig} is not string, IllegalArgumentException will be thrown. + * + * @param context The context of client activity + * @param resourceConfig The {@code PartnerConfig} of target resource + */ + @NonNull + public List<String> getStringArray(@NonNull Context context, PartnerConfig resourceConfig) { + if (resourceConfig.getResourceType() != ResourceType.STRING_ARRAY) { + throw new IllegalArgumentException("Not a string array resource"); + } + + String[] result; + List<String> listResult = new ArrayList<>(); + + try { + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getStringArray(resId); + Collections.addAll(listResult, result); + } catch (NullPointerException exception) { + // fall through + } + + return listResult; + } + + /** * Returns the boolean of given {@code resourceConfig}, or {@code defaultValue} if the given * {@code resourceName} is not found. If the {@code ResourceType} of the given {@code * resourceConfig} is not boolean, IllegalArgumentException will be thrown. @@ -233,8 +332,8 @@ public class PartnerConfigHelper { } /** - * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} not - * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code + * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} is + * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code * resourceConfig} is not dimension, will throw IllegalArgumentException. * * @param context The context of client activity @@ -316,6 +415,39 @@ public class PartnerConfigHelper { } /** + * Returns the integer of given {@code resourceConfig}. If the given {@code resourceConfig} is not + * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code + * resourceConfig} is not dimension, will throw IllegalArgumentException. + * + * @param context The context of client activity + * @param resourceConfig The {@code PartnerConfig} of target resource + * @param defaultValue The default value + */ + public int getInteger(@NonNull Context context, PartnerConfig resourceConfig, int defaultValue) { + if (resourceConfig.getResourceType() != ResourceType.INTEGER) { + throw new IllegalArgumentException("Not a integer resource"); + } + + if (partnerResourceCache.containsKey(resourceConfig)) { + return (int) partnerResourceCache.get(resourceConfig); + } + + int result = defaultValue; + try { + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getInteger(resId); + partnerResourceCache.put(resourceConfig, result); + } catch (NullPointerException exception) { + // fall through + } + return result; + } + + /** * Returns the {@link ResourceEntry} of given {@code resourceConfig}, or {@code null} if the given * {@code resourceConfig} is not found. If the {@link ResourceType} of the given {@code * resourceConfig} is not illustration, IllegalArgumentException will be thrown. @@ -362,17 +494,14 @@ public class PartnerConfigHelper { private void getPartnerConfigBundle(Context context) { if (resultBundle == null || resultBundle.isEmpty()) { try { - Uri contentUri = - new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(SUW_AUTHORITY) - .appendPath(SUW_GET_PARTNER_CONFIG_METHOD) - .build(); resultBundle = context .getContentResolver() .call( - contentUri, SUW_GET_PARTNER_CONFIG_METHOD, /* arg= */ null, /* extras= */ null); + getContentUri(), + SUW_GET_PARTNER_CONFIG_METHOD, + /* arg= */ null, + /* extras= */ null); partnerResourceCache.clear(); } catch (IllegalArgumentException | SecurityException exception) { Log.w(TAG, "Fail to get config from suw provider"); @@ -381,21 +510,134 @@ public class PartnerConfigHelper { } @Nullable - private ResourceEntry getResourceEntryFromKey(Context context, String resourceName) { + @VisibleForTesting + ResourceEntry getResourceEntryFromKey(Context context, String resourceName) { Bundle resourceEntryBundle = resultBundle.getBundle(resourceName); Bundle fallbackBundle = resultBundle.getBundle(KEY_FALLBACK_CONFIG); if (fallbackBundle != null) { resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName)); } - return ResourceEntry.fromBundle(context, resourceEntryBundle); + + return adjustResourceEntryDayNightMode( + context, ResourceEntry.fromBundle(context, resourceEntryBundle)); + } + + /** + * Force to day mode if setup wizard does not support day/night mode and current system is in + * night mode. + */ + private static ResourceEntry adjustResourceEntryDayNightMode( + Context context, ResourceEntry resourceEntry) { + Resources resource = resourceEntry.getResources(); + Configuration configuration = resource.getConfiguration(); + if (!isSetupWizardDayNightEnabled(context) && Util.isNightMode(configuration)) { + if (resourceEntry == null) { + Log.w(TAG, "resourceEntry is null, skip to force day mode."); + return resourceEntry; + } + configuration.uiMode = + Configuration.UI_MODE_NIGHT_NO + | (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); + resource.updateConfiguration(configuration, resource.getDisplayMetrics()); + } + + return resourceEntry; } @VisibleForTesting - public static synchronized void resetForTesting() { + public static synchronized void resetInstance() { instance = null; + suwDayNightEnabledBundle = null; + applyExtendedPartnerConfigBundle = null; + applyDynamicColorBundle = null; } - private TypedValue getTypedValueFromResource(Resources resource, int resId, int type) { + /** + * Checks whether SetupWizard supports the DayNight theme during setup flow; if return false setup + * flow should force to light theme. + * + * <p>Returns true if the setupwizard is listening to system DayNight theme setting. + */ + public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) { + if (suwDayNightEnabledBundle == null) { + try { + suwDayNightEnabledBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_SUW_DAY_NIGHT_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "SetupWizard DayNight supporting status unknown; return as false."); + suwDayNightEnabledBundle = null; + return false; + } + } + + return (suwDayNightEnabledBundle != null + && suwDayNightEnabledBundle.getBoolean(IS_SUW_DAY_NIGHT_ENABLED_METHOD, false)); + } + + /** Returns true if the SetupWizard supports the extended partner configs during setup flow. */ + public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) { + if (applyExtendedPartnerConfigBundle == null) { + try { + applyExtendedPartnerConfigBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w( + TAG, + "SetupWizard extended partner configs supporting status unknown; return as false."); + applyExtendedPartnerConfigBundle = null; + return false; + } + } + + return (applyExtendedPartnerConfigBundle != null + && applyExtendedPartnerConfigBundle.getBoolean( + IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false)); + } + + /** Returns true if the SetupWizard supports the dynamic color during setup flow. */ + public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { + if (applyDynamicColorBundle == null) { + try { + applyDynamicColorBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_DYNAMIC_COLOR_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "SetupWizard dynamic color supporting status unknown; return as false."); + applyDynamicColorBundle = null; + return false; + } + } + + return (applyDynamicColorBundle != null + && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); + } + + @VisibleForTesting + static Uri getContentUri() { + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SUW_AUTHORITY) + .build(); + } + + private static TypedValue getTypedValueFromResource(Resources resource, int resId, int type) { TypedValue value = new TypedValue(); resource.getValue(resId, value, true); if (value.type != type) { @@ -409,8 +651,42 @@ public class PartnerConfigHelper { return value; } - private float getDimensionFromTypedValue(Context context, TypedValue value) { + private static float getDimensionFromTypedValue(Context context, TypedValue value) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); return value.getDimension(displayMetrics); } + + private static void registerContentObserver(Context context) { + if (isSetupWizardDayNightEnabled(context)) { + if (contentObserver != null) { + unregisterContentObserver(context); + } + + Uri contentUri = getContentUri(); + try { + contentObserver = + new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + resetInstance(); + } + }; + context + .getContentResolver() + .registerContentObserver(contentUri, /* notifyForDescendants= */ true, contentObserver); + } catch (SecurityException | NullPointerException | IllegalArgumentException e) { + Log.w(TAG, "Failed to register content observer for " + contentUri + ": " + e); + } + } + } + + private static void unregisterContentObserver(Context context) { + try { + context.getContentResolver().unregisterContentObserver(contentObserver); + contentObserver = null; + } catch (SecurityException | NullPointerException | IllegalArgumentException e) { + Log.w(TAG, "Failed to unregister content observer: " + e); + } + } } |