diff options
Diffstat (limited to 'src/com/android')
101 files changed, 1643 insertions, 7895 deletions
diff --git a/src/com/android/customization/model/color/ColorBundle.java b/src/com/android/customization/model/color/ColorBundle.java deleted file mode 100644 index d34f3fc0..00000000 --- a/src/com/android/customization/model/color/ColorBundle.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2022 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.color; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.android.customization.model.ResourceConstants; -import com.android.systemui.monet.Style; -import com.android.wallpaper.R; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Represents a preset color available for the user to chose as their theming option. - */ -public class ColorBundle extends ColorOption { - - private final PreviewInfo mPreviewInfo; - - @VisibleForTesting ColorBundle(String title, - Map<String, String> overlayPackages, boolean isDefault, Style style, int index, - PreviewInfo previewInfo) { - super(title, overlayPackages, isDefault, style, index); - mPreviewInfo = previewInfo; - } - - @Override - public void bindThumbnailTile(View view) { - Resources res = view.getContext().getResources(); - int primaryColor = mPreviewInfo.resolvePrimaryColor(res); - int secondaryColor = mPreviewInfo.resolveSecondaryColor(res); - - for (int i = 0; i < mPreviewColorIds.length; i++) { - ImageView colorPreviewImageView = view.findViewById(mPreviewColorIds[i]); - int color = i % 2 == 0 ? primaryColor : secondaryColor; - colorPreviewImageView.getDrawable().setColorFilter(color, PorterDuff.Mode.SRC); - } - view.setContentDescription(getContentDescription(view.getContext())); - } - - @Override - public PreviewInfo getPreviewInfo() { - return mPreviewInfo; - } - - @Override - public int getLayoutResId() { - return R.layout.color_option; - } - - @Override - public String getSource() { - return ColorOptionsProvider.COLOR_SOURCE_PRESET; - } - - /** - * The preview information of {@link ColorBundle} - */ - public static class PreviewInfo implements ColorOption.PreviewInfo { - @ColorInt - public final int secondaryColorLight; - @ColorInt public final int secondaryColorDark; - // Monet system palette and accent colors - @ColorInt public final int primaryColorLight; - @ColorInt public final int primaryColorDark; - @Dimension - public final int bottomSheetCornerRadius; - - @ColorInt private int mOverrideSecondaryColorLight = Color.TRANSPARENT; - @ColorInt private int mOverrideSecondaryColorDark = Color.TRANSPARENT; - @ColorInt private int mOverridePrimaryColorLight = Color.TRANSPARENT; - @ColorInt private int mOverridePrimaryColorDark = Color.TRANSPARENT; - - private PreviewInfo( - int secondaryColorLight, int secondaryColorDark, int colorSystemPaletteLight, - int primaryColorDark, @Dimension int cornerRadius) { - this.secondaryColorLight = secondaryColorLight; - this.secondaryColorDark = secondaryColorDark; - this.primaryColorLight = colorSystemPaletteLight; - this.primaryColorDark = primaryColorDark; - this.bottomSheetCornerRadius = cornerRadius; - } - - /** - * Returns the accent color to be applied corresponding with the current configuration's - * UI mode. - * @return one of {@link #secondaryColorDark} or {@link #secondaryColorLight} - */ - @ColorInt - public int resolveSecondaryColor(Resources res) { - boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - if (mOverrideSecondaryColorDark != Color.TRANSPARENT - || mOverrideSecondaryColorLight != Color.TRANSPARENT) { - return night ? mOverrideSecondaryColorDark : mOverrideSecondaryColorLight; - } - return night ? secondaryColorDark : secondaryColorLight; - } - - /** - * Returns the palette (main) color to be applied corresponding with the current - * configuration's UI mode. - * @return one of {@link #secondaryColorDark} or {@link #secondaryColorLight} - */ - @ColorInt - public int resolvePrimaryColor(Resources res) { - boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - if (mOverridePrimaryColorDark != Color.TRANSPARENT - || mOverridePrimaryColorLight != Color.TRANSPARENT) { - return night ? mOverridePrimaryColorDark : mOverridePrimaryColorLight; - } - return night ? primaryColorDark - : primaryColorLight; - } - - /** - * Sets accent colors to override the ones in this bundle - */ - public void setOverrideAccentColors(int overrideColorAccentLight, - int overrideColorAccentDark) { - mOverrideSecondaryColorLight = overrideColorAccentLight; - mOverrideSecondaryColorDark = overrideColorAccentDark; - } - - /** - * Sets palette colors to override the ones in this bundle - */ - public void setOverridePaletteColors(int overrideColorPaletteLight, - int overrideColorPaletteDark) { - mOverridePrimaryColorLight = overrideColorPaletteLight; - mOverridePrimaryColorDark = overrideColorPaletteDark; - } - } - - /** - * The builder of ColorBundle - */ - public static class Builder { - protected String mTitle; - @ColorInt private int mSecondaryColorLight = Color.TRANSPARENT; - @ColorInt private int mSecondaryColorDark = Color.TRANSPARENT; - // System and Monet colors - @ColorInt private int mPrimaryColorLight = Color.TRANSPARENT; - @ColorInt private int mPrimaryColorDark = Color.TRANSPARENT; - private boolean mIsDefault; - private Style mStyle = Style.TONAL_SPOT; - private int mIndex; - protected Map<String, String> mPackages = new HashMap<>(); - - /** - * Builds the ColorBundle - * @param context {@link Context} - * @return new {@link ColorBundle} object - */ - public ColorBundle build(Context context) { - if (mTitle == null) { - mTitle = context.getString(R.string.adaptive_color_title); - } - return new ColorBundle(mTitle, mPackages, mIsDefault, mStyle, mIndex, - createPreviewInfo(context)); - } - - /** - * Creates preview information - * @param context the {@link Context} - * @return the {@link PreviewInfo} object - */ - public PreviewInfo createPreviewInfo(@NonNull Context context) { - Resources system = context.getResources().getSystem(); - return new PreviewInfo(mSecondaryColorLight, - mSecondaryColorDark, mPrimaryColorLight, mPrimaryColorDark, - system.getDimensionPixelOffset( - system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS, "dimen", - ResourceConstants.ANDROID_PACKAGE))); - } - - public Map<String, String> getPackages() { - return Collections.unmodifiableMap(mPackages); - } - - /** - * Gets title of this {@link ColorBundle} object - * @return title string - */ - public String getTitle() { - return mTitle; - } - - /** - * Sets title of bundle - * @param title specified title - * @return this of {@link Builder} - */ - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - /** - * Sets color accent (light) - * @param colorSecondaryLight color accent light in {@link ColorInt} - * @return this of {@link Builder} - */ - public Builder setColorSecondaryLight(@ColorInt int colorSecondaryLight) { - mSecondaryColorLight = colorSecondaryLight; - return this; - } - - /** - * Sets color accent (dark) - * @param colorSecondaryDark color accent dark in {@link ColorInt} - * @return this of {@link Builder} - */ - public Builder setColorSecondaryDark(@ColorInt int colorSecondaryDark) { - mSecondaryColorDark = colorSecondaryDark; - return this; - } - - /** - * Sets color system palette (light) - * @param colorPrimaryLight color system palette in {@link ColorInt} - * @return this of {@link Builder} - */ - public Builder setColorPrimaryLight(@ColorInt int colorPrimaryLight) { - mPrimaryColorLight = colorPrimaryLight; - return this; - } - - /** - * Sets color system palette (dark) - * @param colorPrimaryDark color system palette in {@link ColorInt} - * @return this of {@link Builder} - */ - public Builder setColorPrimaryDark(@ColorInt int colorPrimaryDark) { - mPrimaryColorDark = colorPrimaryDark; - return this; - } - - /** - * Sets overlay package for bundle - * @param category the category of bundle - * @param packageName tha name of package in the category - * @return this of {@link Builder} - */ - public Builder addOverlayPackage(String category, String packageName) { - mPackages.put(category, packageName); - return this; - } - - /** - * Sets the style of this color seed - * @param style color style of {@link Style} - * @return this of {@link Builder} - */ - public Builder setStyle(Style style) { - mStyle = style; - return this; - } - - /** - * Sets color option index of bundle - * @param index color option index - * @return this of {@link Builder} - */ - public Builder setIndex(int index) { - mIndex = index; - return this; - } - - /** - * Sets as default bundle - * @return this of {@link Builder} - */ - public Builder asDefault() { - mIsDefault = true; - return this; - } - } -} diff --git a/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java b/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java deleted file mode 100644 index 55b637f6..00000000 --- a/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (C) 2022 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.color; - -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE; -import static com.android.customization.model.color.ColorUtils.toColorString; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.ColorInt; - -import com.android.systemui.monet.ColorScheme; -import com.android.systemui.monet.Style; - -/** - * Utility class to read all the details of a color bundle for previewing it - * (eg, actual color values) - */ -class ColorBundlePreviewExtractor { - - private static final String TAG = "ColorBundlePreviewExtractor"; - - private final PackageManager mPackageManager; - - ColorBundlePreviewExtractor(Context context) { - mPackageManager = context.getPackageManager(); - } - - void addSecondaryColor(ColorBundle.Builder builder, @ColorInt int color) { - ColorScheme darkColorScheme = new ColorScheme(color, true); - ColorScheme lightColorScheme = new ColorScheme(color, false); - int lightSecondary = lightColorScheme.getAccentColor(); - int darkSecondary = darkColorScheme.getAccentColor(); - builder.addOverlayPackage(OVERLAY_CATEGORY_COLOR, toColorString(color)) - .setColorSecondaryLight(lightSecondary) - .setColorSecondaryDark(darkSecondary); - } - - void addPrimaryColor(ColorBundle.Builder builder, @ColorInt int color) { - ColorScheme darkColorScheme = new ColorScheme(color, true); - ColorScheme lightColorScheme = new ColorScheme(color, false); - int lightPrimary = lightColorScheme.getAccentColor(); - int darkPrimary = darkColorScheme.getAccentColor(); - builder.addOverlayPackage(OVERLAY_CATEGORY_SYSTEM_PALETTE, toColorString(color)) - .setColorPrimaryLight(lightPrimary) - .setColorPrimaryDark(darkPrimary); - } - - void addColorStyle(ColorBundle.Builder builder, String styleName) { - Style s = Style.TONAL_SPOT; - if (!TextUtils.isEmpty(styleName)) { - try { - s = Style.valueOf(styleName); - } catch (IllegalArgumentException e) { - Log.i(TAG, "Unknown style : " + styleName + ". Will default to TONAL_SPOT."); - } - } - builder.setStyle(s); - } -} diff --git a/src/com/android/customization/model/color/ColorCustomizationManager.java b/src/com/android/customization/model/color/ColorCustomizationManager.java index 790c86f2..a09efd26 100644 --- a/src/com/android/customization/model/color/ColorCustomizationManager.java +++ b/src/com/android/customization/model/color/ColorCustomizationManager.java @@ -15,6 +15,11 @@ */ package com.android.customization.model.color; +import static android.stats.style.StyleEnums.COLOR_SOURCE_HOME_SCREEN_WALLPAPER; +import static android.stats.style.StyleEnums.COLOR_SOURCE_LOCK_SCREEN_WALLPAPER; +import static android.stats.style.StyleEnums.COLOR_SOURCE_PRESET_COLOR; +import static android.stats.style.StyleEnums.COLOR_SOURCE_UNSPECIFIED; + import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE; import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET; @@ -27,6 +32,7 @@ import android.app.WallpaperColors; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.graphics.Color; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -41,6 +47,7 @@ import com.android.customization.model.CustomizationManager; import com.android.customization.model.ResourceConstants; import com.android.customization.model.color.ColorOptionsProvider.ColorSource; import com.android.customization.model.theme.OverlayManagerCompat; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.wallpaper.R; import org.json.JSONArray; @@ -182,22 +189,7 @@ public class ColorCustomizationManager implements CustomizationManager<ColorOpti lockWallpaperColors = null; } mProvider.fetch(callback, reload, mHomeWallpaperColors, - lockWallpaperColors, /* shouldUseRevampedUi= */ false); - } - - /** - * Fetch options function for the customization hub revamped UI - * - * TODO (b/276417460): refactor to reduce code repetition with the other fetch options function - */ - public void fetchRevampedUIOptions(OptionsFetchedListener<ColorOption> callback, - boolean reload) { - WallpaperColors lockWallpaperColors = mLockWallpaperColors; - if (lockWallpaperColors != null && mLockWallpaperColors.equals(mHomeWallpaperColors)) { - lockWallpaperColors = null; - } - mProvider.fetch(callback, reload, mHomeWallpaperColors, - lockWallpaperColors, /* shouldUseRevampedUi= */ true); + lockWallpaperColors); } /** @@ -220,6 +212,38 @@ public class ColorCustomizationManager implements CustomizationManager<ColorOpti return mCurrentOverlays; } + /** */ + public int getCurrentColorSourceForLogging() { + String colorSource = getCurrentColorSource(); + if (colorSource == null) { + return COLOR_SOURCE_UNSPECIFIED; + } + return switch (colorSource) { + case ColorOptionsProvider.COLOR_SOURCE_PRESET -> COLOR_SOURCE_PRESET_COLOR; + case ColorOptionsProvider.COLOR_SOURCE_HOME -> COLOR_SOURCE_HOME_SCREEN_WALLPAPER; + case ColorOptionsProvider.COLOR_SOURCE_LOCK -> COLOR_SOURCE_LOCK_SCREEN_WALLPAPER; + default -> COLOR_SOURCE_UNSPECIFIED; + }; + } + + /** */ + public int getCurrentStyleForLogging() { + String style = getCurrentStyle(); + return style != null ? style.hashCode() : 0; + } + + /** */ + public int getCurrentSeedColorForLogging() { + String seedColor = getCurrentOverlays().get(OVERLAY_CATEGORY_SYSTEM_PALETTE); + if (seedColor == null || seedColor.isEmpty()) { + return ThemesUserEventLogger.NULL_SEED_COLOR; + } + if (!seedColor.startsWith("#")) { + seedColor = "#" + seedColor; + } + return Color.parseColor(seedColor); + } + /** * @return The source of the currently applied color. One of * {@link ColorOptionsProvider#COLOR_SOURCE_HOME},{@link ColorOptionsProvider#COLOR_SOURCE_LOCK} diff --git a/src/com/android/customization/model/color/ColorOption.java b/src/com/android/customization/model/color/ColorOption.java index 216bb9ba..f57aa860 100644 --- a/src/com/android/customization/model/color/ColorOption.java +++ b/src/com/android/customization/model/color/ColorOption.java @@ -19,6 +19,7 @@ import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE; import android.content.Context; +import android.graphics.Color; import android.text.TextUtils; import android.util.Log; @@ -27,6 +28,7 @@ import androidx.annotation.VisibleForTesting; import com.android.customization.model.CustomizationManager; import com.android.customization.model.CustomizationOption; import com.android.customization.model.color.ColorOptionsProvider.ColorSource; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.systemui.monet.Style; import com.android.wallpaper.R; @@ -52,8 +54,6 @@ public abstract class ColorOption implements CustomizationOption<ColorOption> { static final String TIMESTAMP_FIELD = "_applied_timestamp"; protected final Map<String, String> mPackagesByCategory; - protected final int[] mPreviewColorIds = {R.id.color_preview_0, R.id.color_preview_1, - R.id.color_preview_2, R.id.color_preview_3}; private final String mTitle; private final boolean mIsDefault; private final Style mStyle; @@ -86,6 +86,9 @@ public abstract class ColorOption implements CustomizationOption<ColorOption> { if (mIsDefault) { String serializedOverlays = colorManager.getStoredOverlays(); + // a default color option is active if the manager has no stored overlays or current + // overlays, or the stored overlay does not contain either category system palette or + // category color return (TextUtils.isEmpty(serializedOverlays) || EMPTY_JSON.equals(serializedOverlays) || colorManager.getCurrentOverlays().isEmpty() || !(serializedOverlays.contains( OVERLAY_CATEGORY_SYSTEM_PALETTE) || serializedOverlays.contains( @@ -100,6 +103,22 @@ public abstract class ColorOption implements CustomizationOption<ColorOption> { } /** + * Gets the seed color from the overlay packages for logging. + * + * @return an int representing the seed color, or NULL_SEED_COLOR + */ + public int getSeedColorForLogging() { + String seedColor = mPackagesByCategory.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); + if (seedColor == null || seedColor.isEmpty()) { + return ThemesUserEventLogger.NULL_SEED_COLOR; + } + if (!seedColor.startsWith("#")) { + seedColor = "#" + seedColor; + } + return Color.parseColor(seedColor); + } + + /** * This is similar to #equals() but it only compares this theme's packages with the other, that * is, it will return true if applying this theme has the same effect of applying the given one. */ @@ -208,6 +227,12 @@ public abstract class ColorOption implements CustomizationOption<ColorOption> { public abstract String getSource(); /** + * @return the source of this color option for logging + */ + @ThemesUserEventLogger.ColorSource + public abstract int getSourceForLogging(); + + /** * @return the style of this color option */ public Style getStyle() { @@ -215,6 +240,11 @@ public abstract class ColorOption implements CustomizationOption<ColorOption> { } /** + * @return the style of this color option for logging + */ + public abstract int getStyleForLogging(); + + /** * @return the index of this color option */ public int getIndex() { diff --git a/src/com/android/customization/model/color/ColorOptionImpl.kt b/src/com/android/customization/model/color/ColorOptionImpl.kt index 3273ce26..f0905283 100644 --- a/src/com/android/customization/model/color/ColorOptionImpl.kt +++ b/src/com/android/customization/model/color/ColorOptionImpl.kt @@ -17,6 +17,7 @@ package com.android.customization.model.color import android.content.Context +import android.stats.style.StyleEnums import android.view.View import androidx.annotation.ColorInt import com.android.customization.model.color.ColorOptionsProvider.ColorSource @@ -68,6 +69,17 @@ class ColorOptionImpl( return source } + override fun getSourceForLogging(): Int { + return when (getSource()) { + ColorOptionsProvider.COLOR_SOURCE_PRESET -> StyleEnums.COLOR_SOURCE_PRESET_COLOR + ColorOptionsProvider.COLOR_SOURCE_HOME -> StyleEnums.COLOR_SOURCE_HOME_SCREEN_WALLPAPER + ColorOptionsProvider.COLOR_SOURCE_LOCK -> StyleEnums.COLOR_SOURCE_LOCK_SCREEN_WALLPAPER + else -> StyleEnums.COLOR_SOURCE_UNSPECIFIED + } + } + + override fun getStyleForLogging(): Int = style.toString().hashCode() + class Builder { var title: String? = null diff --git a/src/com/android/customization/model/color/ColorOptionsProvider.java b/src/com/android/customization/model/color/ColorOptionsProvider.java index 166b4da7..9703907d 100644 --- a/src/com/android/customization/model/color/ColorOptionsProvider.java +++ b/src/com/android/customization/model/color/ColorOptionsProvider.java @@ -70,12 +70,9 @@ public interface ColorOptionsProvider { * @param homeWallpaperColors to get seed colors from * @param lockWallpaperColors WallpaperColors from the lockscreen wallpaper to get seeds from, * if different than homeWallpaperColors - * @param shouldUseRevampedUi fetches color options with new preview mappings for the revamped - * UI if set to true */ void fetch(OptionsFetchedListener<ColorOption> callback, boolean reload, @Nullable WallpaperColors homeWallpaperColors, - @Nullable WallpaperColors lockWallpaperColors, - boolean shouldUseRevampedUi + @Nullable WallpaperColors lockWallpaperColors ); } diff --git a/src/com/android/customization/model/color/ColorProvider.kt b/src/com/android/customization/model/color/ColorProvider.kt index 74a4051b..6fdfd2ca 100644 --- a/src/com/android/customization/model/color/ColorProvider.kt +++ b/src/com/android/customization/model/color/ColorProvider.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.withContext /** * Default implementation of {@link ColorOptionsProvider} that reads preset colors from a stub APK. + * TODO (b/311212666): Make [ColorProvider] and [ColorCustomizationManager] injectable */ class ColorProvider(private val context: Context, stubPackageName: String) : ResourcesApkProvider(context, stubPackageName), ColorOptionsProvider { @@ -68,8 +69,6 @@ class ColorProvider(private val context: Context, stubPackageName: String) : arrayOf(Style.TONAL_SPOT, Style.SPRITZ, Style.VIBRANT, Style.EXPRESSIVE) else arrayOf(Style.TONAL_SPOT) - private val monochromeEnabled = - InjectorProvider.getInjector().getFlags().isMonochromaticThemeEnabled(mContext) private var monochromeBundleName: String? = null private val scope = @@ -93,7 +92,6 @@ class ColorProvider(private val context: Context, stubPackageName: String) : reload: Boolean, homeWallpaperColors: WallpaperColors?, lockWallpaperColors: WallpaperColors?, - shouldUseRevampedUi: Boolean ) { val wallpaperColorsChanged = this.homeWallpaperColors != homeWallpaperColors || @@ -106,13 +104,12 @@ class ColorProvider(private val context: Context, stubPackageName: String) : scope.launch { try { if (colorBundles == null || reload) { - loadPreset(shouldUseRevampedUi) + loadPreset() } if (wallpaperColorsChanged || reload) { loadSeedColors( homeWallpaperColors, lockWallpaperColors, - shouldUseRevampedUi ) } } catch (e: Throwable) { @@ -138,7 +135,6 @@ class ColorProvider(private val context: Context, stubPackageName: String) : private fun loadSeedColors( homeWallpaperColors: WallpaperColors?, lockWallpaperColors: WallpaperColors?, - shouldUseRevampedUi: Boolean, ) { if (homeWallpaperColors == null) return @@ -159,7 +155,6 @@ class ColorProvider(private val context: Context, stubPackageName: String) : if (shouldLockColorsGoFirst) COLOR_SOURCE_LOCK else COLOR_SOURCE_HOME, true, bundles, - shouldUseRevampedUi ) // Second half of the colors buildColorSeeds( @@ -168,7 +163,6 @@ class ColorProvider(private val context: Context, stubPackageName: String) : if (shouldLockColorsGoFirst) COLOR_SOURCE_HOME else COLOR_SOURCE_LOCK, false, bundles, - shouldUseRevampedUi ) } else { buildColorSeeds( @@ -177,35 +171,20 @@ class ColorProvider(private val context: Context, stubPackageName: String) : COLOR_SOURCE_HOME, true, bundles, - shouldUseRevampedUi ) } - if (shouldUseRevampedUi) { - // Insert monochrome in the second position if it is enabled and included in preset - // colors - if (monochromeEnabled) { - monochromeBundleName?.let { - bundles.add( - 1, - buildRevampedUIPreset( - it, - -1, - Style.MONOCHROMATIC, - ColorType.WALLPAPER_COLOR - ) - ) - } + // Insert monochrome in the second position if it is enabled and included in preset + // colors + if (InjectorProvider.getInjector().getFlags().isMonochromaticThemeEnabled(mContext)) { + monochromeBundleName?.let { + bundles.add(1, buildPreset(it, -1, Style.MONOCHROMATIC, ColorType.WALLPAPER_COLOR)) } - bundles.addAll( - colorBundles?.filterNot { - (it as ColorOptionImpl).type == ColorType.WALLPAPER_COLOR - } - ?: emptyList() - ) - } else { - bundles.addAll(colorBundles?.filterNot { it is ColorSeedOption } ?: emptyList()) } + bundles.addAll( + colorBundles?.filterNot { (it as ColorOptionImpl).type == ColorType.WALLPAPER_COLOR } + ?: emptyList() + ) colorBundles = bundles } @@ -215,13 +194,12 @@ class ColorProvider(private val context: Context, stubPackageName: String) : source: String, containsDefault: Boolean, bundles: MutableList<ColorOption>, - shouldUseRevampedUi: Boolean, ) { val seedColors = ColorScheme.getSeedColors(wallpaperColors) val defaultSeed = seedColors.first() - buildBundle(defaultSeed, 0, containsDefault, source, bundles, shouldUseRevampedUi) + buildBundle(defaultSeed, 0, containsDefault, source, bundles) for ((i, colorInt) in seedColors.drop(1).take(maxColors - 1).withIndex()) { - buildBundle(colorInt, i + 1, false, source, bundles, shouldUseRevampedUi) + buildBundle(colorInt, i + 1, false, source, bundles) } } @@ -231,102 +209,41 @@ class ColorProvider(private val context: Context, stubPackageName: String) : isDefault: Boolean, source: String, bundles: MutableList<ColorOption>, - shouldUseRevampedUi: Boolean, ) { // TODO(b/202145216): Measure time cost in the loop. - if (shouldUseRevampedUi) { - for (style in styleList) { - val lightColorScheme = ColorScheme(colorInt, /* darkTheme= */ false, style) - val darkColorScheme = ColorScheme(colorInt, /* darkTheme= */ true, style) - val builder = ColorOptionImpl.Builder() - builder.lightColors = getRevampedUILightColorPreview(lightColorScheme) - builder.darkColors = getRevampedUIDarkColorPreview(darkColorScheme) - builder.addOverlayPackage( - OVERLAY_CATEGORY_SYSTEM_PALETTE, - if (isDefault) "" else toColorString(colorInt) - ) - builder.title = - when (style) { - Style.TONAL_SPOT -> - context.getString(R.string.content_description_dynamic_color_option) - Style.SPRITZ -> - context.getString(R.string.content_description_neutral_color_option) - Style.VIBRANT -> - context.getString(R.string.content_description_vibrant_color_option) - Style.EXPRESSIVE -> - context.getString(R.string.content_description_expressive_color_option) - else -> context.getString(R.string.content_description_dynamic_color_option) - } - builder.source = source - builder.style = style - // Color option index value starts from 1. - builder.index = i + 1 - builder.isDefault = isDefault - builder.type = ColorType.WALLPAPER_COLOR - bundles.add(builder.build()) - } - } else { - for (style in styleList) { - val lightColorScheme = ColorScheme(colorInt, /* darkTheme= */ false, style) - val darkColorScheme = ColorScheme(colorInt, /* darkTheme= */ true, style) - val builder = ColorSeedOption.Builder() - builder - .setLightColors(lightColorScheme.getLightColorPreview()) - .setDarkColors(darkColorScheme.getDarkColorPreview()) - .addOverlayPackage( - OVERLAY_CATEGORY_SYSTEM_PALETTE, - if (isDefault) "" else toColorString(colorInt) - ) - .addOverlayPackage( - OVERLAY_CATEGORY_COLOR, - if (isDefault) "" else toColorString(colorInt) - ) - .setSource(source) - .setStyle(style) - // Color option index value starts from 1. - .setIndex(i + 1) - - if (isDefault) builder.asDefault() - - bundles.add(builder.build()) - } - } - } - - /** - * Returns the colors for the light theme version of the preview of a ColorScheme based on this - * order: top left, top right, bottom left, bottom right - */ - @ColorInt - private fun ColorScheme.getLightColorPreview(): IntArray { - return when (this.style) { - Style.EXPRESSIVE -> - intArrayOf( - setAlphaComponent(this.accent1.s100, ALPHA_MASK), - setAlphaComponent(this.accent1.s100, ALPHA_MASK), - ColorStateList.valueOf(this.neutral2.s500).withLStar(80f).colors[0], - setAlphaComponent(this.accent2.s500, ALPHA_MASK) - ) - else -> - intArrayOf( - setAlphaComponent(this.accent1.s100, ALPHA_MASK), - setAlphaComponent(this.accent1.s100, ALPHA_MASK), - ColorStateList.valueOf(this.accent3.s500).withLStar(85f).colors[0], - setAlphaComponent(this.accent1.s500, ALPHA_MASK) - ) + for (style in styleList) { + val lightColorScheme = ColorScheme(colorInt, /* darkTheme= */ false, style) + val darkColorScheme = ColorScheme(colorInt, /* darkTheme= */ true, style) + val builder = ColorOptionImpl.Builder() + builder.lightColors = getLightColorPreview(lightColorScheme) + builder.darkColors = getDarkColorPreview(darkColorScheme) + builder.addOverlayPackage( + OVERLAY_CATEGORY_SYSTEM_PALETTE, + if (isDefault) "" else toColorString(colorInt) + ) + builder.title = + when (style) { + Style.TONAL_SPOT -> + context.getString(R.string.content_description_dynamic_color_option) + Style.SPRITZ -> + context.getString(R.string.content_description_neutral_color_option) + Style.VIBRANT -> + context.getString(R.string.content_description_vibrant_color_option) + Style.EXPRESSIVE -> + context.getString(R.string.content_description_expressive_color_option) + else -> context.getString(R.string.content_description_dynamic_color_option) + } + builder.source = source + builder.style = style + // Color option index value starts from 1. + builder.index = i + 1 + builder.isDefault = isDefault + builder.type = ColorType.WALLPAPER_COLOR + bundles.add(builder.build()) } } /** - * Returns the color for the dark theme version of the preview of a ColorScheme based on this - * order: top left, top right, bottom left, bottom right - */ - @ColorInt - private fun ColorScheme.getDarkColorPreview(): IntArray { - return getLightColorPreview() - } - - /** * Returns the light theme version of the Revamped UI preview of a ColorScheme based on this * order: top left, top right, bottom left, bottom right * @@ -334,7 +251,7 @@ class ColorProvider(private val context: Context, stubPackageName: String) : * LStar 85, and Tertiary LStar 70 */ @ColorInt - private fun getRevampedUILightColorPreview(colorScheme: ColorScheme): IntArray { + private fun getLightColorPreview(colorScheme: ColorScheme): IntArray { return intArrayOf( setAlphaComponent(colorScheme.accent1.s600, ALPHA_MASK), setAlphaComponent(colorScheme.accent1.s600, ALPHA_MASK), @@ -351,7 +268,7 @@ class ColorProvider(private val context: Context, stubPackageName: String) : * 35, and Tertiary LStar 70 */ @ColorInt - private fun getRevampedUIDarkColorPreview(colorScheme: ColorScheme): IntArray { + private fun getDarkColorPreview(colorScheme: ColorScheme): IntArray { return intArrayOf( setAlphaComponent(colorScheme.accent1.s200, ALPHA_MASK), setAlphaComponent(colorScheme.accent1.s200, ALPHA_MASK), @@ -368,7 +285,7 @@ class ColorProvider(private val context: Context, stubPackageName: String) : * LStar 85, and Tertiary LStar 70 */ @ColorInt - private fun getRevampedUILightMonochromePreview(colorScheme: ColorScheme): IntArray { + private fun getLightMonochromePreview(colorScheme: ColorScheme): IntArray { return intArrayOf( setAlphaComponent(colorScheme.accent1.s1000, ALPHA_MASK), setAlphaComponent(colorScheme.accent1.s1000, ALPHA_MASK), @@ -385,7 +302,7 @@ class ColorProvider(private val context: Context, stubPackageName: String) : * LStar 35, and Tertiary LStar 70 */ @ColorInt - private fun getRevampedUIDarkMonochromePreview(colorScheme: ColorScheme): IntArray { + private fun getDarkMonochromePreview(colorScheme: ColorScheme): IntArray { return intArrayOf( setAlphaComponent(colorScheme.accent1.s10, ALPHA_MASK), setAlphaComponent(colorScheme.accent1.s10, ALPHA_MASK), @@ -398,7 +315,7 @@ class ColorProvider(private val context: Context, stubPackageName: String) : * Returns the Revamped UI preview of a preset ColorScheme based on this order: top left, top * right, bottom left, bottom right */ - private fun getRevampedUIPresetColorPreview(colorScheme: ColorScheme, seed: Int): IntArray { + private fun getPresetColorPreview(colorScheme: ColorScheme, seed: Int): IntArray { val colors = when (colorScheme.style) { Style.FRUIT_SALAD -> intArrayOf(seed, colorScheme.accent1.s200) @@ -414,22 +331,8 @@ class ColorProvider(private val context: Context, stubPackageName: String) : ) } - private fun ColorScheme.getPresetColorPreview(seed: Int): IntArray { - return when (this.style) { - Style.FRUIT_SALAD -> intArrayOf(seed, this.accent1.s100) - Style.TONAL_SPOT -> intArrayOf(this.accentColor, this.accentColor) - Style.MONOCHROMATIC -> - intArrayOf( - setAlphaComponent(0x000000, 255), - setAlphaComponent(0xFFFFFF, 255), - ) - else -> intArrayOf(this.accent1.s100, this.accent1.s100) - } - } - - private suspend fun loadPreset(shouldUseRevampedUi: Boolean) = + private suspend fun loadPreset() = withContext(Dispatchers.IO) { - val extractor = ColorBundlePreviewExtractor(mContext) val bundles: MutableList<ColorOption> = ArrayList() val bundleNames = @@ -438,95 +341,50 @@ class ColorProvider(private val context: Context, stubPackageName: String) : var index = 1 val maxPresetColors = if (themeStyleEnabled) bundleNames.size else MAX_PRESET_COLORS - if (shouldUseRevampedUi) { - // keep track of whether monochrome is included in preset colors to determine - // inclusion in wallpaper colors - var hasMonochrome = false - for (bundleName in bundleNames.take(maxPresetColors)) { - if (themeStyleEnabled) { - val styleName = - try { - getItemStringFromStub(COLOR_BUNDLE_STYLE_PREFIX, bundleName) - } catch (e: Resources.NotFoundException) { - null - } - val style = - try { - if (styleName != null) Style.valueOf(styleName) - else Style.TONAL_SPOT - } catch (e: IllegalArgumentException) { - Style.TONAL_SPOT - } - - if (style == Style.MONOCHROMATIC) { - if (!monochromeEnabled) { - continue - } - hasMonochrome = true - monochromeBundleName = bundleName + // keep track of whether monochrome is included in preset colors to determine + // inclusion in wallpaper colors + var hasMonochrome = false + for (bundleName in bundleNames.take(maxPresetColors)) { + if (themeStyleEnabled) { + val styleName = + try { + getItemStringFromStub(COLOR_BUNDLE_STYLE_PREFIX, bundleName) + } catch (e: Resources.NotFoundException) { + null + } + val style = + try { + if (styleName != null) Style.valueOf(styleName) else Style.TONAL_SPOT + } catch (e: IllegalArgumentException) { + Style.TONAL_SPOT } - bundles.add(buildRevampedUIPreset(bundleName, index, style)) - } else { - bundles.add(buildRevampedUIPreset(bundleName, index, null)) - } - - index++ - } - if (!hasMonochrome) { - monochromeBundleName = null - } - } else { - for (bundleName in bundleNames.take(maxPresetColors)) { - val builder = ColorBundle.Builder() - builder.title = getItemStringFromStub(COLOR_BUNDLE_NAME_PREFIX, bundleName) - builder.setIndex(index) - val colorFromStub = - getItemColorFromStub(COLOR_BUNDLE_MAIN_COLOR_PREFIX, bundleName) - extractor.addPrimaryColor(builder, colorFromStub) - extractor.addSecondaryColor(builder, colorFromStub) - if (themeStyleEnabled) { - val styleName = - try { - getItemStringFromStub(COLOR_BUNDLE_STYLE_PREFIX, bundleName) - } catch (e: Resources.NotFoundException) { - null - } - extractor.addColorStyle(builder, styleName) - val style = - try { - if (styleName != null) Style.valueOf(styleName) - else Style.TONAL_SPOT - } catch (e: IllegalArgumentException) { - Style.TONAL_SPOT - } - if (style == Style.MONOCHROMATIC && !monochromeEnabled) { + if (style == Style.MONOCHROMATIC) { + if ( + !InjectorProvider.getInjector() + .getFlags() + .isMonochromaticThemeEnabled(mContext) + ) { continue } - - val darkColors = - ColorScheme(colorFromStub, /* darkTheme= */ true, style) - .getPresetColorPreview(colorFromStub) - val lightColors = - ColorScheme(colorFromStub, /* darkTheme= */ false, style) - .getPresetColorPreview(colorFromStub) - builder - .setColorPrimaryDark(darkColors[0]) - .setColorSecondaryDark(darkColors[1]) - builder - .setColorPrimaryLight(lightColors[0]) - .setColorSecondaryLight(lightColors[1]) + hasMonochrome = true + monochromeBundleName = bundleName } - - bundles.add(builder.build(mContext)) - index++ + bundles.add(buildPreset(bundleName, index, style)) + } else { + bundles.add(buildPreset(bundleName, index, null)) } + + index++ + } + if (!hasMonochrome) { + monochromeBundleName = null } colorBundles = bundles } - private fun buildRevampedUIPreset( + private fun buildPreset( bundleName: String, index: Int, style: Style? = null, @@ -554,12 +412,12 @@ class ColorProvider(private val context: Context, stubPackageName: String) : when (style) { Style.MONOCHROMATIC -> { - darkColors = getRevampedUIDarkMonochromePreview(darkColorScheme) - lightColors = getRevampedUILightMonochromePreview(lightColorScheme) + darkColors = getDarkMonochromePreview(darkColorScheme) + lightColors = getLightMonochromePreview(lightColorScheme) } else -> { - darkColors = getRevampedUIPresetColorPreview(darkColorScheme, colorFromStub) - lightColors = getRevampedUIPresetColorPreview(lightColorScheme, colorFromStub) + darkColors = getPresetColorPreview(darkColorScheme, colorFromStub) + lightColors = getPresetColorPreview(lightColorScheme, colorFromStub) } } } diff --git a/src/com/android/customization/model/color/ColorSeedOption.java b/src/com/android/customization/model/color/ColorSeedOption.java deleted file mode 100644 index ed38049e..00000000 --- a/src/com/android/customization/model/color/ColorSeedOption.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2022 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.color; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.PorterDuff.Mode; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.ColorInt; -import androidx.annotation.VisibleForTesting; - -import com.android.customization.model.color.ColorOptionsProvider.ColorSource; -import com.android.systemui.monet.Style; -import com.android.wallpaper.R; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Represents a seed color obtained from WallpaperColors, for the user to chose as their theming - * option. - */ -public class ColorSeedOption extends ColorOption { - - private final PreviewInfo mPreviewInfo; - @ColorSource - private final String mSource; - - @VisibleForTesting - ColorSeedOption(String title, Map<String, String> overlayPackages, boolean isDefault, - @ColorSource String source, Style style, int index, PreviewInfo previewInfo) { - super(title, overlayPackages, isDefault, style, index); - mSource = source; - mPreviewInfo = previewInfo; - } - - @Override - public PreviewInfo getPreviewInfo() { - return mPreviewInfo; - } - - @Override - public String getSource() { - return mSource; - } - - @Override - public int getLayoutResId() { - return R.layout.color_option; - } - - @Override - public void bindThumbnailTile(View view) { - Resources res = view.getContext().getResources(); - @ColorInt int[] colors = mPreviewInfo.resolveColors(res); - - for (int i = 0; i < mPreviewColorIds.length; i++) { - ImageView colorPreviewImageView = view.findViewById(mPreviewColorIds[i]); - colorPreviewImageView.getDrawable().setColorFilter(colors[i], Mode.SRC); - } - - view.setContentDescription(getContentDescription(view.getContext())); - } - - @Override - public CharSequence getContentDescription(Context context) { - // Override because we want all options with the same description. - return context.getString(R.string.wallpaper_color_title); - } - - /** - * The preview information of {@link ColorOption} - */ - public static class PreviewInfo implements ColorOption.PreviewInfo { - @ColorInt public int[] lightColors; - @ColorInt public int[] darkColors; - - private PreviewInfo(@ColorInt int[] lightColors, @ColorInt int[] darkColors) { - this.lightColors = lightColors; - this.darkColors = darkColors; - } - - /** - * Returns the colors to be applied corresponding with the current - * configuration's UI mode. - * @param res resources to read to the UI mode configuration from - * @return one of {@link #lightColors} or {@link #darkColors} - */ - @ColorInt - public int[] resolveColors(Resources res) { - boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - return night ? darkColors : lightColors; - } - - /** - * Returns the preview colors based on whether dark theme or light theme colors are - * requested. - * @param darkTheme if true, returns dark theme colors, otherwise returns light theme colors - * @return one of {@link #lightColors} or {@link #darkColors} - */ - @ColorInt - public int[] resolveColors(boolean darkTheme) { - return darkTheme ? darkColors : lightColors; - } - } - - /** - * The builder of ColorSeedOption - */ - public static class Builder { - protected String mTitle; - @ColorInt - private int[] mLightColors; - @ColorInt - private int[] mDarkColors; - @ColorSource - private String mSource; - private boolean mIsDefault; - private Style mStyle = Style.TONAL_SPOT; - private int mIndex; - protected Map<String, String> mPackages = new HashMap<>(); - - /** - * Builds the ColorSeedOption - * @return new {@link ColorOption} object - */ - public ColorSeedOption build() { - return new ColorSeedOption(mTitle, mPackages, mIsDefault, mSource, mStyle, mIndex, - createPreviewInfo()); - } - - /** - * Creates preview information - * @return the {@link PreviewInfo} object - */ - public PreviewInfo createPreviewInfo() { - return new PreviewInfo(mLightColors, mDarkColors); - } - - public Map<String, String> getPackages() { - return Collections.unmodifiableMap(mPackages); - } - - /** - * Gets title of {@link ColorOption} object - * @return title string - */ - public String getTitle() { - return mTitle; - } - - /** - * Sets title of bundle - * @param title specified title - * @return this of {@link ColorBundle.Builder} - */ - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - /** - * Sets the colors for preview in light mode - * @param lightColors {@link ColorInt} colors for light mode - * @return this of {@link Builder} - */ - public Builder setLightColors(@ColorInt int[] lightColors) { - mLightColors = lightColors; - return this; - } - - /** - * Sets the colors for preview in light mode - * @param darkColors {@link ColorInt} colors for light mode - * @return this of {@link Builder} - */ - public Builder setDarkColors(@ColorInt int[] darkColors) { - mDarkColors = darkColors; - return this; - } - - - /** - * Sets overlay package for bundle - * @param category the category of bundle - * @param packageName tha name of package in the category - * @return this of {@link Builder} - */ - public Builder addOverlayPackage(String category, String packageName) { - mPackages.put(category, packageName); - return this; - } - - /** - * Sets the source of this color seed - * @param source typically either {@link ColorOptionsProvider#COLOR_SOURCE_HOME} or - * {@link ColorOptionsProvider#COLOR_SOURCE_LOCK} - * @return this of {@link Builder} - */ - public Builder setSource(@ColorSource String source) { - mSource = source; - return this; - } - - /** - * Sets the source of this color seed - * @param style color style of {@link Style} - * @return this of {@link Builder} - */ - public Builder setStyle(Style style) { - mStyle = style; - return this; - } - - /** - * Sets color option index of seed - * @param index color option index - * @return this of {@link ColorBundle.Builder} - */ - public Builder setIndex(int index) { - mIndex = index; - return this; - } - - /** - * Sets as default bundle - * @return this of {@link Builder} - */ - public Builder asDefault() { - mIsDefault = true; - return this; - } - } -} diff --git a/src/com/android/customization/model/color/ThemedWallpaperColorResources.kt b/src/com/android/customization/model/color/ThemedWallpaperColorResources.kt new file mode 100644 index 00000000..906d9020 --- /dev/null +++ b/src/com/android/customization/model/color/ThemedWallpaperColorResources.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.customization.model.color + +import android.R +import android.app.WallpaperColors +import android.content.Context +import android.provider.Settings +import android.util.Log +import com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_THEME_STYLE +import com.android.systemui.monet.ColorScheme +import com.android.systemui.monet.Style +import org.json.JSONException +import org.json.JSONObject + +class ThemedWallpaperColorResources(wallpaperColors: WallpaperColors, context: Context) : + WallpaperColorResources(wallpaperColors) { + + init { + val wallpaperColorScheme = + ColorScheme( + wallpaperColors = wallpaperColors, + darkTheme = false, + style = fetchThemeStyleFromSetting(context) + ) + addOverlayColor(wallpaperColorScheme.neutral1, R.color.system_neutral1_10) + addOverlayColor(wallpaperColorScheme.neutral2, R.color.system_neutral2_10) + addOverlayColor(wallpaperColorScheme.accent1, R.color.system_accent1_10) + addOverlayColor(wallpaperColorScheme.accent2, R.color.system_accent2_10) + addOverlayColor(wallpaperColorScheme.accent3, R.color.system_accent3_10) + } + + private fun fetchThemeStyleFromSetting(context: Context): Style { + val overlayPackageJson = + Settings.Secure.getString( + context.contentResolver, + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + ) + return if (!overlayPackageJson.isNullOrEmpty()) { + try { + val jsonObject = JSONObject(overlayPackageJson) + Style.valueOf(jsonObject.getString(OVERLAY_CATEGORY_THEME_STYLE)) + } catch (e: (JSONException)) { + Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e) + Style.TONAL_SPOT + } catch (e: IllegalArgumentException) { + Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e) + Style.TONAL_SPOT + } + } else { + Style.TONAL_SPOT + } + } + + companion object { + private const val TAG = "ThemedWallpaperColorResources" + } +} diff --git a/src/com/android/customization/model/grid/GridOption.java b/src/com/android/customization/model/grid/GridOption.java index a5307c9e..347929c4 100644 --- a/src/com/android/customization/model/grid/GridOption.java +++ b/src/com/android/customization/model/grid/GridOption.java @@ -15,14 +15,11 @@ */ package com.android.customization.model.grid; -import android.content.Context; -import android.graphics.PorterDuff.Mode; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; -import android.widget.ImageView; import androidx.annotation.Nullable; @@ -30,7 +27,6 @@ import com.android.customization.model.CustomizationManager; import com.android.customization.model.CustomizationOption; import com.android.customization.widget.GridTileDrawable; import com.android.wallpaper.R; -import com.android.wallpaper.util.ResourceUtils; /** * Represents a grid layout option available in the current launcher. @@ -94,21 +90,7 @@ public class GridOption implements CustomizationOption<GridOption>, Parcelable { @Override public void bindThumbnailTile(View view) { - Context context = view.getContext(); - - int colorFilter = ResourceUtils.getColorAttr(context, - view.isActivated() - ? (mIsCurrent - ? android.R.attr.textColorPrimary - : android.R.attr.textColorPrimaryInverse) - : android.R.attr.textColorTertiary); - mTileDrawable.setColorFilter(colorFilter, Mode.SRC_ATOP); - ((ImageView) view.findViewById(R.id.grid_option_thumbnail)) - .setImageDrawable(mTileDrawable); - - int backgroundResource = view.isActivated() && !mIsCurrent - ? R.drawable.option_border_new_selection : R.drawable.option_border; - view.findViewById(R.id.option_tile).setBackgroundResource(backgroundResource); + // Do nothing. This function will no longer be used in the Revamped UI } @Override diff --git a/src/com/android/customization/model/grid/GridOptionViewModel.java b/src/com/android/customization/model/grid/GridOptionViewModel.java deleted file mode 100644 index 33fa8e179..00000000 --- a/src/com/android/customization/model/grid/GridOptionViewModel.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.grid; - -import androidx.lifecycle.SavedStateHandle; -import androidx.lifecycle.ViewModel; - -/** The class to store status of the grid fragment view. */ -public class GridOptionViewModel extends ViewModel { - private static final String SELECTED_OPTION_KEY = "selected_option"; - private static final String BOTTOM_ACTION_BAR_VISIBLE_KEY = "bottom_action_bar_visible"; - - private SavedStateHandle mState; - - public GridOptionViewModel(SavedStateHandle savedStateHandle) { - mState = savedStateHandle; - } - - /** Gets selected {@link GridOption} from {@link SavedStateHandle} */ - public GridOption getSelectedOption() { - return mState.get(SELECTED_OPTION_KEY); - } - - /** Sets selected {@link GridOption} to {@link SavedStateHandle} */ - public void setSelectedOption(GridOption selectedOption) { - mState.set(SELECTED_OPTION_KEY, selectedOption); - } - - /** Gets bottom action bar visible from {@link SavedStateHandle} */ - public boolean getBottomActionBarVisible() { - return mState.contains(BOTTOM_ACTION_BAR_VISIBLE_KEY) - ? mState.get(BOTTOM_ACTION_BAR_VISIBLE_KEY) - : false; - } - - /** Sets bottom action bar visible to {@link SavedStateHandle} */ - public void setBottomActionBarVisible(boolean bottomActionBarVisible) { - mState.set(BOTTOM_ACTION_BAR_VISIBLE_KEY, bottomActionBarVisible); - } -} diff --git a/src/com/android/customization/model/grid/GridOptionsManager.java b/src/com/android/customization/model/grid/GridOptionsManager.java index 78dbb5b2..bd24cf5f 100644 --- a/src/com/android/customization/model/grid/GridOptionsManager.java +++ b/src/com/android/customization/model/grid/GridOptionsManager.java @@ -27,7 +27,7 @@ import androidx.lifecycle.LiveData; import com.android.customization.model.CustomizationManager; import com.android.customization.module.CustomizationInjector; -import com.android.customization.module.ThemesUserEventLogger; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.wallpaper.R; import com.android.wallpaper.module.InjectorProvider; import com.android.wallpaper.util.PreviewUtils; diff --git a/src/com/android/customization/model/mode/DarkModeSectionController.java b/src/com/android/customization/model/mode/DarkModeSectionController.java index ebeaa567..71398297 100644 --- a/src/com/android/customization/model/mode/DarkModeSectionController.java +++ b/src/com/android/customization/model/mode/DarkModeSectionController.java @@ -39,6 +39,7 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.customization.picker.mode.DarkModeSectionView; import com.android.wallpaper.R; import com.android.wallpaper.model.CustomizationSectionController; @@ -60,16 +61,19 @@ public class DarkModeSectionController implements private Context mContext; private DarkModeSectionView mDarkModeSectionView; private final DarkModeSnapshotRestorer mSnapshotRestorer; + private final ThemesUserEventLogger mThemesUserEventLogger; public DarkModeSectionController( Context context, Lifecycle lifecycle, - DarkModeSnapshotRestorer snapshotRestorer) { + DarkModeSnapshotRestorer snapshotRestorer, + ThemesUserEventLogger themesUserEventLogger) { mContext = context; mLifecycle = lifecycle; mPowerManager = context.getSystemService(PowerManager.class); mLifecycle.addObserver(this); mSnapshotRestorer = snapshotRestorer; + mThemesUserEventLogger = themesUserEventLogger; } @OnLifecycleEvent(Lifecycle.Event.ON_START) @@ -137,6 +141,7 @@ public class DarkModeSectionController implements mDarkModeSectionView.announceForAccessibility( context.getString(R.string.mode_changed)); uiModeManager.setNightModeActivated(viewActivated); + mThemesUserEventLogger.logDarkThemeApplied(viewActivated); mSnapshotRestorer.store(viewActivated); }, /* delayMillis= */ shortDelay); diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java deleted file mode 100644 index 89067c65..00000000 --- a/src/com/android/customization/model/theme/DefaultThemeProvider.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme; - -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources.NotFoundException; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.ResourcesApkProvider; -import com.android.customization.model.theme.ThemeBundle.Builder; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.customization.model.theme.custom.CustomTheme; -import com.android.customization.module.CustomizationPreferences; -import com.android.wallpaper.R; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Default implementation of {@link ThemeBundleProvider} that reads Themes' overlays from a stub APK. - */ -public class DefaultThemeProvider extends ResourcesApkProvider implements ThemeBundleProvider { - - private static final String TAG = "DefaultThemeProvider"; - - private static final String THEMES_ARRAY = "themes"; - private static final String TITLE_PREFIX = "theme_title_"; - private static final String FONT_PREFIX = "theme_overlay_font_"; - private static final String COLOR_PREFIX = "theme_overlay_color_"; - private static final String SHAPE_PREFIX = "theme_overlay_shape_"; - private static final String ICON_ANDROID_PREFIX = "theme_overlay_icon_android_"; - private static final String ICON_LAUNCHER_PREFIX = "theme_overlay_icon_launcher_"; - private static final String ICON_THEMEPICKER_PREFIX = "theme_overlay_icon_themepicker_"; - private static final String ICON_SETTINGS_PREFIX = "theme_overlay_icon_settings_"; - private static final String ICON_SYSUI_PREFIX = "theme_overlay_icon_sysui_"; - - private static final String DEFAULT_THEME_NAME= "default"; - private static final String THEME_TITLE_FIELD = "_theme_title"; - private static final String THEME_ID_FIELD = "_theme_id"; - - private final OverlayThemeExtractor mOverlayProvider; - private List<ThemeBundle> mThemes; - private final CustomizationPreferences mCustomizationPreferences; - - public DefaultThemeProvider(Context context, CustomizationPreferences customizationPrefs) { - super(context, context.getString(R.string.themes_stub_package)); - mOverlayProvider = new OverlayThemeExtractor(context); - mCustomizationPreferences = customizationPrefs; - } - - @Override - public void fetch(OptionsFetchedListener<ThemeBundle> callback, boolean reload) { - if (mThemes == null || reload) { - mThemes = new ArrayList<>(); - loadAll(); - } - - if(callback != null) { - callback.onOptionsLoaded(mThemes); - } - } - - @Override - public boolean isAvailable() { - return mOverlayProvider.isAvailable() && super.isAvailable(); - } - - private void loadAll() { - // Add "Custom" option at the beginning. - mThemes.add(new CustomTheme.Builder() - .setId(CustomTheme.newId()) - .setTitle(mContext.getString(R.string.custom_theme)) - .build(mContext)); - - addDefaultTheme(); - - String[] themeNames = getItemsFromStub(THEMES_ARRAY); - - for (String themeName : themeNames) { - // Default theme needs special treatment (see #addDefaultTheme()) - if (DEFAULT_THEME_NAME.equals(themeName)) { - continue; - } - ThemeBundle.Builder builder = new Builder(); - try { - builder.setTitle(mStubApkResources.getString( - mStubApkResources.getIdentifier(TITLE_PREFIX + themeName, - "string", mStubPackageName))); - - String shapeOverlayPackage = getOverlayPackage(SHAPE_PREFIX, themeName); - mOverlayProvider.addShapeOverlay(builder, shapeOverlayPackage); - - String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, themeName); - mOverlayProvider.addFontOverlay(builder, fontOverlayPackage); - - String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, themeName); - mOverlayProvider.addColorOverlay(builder, colorOverlayPackage); - - String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX, - themeName); - - mOverlayProvider.addAndroidIconOverlay(builder, iconAndroidOverlayPackage); - - String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX, themeName); - - mOverlayProvider.addSysUiIconOverlay(builder, iconSysUiOverlayPackage); - - String iconLauncherOverlayPackage = getOverlayPackage(ICON_LAUNCHER_PREFIX, - themeName); - mOverlayProvider.addNoPreviewIconOverlay(builder, iconLauncherOverlayPackage); - - String iconThemePickerOverlayPackage = getOverlayPackage(ICON_THEMEPICKER_PREFIX, - themeName); - mOverlayProvider.addNoPreviewIconOverlay(builder, - iconThemePickerOverlayPackage); - - String iconSettingsOverlayPackage = getOverlayPackage(ICON_SETTINGS_PREFIX, - themeName); - - mOverlayProvider.addNoPreviewIconOverlay(builder, iconSettingsOverlayPackage); - - mThemes.add(builder.build(mContext)); - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, String.format("Couldn't load part of theme %s, will skip it", themeName), - e); - } - } - - addCustomThemes(); - } - - /** - * Default theme requires different treatment: if there are overlay packages specified in the - * stub apk, we'll use those, otherwise we'll get the System default values. But we cannot skip - * the default theme. - */ - private void addDefaultTheme() { - ThemeBundle.Builder builder = new Builder().asDefault(); - - int titleId = mStubApkResources.getIdentifier(TITLE_PREFIX + DEFAULT_THEME_NAME, - "string", mStubPackageName); - if (titleId > 0) { - builder.setTitle(mStubApkResources.getString(titleId)); - } else { - builder.setTitle(mContext.getString(R.string.default_theme_title)); - } - - try { - String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, DEFAULT_THEME_NAME); - mOverlayProvider.addColorOverlay(builder, colorOverlayPackage); - } catch (NameNotFoundException | NotFoundException e) { - Log.d(TAG, "Didn't find color overlay for default theme, will use system default"); - mOverlayProvider.addSystemDefaultColor(builder); - } - - try { - String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, DEFAULT_THEME_NAME); - mOverlayProvider.addFontOverlay(builder, fontOverlayPackage); - } catch (NameNotFoundException | NotFoundException e) { - Log.d(TAG, "Didn't find font overlay for default theme, will use system default"); - mOverlayProvider.addSystemDefaultFont(builder); - } - - try { - String shapeOverlayPackage = getOverlayPackage(SHAPE_PREFIX, DEFAULT_THEME_NAME); - mOverlayProvider.addShapeOverlay(builder ,shapeOverlayPackage, false); - } catch (NameNotFoundException | NotFoundException e) { - Log.d(TAG, "Didn't find shape overlay for default theme, will use system default"); - mOverlayProvider.addSystemDefaultShape(builder); - } - - List<ShapeAppIcon> icons = new ArrayList<>(); - for (String packageName : mOverlayProvider.getShapePreviewIconPackages()) { - Drawable icon = null; - CharSequence name = null; - try { - icon = mContext.getPackageManager().getApplicationIcon(packageName); - ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfo(packageName, /* flag= */ 0); - name = mContext.getPackageManager().getApplicationLabel(appInfo); - } catch (NameNotFoundException e) { - Log.d(TAG, "Couldn't find app " + packageName + ", won't use it for icon shape" - + "preview"); - } finally { - if (icon != null && !TextUtils.isEmpty(name)) { - icons.add(new ShapeAppIcon(icon, name)); - } - } - } - builder.setShapePreviewIcons(icons); - - try { - String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX, - DEFAULT_THEME_NAME); - mOverlayProvider.addAndroidIconOverlay(builder, iconAndroidOverlayPackage); - } catch (NameNotFoundException | NotFoundException e) { - Log.d(TAG, "Didn't find Android icons overlay for default theme, using system default"); - mOverlayProvider.addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW); - } - - try { - String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX, - DEFAULT_THEME_NAME); - mOverlayProvider.addSysUiIconOverlay(builder, iconSysUiOverlayPackage); - } catch (NameNotFoundException | NotFoundException e) { - Log.d(TAG, - "Didn't find SystemUi icons overlay for default theme, using system default"); - mOverlayProvider.addSystemDefaultIcons(builder, SYSUI_PACKAGE, ICONS_FOR_PREVIEW); - } - - mThemes.add(builder.build(mContext)); - } - - @Override - public void storeCustomTheme(CustomTheme theme) { - if (mThemes == null) { - fetch(options -> { - addCustomThemeAndStore(theme); - }, false); - } else { - addCustomThemeAndStore(theme); - } - } - - private void addCustomThemeAndStore(CustomTheme theme) { - if (!mThemes.contains(theme)) { - mThemes.add(theme); - } else { - mThemes.replaceAll(t -> theme.equals(t) ? theme : t); - } - JSONArray themesArray = new JSONArray(); - mThemes.stream() - .filter(themeBundle -> themeBundle instanceof CustomTheme - && !themeBundle.getPackagesByCategory().isEmpty()) - .forEachOrdered(themeBundle -> addThemeBundleToArray(themesArray, themeBundle)); - mCustomizationPreferences.storeCustomThemes(themesArray.toString()); - } - - private void addThemeBundleToArray(JSONArray themesArray, ThemeBundle themeBundle) { - JSONObject jsonPackages = themeBundle.getJsonPackages(false); - try { - jsonPackages.put(THEME_TITLE_FIELD, themeBundle.getTitle()); - if (themeBundle instanceof CustomTheme) { - jsonPackages.put(THEME_ID_FIELD, ((CustomTheme)themeBundle).getId()); - } - } catch (JSONException e) { - Log.w("Exception saving theme's title", e); - } - themesArray.put(jsonPackages); - } - - @Override - public void removeCustomTheme(CustomTheme theme) { - JSONArray themesArray = new JSONArray(); - mThemes.stream() - .filter(themeBundle -> themeBundle instanceof CustomTheme - && ((CustomTheme) themeBundle).isDefined()) - .forEachOrdered(customTheme -> { - if (!customTheme.equals(theme)) { - addThemeBundleToArray(themesArray, customTheme); - } - }); - mCustomizationPreferences.storeCustomThemes(themesArray.toString()); - } - - private void addCustomThemes() { - String serializedThemes = mCustomizationPreferences.getSerializedCustomThemes(); - int customThemesCount = 0; - if (!TextUtils.isEmpty(serializedThemes)) { - try { - JSONArray customThemes = new JSONArray(serializedThemes); - for (int i = 0; i < customThemes.length(); i++) { - JSONObject jsonTheme = customThemes.getJSONObject(i); - CustomTheme.Builder builder = new CustomTheme.Builder(); - try { - convertJsonToBuilder(jsonTheme, builder); - } catch (NameNotFoundException | NotFoundException e) { - Log.i(TAG, "Couldn't parse serialized custom theme", e); - builder = null; - } - if (builder != null) { - if (TextUtils.isEmpty(builder.getTitle())) { - builder.setTitle(mContext.getString(R.string.custom_theme_title, - customThemesCount + 1)); - } - mThemes.add(builder.build(mContext)); - } else { - Log.w(TAG, "Couldn't read stored custom theme, resetting"); - mThemes.add(new CustomTheme.Builder() - .setId(CustomTheme.newId()) - .setTitle(mContext.getString( - R.string.custom_theme_title, customThemesCount + 1)) - .build(mContext)); - } - customThemesCount++; - } - } catch (JSONException e) { - Log.w(TAG, "Couldn't read stored custom theme, resetting", e); - mThemes.add(new CustomTheme.Builder() - .setId(CustomTheme.newId()) - .setTitle(mContext.getString( - R.string.custom_theme_title, customThemesCount + 1)) - .build(mContext)); - } - } - } - - @Nullable - @Override - public ThemeBundle.Builder parseThemeBundle(String serializedTheme) throws JSONException { - JSONObject theme = new JSONObject(serializedTheme); - try { - ThemeBundle.Builder builder = new ThemeBundle.Builder(); - convertJsonToBuilder(theme, builder); - return builder; - } catch (NameNotFoundException | NotFoundException e) { - Log.i(TAG, "Couldn't parse serialized custom theme", e); - return null; - } - } - - @Nullable - @Override - public CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException { - JSONObject theme = new JSONObject(serializedTheme); - try { - CustomTheme.Builder builder = new CustomTheme.Builder(); - convertJsonToBuilder(theme, builder); - return builder; - } catch (NameNotFoundException | NotFoundException e) { - Log.i(TAG, "Couldn't parse serialized custom theme", e); - return null; - } - } - - private void convertJsonToBuilder(JSONObject theme, ThemeBundle.Builder builder) - throws JSONException, NameNotFoundException, NotFoundException { - Map<String, String> customPackages = new HashMap<>(); - Iterator<String> keysIterator = theme.keys(); - - while (keysIterator.hasNext()) { - String category = keysIterator.next(); - customPackages.put(category, theme.getString(category)); - } - mOverlayProvider.addShapeOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_SHAPE)); - mOverlayProvider.addFontOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_FONT)); - mOverlayProvider.addColorOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_COLOR)); - mOverlayProvider.addAndroidIconOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_ICON_ANDROID)); - mOverlayProvider.addSysUiIconOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_ICON_SYSUI)); - mOverlayProvider.addNoPreviewIconOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_ICON_SETTINGS)); - mOverlayProvider.addNoPreviewIconOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER)); - mOverlayProvider.addNoPreviewIconOverlay(builder, - customPackages.get(OVERLAY_CATEGORY_ICON_THEMEPICKER)); - if (theme.has(THEME_TITLE_FIELD)) { - builder.setTitle(theme.getString(THEME_TITLE_FIELD)); - } - if (builder instanceof CustomTheme.Builder && theme.has(THEME_ID_FIELD)) { - ((CustomTheme.Builder) builder).setId(theme.getString(THEME_ID_FIELD)); - } - } - - @Override - public ThemeBundle findEquivalent(ThemeBundle other) { - if (mThemes == null) { - return null; - } - for (ThemeBundle theme : mThemes) { - if (theme.isEquivalent(other)) { - return theme; - } - } - return null; - } - - private String getOverlayPackage(String prefix, String themeName) { - return getItemStringFromStub(prefix, themeName); - } -} diff --git a/src/com/android/customization/model/theme/OverlayThemeExtractor.java b/src/com/android/customization/model/theme/OverlayThemeExtractor.java deleted file mode 100644 index 816176e7..00000000 --- a/src/com/android/customization/model/theme/OverlayThemeExtractor.java +++ /dev/null @@ -1,293 +0,0 @@ -package com.android.customization.model.theme; - -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW; -import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE; -import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE; - -import android.content.Context; -import android.content.om.OverlayInfo; -import android.content.om.OverlayManager; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.Dimension; -import androidx.annotation.Nullable; - -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.ThemeBundle.Builder; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.wallpaper.R; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -class OverlayThemeExtractor { - - private static final String TAG = "OverlayThemeExtractor"; - - private final Context mContext; - private final Map<String, OverlayInfo> mOverlayInfos = new HashMap<>(); - // List of packages - private final String[] mShapePreviewIconPackages; - - OverlayThemeExtractor(Context context) { - mContext = context; - OverlayManager om = context.getSystemService(OverlayManager.class); - if (om != null) { - Consumer<OverlayInfo> addToMap = overlayInfo -> mOverlayInfos.put( - overlayInfo.getPackageName(), overlayInfo); - - UserHandle user = UserHandle.of(UserHandle.myUserId()); - om.getOverlayInfosForTarget(ANDROID_PACKAGE, user).forEach(addToMap); - om.getOverlayInfosForTarget(SYSUI_PACKAGE, user).forEach(addToMap); - om.getOverlayInfosForTarget(SETTINGS_PACKAGE, user).forEach(addToMap); - om.getOverlayInfosForTarget(ResourceConstants.getLauncherPackage(context), user) - .forEach(addToMap); - om.getOverlayInfosForTarget(context.getPackageName(), user).forEach(addToMap); - } - mShapePreviewIconPackages = context.getResources().getStringArray( - R.array.icon_shape_preview_packages); - } - - boolean isAvailable() { - return !mOverlayInfos.isEmpty(); - } - - void addColorOverlay(Builder builder, String colorOverlayPackage) - throws NameNotFoundException { - if (!TextUtils.isEmpty(colorOverlayPackage)) { - builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage), - colorOverlayPackage) - .setColorAccentLight(loadColor(ResourceConstants.ACCENT_COLOR_LIGHT_NAME, - colorOverlayPackage)) - .setColorAccentDark(loadColor(ResourceConstants.ACCENT_COLOR_DARK_NAME, - colorOverlayPackage)); - } else { - addSystemDefaultColor(builder); - } - } - - void addShapeOverlay(Builder builder, String shapeOverlayPackage) - throws NameNotFoundException { - addShapeOverlay(builder, shapeOverlayPackage, true); - } - - void addShapeOverlay(Builder builder, String shapeOverlayPackage, boolean addPreview) - throws NameNotFoundException { - if (!TextUtils.isEmpty(shapeOverlayPackage)) { - builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage), - shapeOverlayPackage) - .setShapePath( - loadString(ResourceConstants.CONFIG_ICON_MASK, shapeOverlayPackage)) - .setBottomSheetCornerRadius( - loadDimen(ResourceConstants.CONFIG_CORNERRADIUS, shapeOverlayPackage)); - } else { - addSystemDefaultShape(builder); - } - if (addPreview) { - addShapePreviewIcons(builder); - } - } - - private void addShapePreviewIcons(Builder builder) { - List<ShapeAppIcon> icons = new ArrayList<>(); - for (String packageName : mShapePreviewIconPackages) { - Drawable icon = null; - CharSequence name = null; - try { - icon = mContext.getPackageManager().getApplicationIcon(packageName); - // Add the shape icon app name. - ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfo(packageName, /* flag= */ 0); - name = mContext.getPackageManager().getApplicationLabel(appInfo); - } catch (NameNotFoundException e) { - Log.d(TAG, "Couldn't find app " + packageName - + ", won't use it for icon shape preview"); - } finally { - if (icon != null && !TextUtils.isEmpty(name)) { - icons.add(new ShapeAppIcon(icon, name)); - } - } - } - builder.setShapePreviewIcons(icons); - } - - void addNoPreviewIconOverlay(Builder builder, String overlayPackage) { - if (!TextUtils.isEmpty(overlayPackage)) { - builder.addOverlayPackage(getOverlayCategory(overlayPackage), - overlayPackage); - } - } - - void addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage) - throws NameNotFoundException { - if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) { - addIconOverlay(builder, iconSysUiOverlayPackage); - } - } - - void addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage) - throws NameNotFoundException { - if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) { - addIconOverlay(builder, iconAndroidOverlayPackage, ICONS_FOR_PREVIEW); - } else { - addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW); - } - } - - void addIconOverlay(Builder builder, String packageName, String... previewIcons) - throws NameNotFoundException { - builder.addOverlayPackage(getOverlayCategory(packageName), packageName); - for (String iconName : previewIcons) { - builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false)); - } - } - - void addFontOverlay(Builder builder, String fontOverlayPackage) - throws NameNotFoundException { - if (!TextUtils.isEmpty(fontOverlayPackage)) { - builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage), - fontOverlayPackage) - .setBodyFontFamily(loadTypeface( - ResourceConstants.CONFIG_BODY_FONT_FAMILY, - fontOverlayPackage)) - .setHeadlineFontFamily(loadTypeface( - ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY, - fontOverlayPackage)); - } else { - addSystemDefaultFont(builder); - } - } - - void addSystemDefaultIcons(Builder builder, String packageName, - String... previewIcons) { - try { - for (String iconName : previewIcons) { - builder.addIcon(loadIconPreviewDrawable(iconName, packageName, true)); - } - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, "Didn't find android package icons, will skip preview", e); - } - } - - void addSystemDefaultShape(Builder builder) { - Resources system = Resources.getSystem(); - String iconMaskPath = system.getString( - system.getIdentifier(ResourceConstants.CONFIG_ICON_MASK, - "string", ResourceConstants.ANDROID_PACKAGE)); - builder.setShapePath(iconMaskPath) - .setBottomSheetCornerRadius( - system.getDimensionPixelOffset( - system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS, - "dimen", ResourceConstants.ANDROID_PACKAGE))); - } - - void addSystemDefaultColor(Builder builder) { - Resources system = Resources.getSystem(); - int colorAccentLight = system.getColor( - system.getIdentifier(ResourceConstants.ACCENT_COLOR_LIGHT_NAME, "color", - ResourceConstants.ANDROID_PACKAGE), null); - builder.setColorAccentLight(colorAccentLight); - - int colorAccentDark = system.getColor( - system.getIdentifier(ResourceConstants.ACCENT_COLOR_DARK_NAME, "color", - ResourceConstants.ANDROID_PACKAGE), null); - builder.setColorAccentDark(colorAccentDark); - } - - void addSystemDefaultFont(Builder builder) { - Resources system = Resources.getSystem(); - String headlineFontFamily = system.getString(system.getIdentifier( - ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY, "string", - ResourceConstants.ANDROID_PACKAGE)); - String bodyFontFamily = system.getString(system.getIdentifier( - ResourceConstants.CONFIG_BODY_FONT_FAMILY, - "string", ResourceConstants.ANDROID_PACKAGE)); - builder.setHeadlineFontFamily(Typeface.create(headlineFontFamily, Typeface.NORMAL)) - .setBodyFontFamily(Typeface.create(bodyFontFamily, Typeface.NORMAL)); - } - - Typeface loadTypeface(String configName, String fontOverlayPackage) - throws NameNotFoundException, NotFoundException { - - // TODO(santie): check for font being present in system - - Resources overlayRes = mContext.getPackageManager() - .getResourcesForApplication(fontOverlayPackage); - - String fontFamily = overlayRes.getString(overlayRes.getIdentifier(configName, - "string", fontOverlayPackage)); - return Typeface.create(fontFamily, Typeface.NORMAL); - } - - int loadColor(String colorName, String colorPackage) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = mContext.getPackageManager() - .getResourcesForApplication(colorPackage); - return overlayRes.getColor(overlayRes.getIdentifier(colorName, "color", colorPackage), - null); - } - - String loadString(String stringName, String packageName) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = - mContext.getPackageManager().getResourcesForApplication( - packageName); - return overlayRes.getString(overlayRes.getIdentifier(stringName, "string", packageName)); - } - - @Dimension - int loadDimen(String dimenName, String packageName) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = - mContext.getPackageManager().getResourcesForApplication( - packageName); - return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier( - dimenName, "dimen", packageName)); - } - - boolean loadBoolean(String booleanName, String packageName) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = - mContext.getPackageManager().getResourcesForApplication( - packageName); - return overlayRes.getBoolean(overlayRes.getIdentifier( - booleanName, "boolean", packageName)); - } - - Drawable loadIconPreviewDrawable(String drawableName, String packageName, - boolean fromSystem) throws NameNotFoundException, NotFoundException { - - Resources packageRes = - mContext.getPackageManager().getResourcesForApplication( - packageName); - Resources res = fromSystem ? Resources.getSystem() : packageRes; - return res.getDrawable( - packageRes.getIdentifier(drawableName, "drawable", packageName), null); - } - - @Nullable - String getOverlayCategory(String packageName) { - OverlayInfo info = mOverlayInfos.get(packageName); - return info != null ? info.getCategory() : null; - } - - String[] getShapePreviewIconPackages() { - return mShapePreviewIconPackages; - } -}
\ No newline at end of file diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java deleted file mode 100644 index 3a32f257..00000000 --- a/src/com/android/customization/model/theme/ThemeBundle.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme; - -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.customization.model.ResourceConstants.PATH_SIZE; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Path; -import android.graphics.Typeface; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.PathShape; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.Nullable; -import androidx.core.graphics.PathParser; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.CustomizationOption; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.customization.widget.DynamicAdaptiveIconDrawable; -import com.android.wallpaper.R; -import com.android.wallpaper.asset.Asset; -import com.android.wallpaper.asset.BitmapCachingAsset; -import com.android.wallpaper.model.WallpaperInfo; -import com.android.wallpaper.util.ResourceUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Represents a Theme component available in the system as a "persona" bundle. - * Note that in this context a Theme is not related to Android's Styles, but it's rather an - * abstraction representing a series of overlays to be applied to the system. - */ -public class ThemeBundle implements CustomizationOption<ThemeBundle> { - - private static final String TAG = "ThemeBundle"; - private final static String EMPTY_JSON = "{}"; - private final static String TIMESTAMP_FIELD = "_applied_timestamp"; - - private final String mTitle; - private final PreviewInfo mPreviewInfo; - private final boolean mIsDefault; - protected final Map<String, String> mPackagesByCategory; - private WallpaperInfo mOverrideWallpaper; - private Asset mOverrideWallpaperAsset; - private CharSequence mContentDescription; - - protected ThemeBundle(String title, Map<String, String> overlayPackages, - boolean isDefault, PreviewInfo previewInfo) { - mTitle = title; - mIsDefault = isDefault; - mPreviewInfo = previewInfo; - mPackagesByCategory = Collections.unmodifiableMap(removeNullValues(overlayPackages)); - } - - @Override - public String getTitle() { - return mTitle; - } - - @Override - public void bindThumbnailTile(View view) { - Resources res = view.getContext().getResources(); - - ((TextView) view.findViewById(R.id.theme_option_font)).setTypeface( - mPreviewInfo.headlineFontFamily); - if (mPreviewInfo.shapeDrawable != null) { - ((ShapeDrawable) mPreviewInfo.shapeDrawable).getPaint().setColor( - mPreviewInfo.resolveAccentColor(res)); - ((ImageView) view.findViewById(R.id.theme_option_shape)).setImageDrawable( - mPreviewInfo.shapeDrawable); - } - if (!mPreviewInfo.icons.isEmpty()) { - Drawable icon = mPreviewInfo.icons.get(0).getConstantState().newDrawable().mutate(); - icon.setTint(ResourceUtils.getColorAttr( - view.getContext(), android.R.attr.textColorSecondary)); - ((ImageView) view.findViewById(R.id.theme_option_icon)).setImageDrawable( - icon); - } - view.setContentDescription(getContentDescription(view.getContext())); - } - - @Override - public boolean isActive(CustomizationManager<ThemeBundle> manager) { - ThemeManager themeManager = (ThemeManager) manager; - - if (mIsDefault) { - String serializedOverlays = themeManager.getStoredOverlays(); - return TextUtils.isEmpty(serializedOverlays) || EMPTY_JSON.equals(serializedOverlays); - } else { - Map<String, String> currentOverlays = themeManager.getCurrentOverlays(); - return mPackagesByCategory.equals(currentOverlays); - } - } - - @Override - public int getLayoutResId() { - return R.layout.theme_option; - } - - /** - * This is similar to #equals() but it only compares this theme's packages with the other, that - * is, it will return true if applying this theme has the same effect of applying the given one. - */ - public boolean isEquivalent(ThemeBundle other) { - if (other == null) { - return false; - } - if (mIsDefault) { - return other.isDefault() || TextUtils.isEmpty(other.getSerializedPackages()) - || EMPTY_JSON.equals(other.getSerializedPackages()); - } - // Map#equals ensures keys and values are compared. - return mPackagesByCategory.equals(other.mPackagesByCategory); - } - - public PreviewInfo getPreviewInfo() { - return mPreviewInfo; - } - - public void setOverrideThemeWallpaper(WallpaperInfo homeWallpaper) { - mOverrideWallpaper = homeWallpaper; - mOverrideWallpaperAsset = null; - } - - private Asset getOverrideWallpaperAsset(Context context) { - if (mOverrideWallpaperAsset == null) { - mOverrideWallpaperAsset = new BitmapCachingAsset(context, - mOverrideWallpaper.getThumbAsset(context)); - } - return mOverrideWallpaperAsset; - } - - boolean isDefault() { - return mIsDefault; - } - - public Map<String, String> getPackagesByCategory() { - return mPackagesByCategory; - } - - public String getSerializedPackages() { - return getJsonPackages(false).toString(); - } - - public String getSerializedPackagesWithTimestamp() { - return getJsonPackages(true).toString(); - } - - JSONObject getJsonPackages(boolean insertTimestamp) { - if (isDefault()) { - return new JSONObject(); - } - JSONObject json = new JSONObject(mPackagesByCategory); - // Remove items with null values to avoid deserialization issues. - removeNullValues(json); - if (insertTimestamp) { - try { - json.put(TIMESTAMP_FIELD, System.currentTimeMillis()); - } catch (JSONException e) { - Log.e(TAG, "Couldn't add timestamp to serialized themebundle"); - } - } - return json; - } - - private void removeNullValues(JSONObject json) { - Iterator<String> keys = json.keys(); - Set<String> keysToRemove = new HashSet<>(); - while(keys.hasNext()) { - String key = keys.next(); - if (json.isNull(key)) { - keysToRemove.add(key); - } - } - for (String key : keysToRemove) { - json.remove(key); - } - } - - private Map<String, String> removeNullValues(Map<String, String> map) { - return map.entrySet() - .stream() - .filter(entry -> entry.getValue() != null) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - protected CharSequence getContentDescription(Context context) { - if (mContentDescription == null) { - CharSequence defaultName = context.getString(R.string.default_theme_title); - if (isDefault()) { - mContentDescription = defaultName; - } else { - PackageManager pm = context.getPackageManager(); - CharSequence fontName = getOverlayName(pm, OVERLAY_CATEGORY_FONT); - CharSequence iconName = getOverlayName(pm, OVERLAY_CATEGORY_ICON_ANDROID); - CharSequence shapeName = getOverlayName(pm, OVERLAY_CATEGORY_SHAPE); - CharSequence colorName = getOverlayName(pm, OVERLAY_CATEGORY_COLOR); - mContentDescription = context.getString(R.string.theme_description, - TextUtils.isEmpty(fontName) ? defaultName : fontName, - TextUtils.isEmpty(iconName) ? defaultName : iconName, - TextUtils.isEmpty(shapeName) ? defaultName : shapeName, - TextUtils.isEmpty(colorName) ? defaultName : colorName); - } - } - return mContentDescription; - } - - private CharSequence getOverlayName(PackageManager pm, String overlayCategoryFont) { - try { - return pm.getApplicationInfo( - mPackagesByCategory.get(overlayCategoryFont), 0).loadLabel(pm); - } catch (PackageManager.NameNotFoundException e) { - return ""; - } - } - - public static class PreviewInfo { - public final Typeface bodyFontFamily; - public final Typeface headlineFontFamily; - @ColorInt public final int colorAccentLight; - @ColorInt public final int colorAccentDark; - public final List<Drawable> icons; - public final Drawable shapeDrawable; - public final List<ShapeAppIcon> shapeAppIcons; - @Dimension public final int bottomSheeetCornerRadius; - - /** A class to represent an App icon and its name. */ - public static class ShapeAppIcon { - private Drawable mIconDrawable; - private CharSequence mAppName; - - public ShapeAppIcon(Drawable icon, CharSequence appName) { - mIconDrawable = icon; - mAppName = appName; - } - - /** Returns a copy of app icon drawable. */ - public Drawable getDrawableCopy() { - return mIconDrawable.getConstantState().newDrawable().mutate(); - } - - /** Returns the app name. */ - public CharSequence getAppName() { - return mAppName; - } - } - - private PreviewInfo(Context context, Typeface bodyFontFamily, Typeface headlineFontFamily, - int colorAccentLight, int colorAccentDark, List<Drawable> icons, - Drawable shapeDrawable, @Dimension int cornerRadius, - List<ShapeAppIcon> shapeAppIcons) { - this.bodyFontFamily = bodyFontFamily; - this.headlineFontFamily = headlineFontFamily; - this.colorAccentLight = colorAccentLight; - this.colorAccentDark = colorAccentDark; - this.icons = icons; - this.shapeDrawable = shapeDrawable; - this.bottomSheeetCornerRadius = cornerRadius; - this.shapeAppIcons = shapeAppIcons; - } - - /** - * Returns the accent color to be applied corresponding with the current configuration's - * UI mode. - * @return one of {@link #colorAccentDark} or {@link #colorAccentLight} - */ - @ColorInt - public int resolveAccentColor(Resources res) { - return (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES ? colorAccentDark : colorAccentLight; - } - } - - public static class Builder { - protected String mTitle; - private Typeface mBodyFontFamily; - private Typeface mHeadlineFontFamily; - @ColorInt private int mColorAccentLight = -1; - @ColorInt private int mColorAccentDark = -1; - private List<Drawable> mIcons = new ArrayList<>(); - private String mPathString; - private Path mShapePath; - private boolean mIsDefault; - @Dimension private int mCornerRadius; - protected Map<String, String> mPackages = new HashMap<>(); - private List<ShapeAppIcon> mAppIcons = new ArrayList<>(); - - public ThemeBundle build(Context context) { - return new ThemeBundle(mTitle, mPackages, mIsDefault, createPreviewInfo(context)); - } - - public PreviewInfo createPreviewInfo(Context context) { - ShapeDrawable shapeDrawable = null; - List<ShapeAppIcon> shapeIcons = new ArrayList<>(); - Path path = mShapePath; - if (!TextUtils.isEmpty(mPathString)) { - path = PathParser.createPathFromPathData(mPathString); - } - if (path != null) { - PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE); - shapeDrawable = new ShapeDrawable(shape); - shapeDrawable.setIntrinsicHeight((int) PATH_SIZE); - shapeDrawable.setIntrinsicWidth((int) PATH_SIZE); - for (ShapeAppIcon icon : mAppIcons) { - Drawable drawable = icon.mIconDrawable; - if (drawable instanceof AdaptiveIconDrawable) { - AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable; - shapeIcons.add(new ShapeAppIcon( - new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(), - adaptiveIcon.getForeground(), path), - icon.getAppName())); - } else if (drawable instanceof DynamicAdaptiveIconDrawable) { - shapeIcons.add(icon); - } - // TODO: add iconloader library's legacy treatment helper methods for - // non-adaptive icons - } - } - return new PreviewInfo(context, mBodyFontFamily, mHeadlineFontFamily, mColorAccentLight, - mColorAccentDark, mIcons, shapeDrawable, mCornerRadius, shapeIcons); - } - - public Map<String, String> getPackages() { - return Collections.unmodifiableMap(mPackages); - } - - public String getTitle() { - return mTitle; - } - - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - public Builder setBodyFontFamily(@Nullable Typeface bodyFontFamily) { - mBodyFontFamily = bodyFontFamily; - return this; - } - - public Builder setHeadlineFontFamily(@Nullable Typeface headlineFontFamily) { - mHeadlineFontFamily = headlineFontFamily; - return this; - } - - public Builder setColorAccentLight(@ColorInt int colorAccentLight) { - mColorAccentLight = colorAccentLight; - return this; - } - - public Builder setColorAccentDark(@ColorInt int colorAccentDark) { - mColorAccentDark = colorAccentDark; - return this; - } - - public Builder addIcon(Drawable icon) { - mIcons.add(icon); - return this; - } - - public Builder addOverlayPackage(String category, String packageName) { - mPackages.put(category, packageName); - return this; - } - - public Builder setShapePath(String path) { - mPathString = path; - return this; - } - - public Builder setShapePath(Path path) { - mShapePath = path; - return this; - } - - public Builder asDefault() { - mIsDefault = true; - return this; - } - - public Builder setShapePreviewIcons(List<ShapeAppIcon> appIcons) { - mAppIcons.clear(); - mAppIcons.addAll(appIcons); - return this; - } - - public Builder setBottomSheetCornerRadius(@Dimension int radius) { - mCornerRadius = radius; - return this; - } - } -} diff --git a/src/com/android/customization/model/theme/ThemeBundleProvider.java b/src/com/android/customization/model/theme/ThemeBundleProvider.java deleted file mode 100644 index 34342b31..00000000 --- a/src/com/android/customization/model/theme/ThemeBundleProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme; - -import androidx.annotation.Nullable; - -import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.theme.custom.CustomTheme; - -import org.json.JSONException; - -/** - * Interface for a class that can retrieve Themes from the system. - */ -public interface ThemeBundleProvider { - - /** - * Returns whether themes are available in the current setup. - */ - boolean isAvailable(); - - /** - * Retrieve the available themes. - * @param callback called when the themes have been retrieved (or immediately if cached) - * @param reload whether to reload themes if they're cached. - */ - void fetch(OptionsFetchedListener<ThemeBundle> callback, boolean reload); - - void storeCustomTheme(CustomTheme theme); - - void removeCustomTheme(CustomTheme theme); - - @Nullable ThemeBundle.Builder parseThemeBundle(String serializedTheme) throws JSONException; - - @Nullable CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException; - - ThemeBundle findEquivalent(ThemeBundle other); -} diff --git a/src/com/android/customization/model/theme/ThemeBundledWallpaperInfo.java b/src/com/android/customization/model/theme/ThemeBundledWallpaperInfo.java deleted file mode 100644 index 4f0cd6c7..00000000 --- a/src/com/android/customization/model/theme/ThemeBundledWallpaperInfo.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme; - -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Parcel; -import android.util.Log; - -import androidx.annotation.DrawableRes; -import androidx.annotation.StringRes; - -import com.android.wallpaper.asset.Asset; -import com.android.wallpaper.asset.ResourceAsset; -import com.android.wallpaper.model.InlinePreviewIntentFactory; -import com.android.wallpaper.model.WallpaperInfo; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a wallpaper coming from the resources of the theme bundle container APK. - */ -public class ThemeBundledWallpaperInfo extends WallpaperInfo { - public static final Creator<ThemeBundledWallpaperInfo> CREATOR = - new Creator<ThemeBundledWallpaperInfo>() { - @Override - public ThemeBundledWallpaperInfo createFromParcel(Parcel in) { - return new ThemeBundledWallpaperInfo(in); - } - - @Override - public ThemeBundledWallpaperInfo[] newArray(int size) { - return new ThemeBundledWallpaperInfo[size]; - } - }; - - private static final String TAG = "ThemeBundledWallpaperInfo"; - - private final String mPackageName; - private final String mResName; - private final String mCollectionId; - @DrawableRes private final int mDrawableResId; - @StringRes private final int mTitleResId; - @StringRes private final int mAttributionResId; - @StringRes private final int mActionUrlResId; - private List<String> mAttributions; - private String mActionUrl; - private Resources mResources; - private Asset mAsset; - - /** - * Constructs a new theme-bundled static wallpaper model object. - * - * @param drawableResId Resource ID of the raw wallpaper image. - * @param resName The unique name of the wallpaper resource, e.g. "z_wp001". - * @param themeName Unique name of the collection this wallpaper belongs in; used for logging. - * @param titleResId Resource ID of the string for the title attribution. - * @param attributionResId Resource ID of the string for the first subtitle attribution. - */ - public ThemeBundledWallpaperInfo(String packageName, String resName, String themeName, - int drawableResId, int titleResId, int attributionResId, int actionUrlResId) { - mPackageName = packageName; - mResName = resName; - mCollectionId = themeName; - mDrawableResId = drawableResId; - mTitleResId = titleResId; - mAttributionResId = attributionResId; - mActionUrlResId = actionUrlResId; - } - - private ThemeBundledWallpaperInfo(Parcel in) { - super(in); - mPackageName = in.readString(); - mResName = in.readString(); - mCollectionId = in.readString(); - mDrawableResId = in.readInt(); - mTitleResId = in.readInt(); - mAttributionResId = in.readInt(); - mActionUrlResId = in.readInt(); - } - - @Override - public List<String> getAttributions(Context context) { - if (mAttributions == null) { - Resources res = getPackageResources(context); - mAttributions = new ArrayList<>(); - if (mTitleResId != 0) { - mAttributions.add(res.getString(mTitleResId)); - } - if (mAttributionResId != 0) { - mAttributions.add(res.getString(mAttributionResId)); - } - } - - return mAttributions; - } - - @Override - public String getActionUrl(Context context) { - if (mActionUrl == null && mActionUrlResId != 0) { - mActionUrl = getPackageResources(context).getString(mActionUrlResId); - } - return mActionUrl; - } - - @Override - public Asset getAsset(Context context) { - if (mAsset == null) { - Resources res = getPackageResources(context); - mAsset = new ResourceAsset(res, mDrawableResId); - } - - return mAsset; - } - - @Override - public Asset getThumbAsset(Context context) { - return getAsset(context); - } - - @Override - public void showPreview(Activity srcActivity, InlinePreviewIntentFactory factory, - int requestCode, boolean isAssetIdPresent) { - try { - srcActivity.startActivityForResult(factory.newIntent(srcActivity, this, - isAssetIdPresent), requestCode); - } catch (ActivityNotFoundException |SecurityException e) { - Log.e(TAG, "App isn't installed or ThemePicker doesn't have permission to launch", e); - } - } - - @Override - public String getCollectionId(Context unused) { - return mCollectionId; - } - - @Override - public String getWallpaperId() { - return mResName; - } - - public String getResName() { - return mResName; - } - - /** - * Returns the {@link Resources} instance for the theme bundles stub APK. - */ - private Resources getPackageResources(Context context) { - if (mResources != null) { - return mResources; - } - - try { - mResources = context.getPackageManager().getResourcesForApplication(mPackageName); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Could not get app resources for " + mPackageName); - } - return mResources; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeString(mPackageName); - dest.writeString(mResName); - dest.writeString(mCollectionId); - dest.writeInt(mDrawableResId); - dest.writeInt(mTitleResId); - dest.writeInt(mAttributionResId); - dest.writeInt(mActionUrlResId); - } -} diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java deleted file mode 100644 index 85241c13..00000000 --- a/src/com/android/customization/model/theme/ThemeManager.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme; - -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; - -import android.provider.Settings; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.custom.CustomTheme; -import com.android.customization.module.ThemesUserEventLogger; - -import org.json.JSONObject; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class ThemeManager implements CustomizationManager<ThemeBundle> { - - public static final Set<String> THEME_CATEGORIES = new HashSet<>(); - static { - THEME_CATEGORIES.add(OVERLAY_CATEGORY_COLOR); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_FONT); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_SHAPE); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_ANDROID); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SETTINGS); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SYSUI); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_LAUNCHER); - THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_THEMEPICKER); - } - - private final ThemeBundleProvider mProvider; - private final OverlayManagerCompat mOverlayManagerCompat; - - protected final FragmentActivity mActivity; - private final ThemesUserEventLogger mEventLogger; - - private Map<String, String> mCurrentOverlays; - - public ThemeManager(ThemeBundleProvider provider, FragmentActivity activity, - OverlayManagerCompat overlayManagerCompat, - ThemesUserEventLogger logger) { - mProvider = provider; - mActivity = activity; - mOverlayManagerCompat = overlayManagerCompat; - mEventLogger = logger; - } - - @Override - public boolean isAvailable() { - return mOverlayManagerCompat.isAvailable() && mProvider.isAvailable(); - } - - @Override - public void apply(ThemeBundle theme, Callback callback) { - applyOverlays(theme, callback); - } - - private void applyOverlays(ThemeBundle theme, Callback callback) { - boolean allApplied = Settings.Secure.putString(mActivity.getContentResolver(), - ResourceConstants.THEME_SETTING, theme.getSerializedPackagesWithTimestamp()); - if (theme instanceof CustomTheme) { - storeCustomTheme((CustomTheme) theme); - } - mCurrentOverlays = null; - if (allApplied) { - mEventLogger.logThemeApplied(theme, theme instanceof CustomTheme); - callback.onSuccess(); - } else { - callback.onError(null); - } - } - - private void storeCustomTheme(CustomTheme theme) { - mProvider.storeCustomTheme(theme); - } - - @Override - public void fetchOptions(OptionsFetchedListener<ThemeBundle> callback, boolean reload) { - mProvider.fetch(callback, reload); - } - - public Map<String, String> getCurrentOverlays() { - if (mCurrentOverlays == null) { - mCurrentOverlays = mOverlayManagerCompat.getEnabledOverlaysForTargets( - ResourceConstants.getPackagesToOverlay(mActivity)); - mCurrentOverlays.entrySet().removeIf( - categoryAndPackage -> !THEME_CATEGORIES.contains(categoryAndPackage.getKey())); - } - return mCurrentOverlays; - } - - public String getStoredOverlays() { - return Settings.Secure.getString(mActivity.getContentResolver(), - ResourceConstants.THEME_SETTING); - } - - public void removeCustomTheme(CustomTheme theme) { - mProvider.removeCustomTheme(theme); - } - - /** - * @return an existing ThemeBundle that matches the same packages as the given one, if one - * exists, or {@code null} otherwise. - */ - @Nullable - public ThemeBundle findThemeByPackages(ThemeBundle other) { - return mProvider.findEquivalent(other); - } - - /** - * Store empty theme if no theme has been set yet. This will prevent Settings from showing the - * suggestion to select a theme - */ - public void storeEmptyTheme() { - String themeSetting = Settings.Secure.getString(mActivity.getContentResolver(), - ResourceConstants.THEME_SETTING); - if (TextUtils.isEmpty(themeSetting)) { - Settings.Secure.putString(mActivity.getContentResolver(), - ResourceConstants.THEME_SETTING, new JSONObject().toString()); - } - } -} diff --git a/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java b/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java deleted file mode 100644 index f3b950b5..00000000 --- a/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import static com.android.customization.model.ResourceConstants.ACCENT_COLOR_DARK_NAME; -import static com.android.customization.model.ResourceConstants.ACCENT_COLOR_LIGHT_NAME; -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ANDROID_THEME; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.customization.model.ResourceConstants.PATH_SIZE; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.PathShape; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.core.graphics.PathParser; - -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.OverlayManagerCompat; -import com.android.customization.model.theme.custom.ThemeComponentOption.ColorOption; -import com.android.wallpaper.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * Implementation of {@link ThemeComponentOptionProvider} that reads {@link ColorOption}s from - * icon overlays. - */ -public class ColorOptionsProvider extends ThemeComponentOptionProvider<ColorOption> { - - private static final String TAG = "ColorOptionsProvider"; - private final CustomThemeManager mCustomThemeManager; - private final String mDefaultThemePackage; - - public ColorOptionsProvider(Context context, OverlayManagerCompat manager, - CustomThemeManager customThemeManager) { - super(context, manager, OVERLAY_CATEGORY_COLOR); - mCustomThemeManager = customThemeManager; - // System color is set with a static overlay for android.theme category, so let's try to - // find that first, and if that's not present, we'll default to System resources. - // (see #addDefault()) - List<String> themePackages = manager.getOverlayPackagesForCategory( - OVERLAY_CATEGORY_ANDROID_THEME, UserHandle.myUserId(), ANDROID_PACKAGE); - mDefaultThemePackage = themePackages.isEmpty() ? null : themePackages.get(0); - } - - @Override - protected void loadOptions() { - List<Drawable> previewIcons = new ArrayList<>(); - String iconPackage = - mCustomThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_ICON_ANDROID); - if (TextUtils.isEmpty(iconPackage)) { - iconPackage = ANDROID_PACKAGE; - } - for (String iconName : ICONS_FOR_PREVIEW) { - try { - previewIcons.add(loadIconPreviewDrawable(iconName, iconPackage)); - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, String.format("Couldn't load icon in %s for color preview, will skip it", - iconPackage), e); - } - } - String shapePackage = mCustomThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_SHAPE); - if (TextUtils.isEmpty(shapePackage)) { - shapePackage = ANDROID_PACKAGE; - } - Drawable shape = loadShape(shapePackage); - addDefault(previewIcons, shape); - for (String overlayPackage : mOverlayPackages) { - try { - Resources overlayRes = getOverlayResources(overlayPackage); - int lightColor = overlayRes.getColor( - overlayRes.getIdentifier(ACCENT_COLOR_LIGHT_NAME, "color", overlayPackage), - null); - int darkColor = overlayRes.getColor( - overlayRes.getIdentifier(ACCENT_COLOR_DARK_NAME, "color", overlayPackage), - null); - PackageManager pm = mContext.getPackageManager(); - String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString(); - ColorOption option = new ColorOption(overlayPackage, label, lightColor, darkColor); - option.setPreviewIcons(previewIcons); - option.setShapeDrawable(shape); - mOptions.add(option); - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, String.format("Couldn't load color overlay %s, will skip it", - overlayPackage), e); - } - } - } - - private void addDefault(List<Drawable> previewIcons, Drawable shape) { - int lightColor, darkColor; - Resources system = Resources.getSystem(); - try { - Resources r = getOverlayResources(mDefaultThemePackage); - lightColor = r.getColor( - r.getIdentifier(ACCENT_COLOR_LIGHT_NAME, "color", mDefaultThemePackage), - null); - darkColor = r.getColor( - r.getIdentifier(ACCENT_COLOR_DARK_NAME, "color", mDefaultThemePackage), - null); - } catch (NotFoundException | NameNotFoundException e) { - Log.d(TAG, "Didn't find default color, will use system option", e); - - lightColor = system.getColor( - system.getIdentifier(ACCENT_COLOR_LIGHT_NAME, "color", ANDROID_PACKAGE), null); - - darkColor = system.getColor( - system.getIdentifier(ACCENT_COLOR_DARK_NAME, "color", ANDROID_PACKAGE), null); - } - ColorOption option = new ColorOption(null, - mContext.getString(R.string.default_theme_title), lightColor, darkColor); - option.setPreviewIcons(previewIcons); - option.setShapeDrawable(shape); - mOptions.add(option); - } - - private Drawable loadIconPreviewDrawable(String drawableName, String packageName) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = getOverlayResources(packageName); - return overlayRes.getDrawable( - overlayRes.getIdentifier(drawableName, "drawable", packageName), null); - } - - private Drawable loadShape(String packageName) { - String path = null; - try { - Resources r = getOverlayResources(packageName); - - path = ResourceConstants.getIconMask(r, packageName); - } catch (NameNotFoundException e) { - Log.d(TAG, String.format("Couldn't load shape icon for %s, skipping.", packageName), e); - } - ShapeDrawable shapeDrawable = null; - if (!TextUtils.isEmpty(path)) { - PathShape shape = new PathShape(PathParser.createPathFromPathData(path), - PATH_SIZE, PATH_SIZE); - shapeDrawable = new ShapeDrawable(shape); - shapeDrawable.setIntrinsicHeight((int) PATH_SIZE); - shapeDrawable.setIntrinsicWidth((int) PATH_SIZE); - } - return shapeDrawable; - } - -} diff --git a/src/com/android/customization/model/theme/custom/CustomTheme.java b/src/com/android/customization/model/theme/custom/CustomTheme.java deleted file mode 100644 index a1ee1066..00000000 --- a/src/com/android/customization/model/theme/custom/CustomTheme.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import android.content.Context; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.theme.ThemeBundle; -import com.android.wallpaper.R; - -import java.util.Map; -import java.util.UUID; - -public class CustomTheme extends ThemeBundle { - - public static String newId() { - return UUID.randomUUID().toString(); - } - - /** - * Used to uniquely identify a custom theme since names can change. - */ - private final String mId; - - private CustomTheme(@NonNull String id, String title, Map<String, String> overlayPackages, - @Nullable PreviewInfo previewInfo) { - super(title, overlayPackages, false, previewInfo); - mId = id; - } - - public String getId() { - return mId; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof CustomTheme)) { - return false; - } - CustomTheme other = (CustomTheme) obj; - return mId.equals(other.mId); - } - - @Override - public int hashCode() { - return mId.hashCode(); - } - - @Override - public void bindThumbnailTile(View view) { - if (isDefined()) { - super.bindThumbnailTile(view); - } - } - - @Override - public int getLayoutResId() { - return isDefined() ? R.layout.theme_option : R.layout.custom_theme_option; - } - - @Override - public boolean isActive(CustomizationManager<ThemeBundle> manager) { - return isDefined() && super.isActive(manager); - } - - @Override - public boolean isEquivalent(ThemeBundle other) { - return isDefined() && super.isEquivalent(other); - } - - public boolean isDefined() { - return getPreviewInfo() != null; - } - - public static class Builder extends ThemeBundle.Builder { - private String mId; - - @Override - public CustomTheme build(Context context) { - return new CustomTheme(mId, mTitle, mPackages, - mPackages.isEmpty() ? null : createPreviewInfo(context)); - } - - public Builder setId(String id) { - mId = id; - return this; - } - } -} diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java deleted file mode 100644 index 42d73e60..00000000 --- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import android.content.Context; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo; -import com.android.customization.model.theme.ThemeBundleProvider; -import com.android.customization.model.theme.ThemeManager; -import com.android.customization.model.theme.custom.CustomTheme.Builder; - -import org.json.JSONException; - -import java.util.Map; - -public class CustomThemeManager implements CustomizationManager<ThemeComponentOption> { - - private static final String TAG = "CustomThemeManager"; - private static final String KEY_STATE_CURRENT_SELECTION = "CustomThemeManager.currentSelection"; - - private final CustomTheme mOriginalTheme; - private CustomTheme.Builder mBuilder; - - private CustomThemeManager(Map<String, String> overlayPackages, - @Nullable CustomTheme originalTheme) { - mBuilder = new Builder(); - overlayPackages.forEach(mBuilder::addOverlayPackage); - mOriginalTheme = originalTheme; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void apply(ThemeComponentOption option, @Nullable Callback callback) { - option.buildStep(mBuilder); - if (callback != null) { - callback.onSuccess(); - } - } - - public Map<String, String> getOverlayPackages() { - return mBuilder.getPackages(); - } - - public CustomTheme buildPartialCustomTheme(Context context, String id, String title) { - return ((CustomTheme.Builder)mBuilder.setId(id).setTitle(title)).build(context); - } - - @Override - public void fetchOptions(OptionsFetchedListener<ThemeComponentOption> callback, boolean reload) { - //Unused - } - - public CustomTheme getOriginalTheme() { - return mOriginalTheme; - } - - public PreviewInfo buildCustomThemePreviewInfo(Context context) { - return mBuilder.createPreviewInfo(context); - } - - /** Saves the custom theme selections while system config changes. */ - public void saveCustomTheme(Context context, Bundle savedInstanceState) { - CustomTheme customTheme = - buildPartialCustomTheme(context, /* id= */ null, /* title= */ null); - savedInstanceState.putString(KEY_STATE_CURRENT_SELECTION, - customTheme.getSerializedPackages()); - } - - /** Reads the saved custom theme after system config changed. */ - public void readCustomTheme(ThemeBundleProvider themeBundleProvider, - Bundle savedInstanceState) { - String packages = savedInstanceState.getString(KEY_STATE_CURRENT_SELECTION); - if (!TextUtils.isEmpty(packages)) { - try { - mBuilder = themeBundleProvider.parseCustomTheme(packages); - } catch (JSONException e) { - Log.w(TAG, "Couldn't parse provided custom theme."); - } - } else { - Log.w(TAG, "No custom theme being restored."); - } - } - - public static CustomThemeManager create( - @Nullable CustomTheme customTheme, ThemeManager themeManager) { - if (customTheme != null && customTheme.isDefined()) { - return new CustomThemeManager(customTheme.getPackagesByCategory(), customTheme); - } - // Seed the first custom theme with the currently applied theme. - return new CustomThemeManager(themeManager.getCurrentOverlays(), customTheme); - } - -} diff --git a/src/com/android/customization/model/theme/custom/FontOptionsProvider.java b/src/com/android/customization/model/theme/custom/FontOptionsProvider.java deleted file mode 100644 index 53568c9f..00000000 --- a/src/com/android/customization/model/theme/custom/FontOptionsProvider.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.CONFIG_BODY_FONT_FAMILY; -import static com.android.customization.model.ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.Typeface; -import android.util.Log; - -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.OverlayManagerCompat; -import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption; -import com.android.wallpaper.R; - -/** - * Implementation of {@link ThemeComponentOptionProvider} that reads {@link FontOption}s from - * font overlays. - */ -public class FontOptionsProvider extends ThemeComponentOptionProvider<FontOption> { - - private static final String TAG = "FontOptionsProvider"; - - public FontOptionsProvider(Context context, OverlayManagerCompat manager) { - super(context, manager, OVERLAY_CATEGORY_FONT); - } - - @Override - protected void loadOptions() { - addDefault(); - for (String overlayPackage : mOverlayPackages) { - try { - Resources overlayRes = getOverlayResources(overlayPackage); - Typeface headlineFont = Typeface.create( - getFontFamily(overlayPackage, overlayRes, CONFIG_HEADLINE_FONT_FAMILY), - Typeface.NORMAL); - Typeface bodyFont = Typeface.create( - getFontFamily(overlayPackage, overlayRes, CONFIG_BODY_FONT_FAMILY), - Typeface.NORMAL); - PackageManager pm = mContext.getPackageManager(); - String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString(); - mOptions.add(new FontOption(overlayPackage, label, headlineFont, bodyFont)); - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, String.format("Couldn't load font overlay %s, will skip it", - overlayPackage), e); - } - } - } - - private void addDefault() { - Resources system = Resources.getSystem(); - Typeface headlineFont = Typeface.create(system.getString(system.getIdentifier( - ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,"string", ANDROID_PACKAGE)), - Typeface.NORMAL); - Typeface bodyFont = Typeface.create(system.getString(system.getIdentifier( - ResourceConstants.CONFIG_BODY_FONT_FAMILY, - "string", ANDROID_PACKAGE)), - Typeface.NORMAL); - mOptions.add(new FontOption(null, mContext.getString(R.string.default_theme_title), - headlineFont, bodyFont)); - } - - private String getFontFamily(String overlayPackage, Resources overlayRes, String configName) { - return overlayRes.getString(overlayRes.getIdentifier(configName, "string", overlayPackage)); - } -} diff --git a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java b/src/com/android/customization/model/theme/custom/IconOptionsProvider.java deleted file mode 100644 index f7b669b1..00000000 --- a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER; - -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.util.Log; - -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.OverlayManagerCompat; -import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption; -import com.android.wallpaper.R; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Implementation of {@link ThemeComponentOptionProvider} that reads {@link IconOption}s from - * icon overlays. - */ -public class IconOptionsProvider extends ThemeComponentOptionProvider<IconOption> { - - private static final String TAG = "IconOptionsProvider"; - - private final List<String> mSysUiIconsOverlayPackages = new ArrayList<>(); - private final List<String> mSettingsIconsOverlayPackages = new ArrayList<>(); - private final List<String> mLauncherIconsOverlayPackages = new ArrayList<>(); - private final List<String> mThemePickerIconsOverlayPackages = new ArrayList<>(); - - public IconOptionsProvider(Context context, OverlayManagerCompat manager) { - super(context, manager, OVERLAY_CATEGORY_ICON_ANDROID); - String[] targetPackages = ResourceConstants.getPackagesToOverlay(context); - mSysUiIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory( - OVERLAY_CATEGORY_ICON_SYSUI, UserHandle.myUserId(), targetPackages)); - mSettingsIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory( - OVERLAY_CATEGORY_ICON_SETTINGS, UserHandle.myUserId(), targetPackages)); - mLauncherIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory( - OVERLAY_CATEGORY_ICON_LAUNCHER, UserHandle.myUserId(), targetPackages)); - mThemePickerIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory( - OVERLAY_CATEGORY_ICON_THEMEPICKER, UserHandle.myUserId(), targetPackages)); - } - - @Override - protected void loadOptions() { - addDefault(); - - Map<String, IconOption> optionsByPrefix = new HashMap<>(); - for (String overlayPackage : mOverlayPackages) { - IconOption option = addOrUpdateOption(optionsByPrefix, overlayPackage, - OVERLAY_CATEGORY_ICON_ANDROID); - try{ - for (String iconName : ICONS_FOR_PREVIEW) { - option.addIcon(loadIconPreviewDrawable(iconName, overlayPackage)); - } - } catch (NotFoundException | NameNotFoundException e) { - Log.w(TAG, String.format("Couldn't load icon overlay details for %s, will skip it", - overlayPackage), e); - } - } - - for (String overlayPackage : mSysUiIconsOverlayPackages) { - addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_SYSUI); - } - - for (String overlayPackage : mSettingsIconsOverlayPackages) { - addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_SETTINGS); - } - - for (String overlayPackage : mLauncherIconsOverlayPackages) { - addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_LAUNCHER); - } - - for (String overlayPackage : mThemePickerIconsOverlayPackages) { - addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_THEMEPICKER); - } - - for (IconOption option : optionsByPrefix.values()) { - if (option.isValid(mContext)) { - mOptions.add(option); - option.setLabel(mContext.getString(R.string.icon_component_label, mOptions.size())); - } - } - } - - private IconOption addOrUpdateOption(Map<String, IconOption> optionsByPrefix, - String overlayPackage, String category) { - String prefix = overlayPackage.substring(0, overlayPackage.lastIndexOf(".")); - IconOption option; - if (!optionsByPrefix.containsKey(prefix)) { - option = new IconOption(); - optionsByPrefix.put(prefix, option); - } else { - option = optionsByPrefix.get(prefix); - } - option.addOverlayPackage(category, overlayPackage); - return option; - } - - private Drawable loadIconPreviewDrawable(String drawableName, String packageName) - throws NameNotFoundException, NotFoundException { - final Resources resources = ANDROID_PACKAGE.equals(packageName) - ? Resources.getSystem() - : mContext.getPackageManager().getResourcesForApplication(packageName); - return resources.getDrawable( - resources.getIdentifier(drawableName, "drawable", packageName), null); - } - - private void addDefault() { - IconOption option = new IconOption(); - option.setLabel(mContext.getString(R.string.default_theme_title)); - try { - for (String iconName : ICONS_FOR_PREVIEW) { - option.addIcon(loadIconPreviewDrawable(iconName, ANDROID_PACKAGE)); - } - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, "Didn't find SystemUi package icons, will skip option", e); - } - option.addOverlayPackage(OVERLAY_CATEGORY_ICON_ANDROID, null); - option.addOverlayPackage(OVERLAY_CATEGORY_ICON_SYSUI, null); - option.addOverlayPackage(OVERLAY_CATEGORY_ICON_SETTINGS, null); - option.addOverlayPackage(OVERLAY_CATEGORY_ICON_LAUNCHER, null); - option.addOverlayPackage(OVERLAY_CATEGORY_ICON_THEMEPICKER, null); - mOptions.add(option); - } - -} diff --git a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java deleted file mode 100644 index f93b8920..00000000 --- a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE; -import static com.android.customization.model.ResourceConstants.CONFIG_CORNERRADIUS; -import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.customization.model.ResourceConstants.PATH_SIZE; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.Path; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.PathShape; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.Dimension; -import androidx.core.graphics.PathParser; - -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.OverlayManagerCompat; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption; -import com.android.customization.widget.DynamicAdaptiveIconDrawable; -import com.android.wallpaper.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * Implementation of {@link ThemeComponentOptionProvider} that reads {@link ShapeOption}s from - * icon overlays. - */ -public class ShapeOptionsProvider extends ThemeComponentOptionProvider<ShapeOption> { - - private static final String TAG = "ShapeOptionsProvider"; - private final String[] mShapePreviewIconPackages; - private int mThumbSize; - - public ShapeOptionsProvider(Context context, OverlayManagerCompat manager) { - super(context, manager, OVERLAY_CATEGORY_SHAPE); - mShapePreviewIconPackages = context.getResources().getStringArray( - R.array.icon_shape_preview_packages); - mThumbSize = mContext.getResources().getDimensionPixelSize( - R.dimen.component_shape_thumb_size); - } - - @Override - protected void loadOptions() { - addDefault(); - for (String overlayPackage : mOverlayPackages) { - try { - Path path = loadPath(mContext.getPackageManager() - .getResourcesForApplication(overlayPackage), overlayPackage); - PackageManager pm = mContext.getPackageManager(); - String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString(); - mOptions.add(new ShapeOption(overlayPackage, label, path, - loadCornerRadius(overlayPackage), createShapeDrawable(path), - getShapedAppIcons(path))); - } catch (NameNotFoundException | NotFoundException e) { - Log.w(TAG, String.format("Couldn't load shape overlay %s, will skip it", - overlayPackage), e); - } - } - } - - private void addDefault() { - Resources system = Resources.getSystem(); - Path path = loadPath(system, ANDROID_PACKAGE); - mOptions.add(new ShapeOption(null, mContext.getString(R.string.default_theme_title), path, - system.getDimensionPixelOffset( - system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS, - "dimen", ResourceConstants.ANDROID_PACKAGE)), - createShapeDrawable(path), getShapedAppIcons(path))); - } - - private ShapeDrawable createShapeDrawable(Path path) { - PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE); - ShapeDrawable shapeDrawable = new ShapeDrawable(shape); - shapeDrawable.setIntrinsicHeight(mThumbSize); - shapeDrawable.setIntrinsicWidth(mThumbSize); - return shapeDrawable; - } - - private List<ShapeAppIcon> getShapedAppIcons(Path path) { - List<ShapeAppIcon> shapedAppIcons = new ArrayList<>(); - for (String packageName : mShapePreviewIconPackages) { - Drawable icon = null; - CharSequence name = null; - try { - Drawable appIcon = mContext.getPackageManager().getApplicationIcon(packageName); - if (appIcon instanceof AdaptiveIconDrawable) { - AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) appIcon; - icon = new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(), - adaptiveIcon.getForeground(), path); - - ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfo(packageName, /* flag= */ 0); - name = mContext.getPackageManager().getApplicationLabel(appInfo); - } - } catch (NameNotFoundException e) { - Log.d(TAG, "Couldn't find app " + packageName - + ", won't use it for icon shape preview"); - } finally { - if (icon != null && !TextUtils.isEmpty(name)) { - shapedAppIcons.add(new ShapeAppIcon(icon, name)); - } - } - } - return shapedAppIcons; - } - - private Path loadPath(Resources overlayRes, String packageName) { - String shape = overlayRes.getString(overlayRes.getIdentifier(CONFIG_ICON_MASK, "string", - packageName)); - - if (!TextUtils.isEmpty(shape)) { - return PathParser.createPathFromPathData(shape); - } - return null; - } - - @Dimension - private int loadCornerRadius(String packageName) - throws NameNotFoundException, NotFoundException { - - Resources overlayRes = - mContext.getPackageManager().getResourcesForApplication( - packageName); - return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier( - CONFIG_CORNERRADIUS, "dimen", packageName)); - } -} diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java deleted file mode 100644 index 78be0fc0..00000000 --- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.customization.model.ResourceConstants.getLauncherPackage; - -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.content.res.Resources.Theme; -import android.content.res.TypedArray; -import android.graphics.Path; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.StateListDrawable; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.SeekBar; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.graphics.ColorUtils; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.CustomizationOption; -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.customization.model.theme.custom.CustomTheme.Builder; -import com.android.wallpaper.R; -import com.android.wallpaper.util.ResourceUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Represents an option of a component of a custom Theme (for example, a possible color, or font, - * shape, etc). - * Extending classes correspond to each component's options and provide the structure to bind - * preview and thumbnails. - * // TODO (santie): refactor the logic to bind preview cards to reuse between ThemeFragment and - * // here - */ -public abstract class ThemeComponentOption implements CustomizationOption<ThemeComponentOption> { - - protected final Map<String, String> mOverlayPackageNames = new HashMap<>(); - - protected void addOverlayPackage(String category, String packageName) { - mOverlayPackageNames.put(category, packageName); - } - - public Map<String, String> getOverlayPackages() { - return mOverlayPackageNames; - } - - @Override - public String getTitle() { - return null; - } - - public abstract void bindPreview(ViewGroup container); - - public Builder buildStep(Builder builder) { - getOverlayPackages().forEach(builder::addOverlayPackage); - return builder; - } - - public static class FontOption extends ThemeComponentOption { - - private final String mLabel; - private final Typeface mHeadlineFont; - private final Typeface mBodyFont; - - public FontOption(String packageName, String label, Typeface headlineFont, - Typeface bodyFont) { - addOverlayPackage(OVERLAY_CATEGORY_FONT, packageName); - mLabel = label; - mHeadlineFont = headlineFont; - mBodyFont = bodyFont; - } - - @Override - public String getTitle() { - return null; - } - - @Override - public void bindThumbnailTile(View view) { - ((TextView) view.findViewById(R.id.thumbnail_text)).setTypeface( - mHeadlineFont); - view.setContentDescription(mLabel); - } - - @Override - public boolean isActive(CustomizationManager<ThemeComponentOption> manager) { - CustomThemeManager customThemeManager = (CustomThemeManager) manager; - return Objects.equals(getOverlayPackages().get(OVERLAY_CATEGORY_FONT), - customThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_FONT)); - } - - @Override - public int getLayoutResId() { - return R.layout.theme_font_option; - } - - @Override - public void bindPreview(ViewGroup container) { - container.setContentDescription( - container.getContext().getString(R.string.font_preview_content_description)); - - bindPreviewHeader(container, R.string.preview_name_font, R.drawable.ic_font, null); - - ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container); - if (cardBody.getChildCount() == 0) { - LayoutInflater.from(container.getContext()).inflate( - R.layout.preview_card_font_content, - cardBody, true); - } - TextView title = container.findViewById(R.id.font_card_title); - title.setTypeface(mHeadlineFont); - TextView bodyText = container.findViewById(R.id.font_card_body); - bodyText.setTypeface(mBodyFont); - container.findViewById(R.id.font_card_divider).setBackgroundColor( - title.getCurrentTextColor()); - } - - @Override - public Builder buildStep(Builder builder) { - builder.setHeadlineFontFamily(mHeadlineFont).setBodyFontFamily(mBodyFont); - return super.buildStep(builder); - } - } - - void bindPreviewHeader(ViewGroup container, @StringRes int headerTextResId, - @DrawableRes int headerIcon, String drawableName) { - TextView header = container.findViewById(R.id.theme_preview_card_header); - header.setText(headerTextResId); - - Context context = container.getContext(); - Drawable icon; - if (!TextUtils.isEmpty(drawableName)) { - try { - Resources resources = context.getPackageManager() - .getResourcesForApplication(getLauncherPackage(context)); - icon = resources.getDrawable(resources.getIdentifier( - drawableName, "drawable", getLauncherPackage(context)), null); - } catch (NameNotFoundException | NotFoundException e) { - icon = context.getResources().getDrawable(headerIcon, context.getTheme()); - } - } else { - icon = context.getResources().getDrawable(headerIcon, context.getTheme()); - } - int size = context.getResources().getDimensionPixelSize(R.dimen.card_header_icon_size); - icon.setBounds(0, 0, size, size); - - header.setCompoundDrawables(null, icon, null, null); - header.setCompoundDrawableTintList(ColorStateList.valueOf( - header.getCurrentTextColor())); - } - - public static class IconOption extends ThemeComponentOption { - - public static final int THUMBNAIL_ICON_POSITION = 0; - private static int[] mIconIds = { - R.id.preview_icon_0, R.id.preview_icon_1, R.id.preview_icon_2, R.id.preview_icon_3, - R.id.preview_icon_4, R.id.preview_icon_5 - }; - - private List<Drawable> mIcons = new ArrayList<>(); - private String mLabel; - - @Override - public void bindThumbnailTile(View view) { - Resources res = view.getContext().getResources(); - Drawable icon = mIcons.get(THUMBNAIL_ICON_POSITION) - .getConstantState().newDrawable().mutate(); - icon.setTint(ResourceUtils.getColorAttr( - view.getContext(), android.R.attr.textColorSecondary)); - ((ImageView) view.findViewById(R.id.option_icon)).setImageDrawable( - icon); - view.setContentDescription(mLabel); - } - - @Override - public boolean isActive(CustomizationManager<ThemeComponentOption> manager) { - CustomThemeManager customThemeManager = (CustomThemeManager) manager; - Map<String, String> themePackages = customThemeManager.getOverlayPackages(); - if (getOverlayPackages().isEmpty()) { - return themePackages.get(OVERLAY_CATEGORY_ICON_SYSUI) == null && - themePackages.get(OVERLAY_CATEGORY_ICON_SETTINGS) == null && - themePackages.get(OVERLAY_CATEGORY_ICON_ANDROID) == null && - themePackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER) == null && - themePackages.get(OVERLAY_CATEGORY_ICON_THEMEPICKER) == null; - } - for (Map.Entry<String, String> overlayEntry : getOverlayPackages().entrySet()) { - if(!Objects.equals(overlayEntry.getValue(), - themePackages.get(overlayEntry.getKey()))) { - return false; - } - } - return true; - } - - @Override - public int getLayoutResId() { - return R.layout.theme_icon_option; - } - - @Override - public void bindPreview(ViewGroup container) { - container.setContentDescription( - container.getContext().getString(R.string.icon_preview_content_description)); - - bindPreviewHeader(container, R.string.preview_name_icon, R.drawable.ic_widget, - "ic_widget"); - - ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container); - if (cardBody.getChildCount() == 0) { - LayoutInflater.from(container.getContext()).inflate( - R.layout.preview_card_icon_content, cardBody, true); - } - for (int i = 0; i < mIconIds.length && i < mIcons.size(); i++) { - ((ImageView) container.findViewById(mIconIds[i])).setImageDrawable( - mIcons.get(i)); - } - } - - public void addIcon(Drawable previewIcon) { - mIcons.add(previewIcon); - } - - /** - * @return whether this icon option has overlays and previews for all the required packages - */ - public boolean isValid(Context context) { - return getOverlayPackages().keySet().size() == - ResourceConstants.getPackagesToOverlay(context).length; - } - - public void setLabel(String label) { - mLabel = label; - } - - @Override - public Builder buildStep(Builder builder) { - for (Drawable icon : mIcons) { - builder.addIcon(icon); - } - return super.buildStep(builder); - } - } - - public static class ColorOption extends ThemeComponentOption { - - /** - * Ids of views used to represent quick setting tiles in the color preview screen - */ - private static int[] COLOR_TILE_IDS = { - R.id.preview_color_qs_0_bg, R.id.preview_color_qs_1_bg, R.id.preview_color_qs_2_bg - }; - - /** - * Ids of the views for the foreground of the icon, mapping to the corresponding index of - * the actual icon drawable. - */ - static int[][] COLOR_TILES_ICON_IDS = { - new int[]{ R.id.preview_color_qs_0_icon, 0}, - new int[]{ R.id.preview_color_qs_1_icon, 1}, - new int[] { R.id.preview_color_qs_2_icon, 3} - }; - - /** - * Ids of views used to represent control buttons in the color preview screen - */ - private static int[] COLOR_BUTTON_IDS = { - R.id.preview_check_selected, R.id.preview_radio_selected, - R.id.preview_toggle_selected - }; - - @ColorInt private int mColorAccentLight; - @ColorInt private int mColorAccentDark; - /** - * Icons shown as example of QuickSettings tiles in the color preview screen. - */ - private List<Drawable> mIcons = new ArrayList<>(); - - /** - * Drawable with the currently selected shape to be used as background of the sample - * QuickSetting icons in the color preview screen. - */ - private Drawable mShapeDrawable; - - private String mLabel; - - ColorOption(String packageName, String label, @ColorInt int lightColor, - @ColorInt int darkColor) { - addOverlayPackage(OVERLAY_CATEGORY_COLOR, packageName); - mLabel = label; - mColorAccentLight = lightColor; - mColorAccentDark = darkColor; - } - - @Override - public void bindThumbnailTile(View view) { - @ColorInt int color = resolveColor(view.getResources()); - LayerDrawable selectedOption = (LayerDrawable) view.getResources().getDrawable( - R.drawable.color_chip_hollow, view.getContext().getTheme()); - Drawable unselectedOption = view.getResources().getDrawable( - R.drawable.color_chip_filled, view.getContext().getTheme()); - - selectedOption.findDrawableByLayerId(R.id.center_fill).setTintList( - ColorStateList.valueOf(color)); - unselectedOption.setTintList(ColorStateList.valueOf(color)); - - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] {android.R.attr.state_activated}, selectedOption); - stateListDrawable.addState( - new int[] {-android.R.attr.state_activated}, unselectedOption); - - ((ImageView) view.findViewById(R.id.option_tile)).setImageDrawable(stateListDrawable); - view.setContentDescription(mLabel); - } - - @ColorInt - private int resolveColor(Resources res) { - Configuration configuration = res.getConfiguration(); - return (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES ? mColorAccentDark : mColorAccentLight; - } - - @Override - public boolean isActive(CustomizationManager<ThemeComponentOption> manager) { - CustomThemeManager customThemeManager = (CustomThemeManager) manager; - return Objects.equals(getOverlayPackages().get(OVERLAY_CATEGORY_COLOR), - customThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_COLOR)); - } - - @Override - public int getLayoutResId() { - return R.layout.theme_color_option; - } - - @Override - public void bindPreview(ViewGroup container) { - container.setContentDescription( - container.getContext().getString(R.string.color_preview_content_description)); - - bindPreviewHeader(container, R.string.preview_name_color, R.drawable.ic_colorize_24px, - null); - - ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container); - if (cardBody.getChildCount() == 0) { - LayoutInflater.from(container.getContext()).inflate( - R.layout.preview_card_color_content, cardBody, true); - } - Resources res = container.getResources(); - @ColorInt int accentColor = resolveColor(res); - @ColorInt int controlGreyColor = ResourceUtils.getColorAttr( - container.getContext(), - android.R.attr.textColorTertiary); - ColorStateList tintList = new ColorStateList( - new int[][]{ - new int[]{android.R.attr.state_selected}, - new int[]{android.R.attr.state_checked}, - new int[]{-android.R.attr.state_enabled} - }, - new int[] { - accentColor, - accentColor, - controlGreyColor - } - ); - - for (int i = 0; i < COLOR_BUTTON_IDS.length; i++) { - CompoundButton button = container.findViewById(COLOR_BUTTON_IDS[i]); - button.setButtonTintList(tintList); - } - - Switch enabledSwitch = container.findViewById(R.id.preview_toggle_selected); - enabledSwitch.setThumbTintList(tintList); - enabledSwitch.setTrackTintList(tintList); - - ColorStateList seekbarTintList = ColorStateList.valueOf(accentColor); - SeekBar seekbar = container.findViewById(R.id.preview_seekbar); - seekbar.setThumbTintList(seekbarTintList); - seekbar.setProgressTintList(seekbarTintList); - seekbar.setProgressBackgroundTintList(seekbarTintList); - // Disable seekbar - seekbar.setOnTouchListener((view, motionEvent) -> true); - - int iconFgColor = ResourceUtils.getColorAttr(container.getContext(), - android.R.attr.colorBackground); - if (!mIcons.isEmpty() && mShapeDrawable != null) { - for (int i = 0; i < COLOR_TILE_IDS.length; i++) { - Drawable icon = mIcons.get(COLOR_TILES_ICON_IDS[i][1]).getConstantState() - .newDrawable(); - icon.setTint(iconFgColor); - //TODO: load and set the shape. - Drawable bgShape = mShapeDrawable.getConstantState().newDrawable(); - bgShape.setTint(accentColor); - - ImageView bg = container.findViewById(COLOR_TILE_IDS[i]); - bg.setImageDrawable(bgShape); - ImageView fg = container.findViewById(COLOR_TILES_ICON_IDS[i][0]); - fg.setImageDrawable(icon); - } - } - } - - public void setPreviewIcons(List<Drawable> icons) { - mIcons.addAll(icons); - } - - public void setShapeDrawable(@Nullable Drawable shapeDrawable) { - mShapeDrawable = shapeDrawable; - } - - @Override - public Builder buildStep(Builder builder) { - builder.setColorAccentDark(mColorAccentDark).setColorAccentLight(mColorAccentLight); - return super.buildStep(builder); - } - } - - public static class ShapeOption extends ThemeComponentOption { - - private final LayerDrawable mShape; - private final List<ShapeAppIcon> mAppIcons; - private final String mLabel; - private final Path mPath; - private final int mCornerRadius; - private int[] mShapeIconIds = { - R.id.shape_preview_icon_0, R.id.shape_preview_icon_1, R.id.shape_preview_icon_2, - R.id.shape_preview_icon_3, R.id.shape_preview_icon_4, R.id.shape_preview_icon_5 - }; - - ShapeOption(String packageName, String label, Path path, - @Dimension int cornerRadius, Drawable shapeDrawable, - List<ShapeAppIcon> appIcons) { - addOverlayPackage(OVERLAY_CATEGORY_SHAPE, packageName); - mLabel = label; - mAppIcons = appIcons; - mPath = path; - mCornerRadius = cornerRadius; - Drawable background = shapeDrawable.getConstantState().newDrawable(); - Drawable foreground = shapeDrawable.getConstantState().newDrawable(); - mShape = new LayerDrawable(new Drawable[]{background, foreground}); - mShape.setLayerGravity(0, Gravity.CENTER); - mShape.setLayerGravity(1, Gravity.CENTER); - } - - @Override - public void bindThumbnailTile(View view) { - ImageView thumb = view.findViewById(R.id.shape_thumbnail); - Resources res = view.getResources(); - Theme theme = view.getContext().getTheme(); - int borderWidth = 2 * res.getDimensionPixelSize(R.dimen.option_border_width); - - Drawable background = mShape.getDrawable(0); - background.setTintList(res.getColorStateList(R.color.option_border_color, theme)); - - ShapeDrawable foreground = (ShapeDrawable) mShape.getDrawable(1); - - foreground.setIntrinsicHeight(background.getIntrinsicHeight() - borderWidth); - foreground.setIntrinsicWidth(background.getIntrinsicWidth() - borderWidth); - TypedArray ta = view.getContext().obtainStyledAttributes( - new int[]{android.R.attr.colorPrimary}); - int primaryColor = ta.getColor(0, 0); - ta.recycle(); - int foregroundColor = - ResourceUtils.getColorAttr(view.getContext(), android.R.attr.textColorPrimary); - - foreground.setTint(ColorUtils.blendARGB(primaryColor, foregroundColor, .05f)); - - thumb.setImageDrawable(mShape); - view.setContentDescription(mLabel); - } - - @Override - public boolean isActive(CustomizationManager<ThemeComponentOption> manager) { - CustomThemeManager customThemeManager = (CustomThemeManager) manager; - return Objects.equals(getOverlayPackages().get(OVERLAY_CATEGORY_SHAPE), - customThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_SHAPE)); - } - - @Override - public int getLayoutResId() { - return R.layout.theme_shape_option; - } - - @Override - public void bindPreview(ViewGroup container) { - container.setContentDescription( - container.getContext().getString(R.string.shape_preview_content_description)); - - bindPreviewHeader(container, R.string.preview_name_shape, R.drawable.ic_shapes_24px, - null); - - ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container); - if (cardBody.getChildCount() == 0) { - LayoutInflater.from(container.getContext()).inflate( - R.layout.preview_card_shape_content, cardBody, true); - } - for (int i = 0; i < mShapeIconIds.length && i < mAppIcons.size(); i++) { - ImageView iconView = cardBody.findViewById(mShapeIconIds[i]); - iconView.setBackground(mAppIcons.get(i).getDrawableCopy()); - } - } - - @Override - public Builder buildStep(Builder builder) { - builder.setShapePath(mPath) - .setBottomSheetCornerRadius(mCornerRadius) - .setShapePreviewIcons(mAppIcons); - return super.buildStep(builder); - } - } -} diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java b/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java deleted file mode 100644 index 992c47cc..00000000 --- a/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.model.theme.custom; - -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.os.UserHandle; - -import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.ResourceConstants; -import com.android.customization.model.theme.OverlayManagerCompat; - -import java.util.ArrayList; -import java.util.List; - -/** - * Base class used to retrieve Custom Theme Component options (eg, different fonts) - * from the system. - */ -public abstract class ThemeComponentOptionProvider<T extends ThemeComponentOption> { - - protected final Context mContext; - protected final List<String> mOverlayPackages; - protected List<T> mOptions; - - public ThemeComponentOptionProvider(Context context, OverlayManagerCompat manager, - String... categories) { - mContext = context; - mOverlayPackages = new ArrayList<>(); - for (String category : categories) { - mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(category, - UserHandle.myUserId(), ResourceConstants.getPackagesToOverlay(mContext))); - } - } - - /** - * Returns whether there are options for this component available in the current setup. - */ - public boolean isAvailable() { - return !mOverlayPackages.isEmpty(); - } - - /** - * Retrieve the available options for this component. - * @param callback called when the themes have been retrieved (or immediately if cached) - * @param reload whether to reload themes if they're cached. - */ - public void fetch(OptionsFetchedListener<T> callback, boolean reload) { - if (mOptions == null || reload) { - mOptions = new ArrayList<>(); - loadOptions(); - } - - if(callback != null) { - callback.onOptionsLoaded(mOptions); - } - } - - protected abstract void loadOptions(); - - protected Resources getOverlayResources(String overlayPackage) throws NameNotFoundException { - return mContext.getPackageManager().getResourcesForApplication(overlayPackage); - } -} diff --git a/src/com/android/customization/model/themedicon/ThemedIconSectionController.java b/src/com/android/customization/model/themedicon/ThemedIconSectionController.java index 5d551a6a..1cc6d0a5 100644 --- a/src/com/android/customization/model/themedicon/ThemedIconSectionController.java +++ b/src/com/android/customization/model/themedicon/ThemedIconSectionController.java @@ -24,10 +24,12 @@ import androidx.lifecycle.Observer; import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor; import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.customization.picker.themedicon.ThemedIconSectionView; import com.android.wallpaper.R; import com.android.wallpaper.model.CustomizationSectionController; +// TODO (b/311712452): Refactor CustomizationSectionController to use recommended arch UI components /** The {@link CustomizationSectionController} for themed icon section. */ public class ThemedIconSectionController implements CustomizationSectionController<ThemedIconSectionView> { @@ -38,6 +40,7 @@ public class ThemedIconSectionController implements private final ThemedIconInteractor mInteractor; private final ThemedIconSnapshotRestorer mSnapshotRestorer; private final Observer<Boolean> mIsActivatedChangeObserver; + private final ThemesUserEventLogger mThemesUserEventLogger; private ThemedIconSectionView mThemedIconSectionView; private boolean mSavedThemedIconEnabled = false; @@ -46,7 +49,8 @@ public class ThemedIconSectionController implements ThemedIconSwitchProvider themedIconOptionsProvider, ThemedIconInteractor interactor, @Nullable Bundle savedInstanceState, - ThemedIconSnapshotRestorer snapshotRestorer) { + ThemedIconSnapshotRestorer snapshotRestorer, + ThemesUserEventLogger themesUserEventLogger) { mThemedIconOptionsProvider = themedIconOptionsProvider; mInteractor = interactor; mSnapshotRestorer = snapshotRestorer; @@ -55,6 +59,7 @@ public class ThemedIconSectionController implements mThemedIconSectionView.getSwitch().setChecked(isActivated); } }; + mThemesUserEventLogger = themesUserEventLogger; if (savedInstanceState != null) { mSavedThemedIconEnabled = savedInstanceState.getBoolean( @@ -75,7 +80,10 @@ public class ThemedIconSectionController implements mThemedIconSectionView.setViewListener(this::onViewActivated); mThemedIconSectionView.getSwitch().setChecked(mSavedThemedIconEnabled); mThemedIconOptionsProvider.fetchThemedIconEnabled( - enabled -> mThemedIconSectionView.getSwitch().setChecked(enabled)); + enabled -> { + mInteractor.setActivated(enabled); + mThemedIconSectionView.getSwitch().setChecked(enabled); + }); mInteractor.isActivatedAsLiveData().observeForever(mIsActivatedChangeObserver); return mThemedIconSectionView; } @@ -91,6 +99,7 @@ public class ThemedIconSectionController implements } mThemedIconOptionsProvider.setThemedIconEnabled(viewActivated); mInteractor.setActivated(viewActivated); + mThemesUserEventLogger.logThemedIconApplied(viewActivated); mSnapshotRestorer.store(viewActivated); } diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt index 8fd3768d..82203d9b 100644 --- a/src/com/android/customization/module/CustomizationInjector.kt +++ b/src/com/android/customization/module/CustomizationInjector.kt @@ -18,33 +18,21 @@ package com.android.customization.module import android.content.Context import android.content.res.Resources import androidx.activity.ComponentActivity -import androidx.fragment.app.FragmentActivity -import com.android.customization.model.theme.OverlayManagerCompat -import com.android.customization.model.theme.ThemeBundleProvider -import com.android.customization.model.theme.ThemeManager +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor import com.android.customization.picker.clock.ui.view.ClockViewFactory import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel -import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel -import com.android.customization.picker.clock.utils.ClockDescriptionUtils import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor import com.android.systemui.shared.clocks.ClockRegistry -import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.module.Injector +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository interface CustomizationInjector : Injector { fun getCustomizationPreferences(context: Context): CustomizationPreferences - fun getThemeManager( - provider: ThemeBundleProvider, - activity: FragmentActivity, - overlayManagerCompat: OverlayManagerCompat, - logger: ThemesUserEventLogger, - ): ThemeManager - fun getKeyguardQuickAffordancePickerInteractor( context: Context, ): KeyguardQuickAffordancePickerInteractor @@ -53,31 +41,28 @@ interface CustomizationInjector : Injector { fun getClockPickerInteractor(context: Context): ClockPickerInteractor - fun getClockSectionViewModel( - context: Context, - ): ClockSectionViewModel - fun getColorPickerInteractor( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, ): ColorPickerInteractor fun getColorPickerViewModelFactory( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, ): ColorPickerViewModel.Factory fun getClockCarouselViewModelFactory( interactor: ClockPickerInteractor, + clockViewFactory: ClockViewFactory, + resources: Resources, + logger: ThemesUserEventLogger, ): ClockCarouselViewModel.Factory fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory fun getClockSettingsViewModelFactory( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, clockViewFactory: ClockViewFactory, ): ClockSettingsViewModel.Factory - - fun getClockDescriptionUtils(resources: Resources): ClockDescriptionUtils } diff --git a/src/com/android/customization/module/CustomizationPreferences.java b/src/com/android/customization/module/CustomizationPreferences.java deleted file mode 100644 index 0df3ff99..00000000 --- a/src/com/android/customization/module/CustomizationPreferences.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.module; - -import com.android.wallpaper.module.WallpaperPreferences; - -public interface CustomizationPreferences extends WallpaperPreferences { - - String KEY_CUSTOM_THEME= "themepicker_custom_theme"; - String KEY_VISITED_PREFIX = "themepicker_visited_"; - String KEY_THEMED_ICON_ENABLED = "themepicker_themed_icon_enabled"; - - String getSerializedCustomThemes(); - - void storeCustomThemes(String serializedCustomThemes); - - boolean getTabVisited(String id); - - void setTabVisited(String id); - - boolean getThemedIconEnabled(); - - void setThemedIconEnabled(boolean enabled); -} diff --git a/src/com/android/customization/module/CustomizationPreferences.kt b/src/com/android/customization/module/CustomizationPreferences.kt new file mode 100644 index 00000000..4148c3b8 --- /dev/null +++ b/src/com/android/customization/module/CustomizationPreferences.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.module + +import com.android.wallpaper.module.WallpaperPreferences + +interface CustomizationPreferences : WallpaperPreferences { + fun getSerializedCustomThemes(): String? + + fun storeCustomThemes(serializedCustomThemes: String) + + fun getTabVisited(id: String): Boolean + + fun setTabVisited(id: String) + + fun getThemedIconEnabled(): Boolean + + fun setThemedIconEnabled(enabled: Boolean) + + companion object { + const val KEY_CUSTOM_THEME = "themepicker_custom_theme" + const val KEY_VISITED_PREFIX = "themepicker_visited_" + const val KEY_THEMED_ICON_ENABLED = "themepicker_themed_icon_enabled" + } +} diff --git a/src/com/android/customization/module/DefaultCustomizationPreferences.java b/src/com/android/customization/module/DefaultCustomizationPreferences.java deleted file mode 100644 index 4af402f2..00000000 --- a/src/com/android/customization/module/DefaultCustomizationPreferences.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.module; - -import android.content.Context; - -import com.android.wallpaper.module.DefaultWallpaperPreferences; - -public class DefaultCustomizationPreferences extends DefaultWallpaperPreferences - implements CustomizationPreferences { - - public DefaultCustomizationPreferences(Context context) { - super(context); - } - - - @Override - public String getSerializedCustomThemes() { - return mSharedPrefs.getString(KEY_CUSTOM_THEME, null); - } - - @Override - public void storeCustomThemes(String serializedCustomThemes) { - mSharedPrefs.edit().putString(KEY_CUSTOM_THEME, serializedCustomThemes).apply(); - } - - @Override - public boolean getTabVisited(String id) { - return mSharedPrefs.getBoolean(KEY_VISITED_PREFIX + id, false); - } - - @Override - public void setTabVisited(String id) { - mSharedPrefs.edit().putBoolean(KEY_VISITED_PREFIX + id, true).apply(); - } - - @Override - public boolean getThemedIconEnabled() { - return mSharedPrefs.getBoolean(KEY_THEMED_ICON_ENABLED, false); - } - - @Override - public void setThemedIconEnabled(boolean enabled) { - mSharedPrefs.edit().putBoolean(KEY_THEMED_ICON_ENABLED, enabled).apply(); - } -} diff --git a/src/com/android/customization/module/DefaultCustomizationPreferences.kt b/src/com/android/customization/module/DefaultCustomizationPreferences.kt new file mode 100644 index 00000000..49fd1a94 --- /dev/null +++ b/src/com/android/customization/module/DefaultCustomizationPreferences.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.module + +import android.content.Context +import com.android.wallpaper.module.DefaultWallpaperPreferences + +open class DefaultCustomizationPreferences(context: Context) : + DefaultWallpaperPreferences(context), CustomizationPreferences { + + override fun getSerializedCustomThemes(): String? { + return sharedPrefs.getString(CustomizationPreferences.KEY_CUSTOM_THEME, null) + } + + override fun storeCustomThemes(serializedCustomThemes: String) { + sharedPrefs + .edit() + .putString(CustomizationPreferences.KEY_CUSTOM_THEME, serializedCustomThemes) + .apply() + } + + override fun getTabVisited(id: String): Boolean { + return sharedPrefs.getBoolean(CustomizationPreferences.KEY_VISITED_PREFIX + id, false) + } + + override fun setTabVisited(id: String) { + sharedPrefs + .edit() + .putBoolean(CustomizationPreferences.KEY_VISITED_PREFIX + id, true) + .apply() + } + + override fun getThemedIconEnabled(): Boolean { + return sharedPrefs.getBoolean(CustomizationPreferences.KEY_THEMED_ICON_ENABLED, false) + } + + override fun setThemedIconEnabled(enabled: Boolean) { + sharedPrefs + .edit() + .putBoolean(CustomizationPreferences.KEY_THEMED_ICON_ENABLED, enabled) + .apply() + } +} diff --git a/src/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java index bbe6bef1..8347d03c 100644 --- a/src/com/android/customization/module/DefaultCustomizationSections.java +++ b/src/com/android/customization/module/DefaultCustomizationSections.java @@ -9,22 +9,21 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import com.android.customization.model.grid.GridOptionsManager; -import com.android.customization.model.grid.GridSectionController; -import com.android.customization.model.mode.DarkModeSnapshotRestorer; import com.android.customization.model.themedicon.ThemedIconSectionController; import com.android.customization.model.themedicon.ThemedIconSwitchProvider; import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor; import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer; +import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.customization.picker.clock.ui.view.ClockViewFactory; import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel; import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor; -import com.android.customization.picker.color.ui.section.ColorSectionController2; +import com.android.customization.picker.color.ui.section.ColorSectionController; import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel; +import com.android.customization.picker.grid.ui.section.GridSectionController; import com.android.customization.picker.notifications.ui.section.NotificationSectionController; import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel; import com.android.customization.picker.preview.ui.section.PreviewWithClockCarouselSectionController; import com.android.customization.picker.preview.ui.section.PreviewWithThemeSectionController; -import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor; import com.android.customization.picker.quickaffordance.ui.section.KeyguardQuickAffordanceSectionController; import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel; import com.android.customization.picker.settings.ui.section.MoreSettingsSectionController; @@ -32,10 +31,10 @@ import com.android.wallpaper.config.BaseFlags; import com.android.wallpaper.model.CustomizationSectionController; import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController; import com.android.wallpaper.model.PermissionRequester; -import com.android.wallpaper.model.WallpaperColorsViewModel; import com.android.wallpaper.model.WallpaperPreviewNavigator; import com.android.wallpaper.module.CurrentWallpaperInfoFactory; import com.android.wallpaper.module.CustomizationSections; +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository; import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor; import com.android.wallpaper.picker.customization.ui.section.ConnectedSectionController; import com.android.wallpaper.picker.customization.ui.section.WallpaperQuickSwitchSectionController; @@ -49,43 +48,40 @@ import java.util.List; public final class DefaultCustomizationSections implements CustomizationSections { private final ColorPickerViewModel.Factory mColorPickerViewModelFactory; - private final KeyguardQuickAffordancePickerInteractor mKeyguardQuickAffordancePickerInteractor; private final KeyguardQuickAffordancePickerViewModel.Factory mKeyguardQuickAffordancePickerViewModelFactory; private final NotificationSectionViewModel.Factory mNotificationSectionViewModelFactory; private final BaseFlags mFlags; private final ClockCarouselViewModel.Factory mClockCarouselViewModelFactory; private final ClockViewFactory mClockViewFactory; - private final DarkModeSnapshotRestorer mDarkModeSnapshotRestorer; private final ThemedIconSnapshotRestorer mThemedIconSnapshotRestorer; private final ThemedIconInteractor mThemedIconInteractor; private final ColorPickerInteractor mColorPickerInteractor; + private final ThemesUserEventLogger mThemesUserEventLogger; public DefaultCustomizationSections( ColorPickerViewModel.Factory colorPickerViewModelFactory, - KeyguardQuickAffordancePickerInteractor keyguardQuickAffordancePickerInteractor, KeyguardQuickAffordancePickerViewModel.Factory keyguardQuickAffordancePickerViewModelFactory, NotificationSectionViewModel.Factory notificationSectionViewModelFactory, BaseFlags flags, ClockCarouselViewModel.Factory clockCarouselViewModelFactory, ClockViewFactory clockViewFactory, - DarkModeSnapshotRestorer darkModeSnapshotRestorer, ThemedIconSnapshotRestorer themedIconSnapshotRestorer, ThemedIconInteractor themedIconInteractor, - ColorPickerInteractor colorPickerInteractor) { + ColorPickerInteractor colorPickerInteractor, + ThemesUserEventLogger themesUserEventLogger) { mColorPickerViewModelFactory = colorPickerViewModelFactory; - mKeyguardQuickAffordancePickerInteractor = keyguardQuickAffordancePickerInteractor; mKeyguardQuickAffordancePickerViewModelFactory = keyguardQuickAffordancePickerViewModelFactory; mNotificationSectionViewModelFactory = notificationSectionViewModelFactory; mFlags = flags; mClockCarouselViewModelFactory = clockCarouselViewModelFactory; mClockViewFactory = clockViewFactory; - mDarkModeSnapshotRestorer = darkModeSnapshotRestorer; mThemedIconSnapshotRestorer = themedIconSnapshotRestorer; mThemedIconInteractor = themedIconInteractor; mColorPickerInteractor = colorPickerInteractor; + mThemesUserEventLogger = themesUserEventLogger; } @Override @@ -93,7 +89,7 @@ public final class DefaultCustomizationSections implements CustomizationSections Screen screen, FragmentActivity activity, LifecycleOwner lifecycleOwner, - WallpaperColorsViewModel wallpaperColorsViewModel, + WallpaperColorsRepository wallpaperColorsRepository, PermissionRequester permissionRequester, WallpaperPreviewNavigator wallpaperPreviewNavigator, CustomizationSectionNavigationController sectionNavigationController, @@ -114,7 +110,7 @@ public final class DefaultCustomizationSections implements CustomizationSections lifecycleOwner, screen, wallpaperInfoFactory, - wallpaperColorsViewModel, + wallpaperColorsRepository, displayUtils, mClockCarouselViewModelFactory, mClockViewFactory, @@ -131,7 +127,7 @@ public final class DefaultCustomizationSections implements CustomizationSections lifecycleOwner, screen, wallpaperInfoFactory, - wallpaperColorsViewModel, + wallpaperColorsRepository, displayUtils, wallpaperPreviewNavigator, wallpaperInteractor, @@ -144,7 +140,7 @@ public final class DefaultCustomizationSections implements CustomizationSections sectionControllers.add( new ConnectedSectionController( // Theme color section. - new ColorSectionController2( + new ColorSectionController( sectionNavigationController, new ViewModelProvider( activity, @@ -166,7 +162,6 @@ public final class DefaultCustomizationSections implements CustomizationSections sectionControllers.add( new KeyguardQuickAffordanceSectionController( sectionNavigationController, - mKeyguardQuickAffordancePickerInteractor, new ViewModelProvider( activity, mKeyguardQuickAffordancePickerViewModelFactory) @@ -193,7 +188,8 @@ public final class DefaultCustomizationSections implements CustomizationSections ThemedIconSwitchProvider.getInstance(activity), mThemedIconInteractor, savedInstanceState, - mThemedIconSnapshotRestorer)); + mThemedIconSnapshotRestorer, + mThemesUserEventLogger)); // App grid section. sectionControllers.add( diff --git a/src/com/android/customization/module/StatsLogUserEventLogger.java b/src/com/android/customization/module/StatsLogUserEventLogger.java deleted file mode 100644 index 647cdc9a..00000000 --- a/src/com/android/customization/module/StatsLogUserEventLogger.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.module; - -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; -import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__ACTION__APP_LAUNCHED; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_CROP_AND_SET_ACTION; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_DEEP_LINK; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCHER; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCH_ICON; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_PREFERENCE_UNSPECIFIED; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SETTINGS; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SETTINGS_SEARCH; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SUW; -import static com.android.systemui.shared.system.SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_TIPS; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SETTINGS_SEARCH; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_DEEP_LINK; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_LAUNCHER; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_SETTINGS; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_SUW; -import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_TIPS; -import static com.android.wallpaper.util.LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE; - -import android.app.WallpaperManager; -import android.content.Intent; -import android.stats.style.StyleEnums; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import com.android.customization.model.color.ColorOption; -import com.android.customization.model.grid.GridOption; -import com.android.customization.model.theme.ThemeBundle; -import com.android.wallpaper.module.NoOpUserEventLogger; -import com.android.wallpaper.module.WallpaperPreferences; -import com.android.wallpaper.module.WallpaperStatusChecker; - -import java.util.Map; -import java.util.Objects; - -/** - * StatsLog-backed implementation of {@link ThemesUserEventLogger}. - */ -public class StatsLogUserEventLogger extends NoOpUserEventLogger implements ThemesUserEventLogger { - - private final WallpaperPreferences mPreferences; - private final WallpaperStatusChecker mWallpaperStatusChecker; - - public StatsLogUserEventLogger( - WallpaperPreferences preferences, - WallpaperStatusChecker wallpaperStatusChecker) { - mPreferences = preferences; - mWallpaperStatusChecker = wallpaperStatusChecker; - } - - @Override - public void logAppLaunched(Intent launchSource) { - new SysUiStatsLogger(STYLE_UICHANGED__ACTION__APP_LAUNCHED) - .setLaunchedPreference(getAppLaunchSource(launchSource)) - .log(); - } - - @Override - public void logResumed(boolean provisioned, boolean wallpaper) { - new SysUiStatsLogger(StyleEnums.ONRESUME) - .log(); - } - - @Override - public void logStopped() { - new SysUiStatsLogger(StyleEnums.ONSTOP) - .log(); - } - - @Override - public void logActionClicked(String collectionId, int actionLabelResId) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_EXPLORE) - .setWallpaperCategoryHash(getIdHashCode(collectionId)) - .log(); - } - - @Override - public void logIndividualWallpaperSelected(String collectionId) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_SELECT) - .setWallpaperCategoryHash(getIdHashCode(collectionId)) - .log(); - } - - @Override - public void logCategorySelected(String collectionId) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_OPEN_CATEGORY) - .setWallpaperCategoryHash(getIdHashCode(collectionId)) - .log(); - } - - @Override - public void logSnapshot() { - final boolean isLockWallpaperSet = mWallpaperStatusChecker.isLockWallpaperSet(); - final String homeCollectionId = mPreferences.getHomeWallpaperCollectionId(); - final String homeRemoteId = mPreferences.getHomeWallpaperRemoteId(); - final String effects = mPreferences.getHomeWallpaperEffects(); - String homeWallpaperId = TextUtils.isEmpty(homeRemoteId) - ? mPreferences.getHomeWallpaperServiceName() : homeRemoteId; - String lockCollectionId = isLockWallpaperSet ? mPreferences.getLockWallpaperCollectionId() - : homeCollectionId; - String lockWallpaperId = isLockWallpaperSet ? mPreferences.getLockWallpaperRemoteId() - : homeWallpaperId; - - new SysUiStatsLogger(StyleEnums.SNAPSHOT) - .setWallpaperCategoryHash(getIdHashCode(homeCollectionId)) - .setWallpaperIdHash(getIdHashCode(homeWallpaperId)) - .setLockWallpaperCategoryHash(getIdHashCode(lockCollectionId)) - .setLockWallpaperIdHash(getIdHashCode(lockWallpaperId)) - .setFirstLaunchDateSinceSetup(mPreferences.getFirstLaunchDateSinceSetup()) - .setFirstWallpaperApplyDateSinceSetup( - mPreferences.getFirstWallpaperApplyDateSinceSetup()) - .setAppLaunchCount(mPreferences.getAppLaunchCount()) - .setEffectIdHash(getIdHashCode(effects)) - .log(); - } - - @Override - public void logWallpaperSet(String collectionId, @Nullable String wallpaperId, - @Nullable String effects) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_APPLIED) - .setWallpaperCategoryHash(getIdHashCode(collectionId)) - .setWallpaperIdHash(getIdHashCode(wallpaperId)) - .setEffectIdHash(getIdHashCode(effects)) - .log(); - } - - @Override - public void logEffectApply(String effect, @EffectStatus int status, long timeElapsedMillis, - int resultCode) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_EFFECT_APPLIED) - .setEffectPreference(status) - .setEffectIdHash(getIdHashCode(effect)) - .setTimeElapsed(timeElapsedMillis) - .setEffectResultCode(resultCode) - .log(); - } - - @Override - public void logEffectProbe(String effect, @EffectStatus int status) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_EFFECT_PROBE) - .setEffectPreference(status) - .setEffectIdHash(getIdHashCode(effect)) - .log(); - } - - @Override - public void logEffectForegroundDownload(String effect, @EffectStatus int status, - long timeElapsedMillis) { - new SysUiStatsLogger(StyleEnums.WALLPAPER_EFFECT_FG_DOWNLOAD) - .setEffectPreference(status) - .setEffectIdHash(getIdHashCode(effect)) - .setTimeElapsed(timeElapsedMillis) - .log(); - } - - @Nullable - private String getThemePackage(ThemeBundle theme, String category) { - Map<String, String> packages = theme.getPackagesByCategory(); - return packages.get(category); - } - - @Override - public void logThemeSelected(ThemeBundle theme, boolean isCustomTheme) { - new SysUiStatsLogger(StyleEnums.PICKER_SELECT) - .setColorPackageHash( - Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_COLOR))) - .setFontPackageHash(Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_FONT))) - .setShapePackageHash( - Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_SHAPE))) - .log(); - } - - @Override - public void logThemeApplied(ThemeBundle theme, boolean isCustomTheme) { - new SysUiStatsLogger(StyleEnums.PICKER_APPLIED) - .setColorPackageHash( - Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_COLOR))) - .setFontPackageHash(Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_FONT))) - .setShapePackageHash( - Objects.hashCode(getThemePackage(theme, OVERLAY_CATEGORY_SHAPE))) - .log(); - } - - @Override - public void logColorApplied(int action, ColorOption colorOption) { - new SysUiStatsLogger(action) - .setColorPreference(colorOption.getIndex()) - .setColorVariant(colorOption.getStyle().ordinal() + 1) - .log(); - } - - @Override - public void logGridSelected(GridOption grid) { - new SysUiStatsLogger(StyleEnums.PICKER_SELECT) - .setLauncherGrid(grid.cols) - .log(); - } - - @Override - public void logGridApplied(GridOption grid) { - new SysUiStatsLogger(StyleEnums.PICKER_APPLIED) - .setLauncherGrid(grid.cols) - .log(); - } - - private int getAppLaunchSource(Intent launchSource) { - if (launchSource.hasExtra(WALLPAPER_LAUNCH_SOURCE)) { - switch (launchSource.getStringExtra(WALLPAPER_LAUNCH_SOURCE)) { - case LAUNCH_SOURCE_LAUNCHER: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCHER; - case LAUNCH_SOURCE_SETTINGS: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SETTINGS; - case LAUNCH_SOURCE_SUW: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SUW; - case LAUNCH_SOURCE_TIPS: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_TIPS; - case LAUNCH_SOURCE_DEEP_LINK: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_DEEP_LINK; - default: - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_PREFERENCE_UNSPECIFIED; - } - } else if (launchSource.hasExtra(LAUNCH_SETTINGS_SEARCH)) { - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SETTINGS_SEARCH; - } else if (launchSource.getAction() != null && launchSource.getAction().equals( - WallpaperManager.ACTION_CROP_AND_SET_WALLPAPER)) { - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_CROP_AND_SET_ACTION; - } else if (launchSource.getCategories() != null - && launchSource.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCH_ICON; - } else { - return STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_PREFERENCE_UNSPECIFIED; - } - } - - private int getIdHashCode(String id) { - return id != null ? id.hashCode() : 0; - } -} diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt index 497456f6..f22e562b 100644 --- a/src/com/android/customization/module/ThemePickerInjector.kt +++ b/src/com/android/customization/module/ThemePickerInjector.kt @@ -15,7 +15,9 @@ */ package com.android.customization.module +import android.app.Activity import android.app.UiModeManager +import android.app.WallpaperColors import android.app.WallpaperManager import android.content.Context import android.content.Intent @@ -23,40 +25,37 @@ import android.content.res.Resources import android.net.Uri import android.text.TextUtils import androidx.activity.ComponentActivity -import androidx.fragment.app.FragmentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import com.android.customization.model.color.ColorCustomizationManager -import com.android.customization.model.color.ColorOptionsProvider import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET +import com.android.customization.model.color.ThemedWallpaperColorResources +import com.android.customization.model.color.WallpaperColorResources import com.android.customization.model.grid.GridOptionsManager -import com.android.customization.model.grid.data.repository.GridRepositoryImpl -import com.android.customization.model.grid.domain.interactor.GridInteractor -import com.android.customization.model.grid.domain.interactor.GridSnapshotRestorer -import com.android.customization.model.grid.ui.viewmodel.GridScreenViewModel import com.android.customization.model.mode.DarkModeSnapshotRestorer import com.android.customization.model.theme.OverlayManagerCompat -import com.android.customization.model.theme.ThemeBundleProvider -import com.android.customization.model.theme.ThemeManager import com.android.customization.model.themedicon.ThemedIconSwitchProvider import com.android.customization.model.themedicon.data.repository.ThemeIconRepository import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.clock.data.repository.ClockPickerRepositoryImpl import com.android.customization.picker.clock.data.repository.ClockRegistryProvider import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer import com.android.customization.picker.clock.ui.view.ClockViewFactory +import com.android.customization.picker.clock.ui.view.ClockViewFactoryImpl import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel -import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel -import com.android.customization.picker.clock.utils.ClockDescriptionUtils -import com.android.customization.picker.clock.utils.ThemePickerClockDescriptionUtils import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel +import com.android.customization.picker.grid.data.repository.GridRepositoryImpl +import com.android.customization.picker.grid.domain.interactor.GridInteractor +import com.android.customization.picker.grid.domain.interactor.GridSnapshotRestorer +import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel import com.android.customization.picker.notifications.data.repository.NotificationsRepository import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor import com.android.customization.picker.notifications.domain.interactor.NotificationsSnapshotRestorer @@ -69,17 +68,16 @@ import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.customization.data.content.CustomizationProviderClient import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl import com.android.wallpaper.config.BaseFlags -import com.android.wallpaper.dispatchers.BackgroundDispatcher -import com.android.wallpaper.dispatchers.MainDispatcher -import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.module.CustomizationSections import com.android.wallpaper.module.FragmentFactory -import com.android.wallpaper.module.UserEventLogger import com.android.wallpaper.module.WallpaperPicker2Injector import com.android.wallpaper.picker.CustomizationPickerActivity import com.android.wallpaper.picker.customization.data.content.WallpaperClientImpl +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor +import com.android.wallpaper.picker.di.modules.BackgroundDispatcher +import com.android.wallpaper.picker.di.modules.MainDispatcher import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer import com.android.wallpaper.util.ScreenSizeCalculator import javax.inject.Inject @@ -94,9 +92,9 @@ internal constructor( @MainDispatcher private val mainScope: CoroutineScope, @MainDispatcher private val mainDispatcher: CoroutineDispatcher, @BackgroundDispatcher private val bgDispatcher: CoroutineDispatcher, -) : WallpaperPicker2Injector(mainScope, bgDispatcher), CustomizationInjector { + private val userEventLogger: ThemesUserEventLogger, +) : WallpaperPicker2Injector(mainScope, bgDispatcher, userEventLogger), CustomizationInjector { private var customizationSections: CustomizationSections? = null - private var userEventLogger: UserEventLogger? = null private var wallpaperInteractor: WallpaperInteractor? = null private var keyguardQuickAffordancePickerInteractor: KeyguardQuickAffordancePickerInteractor? = null @@ -109,9 +107,8 @@ internal constructor( null private var notificationsSnapshotRestorer: NotificationsSnapshotRestorer? = null private var clockPickerInteractor: ClockPickerInteractor? = null - private var clockSectionViewModel: ClockSectionViewModel? = null private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null - private var clockViewFactories: MutableMap<Int, ClockViewFactory> = HashMap() + private var clockViewFactory: ClockViewFactory? = null private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null private var notificationsInteractor: NotificationsInteractor? = null private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null @@ -123,33 +120,35 @@ internal constructor( private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null private var themedIconInteractor: ThemedIconInteractor? = null private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null - private var clockDescriptionUtils: ClockDescriptionUtils? = null private var gridInteractor: GridInteractor? = null private var gridSnapshotRestorer: GridSnapshotRestorer? = null private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null private var clockRegistryProvider: ClockRegistryProvider? = null override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections { + val appContext = activity.applicationContext + val clockViewFactory = getClockViewFactory(activity) + val resources = activity.resources return customizationSections ?: DefaultCustomizationSections( getColorPickerViewModelFactory( - context = activity, - wallpaperColorsViewModel = getWallpaperColorsViewModel(), - ), - getKeyguardQuickAffordancePickerInteractor(activity), - getKeyguardQuickAffordancePickerViewModelFactory(activity), - NotificationSectionViewModel.Factory( - interactor = getNotificationsInteractor(activity), + context = appContext, + wallpaperColorsRepository = getWallpaperColorsRepository(), ), + getKeyguardQuickAffordancePickerViewModelFactory(appContext), + getNotificationSectionViewModelFactory(appContext), getFlags(), getClockCarouselViewModelFactory( - getClockPickerInteractor(activity.applicationContext), + interactor = getClockPickerInteractor(appContext), + clockViewFactory = clockViewFactory, + resources = resources, + logger = userEventLogger, ), - getClockViewFactory(activity), - getDarkModeSnapshotRestorer(activity), - getThemedIconSnapshotRestorer(activity), + clockViewFactory, + getThemedIconSnapshotRestorer(appContext), getThemedIconInteractor(), - getColorPickerInteractor(activity, getWallpaperColorsViewModel()), + getColorPickerInteractor(appContext, getWallpaperColorsRepository()), + getUserEventLogger(appContext), ) .also { customizationSections = it } } @@ -168,12 +167,7 @@ internal constructor( @Synchronized override fun getUserEventLogger(context: Context): ThemesUserEventLogger { - return userEventLogger as? ThemesUserEventLogger - ?: StatsLogUserEventLogger( - getPreferences(context.applicationContext), - getWallpaperStatusChecker(context.applicationContext), - ) - .also { userEventLogger = it } + return userEventLogger } override fun getFragmentFactory(): FragmentFactory? { @@ -195,7 +189,7 @@ internal constructor( this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context) this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context) this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] = - getColorPickerSnapshotRestorer(context, getWallpaperColorsViewModel()) + getColorPickerSnapshotRestorer(context, getWallpaperColorsRepository()) this[KEY_CLOCKS_SNAPSHOT_RESTORER] = getClockPickerSnapshotRestorer(context) } } @@ -204,16 +198,11 @@ internal constructor( return getPreferences(context) as CustomizationPreferences } - override fun getThemeManager( - provider: ThemeBundleProvider, - activity: FragmentActivity, - overlayManagerCompat: OverlayManagerCompat, - logger: ThemesUserEventLogger - ): ThemeManager { - return ThemeManager(provider, activity, overlayManagerCompat, logger) - } - override fun getWallpaperInteractor(context: Context): WallpaperInteractor { + if (getFlags().isMultiCropEnabled() && getFlags().isMultiCropPreviewUiEnabled()) { + return injectedWallpaperInteractor + } + val appContext = context.applicationContext return wallpaperInteractor ?: WallpaperInteractor( @@ -223,8 +212,8 @@ internal constructor( client = WallpaperClientImpl( context = appContext, - infoFactory = getCurrentWallpaperInfoFactory(appContext), - wallpaperManager = WallpaperManager.getInstance(appContext) + wallpaperManager = WallpaperManager.getInstance(appContext), + wallpaperPreferences = getPreferences(appContext) ), wallpaperPreferences = getPreferences(context = appContext), backgroundDispatcher = bgDispatcher, @@ -232,7 +221,7 @@ internal constructor( shouldHandleReload = { TextUtils.equals( getColorCustomizationManager(appContext).currentColorSource, - ColorOptionsProvider.COLOR_SOURCE_PRESET + COLOR_SOURCE_PRESET, ) } ) @@ -257,6 +246,7 @@ internal constructor( getKeyguardQuickAffordancePickerInteractor(context), getWallpaperInteractor(context), getCurrentWallpaperInfoFactory(context), + getUserEventLogger(context), ) .also { keyguardQuickAffordancePickerViewModelFactory = it } } @@ -267,7 +257,7 @@ internal constructor( val client = getKeyguardQuickAffordancePickerProviderClient(context) val appContext = context.applicationContext return KeyguardQuickAffordancePickerInteractor( - KeyguardQuickAffordancePickerRepository(client, bgDispatcher), + KeyguardQuickAffordancePickerRepository(client, getApplicationCoroutineScope()), client ) { getKeyguardQuickAffordanceSnapshotRestorer(appContext) @@ -300,6 +290,7 @@ internal constructor( return notificationSectionViewModelFactory ?: NotificationSectionViewModel.Factory( interactor = getNotificationsInteractor(context), + logger = getUserEventLogger(context), ) .also { notificationSectionViewModelFactory = it } } @@ -362,30 +353,26 @@ internal constructor( .also { clockPickerInteractor = it } } - override fun getClockSectionViewModel( - context: Context, - ): ClockSectionViewModel { - return clockSectionViewModel - ?: ClockSectionViewModel( - context.applicationContext, - getClockPickerInteractor(context.applicationContext) - ) - .also { clockSectionViewModel = it } - } - override fun getClockCarouselViewModelFactory( interactor: ClockPickerInteractor, + clockViewFactory: ClockViewFactory, + resources: Resources, + logger: ThemesUserEventLogger, ): ClockCarouselViewModel.Factory { return clockCarouselViewModelFactory - ?: ClockCarouselViewModel.Factory(interactor, bgDispatcher).also { - clockCarouselViewModelFactory = it - } + ?: ClockCarouselViewModel.Factory( + interactor, + bgDispatcher, + clockViewFactory, + resources, + logger, + ) + .also { clockCarouselViewModelFactory = it } } override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory { - val activityHashCode = activity.hashCode() - return clockViewFactories[activityHashCode] - ?: ClockViewFactory( + return clockViewFactory + ?: ClockViewFactoryImpl( activity.applicationContext, ScreenSizeCalculator.getInstance() .getScreenSize(activity.windowManager.defaultDisplay), @@ -393,13 +380,13 @@ internal constructor( getClockRegistry(activity.applicationContext), ) .also { - clockViewFactories[activityHashCode] = it + clockViewFactory = it activity.lifecycle.addObserver( object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { super.onDestroy(owner) - clockViewFactories[activityHashCode]?.onDestroy() - clockViewFactories.remove(activityHashCode) + if ((owner as Activity).isChangingConfigurations()) return + clockViewFactory?.onDestroy() } } ) @@ -415,20 +402,27 @@ internal constructor( } } + override fun getWallpaperColorResources( + wallpaperColors: WallpaperColors, + context: Context + ): WallpaperColorResources { + return ThemedWallpaperColorResources(wallpaperColors, context) + } + override fun getColorPickerInteractor( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, ): ColorPickerInteractor { val appContext = context.applicationContext return colorPickerInteractor ?: ColorPickerInteractor( repository = ColorPickerRepositoryImpl( - wallpaperColorsViewModel, + wallpaperColorsRepository, getColorCustomizationManager(appContext) ), snapshotRestorer = { - getColorPickerSnapshotRestorer(appContext, wallpaperColorsViewModel) + getColorPickerSnapshotRestorer(appContext, wallpaperColorsRepository) } ) .also { colorPickerInteractor = it } @@ -436,23 +430,24 @@ internal constructor( override fun getColorPickerViewModelFactory( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, ): ColorPickerViewModel.Factory { return colorPickerViewModelFactory ?: ColorPickerViewModel.Factory( context.applicationContext, - getColorPickerInteractor(context, wallpaperColorsViewModel), + getColorPickerInteractor(context, wallpaperColorsRepository), + userEventLogger, ) .also { colorPickerViewModelFactory = it } } private fun getColorPickerSnapshotRestorer( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, ): ColorPickerSnapshotRestorer { return colorPickerSnapshotRestorer ?: ColorPickerSnapshotRestorer( - getColorPickerInteractor(context, wallpaperColorsViewModel) + getColorPickerInteractor(context, wallpaperColorsRepository) ) .also { colorPickerSnapshotRestorer = it } } @@ -502,7 +497,7 @@ internal constructor( override fun getClockSettingsViewModelFactory( context: Context, - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, clockViewFactory: ClockViewFactory, ): ClockSettingsViewModel.Factory { return clockSettingsViewModelFactory @@ -511,8 +506,9 @@ internal constructor( getClockPickerInteractor(context), getColorPickerInteractor( context, - wallpaperColorsViewModel, + wallpaperColorsRepository, ), + userEventLogger, ) { clockId -> clockId?.let { clockViewFactory.getController(clockId).config.isReactiveToTone } ?: false @@ -520,11 +516,6 @@ internal constructor( .also { clockSettingsViewModelFactory = it } } - override fun getClockDescriptionUtils(resources: Resources): ClockDescriptionUtils { - return clockDescriptionUtils - ?: ThemePickerClockDescriptionUtils().also { clockDescriptionUtils = it } - } - fun getGridScreenViewModelFactory( context: Context, ): ViewModelProvider.Factory { diff --git a/src/com/android/customization/module/ThemesUserEventLogger.java b/src/com/android/customization/module/ThemesUserEventLogger.java deleted file mode 100644 index b1a87b9c..00000000 --- a/src/com/android/customization/module/ThemesUserEventLogger.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.module; - -import com.android.customization.model.color.ColorOption; -import com.android.customization.model.grid.GridOption; -import com.android.customization.model.theme.ThemeBundle; -import com.android.wallpaper.module.UserEventLogger; - -/** - * Extension of {@link UserEventLogger} that adds ThemePicker specific events. - */ -public interface ThemesUserEventLogger extends UserEventLogger { - - void logThemeSelected(ThemeBundle theme, boolean isCustomTheme); - - void logThemeApplied(ThemeBundle theme, boolean isCustomTheme); - - /** - * Logs the color usage while color is applied. - * - * @param action color applied action. - * @param colorOption applied color option. - */ - void logColorApplied(int action, ColorOption colorOption); - - void logGridSelected(GridOption grid); - - void logGridApplied(GridOption grid); - -} diff --git a/src/com/android/customization/module/logging/AppSessionId.kt b/src/com/android/customization/module/logging/AppSessionId.kt new file mode 100644 index 00000000..c831f225 --- /dev/null +++ b/src/com/android/customization/module/logging/AppSessionId.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.module.logging + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppSessionId @Inject constructor() { + + private var sessionId: InstanceId = newInstanceId() + + fun createNewId(): AppSessionId { + sessionId = newInstanceId() + return this + } + + fun getId(): Int { + return sessionId.hashCode() + } + + private fun newInstanceId(): InstanceId = InstanceIdSequence(INSTANCE_ID_MAX).newInstanceId() + + companion object { + // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values + private const val INSTANCE_ID_MAX = 1 shl 20 + } +} diff --git a/src/com/android/customization/module/SysUiStatsLogger.kt b/src/com/android/customization/module/logging/SysUiStatsLogger.kt index 8e97b0b6..111c2c23 100644 --- a/src/com/android/customization/module/SysUiStatsLogger.kt +++ b/src/com/android/customization/module/logging/SysUiStatsLogger.kt @@ -13,11 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.customization.module - -import android.stats.style.StyleEnums +package com.android.customization.module.logging + +import android.stats.style.StyleEnums.CLOCK_SIZE_UNSPECIFIED +import android.stats.style.StyleEnums.COLOR_SOURCE_UNSPECIFIED +import android.stats.style.StyleEnums.DATE_PREFERENCE_UNSPECIFIED +import android.stats.style.StyleEnums.EFFECT_PREFERENCE_UNSPECIFIED +import android.stats.style.StyleEnums.LAUNCHED_PREFERENCE_UNSPECIFIED +import android.stats.style.StyleEnums.LOCATION_PREFERENCE_UNSPECIFIED +import android.stats.style.StyleEnums.SET_WALLPAPER_ENTRY_POINT_UNSPECIFIED +import android.stats.style.StyleEnums.WALLPAPER_DESTINATION_UNSPECIFIED import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.shared.system.SysUiStatsLog.STYLE_UI_CHANGED +import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint /** The builder for [SysUiStatsLog]. */ class SysUiStatsLogger(val action: Int) { @@ -30,10 +38,10 @@ class SysUiStatsLogger(val action: Int) { private var wallpaperCategoryHash = 0 private var wallpaperIdHash = 0 private var colorPreference = 0 - private var locationPreference = StyleEnums.EFFECT_PREFERENCE_UNSPECIFIED - private var datePreference = StyleEnums.DATE_PREFERENCE_UNSPECIFIED - private var launchedPreference = StyleEnums.LAUNCHED_PREFERENCE_UNSPECIFIED - private var effectPreference = StyleEnums.EFFECT_PREFERENCE_UNSPECIFIED + private var locationPreference = LOCATION_PREFERENCE_UNSPECIFIED + private var datePreference = DATE_PREFERENCE_UNSPECIFIED + private var launchedPreference = LAUNCHED_PREFERENCE_UNSPECIFIED + private var effectPreference = EFFECT_PREFERENCE_UNSPECIFIED private var effectIdHash = 0 private var lockWallpaperCategoryHash = 0 private var lockWallpaperIdHash = 0 @@ -43,14 +51,21 @@ class SysUiStatsLogger(val action: Int) { private var colorVariant = 0 private var timeElapsedMillis = 0L private var effectResultCode = -1 + private var appSessionId = 0 + private var setWallpaperEntryPoint = SET_WALLPAPER_ENTRY_POINT_UNSPECIFIED + private var wallpaperDestination = WALLPAPER_DESTINATION_UNSPECIFIED + private var colorSource = COLOR_SOURCE_UNSPECIFIED + private var seedColor = 0 + private var clockSize = CLOCK_SIZE_UNSPECIFIED + private var toggleOn = false + private var shortcut = "" + private var shortcutSlotId = "" fun setColorPackageHash(colorPackageHash: Int) = apply { this.colorPackageHash = colorPackageHash } - fun setFontPackageHash(fontPackageHash: Int) = apply { - this.fontPackageHash = fontPackageHash - } + fun setFontPackageHash(fontPackageHash: Int) = apply { this.fontPackageHash = fontPackageHash } fun setShapePackageHash(shapePackageHash: Int) = apply { this.shapePackageHash = shapePackageHash @@ -66,13 +81,9 @@ class SysUiStatsLogger(val action: Int) { this.wallpaperCategoryHash = wallpaperCategoryHash } - fun setWallpaperIdHash(wallpaperIdHash: Int) = apply { - this.wallpaperIdHash = wallpaperIdHash - } + fun setWallpaperIdHash(wallpaperIdHash: Int) = apply { this.wallpaperIdHash = wallpaperIdHash } - fun setColorPreference(colorPreference: Int) = apply { - this.colorPreference = colorPreference - } + fun setColorPreference(colorPreference: Int) = apply { this.colorPreference = colorPreference } fun setLocationPreference(locationPreference: Int) = apply { this.locationPreference = locationPreference @@ -111,13 +122,35 @@ class SysUiStatsLogger(val action: Int) { fun setColorVariant(colorVariant: Int) = apply { this.colorVariant = colorVariant } fun setTimeElapsed(timeElapsedMillis: Long) = apply { - this.timeElapsedMillis = timeElapsedMillis + this.timeElapsedMillis = timeElapsedMillis } fun setEffectResultCode(effectResultCode: Int) = apply { this.effectResultCode = effectResultCode } + fun setAppSessionId(sessionId: Int) = apply { this.appSessionId = sessionId } + + fun setSetWallpaperEntryPoint(@SetWallpaperEntryPoint setWallpaperEntryPoint: Int) = apply { + this.setWallpaperEntryPoint = setWallpaperEntryPoint + } + + fun setWallpaperDestination(wallpaperDestination: Int) = apply { + this.wallpaperDestination = wallpaperDestination + } + + fun setColorSource(colorSource: Int) = apply { this.colorSource = colorSource } + + fun setSeedColor(seedColor: Int) = apply { this.seedColor = seedColor } + + fun setClockSize(clockSize: Int) = apply { this.clockSize = clockSize } + + fun setToggleOn(toggleOn: Boolean) = apply { this.toggleOn = toggleOn } + + fun setShortcut(shortcut: String) = apply { this.shortcut = shortcut } + + fun setShortcutSlotId(shortcutSlotId: String) = apply { this.shortcutSlotId = shortcutSlotId } + fun log() { SysUiStatsLog.write( STYLE_UI_CHANGED, @@ -143,6 +176,15 @@ class SysUiStatsLogger(val action: Int) { colorVariant, timeElapsedMillis, effectResultCode, + appSessionId, + setWallpaperEntryPoint, + wallpaperDestination, + colorSource, + seedColor, + clockSize, + toggleOn, + shortcut, + shortcutSlotId, ) } } diff --git a/src/com/android/customization/module/logging/ThemesUserEventLogger.kt b/src/com/android/customization/module/logging/ThemesUserEventLogger.kt new file mode 100644 index 00000000..60fd062a --- /dev/null +++ b/src/com/android/customization/module/logging/ThemesUserEventLogger.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.module.logging + +import android.stats.style.StyleEnums +import androidx.annotation.IntDef +import com.android.customization.model.grid.GridOption +import com.android.wallpaper.module.logging.UserEventLogger + +/** Extension of [UserEventLogger] that adds ThemePicker specific events. */ +interface ThemesUserEventLogger : UserEventLogger { + + fun logThemeColorApplied(@ColorSource source: Int, style: Int, seedColor: Int) + + fun logGridApplied(grid: GridOption) + + fun logClockApplied(clockId: String) + + fun logClockColorApplied(seedColor: Int) + + fun logClockSizeApplied(@ClockSize clockSize: Int) + + fun logThemedIconApplied(useThemeIcon: Boolean) + + fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean) + + fun logShortcutApplied(shortcut: String, shortcutSlotId: String) + + fun logDarkThemeApplied(useDarkTheme: Boolean) + + @IntDef( + StyleEnums.COLOR_SOURCE_UNSPECIFIED, + StyleEnums.COLOR_SOURCE_HOME_SCREEN_WALLPAPER, + StyleEnums.COLOR_SOURCE_LOCK_SCREEN_WALLPAPER, + StyleEnums.COLOR_SOURCE_PRESET_COLOR, + ) + @Retention(AnnotationRetention.SOURCE) + annotation class ColorSource + + @IntDef( + StyleEnums.CLOCK_SIZE_UNSPECIFIED, + StyleEnums.CLOCK_SIZE_DYNAMIC, + StyleEnums.CLOCK_SIZE_SMALL, + ) + @Retention(AnnotationRetention.SOURCE) + annotation class ClockSize + + companion object { + const val NULL_SEED_COLOR = 0 + } +} diff --git a/src/com/android/customization/module/logging/ThemesUserEventLoggerImpl.kt b/src/com/android/customization/module/logging/ThemesUserEventLoggerImpl.kt new file mode 100644 index 00000000..3f4a6dc4 --- /dev/null +++ b/src/com/android/customization/module/logging/ThemesUserEventLoggerImpl.kt @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2019 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.module.logging + +import android.app.WallpaperManager +import android.content.Intent +import android.stats.style.StyleEnums.APP_LAUNCHED +import android.stats.style.StyleEnums.CLOCK_APPLIED +import android.stats.style.StyleEnums.CLOCK_COLOR_APPLIED +import android.stats.style.StyleEnums.CLOCK_SIZE_APPLIED +import android.stats.style.StyleEnums.DARK_THEME_APPLIED +import android.stats.style.StyleEnums.GRID_APPLIED +import android.stats.style.StyleEnums.LAUNCHED_CROP_AND_SET_ACTION +import android.stats.style.StyleEnums.LAUNCHED_DEEP_LINK +import android.stats.style.StyleEnums.LAUNCHED_KEYGUARD +import android.stats.style.StyleEnums.LAUNCHED_LAUNCHER +import android.stats.style.StyleEnums.LAUNCHED_LAUNCH_ICON +import android.stats.style.StyleEnums.LAUNCHED_PREFERENCE_UNSPECIFIED +import android.stats.style.StyleEnums.LAUNCHED_SETTINGS +import android.stats.style.StyleEnums.LAUNCHED_SETTINGS_SEARCH +import android.stats.style.StyleEnums.LAUNCHED_SUW +import android.stats.style.StyleEnums.LAUNCHED_TIPS +import android.stats.style.StyleEnums.LOCK_SCREEN_NOTIFICATION_APPLIED +import android.stats.style.StyleEnums.RESET_APPLIED +import android.stats.style.StyleEnums.SHORTCUT_APPLIED +import android.stats.style.StyleEnums.SNAPSHOT +import android.stats.style.StyleEnums.THEMED_ICON_APPLIED +import android.stats.style.StyleEnums.THEME_COLOR_APPLIED +import android.stats.style.StyleEnums.WALLPAPER_APPLIED +import android.stats.style.StyleEnums.WALLPAPER_DESTINATION_HOME_AND_LOCK_SCREEN +import android.stats.style.StyleEnums.WALLPAPER_DESTINATION_HOME_SCREEN +import android.stats.style.StyleEnums.WALLPAPER_DESTINATION_LOCK_SCREEN +import android.stats.style.StyleEnums.WALLPAPER_EFFECT_APPLIED +import android.stats.style.StyleEnums.WALLPAPER_EFFECT_FG_DOWNLOAD +import android.stats.style.StyleEnums.WALLPAPER_EFFECT_PROBE +import android.stats.style.StyleEnums.WALLPAPER_EXPLORE +import android.text.TextUtils +import com.android.customization.model.color.ColorCustomizationManager +import com.android.customization.model.grid.GridOption +import com.android.customization.module.logging.ThemesUserEventLogger.ClockSize +import com.android.customization.module.logging.ThemesUserEventLogger.ColorSource +import com.android.wallpaper.module.WallpaperPreferences +import com.android.wallpaper.module.logging.UserEventLogger.EffectStatus +import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint +import com.android.wallpaper.module.logging.UserEventLogger.WallpaperDestination +import com.android.wallpaper.util.ActivityUtils +import com.android.wallpaper.util.LaunchSourceUtils +import javax.inject.Inject +import javax.inject.Singleton + +/** StatsLog-backed implementation of [ThemesUserEventLogger]. */ +@Singleton +class ThemesUserEventLoggerImpl +@Inject +constructor( + private val preferences: WallpaperPreferences, + private val colorManager: ColorCustomizationManager, + private val appSessionId: AppSessionId, +) : ThemesUserEventLogger { + + override fun logSnapshot() { + SysUiStatsLogger(SNAPSHOT) + .setWallpaperCategoryHash(preferences.getHomeCategoryHash()) + .setWallpaperIdHash(preferences.getHomeWallpaperIdHash()) + .setLockWallpaperCategoryHash(preferences.getLockCategoryHash()) + .setLockWallpaperIdHash(preferences.getLockWallpaperIdHash()) + .setEffectIdHash(preferences.getHomeWallpaperEffectsIdHash()) + .setColorSource(colorManager.currentColorSourceForLogging) + .setColorVariant(colorManager.currentStyleForLogging) + .setSeedColor(colorManager.currentSeedColorForLogging) + .log() + } + + override fun logAppLaunched(launchSource: Intent) { + SysUiStatsLogger(APP_LAUNCHED) + .setAppSessionId(appSessionId.createNewId().getId()) + .setLaunchedPreference(launchSource.getAppLaunchSource()) + .log() + } + + override fun logWallpaperApplied( + collectionId: String?, + wallpaperId: String?, + effects: String?, + @SetWallpaperEntryPoint setWallpaperEntryPoint: Int, + @WallpaperDestination destination: Int, + ) { + val categoryHash = getIdHashCode(collectionId) + val wallpaperIdHash = getIdHashCode(wallpaperId) + val isHomeWallpaperSet = + destination == WALLPAPER_DESTINATION_HOME_SCREEN || + destination == WALLPAPER_DESTINATION_HOME_AND_LOCK_SCREEN + val isLockWallpaperSet = + destination == WALLPAPER_DESTINATION_LOCK_SCREEN || + destination == WALLPAPER_DESTINATION_HOME_AND_LOCK_SCREEN + SysUiStatsLogger(WALLPAPER_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setWallpaperCategoryHash(if (isHomeWallpaperSet) categoryHash else 0) + .setWallpaperIdHash(if (isHomeWallpaperSet) wallpaperIdHash else 0) + .setLockWallpaperCategoryHash(if (isLockWallpaperSet) categoryHash else 0) + .setLockWallpaperIdHash(if (isLockWallpaperSet) wallpaperIdHash else 0) + .setEffectIdHash(getIdHashCode(effects)) + .setSetWallpaperEntryPoint(setWallpaperEntryPoint) + .setWallpaperDestination(destination) + .log() + } + + override fun logEffectApply( + effect: String, + @EffectStatus status: Int, + timeElapsedMillis: Long, + resultCode: Int + ) { + SysUiStatsLogger(WALLPAPER_EFFECT_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setEffectPreference(status) + .setEffectIdHash(getIdHashCode(effect)) + .setTimeElapsed(timeElapsedMillis) + .setEffectResultCode(resultCode) + .log() + } + + override fun logEffectProbe(effect: String, @EffectStatus status: Int) { + SysUiStatsLogger(WALLPAPER_EFFECT_PROBE) + .setAppSessionId(appSessionId.getId()) + .setEffectPreference(status) + .setEffectIdHash(getIdHashCode(effect)) + .log() + } + + override fun logEffectForegroundDownload( + effect: String, + @EffectStatus status: Int, + timeElapsedMillis: Long + ) { + SysUiStatsLogger(WALLPAPER_EFFECT_FG_DOWNLOAD) + .setAppSessionId(appSessionId.getId()) + .setEffectPreference(status) + .setEffectIdHash(getIdHashCode(effect)) + .setTimeElapsed(timeElapsedMillis) + .log() + } + + override fun logResetApplied() { + SysUiStatsLogger(RESET_APPLIED).setAppSessionId(appSessionId.getId()).log() + } + + override fun logWallpaperExploreButtonClicked() { + SysUiStatsLogger(WALLPAPER_EXPLORE).setAppSessionId(appSessionId.getId()).log() + } + + override fun logThemeColorApplied( + @ColorSource source: Int, + style: Int, + seedColor: Int, + ) { + SysUiStatsLogger(THEME_COLOR_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setColorSource(source) + .setColorVariant(style) + .setSeedColor(seedColor) + .log() + } + + override fun logGridApplied(grid: GridOption) { + SysUiStatsLogger(GRID_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setLauncherGrid(grid.getLauncherGridInt()) + .log() + } + + override fun logClockApplied(clockId: String) { + SysUiStatsLogger(CLOCK_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setClockPackageHash(getIdHashCode(clockId)) + .log() + } + + override fun logClockColorApplied(seedColor: Int) { + SysUiStatsLogger(CLOCK_COLOR_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setSeedColor(seedColor) + .log() + } + + override fun logClockSizeApplied(@ClockSize clockSize: Int) { + SysUiStatsLogger(CLOCK_SIZE_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setClockSize(clockSize) + .log() + } + + override fun logThemedIconApplied(useThemeIcon: Boolean) { + SysUiStatsLogger(THEMED_ICON_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setToggleOn(useThemeIcon) + .log() + } + + override fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean) { + SysUiStatsLogger(LOCK_SCREEN_NOTIFICATION_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setToggleOn(showLockScreenNotifications) + .log() + } + + override fun logShortcutApplied(shortcut: String, shortcutSlotId: String) { + SysUiStatsLogger(SHORTCUT_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setShortcut(shortcut) + .setShortcutSlotId(shortcutSlotId) + .log() + } + + override fun logDarkThemeApplied(useDarkTheme: Boolean) { + SysUiStatsLogger(DARK_THEME_APPLIED) + .setAppSessionId(appSessionId.getId()) + .setToggleOn(useDarkTheme) + .log() + } + + /** + * The grid integer depends on the column and row numbers. For example: 4x5 is 405 13x37 is 1337 + * The upper limit for the column / row count is 99. + */ + private fun GridOption.getLauncherGridInt(): Int { + return cols * 100 + rows + } + + private fun Intent.getAppLaunchSource(): Int { + return if (hasExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) { + when (getStringExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) { + LaunchSourceUtils.LAUNCH_SOURCE_LAUNCHER -> LAUNCHED_LAUNCHER + LaunchSourceUtils.LAUNCH_SOURCE_SETTINGS -> LAUNCHED_SETTINGS + LaunchSourceUtils.LAUNCH_SOURCE_SUW -> LAUNCHED_SUW + LaunchSourceUtils.LAUNCH_SOURCE_TIPS -> LAUNCHED_TIPS + LaunchSourceUtils.LAUNCH_SOURCE_DEEP_LINK -> LAUNCHED_DEEP_LINK + LaunchSourceUtils.LAUNCH_SOURCE_KEYGUARD -> LAUNCHED_KEYGUARD + else -> LAUNCHED_PREFERENCE_UNSPECIFIED + } + } else if (ActivityUtils.isLaunchedFromSettingsSearch(this)) { + LAUNCHED_SETTINGS_SEARCH + } else if (action != null && action == WallpaperManager.ACTION_CROP_AND_SET_WALLPAPER) { + LAUNCHED_CROP_AND_SET_ACTION + } else if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { + LAUNCHED_LAUNCH_ICON + } else { + LAUNCHED_PREFERENCE_UNSPECIFIED + } + } + + /** If not set, the output hash is 0. */ + private fun WallpaperPreferences.getHomeCategoryHash(): Int { + return getIdHashCode(getHomeWallpaperCollectionId()) + } + + /** If not set, the output hash is 0. */ + private fun WallpaperPreferences.getHomeWallpaperIdHash(): Int { + val remoteId = getHomeWallpaperRemoteId() + val wallpaperId = + if (!TextUtils.isEmpty(remoteId)) remoteId else getHomeWallpaperServiceName() + return getIdHashCode(wallpaperId) + } + + /** If not set, the output hash is 0. */ + private fun WallpaperPreferences.getLockCategoryHash(): Int { + return getIdHashCode(getLockWallpaperCollectionId()) + } + + /** If not set, the output hash is 0. */ + private fun WallpaperPreferences.getLockWallpaperIdHash(): Int { + val remoteId = getLockWallpaperRemoteId() + val wallpaperId = + if (!TextUtils.isEmpty(remoteId)) remoteId else getLockWallpaperServiceName() + return getIdHashCode(wallpaperId) + } + + /** If not set, the output hash is 0. */ + private fun WallpaperPreferences.getHomeWallpaperEffectsIdHash(): Int { + return getIdHashCode(getHomeWallpaperEffects()) + } + + private fun getIdHashCode(id: String?): Int { + return id?.hashCode() ?: 0 + } +} diff --git a/src/com/android/customization/picker/WallpaperPreviewer.java b/src/com/android/customization/picker/WallpaperPreviewer.java index d74bfaea..18bc89c7 100644 --- a/src/com/android/customization/picker/WallpaperPreviewer.java +++ b/src/com/android/customization/picker/WallpaperPreviewer.java @@ -241,7 +241,7 @@ public class WallpaperPreviewer implements LifecycleObserver { () -> mFadeInScrim.setVisibility(View.INVISIBLE)); } } - }, mWallpaperSurface, WallpaperConnection.WHICH_PREVIEW.PREVIEW_CURRENT); + }, mWallpaperSurface, WallpaperConnection.WhichPreview.PREVIEW_CURRENT); mWallpaperConnection.setVisibility(true); mHomePreview.post(() -> { diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt index 004103f3..cc4079a1 100644 --- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt +++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt @@ -21,7 +21,7 @@ import androidx.annotation.ColorInt import androidx.annotation.IntRange import com.android.customization.picker.clock.shared.ClockSize import com.android.customization.picker.clock.shared.model.ClockMetadataModel -import com.android.systemui.plugins.ClockMetadata +import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.shared.clocks.ClockRegistry import com.android.wallpaper.settings.data.repository.SecureSettingsRepository import kotlinx.coroutines.CoroutineDispatcher @@ -187,7 +187,6 @@ class ClockPickerRepositoryImpl( ): ClockMetadataModel { return ClockMetadataModel( clockId = clockId, - name = name, isSelected = isSelected, selectedColorId = selectedColorId, colorToneProgress = colorTone, diff --git a/src/com/android/customization/picker/clock/shared/ClockSize.kt b/src/com/android/customization/picker/clock/shared/ClockSize.kt index 279ee54b..9b35f73f 100644 --- a/src/com/android/customization/picker/clock/shared/ClockSize.kt +++ b/src/com/android/customization/picker/clock/shared/ClockSize.kt @@ -16,7 +16,18 @@ */ package com.android.customization.picker.clock.shared +import android.stats.style.StyleEnums +import com.android.customization.module.logging.ThemesUserEventLogger + enum class ClockSize { DYNAMIC, SMALL, } + +@ThemesUserEventLogger.ClockSize +fun ClockSize.toClockSizeForLogging(): Int { + return when (this) { + ClockSize.DYNAMIC -> StyleEnums.CLOCK_SIZE_DYNAMIC + ClockSize.SMALL -> StyleEnums.CLOCK_SIZE_SMALL + } +} diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt index 25225075..6e2bfb38 100644 --- a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt +++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt @@ -23,7 +23,6 @@ import androidx.annotation.IntRange /** Model for clock metadata. */ data class ClockMetadataModel( val clockId: String, - val name: String, val isSelected: Boolean, val selectedColorId: String?, @IntRange(from = 0, to = 100) val colorToneProgress: Int, diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt index 6bd717b7..e2616c76 100644 --- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt +++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt @@ -58,11 +58,6 @@ object ClockCarouselViewBinder { carouselView.transitionToPrevious() } ) - screenPreviewClickView.accessibilityDelegate = carouselAccessibilityDelegate - screenPreviewClickView.setOnSideClickedListener { isStart -> - if (isStart) carouselView.scrollToPrevious() else carouselView.scrollToNext() - } - lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { @@ -76,6 +71,11 @@ object ClockCarouselViewBinder { }, isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth, ) + screenPreviewClickView.accessibilityDelegate = carouselAccessibilityDelegate + screenPreviewClickView.setOnSideClickedListener { isStart -> + if (isStart) carouselView.scrollToPrevious() + else carouselView.scrollToNext() + } } } diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt deleted file mode 100644 index 7dc0d0c4..00000000 --- a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.customization.picker.clock.ui.binder - -import android.view.View -import android.widget.TextView -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel -import com.android.wallpaper.R -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -object ClockSectionViewBinder { - fun bind( - view: View, - viewModel: ClockSectionViewModel, - lifecycleOwner: LifecycleOwner, - onClicked: () -> Unit, - ) { - view.setOnClickListener { onClicked() } - - val selectedClockColorAndSize: TextView = - view.requireViewById(R.id.selected_clock_color_and_size) - - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.selectedClockColorAndSizeText.collectLatest { - selectedClockColorAndSize.text = it - } - } - } - } - } -} diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt index 6e745d54..d17cdf8a 100644 --- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt +++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt @@ -15,11 +15,18 @@ */ package com.android.customization.picker.clock.ui.binder +import android.content.Context import android.content.res.Configuration +import android.text.Spannable +import android.text.SpannableString +import android.text.style.TextAppearanceSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.RadioGroup +import android.widget.RadioGroup.OnCheckedChangeListener import android.widget.SeekBar import androidx.core.view.doOnPreDraw import androidx.core.view.isInvisible @@ -35,7 +42,6 @@ import com.android.customization.picker.clock.shared.ClockSize import com.android.customization.picker.clock.ui.adapter.ClockSettingsTabAdapter import com.android.customization.picker.clock.ui.view.ClockCarouselView import com.android.customization.picker.clock.ui.view.ClockHostView -import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup import com.android.customization.picker.clock.ui.view.ClockViewFactory import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder @@ -83,14 +89,27 @@ object ClockSettingsBinder { } ) - val sizeOptions = - view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group) - sizeOptions.onRadioButtonClickListener = - object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener { - override fun onClick(size: ClockSize) { - viewModel.setClockSize(size) - } + val onCheckedChangeListener = OnCheckedChangeListener { _, id -> + when (id) { + R.id.radio_dynamic -> viewModel.setClockSize(ClockSize.DYNAMIC) + R.id.radio_small -> viewModel.setClockSize(ClockSize.SMALL) } + } + val clockSizeRadioGroup = + view.requireViewById<RadioGroup>(R.id.clock_size_radio_button_group) + clockSizeRadioGroup.setOnCheckedChangeListener(onCheckedChangeListener) + view.requireViewById<RadioButton>(R.id.radio_dynamic).text = + getRadioText( + view.context.applicationContext, + view.resources.getString(R.string.clock_size_dynamic), + view.resources.getString(R.string.clock_size_dynamic_description) + ) + view.requireViewById<RadioButton>(R.id.radio_small).text = + getRadioText( + view.context.applicationContext, + view.resources.getString(R.string.clock_size_small), + view.resources.getString(R.string.clock_size_small_description) + ) val colorOptionContainer = view.requireViewById<View>(R.id.color_picker_container) lifecycleOwner.lifecycleScope.launch { @@ -110,11 +129,11 @@ object ClockSettingsBinder { when (tab) { ClockSettingsViewModel.Tab.COLOR -> { colorOptionContainer.isVisible = true - sizeOptions.isInvisible = true + clockSizeRadioGroup.isInvisible = true } ClockSettingsViewModel.Tab.SIZE -> { colorOptionContainer.isInvisible = true - sizeOptions.isVisible = true + clockSizeRadioGroup.isVisible = true } } } @@ -122,6 +141,7 @@ object ClockSettingsBinder { launch { viewModel.colorOptions.collect { colorOptions -> + colorOptionContainerListView.removeAllViews() colorOptions.forEachIndexed { index, colorOption -> colorOption.payload?.let { payload -> val item = @@ -189,16 +209,28 @@ object ClockSettingsBinder { clockHostView.addView(clockView) when (size) { ClockSize.DYNAMIC -> { - sizeOptions.radioButtonDynamic.isChecked = true - sizeOptions.radioButtonSmall.isChecked = false + // When clock size data flow emits clock size signal, we want + // to update the view without triggering on checked change, + // which is supposed to be triggered by user interaction only. + clockSizeRadioGroup.setOnCheckedChangeListener(null) + clockSizeRadioGroup.check(R.id.radio_dynamic) + clockSizeRadioGroup.setOnCheckedChangeListener( + onCheckedChangeListener + ) clockHostView.doOnPreDraw { it.pivotX = it.width / 2F it.pivotY = it.height / 2F } } ClockSize.SMALL -> { - sizeOptions.radioButtonDynamic.isChecked = false - sizeOptions.radioButtonSmall.isChecked = true + // When clock size data flow emits clock size signal, we want + // to update the view without triggering on checked change, + // which is supposed to be triggered by user interaction only. + clockSizeRadioGroup.setOnCheckedChangeListener(null) + clockSizeRadioGroup.check(R.id.radio_small) + clockSizeRadioGroup.setOnCheckedChangeListener( + onCheckedChangeListener + ) clockHostView.doOnPreDraw { it.pivotX = ClockCarouselView.getCenteredHostViewPivotX(it) it.pivotY = 0F @@ -238,4 +270,25 @@ object ClockSettingsBinder { } ) } + + private fun getRadioText( + context: Context, + title: String, + description: String + ): SpannableString { + val text = SpannableString(title + "\n" + description) + text.setSpan( + TextAppearanceSpan(context, R.style.SectionTitleTextStyle), + 0, + title.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + text.setSpan( + TextAppearanceSpan(context, R.style.SectionSubtitleTextStyle), + title.length + 1, + title.length + 1 + description.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + return text + } } diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt index 4805f376..dc70633e 100644 --- a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt +++ b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt @@ -68,7 +68,7 @@ class ClockSettingsFragment : AppbarFragment() { val injector = InjectorProvider.getInjector() as ThemePickerInjector val lockScreenView: CardView = view.requireViewById(R.id.lock_preview) - val colorViewModel = injector.getWallpaperColorsViewModel() + val wallpaperColorsRepository = injector.getWallpaperColorsRepository() val displayUtils = injector.getDisplayUtils(context) ScreenPreviewBinder.bind( activity = activity, @@ -88,18 +88,18 @@ class ClockSettingsFragment : AppbarFragment() { injector .getCurrentWallpaperInfoFactory(context) .createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - continuation.resume( - lockWallpaper ?: homeWallpaper, - null, - ) - }, + context, forceReload, - ) + ) { homeWallpaper, lockWallpaper, _ -> + continuation.resume( + lockWallpaper ?: homeWallpaper, + null, + ) + } } }, onWallpaperColorChanged = { colors -> - colorViewModel.setLockWallpaperColors(colors) + wallpaperColorsRepository.setLockWallpaperColors(colors) }, initialExtrasProvider = { Bundle().apply { @@ -125,7 +125,7 @@ class ClockSettingsFragment : AppbarFragment() { this, injector.getClockSettingsViewModelFactory( context, - injector.getWallpaperColorsViewModel(), + injector.getWallpaperColorsRepository(), injector.getClockViewFactory(activity), ), ) diff --git a/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt deleted file mode 100644 index b47c2433..00000000 --- a/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2022 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.clock.ui.section - -import android.content.Context -import android.view.LayoutInflater -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import com.android.customization.picker.clock.ui.binder.ClockSectionViewBinder -import com.android.customization.picker.clock.ui.fragment.ClockSettingsFragment -import com.android.customization.picker.clock.ui.view.ClockSectionView -import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel -import com.android.wallpaper.R -import com.android.wallpaper.config.BaseFlags -import com.android.wallpaper.model.CustomizationSectionController -import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController -import kotlinx.coroutines.launch - -/** A [CustomizationSectionController] for clock customization. */ -class ClockSectionController( - private val navigationController: CustomizationSectionNavigationController, - private val lifecycleOwner: LifecycleOwner, - private val flag: BaseFlags, - private val viewModel: ClockSectionViewModel, -) : CustomizationSectionController<ClockSectionView> { - - override fun isAvailable(context: Context): Boolean { - return flag.isCustomClocksEnabled(context!!) - } - - override fun createView(context: Context): ClockSectionView { - val view = - LayoutInflater.from(context) - .inflate( - R.layout.clock_section_view, - null, - ) as ClockSectionView - lifecycleOwner.lifecycleScope.launch { - ClockSectionViewBinder.bind( - view = view, - viewModel = viewModel, - lifecycleOwner = lifecycleOwner - ) { - navigationController.navigateTo(ClockSettingsFragment()) - } - } - return view - } -} diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt index d4f501b7..cae4e06b 100644 --- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt +++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt @@ -31,7 +31,7 @@ import androidx.core.view.get import androidx.core.view.isNotEmpty import com.android.customization.picker.clock.shared.ClockSize import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselItemViewModel -import com.android.systemui.plugins.ClockController +import com.android.systemui.plugins.clocks.ClockController import com.android.wallpaper.R import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout import java.lang.Float.max @@ -384,7 +384,7 @@ class ClockCarouselView( ) : Carousel.Adapter { fun getContentDescription(index: Int, resources: Resources): String { - return clocks[index].getContentDescription(resources) + return clocks[index].contentDescription } override fun count(): Int { diff --git a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt deleted file mode 100644 index 909491a3..00000000 --- a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2023 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.clock.ui.view - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import android.widget.RadioButton -import com.android.customization.picker.clock.shared.ClockSize -import com.android.wallpaper.R - -/** The radio button group to pick the clock size. */ -class ClockSizeRadioButtonGroup( - context: Context, - attrs: AttributeSet?, -) : FrameLayout(context, attrs) { - - interface OnRadioButtonClickListener { - fun onClick(size: ClockSize) - } - - val radioButtonDynamic: RadioButton - val radioButtonSmall: RadioButton - var onRadioButtonClickListener: OnRadioButtonClickListener? = null - - init { - LayoutInflater.from(context).inflate(R.layout.clock_size_radio_button_group, this, true) - radioButtonDynamic = requireViewById(R.id.radio_button_dynamic) - val buttonDynamic = requireViewById<View>(R.id.button_container_dynamic) - buttonDynamic.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.DYNAMIC) } - radioButtonSmall = requireViewById(R.id.radio_button_large) - val buttonLarge = requireViewById<View>(R.id.button_container_small) - buttonLarge.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.SMALL) } - } -} diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt index 3f6f423f..2ab162d3 100644 --- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt +++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt @@ -15,226 +15,38 @@ */ package com.android.customization.picker.clock.ui.view -import android.app.WallpaperColors -import android.app.WallpaperManager -import android.content.Context -import android.content.res.Resources -import android.graphics.Point -import android.graphics.Rect import android.view.View -import android.widget.FrameLayout import androidx.annotation.ColorInt -import androidx.core.text.util.LocalePreferences import androidx.lifecycle.LifecycleOwner -import com.android.systemui.plugins.ClockController -import com.android.systemui.plugins.WeatherData -import com.android.systemui.shared.clocks.ClockRegistry -import com.android.wallpaper.R -import com.android.wallpaper.util.TimeUtils.TimeTicker -import java.util.concurrent.ConcurrentHashMap +import com.android.systemui.plugins.clocks.ClockController -/** - * Provide reusable clock view and related util functions. - * - * @property screenSize The Activity or Fragment's window size. - */ -class ClockViewFactory( - private val appContext: Context, - val screenSize: Point, - private val wallpaperManager: WallpaperManager, - private val registry: ClockRegistry, -) { - private val resources = appContext.resources - private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap() - private val clockControllers: HashMap<String, ClockController> = HashMap() - private val smallClockFrames: HashMap<String, FrameLayout> = HashMap() +interface ClockViewFactory { - fun getController(clockId: String): ClockController { - return clockControllers[clockId] - ?: initClockController(clockId).also { clockControllers[clockId] = it } - } + fun getController(clockId: String): ClockController /** * Reset the large view to its initial state when getting the view. This is because some view * configs, e.g. animation state, might change during the reuse of the clock view in the app. */ - fun getLargeView(clockId: String): View { - return getController(clockId).largeClock.let { - it.animations.onPickerCarouselSwiping(1F) - it.view - } - } + fun getLargeView(clockId: String): View /** * Reset the small view to its initial state when getting the view. This is because some view * configs, e.g. translation X, might change during the reuse of the clock view in the app. */ - fun getSmallView(clockId: String): View { - val smallClockFrame = - smallClockFrames[clockId] - ?: createSmallClockFrame().also { - it.addView(getController(clockId).smallClock.view) - smallClockFrames[clockId] = it - } - smallClockFrame.translationX = 0F - smallClockFrame.translationY = 0F - return smallClockFrame - } - - private fun createSmallClockFrame(): FrameLayout { - val smallClockFrame = FrameLayout(appContext) - val layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - resources.getDimensionPixelSize(R.dimen.small_clock_height) - ) - layoutParams.topMargin = getSmallClockTopMargin() - layoutParams.marginStart = getSmallClockStartPadding() - smallClockFrame.layoutParams = layoutParams - smallClockFrame.clipChildren = false - return smallClockFrame - } - - private fun getSmallClockTopMargin() = - getStatusBarHeight(appContext.resources) + - appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) - - private fun getSmallClockStartPadding() = - appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start) - - fun updateColorForAllClocks(@ColorInt seedColor: Int?) { - clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) } - } - - fun updateColor(clockId: String, @ColorInt seedColor: Int?) { - clockControllers[clockId]?.events?.onSeedColorChanged(seedColor) - } - - fun updateRegionDarkness() { - val isRegionDark = isLockscreenWallpaperDark() - clockControllers.values.forEach { - it.largeClock.events.onRegionDarknessChanged(isRegionDark) - it.smallClock.events.onRegionDarknessChanged(isRegionDark) - } - } - - private fun isLockscreenWallpaperDark(): Boolean { - val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK) - return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0 - } - - fun updateTimeFormat(clockId: String) { - getController(clockId) - .events - .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext)) - } + fun getSmallView(clockId: String): View - fun registerTimeTicker(owner: LifecycleOwner) { - val hashCode = owner.hashCode() - if (timeTickListeners.keys.contains(hashCode)) { - return - } + fun updateColorForAllClocks(@ColorInt seedColor: Int?) - timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() } - } + fun updateColor(clockId: String, @ColorInt seedColor: Int?) - fun onDestroy() { - timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) } - timeTickListeners.clear() - clockControllers.clear() - smallClockFrames.clear() - } + fun updateRegionDarkness() - private fun onTimeTick() { - clockControllers.values.forEach { - it.largeClock.events.onTimeTick() - it.smallClock.events.onTimeTick() - } - } + fun updateTimeFormat(clockId: String) - fun unregisterTimeTicker(owner: LifecycleOwner) { - val hashCode = owner.hashCode() - timeTickListeners[hashCode]?.let { - appContext.unregisterReceiver(it) - timeTickListeners.remove(hashCode) - } - } - - private fun initClockController(clockId: String): ClockController { - val controller = - registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) } - checkNotNull(controller) - - val isWallpaperDark = isLockscreenWallpaperDark() - // Initialize large clock - controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark) - controller.largeClock.events.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() - ) - controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion()) - - // Initialize small clock - controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark) - controller.smallClock.events.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() - ) - controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion()) - - // Use placeholder for weather clock preview in picker. - // Use locale default temp unit since assistant default is not available in this context. - val useCelsius = - LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS - controller.events.onWeatherDataChanged( - WeatherData( - description = DESCRIPTION_PLACEHODLER, - state = WEATHERICON_PLACEHOLDER, - temperature = - if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER - else TEMPERATURE_FAHRENHEIT_PLACEHOLDER, - useCelsius = useCelsius, - ) - ) - return controller - } - - /** - * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a - * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale - * and position the clock view - */ - private fun getLargeClockRegion(): Rect { - val largeClockTopMargin = - resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin) - val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2 - val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2) - return Rect(0, top, screenSize.x, (top + targetHeight)) - } - - /** - * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a - * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale - * and position the clock view - */ - private fun getSmallClockRegion(): Rect { - val topMargin = getSmallClockTopMargin() - val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height) - return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight) - } + fun registerTimeTicker(owner: LifecycleOwner) - companion object { - const val DESCRIPTION_PLACEHODLER = "" - const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58 - const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21 - val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY - const val USE_CELSIUS_PLACEHODLER = false + fun onDestroy() - private fun getStatusBarHeight(resource: Resources): Int { - var result = 0 - val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android") - if (resourceId > 0) { - result = resource.getDimensionPixelSize(resourceId) - } - return result - } - } + fun unregisterTimeTicker(owner: LifecycleOwner) } diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt new file mode 100644 index 00000000..5caea58d --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2023 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.customization.picker.clock.ui.view + +import android.app.WallpaperColors +import android.app.WallpaperManager +import android.content.Context +import android.content.res.Resources +import android.graphics.Point +import android.graphics.Rect +import android.view.View +import android.widget.FrameLayout +import androidx.annotation.ColorInt +import androidx.core.text.util.LocalePreferences +import androidx.lifecycle.LifecycleOwner +import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.shared.clocks.ClockRegistry +import com.android.wallpaper.R +import com.android.wallpaper.util.TimeUtils.TimeTicker +import java.util.concurrent.ConcurrentHashMap + +/** + * Provide reusable clock view and related util functions. + * + * @property screenSize The Activity or Fragment's window size. + */ +class ClockViewFactoryImpl( + private val appContext: Context, + val screenSize: Point, + private val wallpaperManager: WallpaperManager, + private val registry: ClockRegistry, +) : ClockViewFactory { + private val resources = appContext.resources + private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap() + private val clockControllers: HashMap<String, ClockController> = HashMap() + private val smallClockFrames: HashMap<String, FrameLayout> = HashMap() + + override fun getController(clockId: String): ClockController { + return clockControllers[clockId] + ?: initClockController(clockId).also { clockControllers[clockId] = it } + } + + /** + * Reset the large view to its initial state when getting the view. This is because some view + * configs, e.g. animation state, might change during the reuse of the clock view in the app. + */ + override fun getLargeView(clockId: String): View { + return getController(clockId).largeClock.let { + it.animations.onPickerCarouselSwiping(1F) + it.view + } + } + + /** + * Reset the small view to its initial state when getting the view. This is because some view + * configs, e.g. translation X, might change during the reuse of the clock view in the app. + */ + override fun getSmallView(clockId: String): View { + val smallClockFrame = + smallClockFrames[clockId] + ?: createSmallClockFrame().also { + it.addView(getController(clockId).smallClock.view) + smallClockFrames[clockId] = it + } + smallClockFrame.translationX = 0F + smallClockFrame.translationY = 0F + return smallClockFrame + } + + private fun createSmallClockFrame(): FrameLayout { + val smallClockFrame = FrameLayout(appContext) + val layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + resources.getDimensionPixelSize(R.dimen.small_clock_height) + ) + layoutParams.topMargin = getSmallClockTopMargin() + layoutParams.marginStart = getSmallClockStartPadding() + smallClockFrame.layoutParams = layoutParams + smallClockFrame.clipChildren = false + return smallClockFrame + } + + private fun getSmallClockTopMargin() = + getStatusBarHeight(appContext.resources) + + appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) + + private fun getSmallClockStartPadding() = + appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start) + + override fun updateColorForAllClocks(@ColorInt seedColor: Int?) { + clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) } + } + + override fun updateColor(clockId: String, @ColorInt seedColor: Int?) { + clockControllers[clockId]?.events?.onSeedColorChanged(seedColor) + } + + override fun updateRegionDarkness() { + val isRegionDark = isLockscreenWallpaperDark() + clockControllers.values.forEach { + it.largeClock.events.onRegionDarknessChanged(isRegionDark) + it.smallClock.events.onRegionDarknessChanged(isRegionDark) + } + } + + private fun isLockscreenWallpaperDark(): Boolean { + val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK) + return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0 + } + + override fun updateTimeFormat(clockId: String) { + getController(clockId) + .events + .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext)) + } + + override fun registerTimeTicker(owner: LifecycleOwner) { + val hashCode = owner.hashCode() + if (timeTickListeners.keys.contains(hashCode)) { + return + } + + timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() } + } + + override fun onDestroy() { + timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) } + timeTickListeners.clear() + clockControllers.clear() + smallClockFrames.clear() + } + + private fun onTimeTick() { + clockControllers.values.forEach { + it.largeClock.events.onTimeTick() + it.smallClock.events.onTimeTick() + } + } + + override fun unregisterTimeTicker(owner: LifecycleOwner) { + val hashCode = owner.hashCode() + timeTickListeners[hashCode]?.let { + appContext.unregisterReceiver(it) + timeTickListeners.remove(hashCode) + } + } + + private fun initClockController(clockId: String): ClockController { + val controller = + registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) } + checkNotNull(controller) + + val isWallpaperDark = isLockscreenWallpaperDark() + // Initialize large clock + controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark) + controller.largeClock.events.onFontSettingChanged( + resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() + ) + controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion()) + + // Initialize small clock + controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark) + controller.smallClock.events.onFontSettingChanged( + resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + ) + controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion()) + + // Use placeholder for weather clock preview in picker. + // Use locale default temp unit since assistant default is not available in this context. + val useCelsius = + LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS + controller.events.onWeatherDataChanged( + WeatherData( + description = DESCRIPTION_PLACEHODLER, + state = WEATHERICON_PLACEHOLDER, + temperature = + if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER + else TEMPERATURE_FAHRENHEIT_PLACEHOLDER, + useCelsius = useCelsius, + ) + ) + return controller + } + + /** + * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a + * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale + * and position the clock view + */ + private fun getLargeClockRegion(): Rect { + val largeClockTopMargin = + resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin) + val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2 + val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2) + return Rect(0, top, screenSize.x, (top + targetHeight)) + } + + /** + * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a + * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale + * and position the clock view + */ + private fun getSmallClockRegion(): Rect { + val topMargin = getSmallClockTopMargin() + val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height) + return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight) + } + + companion object { + const val DESCRIPTION_PLACEHODLER = "" + const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58 + const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21 + val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY + const val USE_CELSIUS_PLACEHODLER = false + + private fun getStatusBarHeight(resource: Resources): Int { + var result = 0 + val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android") + if (resourceId > 0) { + result = resource.getDimensionPixelSize(resourceId) + } + return result + } + } +} diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt index 98114260..e5ac953c 100644 --- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt +++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt @@ -15,20 +15,8 @@ */ package com.android.customization.picker.clock.ui.viewmodel -import android.content.res.Resources -import com.android.customization.module.CustomizationInjector -import com.android.wallpaper.R -import com.android.wallpaper.module.InjectorProvider - -class ClockCarouselItemViewModel(val clockId: String, val isSelected: Boolean) { - - /** Description for accessibility purposes when a clock is selected. */ - fun getContentDescription(resources: Resources): String { - val clockContent = - (InjectorProvider.getInjector() as? CustomizationInjector) - ?.getClockDescriptionUtils(resources) - ?.getDescription(clockId) - ?: "" - return resources.getString(R.string.select_clock_action_description, clockContent) - } -} +class ClockCarouselItemViewModel( + val clockId: String, + val isSelected: Boolean, + val contentDescription: String, +) diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt index 27c25a20..3f6394be 100644 --- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt +++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt @@ -15,12 +15,15 @@ */ package com.android.customization.picker.clock.ui.viewmodel +import android.content.res.Resources import android.graphics.Color import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor import com.android.customization.picker.clock.shared.ClockSize +import com.android.customization.picker.clock.ui.view.ClockViewFactory import com.android.wallpaper.R import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -43,6 +46,9 @@ import kotlinx.coroutines.launch class ClockCarouselViewModel( private val interactor: ClockPickerInteractor, private val backgroundDispatcher: CoroutineDispatcher, + private val clockViewFactory: ClockViewFactory, + private val resources: Resources, + private val logger: ThemesUserEventLogger, ) : ViewModel() { @OptIn(ExperimentalCoroutinesApi::class) val allClocks: StateFlow<List<ClockCarouselItemViewModel>> = @@ -50,7 +56,14 @@ class ClockCarouselViewModel( .mapLatest { allClocks -> // Delay to avoid the case that the full list of clocks is not initiated. delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS) - allClocks.map { ClockCarouselItemViewModel(it.clockId, it.isSelected) } + allClocks.map { + val contentDescription = + resources.getString( + R.string.select_clock_action_description, + clockViewFactory.getController(it.clockId).config.description + ) + ClockCarouselItemViewModel(it.clockId, it.isSelected, contentDescription) + } } .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) @@ -111,18 +124,27 @@ class ClockCarouselViewModel( fun setSelectedClock(clockId: String) { setSelectedClockJob?.cancel() setSelectedClockJob = - viewModelScope.launch(backgroundDispatcher) { interactor.setSelectedClock(clockId) } + viewModelScope.launch(backgroundDispatcher) { + interactor.setSelectedClock(clockId) + logger.logClockApplied(clockId) + } } class Factory( private val interactor: ClockPickerInteractor, private val backgroundDispatcher: CoroutineDispatcher, + private val clockViewFactory: ClockViewFactory, + private val resources: Resources, + private val logger: ThemesUserEventLogger, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") return ClockCarouselViewModel( interactor = interactor, backgroundDispatcher = backgroundDispatcher, + clockViewFactory = clockViewFactory, + resources = resources, + logger = logger, ) as T } diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt deleted file mode 100644 index 8a655225..00000000 --- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.android.customization.picker.clock.ui.viewmodel - -import android.content.Context -import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor -import com.android.customization.picker.clock.shared.ClockSize -import com.android.wallpaper.R -import java.util.Locale -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map - -/** View model for the clock section view on the lockscreen customization surface. */ -class ClockSectionViewModel(context: Context, interactor: ClockPickerInteractor) { - val appContext: Context = context.applicationContext - val clockColorMap: Map<String, ClockColorViewModel> = - ClockColorViewModel.getPresetColorMap(appContext.resources) - val selectedClockColorAndSizeText: Flow<String> = - combine(interactor.selectedColorId, interactor.selectedClockSize, ::Pair).map { - (selectedColorId, selectedClockSize) -> - val colorText = - clockColorMap[selectedColorId]?.colorName - ?: appContext.getString(R.string.default_theme_title) - val sizeText = - when (selectedClockSize) { - ClockSize.SMALL -> appContext.getString(R.string.clock_size_small) - ClockSize.DYNAMIC -> appContext.getString(R.string.clock_size_dynamic) - } - appContext - .getString(R.string.clock_color_and_size_description, colorText, sizeText) - .lowercase() - .replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - } - } -} diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt index a498c716..d0e4f8fe 100644 --- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt +++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt @@ -21,9 +21,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.android.customization.model.color.ColorOptionImpl +import com.android.customization.module.logging.ThemesUserEventLogger +import com.android.customization.module.logging.ThemesUserEventLogger.Companion.NULL_SEED_COLOR import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor import com.android.customization.picker.clock.shared.ClockSize import com.android.customization.picker.clock.shared.model.ClockMetadataModel +import com.android.customization.picker.clock.shared.toClockSizeForLogging import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.color.shared.model.ColorOptionModel import com.android.customization.picker.color.shared.model.ColorType @@ -53,6 +56,7 @@ private constructor( private val clockPickerInteractor: ClockPickerInteractor, private val colorPickerInteractor: ColorPickerInteractor, private val getIsReactiveToTone: (clockId: String?) -> Boolean, + private val logger: ThemesUserEventLogger, ) : ViewModel() { enum class Tab { @@ -106,15 +110,17 @@ private constructor( suspend fun onSliderProgressStop(progress: Int) { val selectedColorId = selectedColorId.value ?: return val clockColorViewModel = colorMap[selectedColorId] ?: return + val seedColor = + blendColorWithTone( + color = clockColorViewModel.color, + colorTone = clockColorViewModel.getColorTone(progress), + ) clockPickerInteractor.setClockColor( selectedColorId = selectedColorId, colorToneProgress = progress, - seedColor = - blendColorWithTone( - color = clockColorViewModel.color, - colorTone = clockColorViewModel.getColorTone(progress), - ) + seedColor = seedColor, ) + logger.logClockColorApplied(seedColor) } @OptIn(ExperimentalCoroutinesApi::class) @@ -169,18 +175,20 @@ private constructor( } else { { viewModelScope.launch { + val seedColor = + blendColorWithTone( + color = colorModel.color, + colorTone = + colorModel.getColorTone( + colorToneProgress, + ), + ) clockPickerInteractor.setClockColor( selectedColorId = colorModel.colorId, colorToneProgress = colorToneProgress, - seedColor = - blendColorWithTone( - color = colorModel.color, - colorTone = - colorModel.getColorTone( - colorToneProgress, - ), - ), + seedColor = seedColor, ) + logger.logClockColorApplied(seedColor) } } } @@ -244,6 +252,7 @@ private constructor( ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS, seedColor = null, ) + logger.logClockColorApplied(NULL_SEED_COLOR) } } } @@ -254,7 +263,10 @@ private constructor( val selectedClockSize: Flow<ClockSize> = clockPickerInteractor.selectedClockSize fun setClockSize(size: ClockSize) { - viewModelScope.launch { clockPickerInteractor.setClockSize(size) } + viewModelScope.launch { + clockPickerInteractor.setClockSize(size) + logger.logClockSizeApplied(size.toClockSizeForLogging()) + } } private val _selectedTabPosition = MutableStateFlow(Tab.COLOR) @@ -304,6 +316,7 @@ private constructor( private val context: Context, private val clockPickerInteractor: ClockPickerInteractor, private val colorPickerInteractor: ColorPickerInteractor, + private val logger: ThemesUserEventLogger, private val getIsReactiveToTone: (clockId: String?) -> Boolean, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @@ -312,6 +325,7 @@ private constructor( context = context, clockPickerInteractor = clockPickerInteractor, colorPickerInteractor = colorPickerInteractor, + logger = logger, getIsReactiveToTone = getIsReactiveToTone, ) as T diff --git a/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt b/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt deleted file mode 100644 index 28ea4a3f..00000000 --- a/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2023 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.clock.utils - -/** Provides clock description for accessibility purposes. */ -interface ClockDescriptionUtils { - - /** - * TODO (b/287507746) : Migrate the clock description to system UI or a shared library, instead - * of preserving at the Wallpaper Picker side. - */ - fun getDescription(clockId: String): String -} diff --git a/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt b/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt deleted file mode 100644 index a04ebfff..00000000 --- a/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2023 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.clock.utils - -class ThemePickerClockDescriptionUtils : ClockDescriptionUtils { - override fun getDescription(clockId: String): String { - return "" - } -} diff --git a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt index 6540ce06..942a8460 100644 --- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt +++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt @@ -24,8 +24,8 @@ import com.android.customization.model.color.ColorOptionImpl import com.android.customization.picker.color.shared.model.ColorOptionModel import com.android.customization.picker.color.shared.model.ColorType import com.android.systemui.monet.Style -import com.android.wallpaper.model.WallpaperColorsModel -import com.android.wallpaper.model.WallpaperColorsViewModel +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository +import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -37,14 +37,14 @@ import kotlinx.coroutines.suspendCancellableCoroutine // TODO (b/262924623): refactor to remove dependency on ColorCustomizationManager & ColorOption // TODO (b/268203200): Create test for ColorPickerRepositoryImpl class ColorPickerRepositoryImpl( - wallpaperColorsViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, private val colorManager: ColorCustomizationManager, ) : ColorPickerRepository { private val homeWallpaperColors: StateFlow<WallpaperColorsModel?> = - wallpaperColorsViewModel.homeWallpaperColors + wallpaperColorsRepository.homeWallpaperColors private val lockWallpaperColors: StateFlow<WallpaperColorsModel?> = - wallpaperColorsViewModel.lockWallpaperColors + wallpaperColorsRepository.lockWallpaperColors private var selectedColorOption: MutableStateFlow<ColorOptionModel> = MutableStateFlow(getCurrentColorOption()) @@ -78,7 +78,7 @@ class ColorPickerRepositoryImpl( homeColorsLoaded.colors, lockColorsLoaded.colors ) - colorManager.fetchRevampedUIOptions( + colorManager.fetchOptions( object : CustomizationManager.OptionsFetchedListener<ColorOption?> { override fun onOptionsLoaded(options: MutableList<ColorOption?>?) { val wallpaperColorOptions: MutableList<ColorOptionModel> = diff --git a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt index bb2ef9d3..f35d934d 100644 --- a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt +++ b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt @@ -19,10 +19,12 @@ package com.android.customization.picker.color.data.repository import android.content.Context import android.graphics.Color import android.text.TextUtils +import com.android.customization.model.ResourceConstants import com.android.customization.model.color.ColorOptionImpl import com.android.customization.model.color.ColorOptionsProvider import com.android.customization.picker.color.shared.model.ColorOptionModel import com.android.customization.picker.color.shared.model.ColorType +import com.android.systemui.monet.Style import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -49,6 +51,53 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos } fun setOptions( + wallpaperOptions: List<ColorOptionImpl>, + presetOptions: List<ColorOptionImpl>, + selectedColorOptionType: ColorType, + selectedColorOptionIndex: Int + ) { + _colorOptions.value = + mapOf( + ColorType.WALLPAPER_COLOR to + buildList { + for ((index, colorOption) in wallpaperOptions.withIndex()) { + val isSelected = + selectedColorOptionType == ColorType.WALLPAPER_COLOR && + selectedColorOptionIndex == index + val colorOptionModel = + ColorOptionModel( + key = "${ColorType.WALLPAPER_COLOR}::$index", + colorOption = colorOption, + isSelected = isSelected + ) + if (isSelected) { + selectedColorOption = colorOptionModel + } + add(colorOptionModel) + } + }, + ColorType.PRESET_COLOR to + buildList { + for ((index, colorOption) in presetOptions.withIndex()) { + val isSelected = + selectedColorOptionType == ColorType.PRESET_COLOR && + selectedColorOptionIndex == index + val colorOptionModel = + ColorOptionModel( + key = "${ColorType.PRESET_COLOR}::$index", + colorOption = colorOption, + isSelected = isSelected + ) + if (isSelected) { + selectedColorOption = colorOptionModel + } + add(colorOptionModel) + } + }, + ) + } + + fun setOptions( numWallpaperOptions: Int, numPresetOptions: Int, selectedColorOptionType: ColorType, @@ -111,6 +160,22 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos return builder.build() } + fun buildPresetOption(style: Style, seedColor: String): ColorOptionImpl { + val builder = ColorOptionImpl.Builder() + builder.lightColors = + intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT) + builder.darkColors = + intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT) + builder.type = ColorType.PRESET_COLOR + builder.source = ColorOptionsProvider.COLOR_SOURCE_PRESET + builder.style = style + builder.title = "Preset" + builder + .addOverlayPackage("TEST_PACKAGE_TYPE", "preset_color") + .addOverlayPackage(ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColor) + return builder.build() + } + private fun buildWallpaperOption(index: Int): ColorOptionImpl { val builder = ColorOptionImpl.Builder() builder.lightColors = @@ -127,6 +192,22 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos return builder.build() } + fun buildWallpaperOption(source: String, style: Style, seedColor: String): ColorOptionImpl { + val builder = ColorOptionImpl.Builder() + builder.lightColors = + intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT) + builder.darkColors = + intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT) + builder.type = ColorType.WALLPAPER_COLOR + builder.source = source + builder.style = style + builder.title = "Dynamic" + builder + .addOverlayPackage("TEST_PACKAGE_TYPE", "wallpaper_color") + .addOverlayPackage(ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColor) + return builder.build() + } + override suspend fun select(colorOptionModel: ColorOptionModel) { val colorOptions = _colorOptions.value val wallpaperColorOptions = colorOptions[ColorType.WALLPAPER_COLOR]!! diff --git a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt index 0f82f494..9838c317 100644 --- a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt +++ b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt @@ -62,7 +62,7 @@ object ColorPickerBinder { colorTypeTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP)) val colorOptionAdapter = OptionItemAdapter( - layoutResourceId = R.layout.color_option_2, + layoutResourceId = R.layout.color_option, lifecycleOwner = lifecycleOwner, bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel -> val colorOptionIconView = foregroundView as? ColorOptionIconView diff --git a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt index 4ef29d6e..2c006090 100644 --- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt +++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt @@ -33,11 +33,11 @@ import com.android.customization.model.mode.DarkModeSectionController import com.android.customization.module.ThemePickerInjector import com.android.customization.picker.color.ui.binder.ColorPickerBinder import com.android.wallpaper.R -import com.android.wallpaper.model.WallpaperColorsModel -import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.module.CustomizationSections import com.android.wallpaper.module.InjectorProvider import com.android.wallpaper.picker.AppbarFragment +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository +import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel import com.android.wallpaper.util.DisplayUtils @@ -76,7 +76,7 @@ class ColorPickerFragment : AppbarFragment() { val homeScreenView: CardView = view.requireViewById(R.id.home_preview) val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext()) val displayUtils: DisplayUtils = injector.getDisplayUtils(requireContext()) - val wcViewModel = injector.getWallpaperColorsViewModel() + val wallpaperColorsRepository = injector.getWallpaperColorsRepository() val wallpaperManager = WallpaperManager.getInstance(requireContext()) binding = @@ -87,7 +87,7 @@ class ColorPickerFragment : AppbarFragment() { requireActivity(), injector.getColorPickerViewModelFactory( context = requireContext(), - wallpaperColorsViewModel = wcViewModel, + wallpaperColorsRepository = wallpaperColorsRepository, ), ) .get(), @@ -114,27 +114,27 @@ class ColorPickerFragment : AppbarFragment() { wallpaperInfoProvider = { forceReload -> suspendCancellableCoroutine { continuation -> wallpaperInfoFactory.createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - lifecycleScope.launch { - if ( - wcViewModel.lockWallpaperColors.value - is WallpaperColorsModel.Loading - ) { - loadInitialColors( - wallpaperManager, - wcViewModel, - CustomizationSections.Screen.LOCK_SCREEN - ) - } - } - continuation.resume(lockWallpaper ?: homeWallpaper, null) - }, + context, forceReload, - ) + ) { homeWallpaper, lockWallpaper, _ -> + lifecycleScope.launch { + if ( + wallpaperColorsRepository.lockWallpaperColors.value + is WallpaperColorsModel.Loading + ) { + loadInitialColors( + wallpaperManager, + wallpaperColorsRepository, + CustomizationSections.Screen.LOCK_SCREEN + ) + } + } + continuation.resume(lockWallpaper ?: homeWallpaper, null) + } } }, onWallpaperColorChanged = { colors -> - wcViewModel.setLockWallpaperColors(colors) + wallpaperColorsRepository.setLockWallpaperColors(colors) }, wallpaperInteractor = injector.getWallpaperInteractor(requireContext()), screen = CustomizationSections.Screen.LOCK_SCREEN, @@ -165,27 +165,27 @@ class ColorPickerFragment : AppbarFragment() { wallpaperInfoProvider = { forceReload -> suspendCancellableCoroutine { continuation -> wallpaperInfoFactory.createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - lifecycleScope.launch { - if ( - wcViewModel.homeWallpaperColors.value - is WallpaperColorsModel.Loading - ) { - loadInitialColors( - wallpaperManager, - wcViewModel, - CustomizationSections.Screen.HOME_SCREEN - ) - } - } - continuation.resume(homeWallpaper ?: lockWallpaper, null) - }, + context, forceReload, - ) + ) { homeWallpaper, lockWallpaper, _ -> + lifecycleScope.launch { + if ( + wallpaperColorsRepository.homeWallpaperColors.value + is WallpaperColorsModel.Loading + ) { + loadInitialColors( + wallpaperManager, + wallpaperColorsRepository, + CustomizationSections.Screen.HOME_SCREEN + ) + } + } + continuation.resume(homeWallpaper ?: lockWallpaper, null) + } } }, onWallpaperColorChanged = { colors -> - wcViewModel.setHomeWallpaperColors(colors) + wallpaperColorsRepository.setHomeWallpaperColors(colors) }, wallpaperInteractor = injector.getWallpaperInteractor(requireContext()), screen = CustomizationSections.Screen.HOME_SCREEN, @@ -202,7 +202,8 @@ class ColorPickerFragment : AppbarFragment() { DarkModeSectionController( context, lifecycle, - injector.getDarkModeSnapshotRestorer(requireContext()) + injector.getDarkModeSnapshotRestorer(requireContext()), + injector.getUserEventLogger(requireContext()), ) .createView(requireContext()) darkModeSectionView.background = null @@ -218,7 +219,7 @@ class ColorPickerFragment : AppbarFragment() { private suspend fun loadInitialColors( wallpaperManager: WallpaperManager, - colorViewModel: WallpaperColorsViewModel, + colorViewModel: WallpaperColorsRepository, screen: CustomizationSections.Screen, ) { withContext(Dispatchers.IO) { diff --git a/src/com/android/customization/picker/color/ui/section/ColorSectionController2.kt b/src/com/android/customization/picker/color/ui/section/ColorSectionController.kt index f1c982b4..a36fd80a 100644 --- a/src/com/android/customization/picker/color/ui/section/ColorSectionController2.kt +++ b/src/com/android/customization/picker/color/ui/section/ColorSectionController.kt @@ -22,37 +22,37 @@ import android.view.LayoutInflater import androidx.lifecycle.LifecycleOwner import com.android.customization.picker.color.ui.binder.ColorSectionViewBinder import com.android.customization.picker.color.ui.fragment.ColorPickerFragment -import com.android.customization.picker.color.ui.view.ColorSectionView2 +import com.android.customization.picker.color.ui.view.ColorSectionView import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel import com.android.wallpaper.R import com.android.wallpaper.model.CustomizationSectionController import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController as NavigationController -class ColorSectionController2( +class ColorSectionController( private val navigationController: NavigationController, private val viewModel: ColorPickerViewModel, private val lifecycleOwner: LifecycleOwner -) : CustomizationSectionController<ColorSectionView2> { +) : CustomizationSectionController<ColorSectionView> { override fun isAvailable(context: Context): Boolean { return true } - override fun createView(context: Context): ColorSectionView2 { + override fun createView(context: Context): ColorSectionView { return createView(context, CustomizationSectionController.ViewCreationParams()) } override fun createView( context: Context, params: CustomizationSectionController.ViewCreationParams - ): ColorSectionView2 { + ): ColorSectionView { @SuppressWarnings("It is fine to inflate with null parent for our need.") val view = LayoutInflater.from(context) .inflate( - R.layout.color_section_view2, + R.layout.color_section_view, null, - ) as ColorSectionView2 + ) as ColorSectionView ColorSectionViewBinder.bind( view = view, viewModel = viewModel, diff --git a/src/com/android/customization/picker/color/ui/view/ColorSectionView2.kt b/src/com/android/customization/picker/color/ui/view/ColorSectionView.kt index 7a8f21af..a89292d8 100644 --- a/src/com/android/customization/picker/color/ui/view/ColorSectionView2.kt +++ b/src/com/android/customization/picker/color/ui/view/ColorSectionView.kt @@ -23,4 +23,4 @@ import com.android.wallpaper.picker.SectionView * The class inherits from {@link SectionView} as the view representing the color section of the * customization picker. It displays a list of color options and an overflow option. */ -class ColorSectionView2(context: Context, attrs: AttributeSet?) : SectionView(context, attrs) +class ColorSectionView(context: Context, attrs: AttributeSet?) : SectionView(context, attrs) diff --git a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt index 67c68387..ed83136e 100644 --- a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt +++ b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.android.customization.model.color.ColorOptionImpl +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.color.shared.model.ColorType import com.android.wallpaper.R @@ -43,6 +44,7 @@ class ColorPickerViewModel private constructor( context: Context, private val interactor: ColorPickerInteractor, + private val logger: ThemesUserEventLogger, ) : ViewModel() { private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null) @@ -142,6 +144,14 @@ private constructor( { viewModelScope.launch { interactor.select(colorOptionModel) + logger.logThemeColorApplied( + colorOptionModel.colorOption + .sourceForLogging, + colorOptionModel.colorOption + .styleForLogging, + colorOptionModel.colorOption + .seedColorForLogging, + ) } } } @@ -205,12 +215,14 @@ private constructor( class Factory( private val context: Context, private val interactor: ColorPickerInteractor, + private val logger: ThemesUserEventLogger, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") return ColorPickerViewModel( context = context, interactor = interactor, + logger = logger, ) as T } diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java deleted file mode 100644 index 4de1dab7..00000000 --- a/src/com/android/customization/picker/grid/GridFragment.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.grid; - -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT; - -import android.content.Context; -import android.graphics.Point; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityManager; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; -import androidx.core.widget.ContentLoadingProgressBar; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.customization.model.CustomizationManager.Callback; -import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.CustomizationOption; -import com.android.customization.model.grid.GridOption; -import com.android.customization.model.grid.GridOptionViewModel; -import com.android.customization.model.grid.GridOptionsManager; -import com.android.customization.module.ThemesUserEventLogger; -import com.android.customization.picker.WallpaperPreviewer; -import com.android.customization.widget.OptionSelectorController; -import com.android.customization.widget.OptionSelectorController.CheckmarkStyle; -import com.android.wallpaper.R; -import com.android.wallpaper.model.WallpaperInfo; -import com.android.wallpaper.module.CurrentWallpaperInfoFactory; -import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.picker.AppbarFragment; -import com.android.wallpaper.util.LaunchUtils; -import com.android.wallpaper.util.ScreenSizeCalculator; -import com.android.wallpaper.widget.BottomActionBar; - -import com.bumptech.glide.Glide; - -import java.util.List; -import java.util.Locale; - -/** - * Fragment that contains the UI for selecting and applying a GridOption. - */ -public class GridFragment extends AppbarFragment { - - private static final String TAG = "GridFragment"; - - private WallpaperInfo mHomeWallpaper; - private RecyclerView mOptionsContainer; - private OptionSelectorController<GridOption> mOptionsController; - private GridOptionsManager mGridManager; - private ContentLoadingProgressBar mLoading; - private ConstraintLayout mContent; - private View mError; - private BottomActionBar mBottomActionBar; - private ThemesUserEventLogger mEventLogger; - private GridOptionPreviewer mGridOptionPreviewer; - private GridOptionViewModel mGridOptionViewModel; - - private final Callback mApplyGridCallback = new Callback() { - @Override - public void onSuccess() { - mGridManager.fetchOptions(unused -> {}, true); - Toast.makeText(getContext(), R.string.applied_grid_msg, Toast.LENGTH_SHORT).show(); - getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - getActivity().finish(); - - // Go back to launcher home - LaunchUtils.launchHome(getContext()); - } - - @Override - public void onError(@Nullable Throwable throwable) { - // Since we disabled it when clicked apply button. - mBottomActionBar.enableActions(); - mBottomActionBar.hide(); - mGridOptionViewModel.setBottomActionBarVisible(false); - //TODO(chihhangchuang): handle - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mGridOptionViewModel = new ViewModelProvider(requireActivity()).get( - GridOptionViewModel.class); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate( - R.layout.fragment_grid_picker, container, /* attachToRoot */ false); - setUpToolbar(view); - mContent = view.findViewById(R.id.content_section); - mOptionsContainer = view.findViewById(R.id.options_container); - AccessibilityManager accessibilityManager = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - // Make Talkback focus won't reset when notifyDataSetChange - mOptionsContainer.setItemAnimator(null); - } - - // Set aspect ratio on the preview card dynamically. - Point mScreenSize; - ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance(); - mScreenSize = screenSizeCalculator.getScreenSize( - requireActivity().getWindowManager().getDefaultDisplay()); - ConstraintSet set = new ConstraintSet(); - set.clone(mContent); - String ratio = String.format(Locale.US, "%d:%d", mScreenSize.x, mScreenSize.y); - set.setDimensionRatio(R.id.preview_card_container, ratio); - set.applyTo(mContent); - - mLoading = view.findViewById(R.id.loading_indicator); - mError = view.findViewById(R.id.error_section); - - // For nav bar edge-to-edge effect. - view.setOnApplyWindowInsetsListener((v, windowInsets) -> { - v.setPadding( - v.getPaddingLeft(), - windowInsets.getSystemWindowInsetTop(), - v.getPaddingRight(), - windowInsets.getSystemWindowInsetBottom()); - return windowInsets.consumeSystemWindowInsets(); - }); - - // Clear memory cache whenever grid fragment view is being loaded. - Glide.get(getContext()).clearMemory(); - - mGridManager = GridOptionsManager.getInstance(getContext()); - mEventLogger = (ThemesUserEventLogger) InjectorProvider.getInjector() - .getUserEventLogger(getContext()); - setUpOptions(); - - SurfaceView wallpaperSurface = view.findViewById(R.id.wallpaper_preview_surface); - WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(getLifecycle(), - getActivity(), view.findViewById(R.id.wallpaper_preview_image), wallpaperSurface, - view.findViewById(R.id.grid_fadein_scrim)); - // Loads current Wallpaper. - CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() - .getCurrentWallpaperInfoFactory(getContext().getApplicationContext()); - factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> { - mHomeWallpaper = homeWallpaper; - wallpaperPreviewer.setWallpaper(mHomeWallpaper, /* listener= */ null); - }, false); - - mGridOptionPreviewer = new GridOptionPreviewer(mGridManager, - view.findViewById(R.id.grid_preview_container)); - - return view; - } - - @Override - public boolean onBackPressed() { - mGridOptionViewModel.setSelectedOption(null); - mGridOptionViewModel.setBottomActionBarVisible(false); - return super.onBackPressed(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mGridOptionPreviewer != null) { - mGridOptionPreviewer.release(); - } - } - - @Override - public CharSequence getDefaultTitle() { - return getString(R.string.grid_title); - } - - @Override - protected void onBottomActionBarReady(BottomActionBar bottomActionBar) { - super.onBottomActionBarReady(bottomActionBar); - mBottomActionBar = bottomActionBar; - mBottomActionBar.showActionsOnly(APPLY_TEXT); - mBottomActionBar.setActionClickListener(APPLY_TEXT, - v -> applyGridOption(mGridOptionViewModel.getSelectedOption())); - mBottomActionBar.setActionAccessibilityTraversalAfter(APPLY_TEXT, - mOptionsContainer.getId()); - } - - private void applyGridOption(GridOption gridOption) { - mBottomActionBar.disableActions(); - mGridManager.apply(gridOption, mApplyGridCallback); - } - - private void setUpOptions() { - hideError(); - mLoading.show(); - mGridManager.fetchOptions(new OptionsFetchedListener<GridOption>() { - @Override - public void onOptionsLoaded(List<GridOption> options) { - mLoading.hide(); - mOptionsController = new OptionSelectorController<>( - mOptionsContainer, options, /* useGrid= */ false, - CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED); - mOptionsController.initOptions(mGridManager); - GridOption previouslySelectedOption = findEquivalent(options, - mGridOptionViewModel.getSelectedOption()); - mGridOptionViewModel.setSelectedOption( - previouslySelectedOption != null - ? previouslySelectedOption - : getActiveOption(options)); - - mOptionsController.setSelectedOption(mGridOptionViewModel.getSelectedOption()); - onOptionSelected(mGridOptionViewModel.getSelectedOption()); - restoreBottomActionBarVisibility(); - - mOptionsController.addListener(selectedOption -> { - String title = selectedOption.getTitle(); - int stringId = R.string.option_previewed_description; - if (selectedOption.isActive(mGridManager)) { - stringId = R.string.option_applied_previewed_description; - } - CharSequence cd = getContext().getString(stringId, title); - mOptionsContainer.announceForAccessibility(cd); - onOptionSelected(selectedOption); - mBottomActionBar.show(); - mGridOptionViewModel.setBottomActionBarVisible(true); - }); - } - - @Override - public void onError(@Nullable Throwable throwable) { - if (throwable != null) { - Log.e(TAG, "Error loading grid options", throwable); - } - showError(); - } - }, /*reload= */ true); - } - - private GridOption getActiveOption(List<GridOption> options) { - return options.stream() - .filter(option -> option.isActive(mGridManager)) - .findAny() - // For development only, as there should always be a grid set. - .orElse(options.get(0)); - } - - @Nullable - private GridOption findEquivalent(List<GridOption> options, GridOption target) { - return options.stream() - .filter(option -> option.equals(target)) - .findAny() - .orElse(null); - } - - private void hideError() { - mContent.setVisibility(View.VISIBLE); - mError.setVisibility(View.GONE); - } - - private void showError() { - mLoading.hide(); - mContent.setVisibility(View.GONE); - mError.setVisibility(View.VISIBLE); - } - - private void onOptionSelected(CustomizationOption selectedOption) { - mGridOptionViewModel.setSelectedOption((GridOption) selectedOption); - mEventLogger.logGridSelected(mGridOptionViewModel.getSelectedOption()); - mGridOptionPreviewer.setGridOption(mGridOptionViewModel.getSelectedOption()); - } - - private void restoreBottomActionBarVisibility() { - if (mGridOptionViewModel.getBottomActionBarVisible()) { - mBottomActionBar.show(); - } else { - mBottomActionBar.hide(); - } - } -} diff --git a/src/com/android/customization/picker/grid/GridOptionPreviewer.java b/src/com/android/customization/picker/grid/GridOptionPreviewer.java deleted file mode 100644 index 7786d35c..00000000 --- a/src/com/android/customization/picker/grid/GridOptionPreviewer.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2020 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.grid; - -import android.content.Context; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.ViewGroup; - -import com.android.customization.model.grid.GridOption; -import com.android.customization.model.grid.GridOptionsManager; -import com.android.wallpaper.R; -import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback; -import com.android.wallpaper.util.PreviewUtils; -import com.android.wallpaper.util.SurfaceViewUtils; - -/** A class to load the {@link GridOption} preview to the view. */ -class GridOptionPreviewer { - - private final GridOptionsManager mGridManager; - private final ViewGroup mPreviewContainer; - - private SurfaceView mGridOptionSurface; - private GridOption mGridOption; - private GridOptionSurfaceHolderCallback mSurfaceCallback; - - GridOptionPreviewer(GridOptionsManager gridManager, ViewGroup previewContainer) { - mGridManager = gridManager; - mPreviewContainer = previewContainer; - } - - /** Loads the Grid option into the container view. */ - public void setGridOption(GridOption gridOption) { - mGridOption = gridOption; - if (mGridOption != null) { - updateWorkspacePreview(); - } - } - - /** Releases the view resource. */ - public void release() { - if (mGridOptionSurface != null) { - mSurfaceCallback.cleanUp(); - mGridOptionSurface = null; - } - mPreviewContainer.removeAllViews(); - } - - private void updateWorkspacePreview() { - // Reattach SurfaceView to trigger #surfaceCreated to update preview for different option. - mPreviewContainer.removeAllViews(); - if (mSurfaceCallback != null) { - mSurfaceCallback.cleanUp(); - mSurfaceCallback.resetLastSurface(); - if (mGridOptionSurface != null) { - mGridOptionSurface.getHolder().removeCallback(mSurfaceCallback); - } - } - mGridOptionSurface = new SurfaceView(mPreviewContainer.getContext()); - mGridOptionSurface.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - mGridOptionSurface.setZOrderMediaOverlay(true); - mSurfaceCallback = new GridOptionSurfaceHolderCallback(mGridOptionSurface, - mGridOptionSurface.getContext()); - mGridOptionSurface.getHolder().addCallback(mSurfaceCallback); - mPreviewContainer.addView(mGridOptionSurface); - } - - private class GridOptionSurfaceHolderCallback extends WorkspaceSurfaceHolderCallback { - private GridOptionSurfaceHolderCallback(SurfaceView workspaceSurface, Context context) { - super( - workspaceSurface, - new PreviewUtils( - context, context.getString(R.string.grid_control_metadata_name))); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (mGridOption != null) { - super.surfaceCreated(holder); - } - } - - @Override - protected void requestPreview(SurfaceView workspaceSurface, - PreviewUtils.WorkspacePreviewCallback callback) { - mGridManager.renderPreview( - SurfaceViewUtils.createSurfaceViewRequest(workspaceSurface), - mGridOption.name, callback); - } - } -} diff --git a/src/com/android/customization/model/grid/data/repository/GridRepository.kt b/src/com/android/customization/picker/grid/data/repository/GridRepository.kt index 4379dad5..f3844294 100644 --- a/src/com/android/customization/model/grid/data/repository/GridRepository.kt +++ b/src/com/android/customization/picker/grid/data/repository/GridRepository.kt @@ -15,15 +15,15 @@ * */ -package com.android.customization.model.grid.data.repository +package com.android.customization.picker.grid.data.repository import androidx.lifecycle.asFlow import com.android.customization.model.CustomizationManager import com.android.customization.model.CustomizationManager.Callback import com.android.customization.model.grid.GridOption import com.android.customization.model.grid.GridOptionsManager -import com.android.customization.model.grid.shared.model.GridOptionItemModel -import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import com.android.customization.picker.grid.shared.model.GridOptionItemModel +import com.android.customization.picker.grid.shared.model.GridOptionItemsModel import kotlin.coroutines.resume import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope diff --git a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt b/src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt index 7abd605b..02e16ddf 100644 --- a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt +++ b/src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt @@ -15,13 +15,13 @@ * */ -package com.android.customization.model.grid.domain.interactor +package com.android.customization.picker.grid.domain.interactor import com.android.customization.model.CustomizationManager import com.android.customization.model.grid.GridOption -import com.android.customization.model.grid.data.repository.GridRepository -import com.android.customization.model.grid.shared.model.GridOptionItemModel -import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import com.android.customization.picker.grid.data.repository.GridRepository +import com.android.customization.picker.grid.shared.model.GridOptionItemModel +import com.android.customization.picker.grid.shared.model.GridOptionItemsModel import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/src/com/android/customization/model/grid/domain/interactor/GridSnapshotRestorer.kt b/src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt index 19d4c77e..74d77f75 100644 --- a/src/com/android/customization/model/grid/domain/interactor/GridSnapshotRestorer.kt +++ b/src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt @@ -15,10 +15,10 @@ * */ -package com.android.customization.model.grid.domain.interactor +package com.android.customization.picker.grid.domain.interactor import android.util.Log -import com.android.customization.model.grid.shared.model.GridOptionItemModel +import com.android.customization.picker.grid.shared.model.GridOptionItemModel import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot diff --git a/src/com/android/customization/model/grid/shared/model/GridOptionItemModel.kt b/src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt index 2eabeab5..1fb01be0 100644 --- a/src/com/android/customization/model/grid/shared/model/GridOptionItemModel.kt +++ b/src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.shared.model +package com.android.customization.picker.grid.shared.model import kotlinx.coroutines.flow.StateFlow diff --git a/src/com/android/customization/model/grid/shared/model/GridOptionItemsModel.kt b/src/com/android/customization/picker/grid/shared/model/GridOptionItemsModel.kt index e969be88..e5b33c52 100644 --- a/src/com/android/customization/model/grid/shared/model/GridOptionItemsModel.kt +++ b/src/com/android/customization/picker/grid/shared/model/GridOptionItemsModel.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.shared.model +package com.android.customization.picker.grid.shared.model sealed class GridOptionItemsModel { data class Loaded( diff --git a/src/com/android/customization/model/grid/ui/binder/GridIconViewBinder.kt b/src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt index fba89a74..9fc88a0e 100644 --- a/src/com/android/customization/model/grid/ui/binder/GridIconViewBinder.kt +++ b/src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt @@ -1,7 +1,7 @@ -package com.android.customization.model.grid.ui.binder +package com.android.customization.picker.grid.ui.binder import android.widget.ImageView -import com.android.customization.model.grid.ui.viewmodel.GridIconViewModel +import com.android.customization.picker.grid.ui.viewmodel.GridIconViewModel import com.android.customization.widget.GridTileDrawable object GridIconViewBinder { diff --git a/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt b/src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt index 56fe425d..bcb37379 100644 --- a/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt +++ b/src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.ui.binder +package com.android.customization.picker.grid.ui.binder import android.view.View import android.widget.Button @@ -26,9 +26,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.android.customization.model.grid.ui.viewmodel.GridIconViewModel -import com.android.customization.model.grid.ui.viewmodel.GridScreenViewModel import com.android.customization.picker.common.ui.view.ItemSpacing +import com.android.customization.picker.grid.ui.viewmodel.GridIconViewModel +import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel import com.android.wallpaper.R import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder @@ -55,7 +55,7 @@ object GridScreenBinder { optionView.addItemDecoration(ItemSpacing(ItemSpacing.ITEM_SPACING_DP)) val adapter = OptionItemAdapter( - layoutResourceId = R.layout.grid_option_2, + layoutResourceId = R.layout.grid_option, lifecycleOwner = lifecycleOwner, backgroundDispatcher = backgroundDispatcher, foregroundTintSpec = diff --git a/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt b/src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt index 9e99efee..2a301b40 100644 --- a/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt +++ b/src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.ui.fragment +package com.android.customization.picker.grid.ui.fragment import android.os.Bundle import android.util.Log @@ -30,10 +30,10 @@ import androidx.lifecycle.ViewModelProvider import androidx.transition.Transition import androidx.transition.doOnStart import com.android.customization.model.CustomizationManager.Callback -import com.android.customization.model.grid.domain.interactor.GridInteractor -import com.android.customization.model.grid.ui.binder.GridScreenBinder -import com.android.customization.model.grid.ui.viewmodel.GridScreenViewModel import com.android.customization.module.ThemePickerInjector +import com.android.customization.picker.grid.domain.interactor.GridInteractor +import com.android.customization.picker.grid.ui.binder.GridScreenBinder +import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel import com.android.wallpaper.R import com.android.wallpaper.config.BaseFlags import com.android.wallpaper.module.CurrentWallpaperInfoFactory @@ -48,10 +48,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine -private val TAG = GridFragment2::class.java.simpleName +private val TAG = GridFragment::class.java.simpleName @OptIn(ExperimentalCoroutinesApi::class) -class GridFragment2 : AppbarFragment() { +class GridFragment : AppbarFragment() { private lateinit var gridInteractor: GridInteractor @@ -185,11 +185,11 @@ class GridFragment2 : AppbarFragment() { wallpaperInfoProvider = { suspendCancellableCoroutine { continuation -> wallpaperInfoFactory.createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - continuation.resume(homeWallpaper ?: lockWallpaper, null) - }, + context, /* forceRefresh= */ true, - ) + ) { homeWallpaper, lockWallpaper, _ -> + continuation.resume(homeWallpaper ?: lockWallpaper, null) + } } }, wallpaperInteractor = wallpaperInteractor, diff --git a/src/com/android/customization/model/grid/GridSectionController.java b/src/com/android/customization/picker/grid/ui/section/GridSectionController.java index c50bfcc2..6ae9acd9 100644 --- a/src/com/android/customization/model/grid/GridSectionController.java +++ b/src/com/android/customization/picker/grid/ui/section/GridSectionController.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.customization.model.grid; +package com.android.customization.picker.grid.ui.section; import android.content.Context; import android.util.Log; @@ -27,9 +27,10 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.Observer; import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.grid.ui.fragment.GridFragment2; -import com.android.customization.picker.grid.GridFragment; -import com.android.customization.picker.grid.GridSectionView; +import com.android.customization.model.grid.GridOption; +import com.android.customization.model.grid.GridOptionsManager; +import com.android.customization.picker.grid.ui.fragment.GridFragment; +import com.android.customization.picker.grid.ui.view.GridSectionView; import com.android.wallpaper.R; import com.android.wallpaper.model.CustomizationSectionController; @@ -42,7 +43,6 @@ public class GridSectionController implements CustomizationSectionController<Gri private final GridOptionsManager mGridOptionsManager; private final CustomizationSectionNavigationController mSectionNavigationController; - private final boolean mIsRevampedUiEnabled; private final Observer<Object> mOptionChangeObserver; private final LifecycleOwner mLifecycleOwner; private TextView mSectionDescription; @@ -55,7 +55,6 @@ public class GridSectionController implements CustomizationSectionController<Gri boolean isRevampedUiEnabled) { mGridOptionsManager = gridOptionsManager; mSectionNavigationController = sectionNavigationController; - mIsRevampedUiEnabled = isRevampedUiEnabled; mLifecycleOwner = lifecycleOwner; mOptionChangeObserver = o -> updateUi(/* reload= */ true); } @@ -74,20 +73,13 @@ public class GridSectionController implements CustomizationSectionController<Gri // Fetch grid options to show currently set grid. updateUi(/* The result is getting when calling isAvailable(), so reload= */ false); - if (mIsRevampedUiEnabled) { - mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).observe( - mLifecycleOwner, - mOptionChangeObserver); - } + mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).observe( + mLifecycleOwner, + mOptionChangeObserver); gridSectionView.setOnClickListener( v -> { - final Fragment gridFragment; - if (mIsRevampedUiEnabled) { - gridFragment = new GridFragment2(); - } else { - gridFragment = new GridFragment(); - } + final Fragment gridFragment = new GridFragment(); mSectionNavigationController.navigateTo(gridFragment); }); @@ -96,7 +88,7 @@ public class GridSectionController implements CustomizationSectionController<Gri @Override public void release() { - if (mIsRevampedUiEnabled && mGridOptionsManager.isAvailable()) { + if (mGridOptionsManager.isAvailable()) { mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).removeObserver( mOptionChangeObserver ); diff --git a/src/com/android/customization/picker/grid/GridSectionView.java b/src/com/android/customization/picker/grid/ui/view/GridSectionView.java index 58468e10..545ef197 100644 --- a/src/com/android/customization/picker/grid/GridSectionView.java +++ b/src/com/android/customization/picker/grid/ui/view/GridSectionView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.customization.picker.grid; +package com.android.customization.picker.grid.ui.view; import android.content.Context; import android.util.AttributeSet; diff --git a/src/com/android/customization/model/grid/ui/viewmodel/GridIconViewModel.kt b/src/com/android/customization/picker/grid/ui/viewmodel/GridIconViewModel.kt index 3942d7cc..d12dc6c3 100644 --- a/src/com/android/customization/model/grid/ui/viewmodel/GridIconViewModel.kt +++ b/src/com/android/customization/picker/grid/ui/viewmodel/GridIconViewModel.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.ui.viewmodel +package com.android.customization.picker.grid.ui.viewmodel data class GridIconViewModel( val columns: Int, diff --git a/src/com/android/customization/model/grid/ui/viewmodel/GridScreenViewModel.kt b/src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt index c11a5947..179127d1 100644 --- a/src/com/android/customization/model/grid/ui/viewmodel/GridScreenViewModel.kt +++ b/src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt @@ -15,7 +15,7 @@ * */ -package com.android.customization.model.grid.ui.viewmodel +package com.android.customization.picker.grid.ui.viewmodel import android.annotation.SuppressLint import android.content.Context @@ -24,8 +24,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.android.customization.model.ResourceConstants -import com.android.customization.model.grid.domain.interactor.GridInteractor -import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import com.android.customization.picker.grid.domain.interactor.GridInteractor +import com.android.customization.picker.grid.shared.model.GridOptionItemsModel import com.android.wallpaper.picker.common.text.ui.viewmodel.Text import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel import kotlinx.coroutines.flow.Flow diff --git a/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt index 954efa24..1a5254f8 100644 --- a/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt +++ b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt @@ -21,6 +21,7 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -31,6 +32,7 @@ class NotificationSectionViewModel @VisibleForTesting constructor( private val interactor: NotificationsInteractor, + private val logger: ThemesUserEventLogger, ) : ViewModel() { /** Whether the switch should be on. */ @@ -39,16 +41,23 @@ constructor( /** Notifies that the section has been clicked. */ fun onClicked() { - viewModelScope.launch { interactor.toggleShowNotificationsOnLockScreenEnabled() } + viewModelScope.launch { + interactor.toggleShowNotificationsOnLockScreenEnabled() + logger.logLockScreenNotificationApplied( + interactor.getSettings().isShowNotificationsOnLockScreenEnabled + ) + } } class Factory( private val interactor: NotificationsInteractor, + private val logger: ThemesUserEventLogger, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return NotificationSectionViewModel( interactor = interactor, + logger = logger, ) as T } diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt index 71dfe1da..eb25af7a 100644 --- a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt +++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt @@ -42,10 +42,10 @@ import com.android.customization.picker.color.domain.interactor.ColorPickerInter import com.android.wallpaper.R import com.android.wallpaper.model.CustomizationSectionController import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController -import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.model.WallpaperPreviewNavigator import com.android.wallpaper.module.CurrentWallpaperInfoFactory import com.android.wallpaper.module.CustomizationSections +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController @@ -64,7 +64,7 @@ class PreviewWithClockCarouselSectionController( private val lifecycleOwner: LifecycleOwner, private val screen: CustomizationSections.Screen, wallpaperInfoFactory: CurrentWallpaperInfoFactory, - colorViewModel: WallpaperColorsViewModel, + wallpaperColorsRepository: WallpaperColorsRepository, displayUtils: DisplayUtils, clockCarouselViewModelFactory: ClockCarouselViewModel.Factory, private val clockViewFactory: ClockViewFactory, @@ -82,7 +82,7 @@ class PreviewWithClockCarouselSectionController( lifecycleOwner, screen, wallpaperInfoFactory, - colorViewModel, + wallpaperColorsRepository, displayUtils, wallpaperPreviewNavigator, wallpaperInteractor, diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt index c4d6be45..b3e778ba 100644 --- a/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt +++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt @@ -25,16 +25,17 @@ import com.android.customization.model.themedicon.domain.interactor.ThemedIconIn import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.preview.ui.viewmodel.PreviewWithThemeViewModel import com.android.wallpaper.R -import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.model.WallpaperPreviewNavigator import com.android.wallpaper.module.CurrentWallpaperInfoFactory import com.android.wallpaper.module.CustomizationSections +import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel import com.android.wallpaper.util.DisplayUtils import com.android.wallpaper.util.PreviewUtils +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine /** @@ -46,7 +47,7 @@ open class PreviewWithThemeSectionController( lifecycleOwner: LifecycleOwner, private val screen: CustomizationSections.Screen, private val wallpaperInfoFactory: CurrentWallpaperInfoFactory, - private val colorViewModel: WallpaperColorsViewModel, + private val wallpaperColorsRepository: WallpaperColorsRepository, displayUtils: DisplayUtils, wallpaperPreviewNavigator: WallpaperPreviewNavigator, private val wallpaperInteractor: WallpaperInteractor, @@ -61,7 +62,7 @@ open class PreviewWithThemeSectionController( lifecycleOwner, screen, wallpaperInfoFactory, - colorViewModel, + wallpaperColorsRepository, displayUtils, wallpaperPreviewNavigator, wallpaperInteractor, @@ -69,6 +70,7 @@ open class PreviewWithThemeSectionController( isTwoPaneAndSmallWidth, customizationPickerViewModel, ) { + @OptIn(ExperimentalCoroutinesApi::class) override fun createScreenPreviewViewModel(context: Context): ScreenPreviewViewModel { return PreviewWithThemeViewModel( previewUtils = @@ -92,28 +94,28 @@ open class PreviewWithThemeSectionController( wallpaperInfoProvider = { forceReload -> suspendCancellableCoroutine { continuation -> wallpaperInfoFactory.createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - val wallpaper = - if (isOnLockScreen) { - lockWallpaper ?: homeWallpaper - } else { - homeWallpaper ?: lockWallpaper - } - loadInitialColors( - context = context, - screen = screen, - ) - continuation.resume(wallpaper, null) - }, + context, forceReload, - ) + ) { homeWallpaper, lockWallpaper, _ -> + val wallpaper = + if (isOnLockScreen) { + lockWallpaper ?: homeWallpaper + } else { + homeWallpaper ?: lockWallpaper + } + loadInitialColors( + context = context, + screen = screen, + ) + continuation.resume(wallpaper, null) + } } }, onWallpaperColorChanged = { colors -> if (isOnLockScreen) { - colorViewModel.setLockWallpaperColors(colors) + wallpaperColorsRepository.setLockWallpaperColors(colors) } else { - colorViewModel.setHomeWallpaperColors(colors) + wallpaperColorsRepository.setHomeWallpaperColors(colors) } }, initialExtrasProvider = { getInitialExtras(isOnLockScreen) }, diff --git a/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt b/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt index b17af80d..6bfe3484 100644 --- a/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt +++ b/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt @@ -21,11 +21,11 @@ import com.android.customization.picker.quickaffordance.shared.model.KeyguardQui import com.android.customization.picker.quickaffordance.shared.model.KeyguardQuickAffordancePickerSelectionModel as SelectionModel import com.android.customization.picker.quickaffordance.shared.model.KeyguardQuickAffordancePickerSlotModel as SlotModel import com.android.systemui.shared.customization.data.content.CustomizationProviderClient as Client -import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.shareIn /** * Abstracts access to application state related to functionality for selecting, picking, or setting @@ -33,39 +33,25 @@ import kotlinx.coroutines.withContext */ class KeyguardQuickAffordancePickerRepository( private val client: Client, - private val backgroundDispatcher: CoroutineDispatcher, + private val scope: CoroutineScope ) { - /** Whether the feature is enabled. */ - val isFeatureEnabled: Flow<Boolean> = - client.observeFlags().map { flags -> flags.isFeatureEnabled() } - /** List of slots available on the device. */ val slots: Flow<List<SlotModel>> = client.observeSlots().map { slots -> slots.map { slot -> slot.toModel() } } /** List of all available quick affordances. */ val affordances: Flow<List<AffordanceModel>> = - client.observeAffordances().map { affordances -> - affordances.map { affordance -> affordance.toModel() } - } + client + .observeAffordances() + .map { affordances -> affordances.map { affordance -> affordance.toModel() } } + .shareIn(scope, replay = 1, started = SharingStarted.Lazily) /** List of slot-affordance pairs, modeling what the user has currently chosen for each slot. */ val selections: Flow<List<SelectionModel>> = - client.observeSelections().map { selections -> - selections.map { selection -> selection.toModel() } - } - - suspend fun isFeatureEnabled(): Boolean { - return withContext(backgroundDispatcher) { client.queryFlags().isFeatureEnabled() } - } - - private fun List<Client.Flag>.isFeatureEnabled(): Boolean { - return find { flag -> - flag.name == - Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED - } - ?.value == true - } + client + .observeSelections() + .map { selections -> selections.map { selection -> selection.toModel() } } + .shareIn(scope, replay = 1, started = SharingStarted.Lazily) private fun Client.Slot.toModel(): SlotModel { return SlotModel( diff --git a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt index f154de65..3eca6241 100644 --- a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt +++ b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt @@ -64,7 +64,7 @@ class KeyguardQuickAffordancePickerInteractor( } /** Unselects all affordances from the slot with the given ID. */ - suspend fun unselectAll(slotId: String) { + suspend fun unselectAllFromSlot(slotId: String) { client.deleteAllSelections( slotId = slotId, ) @@ -72,15 +72,15 @@ class KeyguardQuickAffordancePickerInteractor( snapshotRestorer.get().storeSnapshot() } + /** Unselects all affordances from all slots. */ + suspend fun unselectAll() { + client.querySlots().forEach { client.deleteAllSelections(it.id) } + } + /** Returns a [Drawable] for the given resource ID, from the system UI package. */ suspend fun getAffordanceIcon( @DrawableRes iconResourceId: Int, ): Drawable { return client.getAffordanceIcon(iconResourceId) } - - /** Returns `true` if the feature is enabled; `false` otherwise. */ - suspend fun isFeatureEnabled(): Boolean { - return repository.isFeatureEnabled() - } } diff --git a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt index 3c7928ce..fee0cb51 100644 --- a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt +++ b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt @@ -42,9 +42,14 @@ class KeyguardQuickAffordanceSnapshotRestorer( } override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) { + // reset all current selections + interactor.unselectAll() + + val allSelections = checkNotNull(snapshot.args[KEY_SELECTIONS]) + if (allSelections.isEmpty()) return + val selections: List<Pair<String, String>> = - checkNotNull(snapshot.args[KEY_SELECTIONS]).split(SELECTION_SEPARATOR).map { selection - -> + allSelections.split(SELECTION_SEPARATOR).map { selection -> val (slotId, affordanceId) = selection.split(SLOT_AFFORDANCE_SEPARATOR) slotId to affordanceId } diff --git a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt index 8891b03f..0e3b7167 100644 --- a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt +++ b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt @@ -67,7 +67,9 @@ class SlotTabAdapter : RecyclerView.Adapter<SlotTabAdapter.ViewHolder>() { .find { it.isSelected.value } ?.text ?.asString(holder.itemView.context) - stateDescription?.let { holder.itemView.stateDescription = it } + holder.itemView.stateDescription = + stateDescription + ?: holder.itemView.resources.getString(R.string.keyguard_affordance_none) } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt index 091f484e..3ac52ad5 100644 --- a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt +++ b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt @@ -20,7 +20,11 @@ package com.android.customization.picker.quickaffordance.ui.binder import android.app.Dialog import android.content.Context import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent import android.widget.ImageView +import androidx.core.view.AccessibilityDelegateCompat +import androidx.core.view.ViewCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope @@ -62,6 +66,26 @@ object KeyguardQuickAffordancePickerBinder { slotTabView.layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false) slotTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP)) + + // Setting a custom accessibility delegate so that the default content descriptions + // for items in a list aren't announced (for left & right shortcuts). We populate + // the content description for these shortcuts later on with the right (expected) + // values. + val slotTabViewDelegate: AccessibilityDelegateCompat = + object : AccessibilityDelegateCompat() { + override fun onRequestSendAccessibilityEvent( + host: ViewGroup, + child: View, + event: AccessibilityEvent + ): Boolean { + if (event.eventType != AccessibilityEvent.TYPE_VIEW_FOCUSED) { + child.contentDescription = null + } + return super.onRequestSendAccessibilityEvent(host, child, event) + } + } + + ViewCompat.setAccessibilityDelegate(slotTabView, slotTabViewDelegate) val affordancesAdapter = OptionItemAdapter( layoutResourceId = R.layout.keyguard_quick_affordance, diff --git a/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt b/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt index e0beeff0..0c7b250d 100644 --- a/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt +++ b/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt @@ -20,27 +20,23 @@ package com.android.customization.picker.quickaffordance.ui.section import android.content.Context import android.view.LayoutInflater import androidx.lifecycle.LifecycleOwner -import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor import com.android.customization.picker.quickaffordance.ui.binder.KeyguardQuickAffordanceSectionViewBinder import com.android.customization.picker.quickaffordance.ui.fragment.KeyguardQuickAffordancePickerFragment import com.android.customization.picker.quickaffordance.ui.view.KeyguardQuickAffordanceSectionView import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel import com.android.wallpaper.R +import com.android.wallpaper.config.BaseFlags import com.android.wallpaper.model.CustomizationSectionController import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController as NavigationController -import kotlinx.coroutines.runBlocking class KeyguardQuickAffordanceSectionController( private val navigationController: NavigationController, - private val interactor: KeyguardQuickAffordancePickerInteractor, private val viewModel: KeyguardQuickAffordancePickerViewModel, private val lifecycleOwner: LifecycleOwner, ) : CustomizationSectionController<KeyguardQuickAffordanceSectionView> { - private val isFeatureEnabled: Boolean = runBlocking { interactor.isFeatureEnabled() } - override fun isAvailable(context: Context): Boolean { - return isFeatureEnabled + return BaseFlags.get().isKeyguardQuickAffordanceEnabled(context) } override fun createView(context: Context): KeyguardQuickAffordanceSectionView { diff --git a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt index f832cdeb..260c0d3b 100644 --- a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt +++ b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt @@ -26,6 +26,7 @@ import androidx.annotation.DrawableRes import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants @@ -63,6 +64,7 @@ private constructor( private val quickAffordanceInteractor: KeyguardQuickAffordancePickerInteractor, private val wallpaperInteractor: WallpaperInteractor, private val wallpaperInfoFactory: CurrentWallpaperInfoFactory, + private val logger: ThemesUserEventLogger, ) : ViewModel() { @SuppressLint("StaticFieldLeak") private val applicationContext = context.applicationContext @@ -92,11 +94,11 @@ private constructor( wallpaperInfoProvider = { forceReload -> suspendCancellableCoroutine { continuation -> wallpaperInfoFactory.createCurrentWallpaperInfos( - { homeWallpaper, lockWallpaper, _ -> - continuation.resume(lockWallpaper ?: homeWallpaper, null) - }, + context, forceReload, - ) + ) { homeWallpaper, lockWallpaper, _ -> + continuation.resume(lockWallpaper ?: homeWallpaper, null) + } } }, wallpaperInteractor = wallpaperInteractor, @@ -158,7 +160,8 @@ private constructor( Icon.Loaded( drawable = getAffordanceIcon(affordanceModel.iconResourceId), - contentDescription = null, + contentDescription = + Text.Loaded(getSlotContentDescription(slot.id)), ), text = Text.Loaded(affordanceModel.name), isSelected = MutableStateFlow(true) as StateFlow<Boolean>, @@ -214,7 +217,13 @@ private constructor( if (!isSelected) { { viewModelScope.launch { - quickAffordanceInteractor.unselectAll(selectedSlotId) + quickAffordanceInteractor.unselectAllFromSlot( + selectedSlotId + ) + logger.logShortcutApplied( + shortcut = "none", + shortcutSlotId = selectedSlotId, + ) } } } else { @@ -250,6 +259,10 @@ private constructor( slotId = selectedSlotId, affordanceId = affordance.id, ) + logger.logShortcutApplied( + shortcut = affordance.id, + shortcutSlotId = selectedSlotId, + ) } } } else { @@ -423,6 +436,18 @@ private constructor( ) } + private fun getSlotContentDescription(slotId: String): String { + return applicationContext.getString( + when (slotId) { + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START -> + R.string.keyguard_slot_name_bottom_start + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END -> + R.string.keyguard_slot_name_bottom_end + else -> error("No accessibility label for slot with ID \"$slotId\"!") + } + ) + } + private suspend fun getAffordanceIcon(@DrawableRes iconResourceId: Int): Drawable { return quickAffordanceInteractor.getAffordanceIcon(iconResourceId) } @@ -463,6 +488,7 @@ private constructor( private val quickAffordanceInteractor: KeyguardQuickAffordancePickerInteractor, private val wallpaperInteractor: WallpaperInteractor, private val wallpaperInfoFactory: CurrentWallpaperInfoFactory, + private val logger: ThemesUserEventLogger, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") @@ -471,6 +497,7 @@ private constructor( quickAffordanceInteractor = quickAffordanceInteractor, wallpaperInteractor = wallpaperInteractor, wallpaperInfoFactory = wallpaperInfoFactory, + logger = logger, ) as T } diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java deleted file mode 100644 index 62a2f266..00000000 --- a/src/com/android/customization/picker/theme/CustomThemeActivity.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import android.app.AlertDialog.Builder; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import com.android.customization.model.CustomizationManager.Callback; -import com.android.customization.model.theme.DefaultThemeProvider; -import com.android.customization.model.theme.OverlayManagerCompat; -import com.android.customization.model.theme.ThemeBundle; -import com.android.customization.model.theme.ThemeBundleProvider; -import com.android.customization.model.theme.ThemeManager; -import com.android.customization.model.theme.custom.ColorOptionsProvider; -import com.android.customization.model.theme.custom.CustomTheme; -import com.android.customization.model.theme.custom.CustomThemeManager; -import com.android.customization.model.theme.custom.FontOptionsProvider; -import com.android.customization.model.theme.custom.IconOptionsProvider; -import com.android.customization.model.theme.custom.ShapeOptionsProvider; -import com.android.customization.model.theme.custom.ThemeComponentOption; -import com.android.customization.model.theme.custom.ThemeComponentOption.ColorOption; -import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption; -import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption; -import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption; -import com.android.customization.model.theme.custom.ThemeComponentOptionProvider; -import com.android.customization.module.CustomizationInjector; -import com.android.customization.module.ThemesUserEventLogger; -import com.android.customization.picker.theme.CustomThemeStepFragment.CustomThemeComponentStepHost; -import com.android.wallpaper.R; -import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost; - -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.List; - -public class CustomThemeActivity extends FragmentActivity implements - AppbarFragmentHost, CustomThemeComponentStepHost { - public static final String EXTRA_THEME_ID = "CustomThemeActivity.ThemeId"; - public static final String EXTRA_THEME_TITLE = "CustomThemeActivity.ThemeTitle"; - public static final String EXTRA_THEME_PACKAGES = "CustomThemeActivity.ThemePackages"; - public static final int REQUEST_CODE_CUSTOM_THEME = 1; - public static final int RESULT_THEME_DELETED = 10; - public static final int RESULT_THEME_APPLIED = 20; - - private static final String TAG = "CustomThemeActivity"; - private static final String KEY_STATE_CURRENT_STEP = "CustomThemeActivity.currentStep"; - - private ThemesUserEventLogger mUserEventLogger; - private List<ComponentStep<?>> mSteps; - private int mCurrentStep; - private CustomThemeManager mCustomThemeManager; - private ThemeManager mThemeManager; - private TextView mNextButton; - private TextView mPreviousButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector(); - mUserEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(this); - ThemeBundleProvider themeProvider = - new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)); - Intent intent = getIntent(); - CustomTheme customTheme = null; - if (intent != null && intent.hasExtra(EXTRA_THEME_PACKAGES) - && intent.hasExtra(EXTRA_THEME_TITLE) && intent.hasExtra(EXTRA_THEME_ID)) { - try { - CustomTheme.Builder themeBuilder = themeProvider.parseCustomTheme( - intent.getStringExtra(EXTRA_THEME_PACKAGES)); - if (themeBuilder != null) { - themeBuilder.setId(intent.getStringExtra(EXTRA_THEME_ID)); - themeBuilder.setTitle(intent.getStringExtra(EXTRA_THEME_TITLE)); - customTheme = themeBuilder.build(this); - } - } catch (JSONException e) { - Log.w(TAG, "Couldn't parse provided custom theme, will override it"); - } - } - - mThemeManager = injector.getThemeManager( - new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)), - this, - new OverlayManagerCompat(this), - mUserEventLogger); - mThemeManager.fetchOptions(null, false); - mCustomThemeManager = CustomThemeManager.create(customTheme, mThemeManager); - if (savedInstanceState != null) { - mCustomThemeManager.readCustomTheme(themeProvider, savedInstanceState); - } - - int currentStep = 0; - if (savedInstanceState != null) { - currentStep = savedInstanceState.getInt(KEY_STATE_CURRENT_STEP); - } - initSteps(currentStep); - - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_custom_theme); - mNextButton = findViewById(R.id.next_button); - mNextButton.setOnClickListener(view -> onNextOrApply()); - mPreviousButton = findViewById(R.id.previous_button); - mPreviousButton.setOnClickListener(view -> onBackPressed()); - - FragmentManager fm = getSupportFragmentManager(); - Fragment fragment = fm.findFragmentById(R.id.fragment_container); - if (fragment == null) { - // Navigate to the first step - navigateToStep(0); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(KEY_STATE_CURRENT_STEP, mCurrentStep); - if (mCustomThemeManager != null) { - mCustomThemeManager.saveCustomTheme(this, outState); - } - } - - private void navigateToStep(int i) { - FragmentManager fragmentManager = getSupportFragmentManager(); - ComponentStep step = mSteps.get(i); - Fragment fragment = step.getFragment(mCustomThemeManager.getOriginalTheme().getTitle()); - - FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - fragmentTransaction.replace(R.id.fragment_container, fragment); - // Don't add step 0 to the back stack so that going back from it just finishes the Activity - if (i > 0) { - fragmentTransaction.addToBackStack("Step " + i); - } - fragmentTransaction.commit(); - fragmentManager.executePendingTransactions(); - updateNavigationButtonLabels(); - } - - private void initSteps(int currentStep) { - mSteps = new ArrayList<>(); - OverlayManagerCompat manager = new OverlayManagerCompat(this); - mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0)); - mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1)); - mSteps.add(new ColorStep(new ColorOptionsProvider(this, manager, mCustomThemeManager), 2)); - mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3)); - mSteps.add(new NameStep(4)); - mCurrentStep = currentStep; - } - - private void onNextOrApply() { - CustomThemeStepFragment stepFragment = getCurrentStepFragment(); - if (stepFragment instanceof CustomThemeComponentFragment) { - CustomThemeComponentFragment fragment = (CustomThemeComponentFragment) stepFragment; - mCustomThemeManager.apply(fragment.getSelectedOption(), new Callback() { - @Override - public void onSuccess() { - navigateToStep(mCurrentStep + 1); - } - - @Override - public void onError(@Nullable Throwable throwable) { - Log.w(TAG, "Error applying custom theme component", throwable); - Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg, - Toast.LENGTH_LONG).show(); - } - }); - } else if (stepFragment instanceof CustomThemeNameFragment) { - CustomThemeNameFragment fragment = (CustomThemeNameFragment) stepFragment; - CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme(); - - // We're on the last step, apply theme and leave - CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(this, - originalTheme.getId(), fragment.getThemeName()); - - // If the current theme is equal to the original theme being edited, then - // don't search for an equivalent, let the user apply the same one by keeping - // it null. - ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply)) - ? null : mThemeManager.findThemeByPackages(themeToApply); - - if (equivalent != null) { - Builder builder = - new Builder(CustomThemeActivity.this); - builder.setTitle(getString(R.string.use_style_instead_title, - equivalent.getTitle())) - .setMessage(getString(R.string.use_style_instead_body, - equivalent.getTitle())) - .setPositiveButton(getString(R.string.use_style_button, - equivalent.getTitle()), - (dialogInterface, i) -> applyTheme(equivalent)) - .setNegativeButton(R.string.no_thanks, null) - .create() - .show(); - } else { - applyTheme(themeToApply); - } - } else { - throw new IllegalStateException("Unknown CustomThemeStepFragment"); - } - } - - private void applyTheme(ThemeBundle themeToApply) { - mThemeManager.apply(themeToApply, new Callback() { - @Override - public void onSuccess() { - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - Toast.makeText(getApplicationContext(), R.string.applied_theme_msg, - Toast.LENGTH_LONG).show(); - setResult(RESULT_THEME_APPLIED); - finish(); - } - - @Override - public void onError(@Nullable Throwable throwable) { - Log.w(TAG, "Error applying custom theme", throwable); - Toast.makeText(CustomThemeActivity.this, - R.string.apply_theme_error_msg, - Toast.LENGTH_LONG).show(); - } - }); - } - - private CustomThemeStepFragment getCurrentStepFragment() { - return (CustomThemeStepFragment) - getSupportFragmentManager().findFragmentById(R.id.fragment_container); - } - - @Override - public void setCurrentStep(int i) { - mCurrentStep = i; - updateNavigationButtonLabels(); - } - - private void updateNavigationButtonLabels() { - mPreviousButton.setVisibility(mCurrentStep == 0 ? View.INVISIBLE : View.VISIBLE); - mNextButton.setText((mCurrentStep < mSteps.size() -1) ? R.string.custom_theme_next - : R.string.apply_btn); - } - - @Override - public void delete() { - mThemeManager.removeCustomTheme(mCustomThemeManager.getOriginalTheme()); - setResult(RESULT_THEME_DELETED); - finish(); - } - - @Override - public void cancel() { - finish(); - } - - @Override - public ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider( - int position) { - return mSteps.get(position).provider; - } - - @Override - public CustomThemeManager getCustomThemeManager() { - return mCustomThemeManager; - } - - @Override - public void onUpArrowPressed() { - // Skip it because CustomThemeStepFragment will implement cancel button - // (instead of up arrow) on action bar. - } - - @Override - public boolean isUpArrowSupported() { - // Skip it because CustomThemeStepFragment will implement cancel button - // (instead of up arrow) on action bar. - return false; - } - - /** - * Represents a step in selecting a custom theme, picking a particular component (eg font, - * color, shape, etc). - * Each step has a Fragment instance associated that instances of this class will provide. - */ - private static abstract class ComponentStep<T extends ThemeComponentOption> { - @StringRes final int titleResId; - @StringRes final int accessibilityResId; - final ThemeComponentOptionProvider<T> provider; - final int position; - private CustomThemeStepFragment mFragment; - - protected ComponentStep(@StringRes int titleResId, @StringRes int accessibilityResId, - ThemeComponentOptionProvider<T> provider, int position) { - this.titleResId = titleResId; - this.accessibilityResId = accessibilityResId; - this.provider = provider; - this.position = position; - } - - CustomThemeStepFragment getFragment(String title) { - if (mFragment == null) { - mFragment = createFragment(title); - } - return mFragment; - } - - /** - * @return a newly created fragment that will handle this step's UI. - */ - abstract CustomThemeStepFragment createFragment(String title); - } - - private class FontStep extends ComponentStep<FontOption> { - - protected FontStep(ThemeComponentOptionProvider<FontOption> provider, - int position) { - super(R.string.font_component_title, R.string.accessibility_custom_font_title, provider, - position); - } - - @Override - CustomThemeComponentFragment createFragment(String title) { - return CustomThemeComponentFragment.newInstance( - title, - position, - titleResId, - accessibilityResId); - } - } - - private class IconStep extends ComponentStep<IconOption> { - - protected IconStep(ThemeComponentOptionProvider<IconOption> provider, - int position) { - super(R.string.icon_component_title, R.string.accessibility_custom_icon_title, provider, - position); - } - - @Override - CustomThemeComponentFragment createFragment(String title) { - return CustomThemeComponentFragment.newInstance( - title, - position, - titleResId, - accessibilityResId); - } - } - - private class ColorStep extends ComponentStep<ColorOption> { - - protected ColorStep(ThemeComponentOptionProvider<ColorOption> provider, - int position) { - super(R.string.color_component_title, R.string.accessibility_custom_color_title, - provider, position); - } - - @Override - CustomThemeComponentFragment createFragment(String title) { - return CustomThemeComponentFragment.newInstance( - title, - position, - titleResId, - accessibilityResId); - } - } - - private class ShapeStep extends ComponentStep<ShapeOption> { - - protected ShapeStep(ThemeComponentOptionProvider<ShapeOption> provider, - int position) { - super(R.string.shape_component_title, R.string.accessibility_custom_shape_title, - provider, position); - } - - @Override - CustomThemeComponentFragment createFragment(String title) { - return CustomThemeComponentFragment.newInstance( - title, - position, - titleResId, - accessibilityResId); - } - } - - private class NameStep extends ComponentStep { - - protected NameStep(int position) { - super(R.string.name_component_title, R.string.accessibility_custom_name_title, null, - position); - } - - @Override - CustomThemeNameFragment createFragment(String title) { - return CustomThemeNameFragment.newInstance( - title, - position, - titleResId, - accessibilityResId); - } - } -} diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java deleted file mode 100644 index a1e99677..00000000 --- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.customization.model.theme.custom.ThemeComponentOption; -import com.android.customization.model.theme.custom.ThemeComponentOptionProvider; -import com.android.customization.widget.OptionSelectorController; -import com.android.customization.widget.OptionSelectorController.CheckmarkStyle; -import com.android.wallpaper.R; -import com.android.wallpaper.picker.AppbarFragment; - -public class CustomThemeComponentFragment extends CustomThemeStepFragment { - private static final String ARG_USE_GRID_LAYOUT = "CustomThemeComponentFragment.use_grid";; - - public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position, - int titleResId, int accessibilityResId) { - return newInstance(toolbarTitle, position, titleResId, accessibilityResId, false); - } - - public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position, - int titleResId, int accessibilityResId, boolean allowGridLayout) { - CustomThemeComponentFragment fragment = new CustomThemeComponentFragment(); - Bundle arguments = AppbarFragment.createArguments(toolbarTitle); - arguments.putInt(ARG_KEY_POSITION, position); - arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId); - arguments.putInt(ARG_KEY_ACCESSIBILITY_RES_ID, accessibilityResId); - arguments.putBoolean(ARG_USE_GRID_LAYOUT, allowGridLayout); - fragment.setArguments(arguments); - return fragment; - } - - private ThemeComponentOptionProvider<? extends ThemeComponentOption> mProvider; - private boolean mUseGridLayout; - - private RecyclerView mOptionsContainer; - private OptionSelectorController<ThemeComponentOption> mOptionsController; - private ThemeComponentOption mSelectedOption; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mUseGridLayout = getArguments().getBoolean(ARG_USE_GRID_LAYOUT); - mProvider = mHost.getComponentOptionProvider(mPosition); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - mOptionsContainer = view.findViewById(R.id.options_container); - mPreviewContainer = view.findViewById(R.id.component_preview_content); - mTitle = view.findViewById(R.id.component_options_title); - mTitle.setText(mTitleResId); - setUpOptions(); - - return view; - } - - @Override - protected int getFragmentLayoutResId() { - return R.layout.fragment_custom_theme_component; - } - - public ThemeComponentOption getSelectedOption() { - return mSelectedOption; - } - - private void bindPreview() { - mSelectedOption.bindPreview(mPreviewContainer); - } - - private void setUpOptions() { - mProvider.fetch(options -> { - mOptionsController = new OptionSelectorController( - mOptionsContainer, options, mUseGridLayout, CheckmarkStyle.NONE); - - mOptionsController.addListener(selected -> { - mSelectedOption = (ThemeComponentOption) selected; - bindPreview(); - // Preview and apply. The selection will be kept whatever user goes to previous page - // or encounter system config changes, the current selection can be recovered. - mCustomThemeManager.apply(mSelectedOption, /* callback= */ null); - }); - mOptionsController.initOptions(mCustomThemeManager); - - for (ThemeComponentOption option : options) { - if (option.isActive(mCustomThemeManager)) { - mSelectedOption = option; - break; - } - } - if (mSelectedOption == null) { - mSelectedOption = options.get(0); - } - mOptionsController.setSelectedOption(mSelectedOption); - }, false); - } -} diff --git a/src/com/android/customization/picker/theme/CustomThemeNameFragment.java b/src/com/android/customization/picker/theme/CustomThemeNameFragment.java deleted file mode 100644 index ea9099fc..00000000 --- a/src/com/android/customization/picker/theme/CustomThemeNameFragment.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2019 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.customization.model.theme.ThemeBundle.PreviewInfo; -import com.android.customization.model.theme.custom.CustomTheme; -import com.android.customization.module.CustomizationInjector; -import com.android.customization.module.CustomizationPreferences; -import com.android.customization.picker.WallpaperPreviewer; -import com.android.wallpaper.R; -import com.android.wallpaper.module.CurrentWallpaperInfoFactory; -import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.picker.AppbarFragment; - -import org.json.JSONArray; -import org.json.JSONException; - -/** Fragment of naming a custom theme. */ -public class CustomThemeNameFragment extends CustomThemeStepFragment { - - private static final String TAG = "CustomThemeNameFragment"; - - public static CustomThemeNameFragment newInstance(CharSequence toolbarTitle, int position, - int titleResId, int accessibilityResId) { - CustomThemeNameFragment fragment = new CustomThemeNameFragment(); - Bundle arguments = AppbarFragment.createArguments(toolbarTitle); - arguments.putInt(ARG_KEY_POSITION, position); - arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId); - arguments.putInt(ARG_KEY_ACCESSIBILITY_RES_ID, accessibilityResId); - fragment.setArguments(arguments); - return fragment; - } - - private EditText mNameEditor; - private ImageView mWallpaperImage; - private ThemeOptionPreviewer mThemeOptionPreviewer; - private CustomizationPreferences mCustomizationPreferences; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - mTitle = view.findViewById(R.id.component_options_title); - mTitle.setText(mTitleResId); - CurrentWallpaperInfoFactory currentWallpaperFactory = InjectorProvider.getInjector() - .getCurrentWallpaperInfoFactory(getActivity().getApplicationContext()); - CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector(); - mCustomizationPreferences = injector.getCustomizationPreferences(getContext()); - - // Set theme option. - ViewGroup previewContainer = view.findViewById(R.id.theme_preview_container); - mThemeOptionPreviewer = new ThemeOptionPreviewer(getLifecycle(), getContext(), - previewContainer); - PreviewInfo previewInfo = mCustomThemeManager.buildCustomThemePreviewInfo(getContext()); - mThemeOptionPreviewer.setPreviewInfo(previewInfo); - - // Set wallpaper background. - mWallpaperImage = view.findViewById(R.id.wallpaper_preview_image); - final WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer( - getLifecycle(), - getActivity(), - mWallpaperImage, - view.findViewById(R.id.wallpaper_preview_surface)); - currentWallpaperFactory.createCurrentWallpaperInfos( - (homeWallpaper, lockWallpaper, presentationMode) -> { - wallpaperPreviewer.setWallpaper(homeWallpaper, - mThemeOptionPreviewer::updateColorForLauncherWidgets); - }, false); - - // Set theme default name. - mNameEditor = view.findViewById(R.id.custom_theme_name); - mNameEditor.setText(getOriginalThemeName()); - return view; - } - - private String getOriginalThemeName() { - CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme(); - if (originalTheme == null || !originalTheme.isDefined()) { - // For new custom theme. use custom themes amount plus 1 as default naming. - String serializedThemes = mCustomizationPreferences.getSerializedCustomThemes(); - int customThemesCount = 0; - if (!TextUtils.isEmpty(serializedThemes)) { - try { - JSONArray customThemes = new JSONArray(serializedThemes); - customThemesCount = customThemes.length(); - } catch (JSONException e) { - Log.w(TAG, "Couldn't read stored custom theme"); - } - } - return getContext().getString( - R.string.custom_theme_title, customThemesCount + 1); - } else { - // For existing custom theme, keep its name as default naming. - return originalTheme.getTitle(); - } - } - - @Override - protected int getFragmentLayoutResId() { - return R.layout.fragment_custom_theme_name; - } - - public String getThemeName() { - return mNameEditor.getText().toString(); - } -} diff --git a/src/com/android/customization/picker/theme/CustomThemeStepFragment.java b/src/com/android/customization/picker/theme/CustomThemeStepFragment.java deleted file mode 100644 index 3f07431d..00000000 --- a/src/com/android/customization/picker/theme/CustomThemeStepFragment.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.android.customization.picker.theme; - -import android.app.AlertDialog; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.android.customization.model.theme.custom.CustomThemeManager; -import com.android.customization.model.theme.custom.ThemeComponentOption; -import com.android.customization.model.theme.custom.ThemeComponentOptionProvider; -import com.android.wallpaper.R; -import com.android.wallpaper.picker.AppbarFragment; - -abstract class CustomThemeStepFragment extends AppbarFragment { - protected static final String ARG_KEY_POSITION = "CustomThemeStepFragment.position"; - protected static final String ARG_KEY_TITLE_RES_ID = "CustomThemeStepFragment.title_res"; - protected static final String ARG_KEY_ACCESSIBILITY_RES_ID = - "CustomThemeStepFragment.accessibility_res"; - protected CustomThemeComponentStepHost mHost; - protected CustomThemeManager mCustomThemeManager; - protected int mPosition; - protected ViewGroup mPreviewContainer; - protected TextView mTitle; - @StringRes - protected int mTitleResId; - @StringRes - protected int mAccessibilityResId; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mHost = (CustomThemeComponentStepHost) context; - } - - @Override - public void onResume() { - super.onResume(); - mHost.setCurrentStep(mPosition); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mPosition = getArguments().getInt(ARG_KEY_POSITION); - mTitleResId = getArguments().getInt(ARG_KEY_TITLE_RES_ID); - mAccessibilityResId = getArguments().getInt(ARG_KEY_ACCESSIBILITY_RES_ID); - mCustomThemeManager = mHost.getCustomThemeManager(); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate( - getFragmentLayoutResId(), container, /* attachToRoot */ false); - // No original theme means it's a new one, so no toolbar icon for deleting it is needed - if (mCustomThemeManager.getOriginalTheme() == null - || !mCustomThemeManager.getOriginalTheme().isDefined()) { - setUpToolbar(view); - } else { - setUpToolbar(view, R.menu.custom_theme_editor_menu); - mToolbar.getMenu().getItem(0).setIconTintList( - getContext().getColorStateList(R.color.toolbar_icon_tint)); - } - Drawable closeIcon = getResources().getDrawable(R.drawable.ic_close_24px, null).mutate(); - closeIcon.setTintList(getResources().getColorStateList(R.color.toolbar_icon_tint, null)); - mToolbar.setNavigationIcon(closeIcon); - - mToolbar.setNavigationContentDescription(R.string.cancel); - mToolbar.setNavigationOnClickListener(v -> mHost.cancel()); - - mPreviewContainer = view.findViewById(R.id.component_preview_content); - return view; - } - - @Override - protected String getAccessibilityTitle() { - return getString(mAccessibilityResId); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.custom_theme_delete) { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setMessage(R.string.delete_custom_theme_confirmation) - .setPositiveButton(R.string.delete_custom_theme_button, - (dialogInterface, i) -> mHost.delete()) - .setNegativeButton(R.string.cancel, null) - .create() - .show(); - return true; - } - return super.onMenuItemClick(item); - } - - protected abstract int getFragmentLayoutResId(); - - public interface CustomThemeComponentStepHost { - void delete(); - void cancel(); - ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider( - int position); - - CustomThemeManager getCustomThemeManager(); - - void setCurrentStep(int step); - } -} diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java deleted file mode 100644 index 3a9a56f5..00000000 --- a/src/com/android/customization/picker/theme/ThemeFragment.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import static android.app.Activity.RESULT_OK; - -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY; -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.CUSTOMIZE; -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.widget.ContentLoadingProgressBar; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.customization.model.CustomizationManager.Callback; -import com.android.customization.model.CustomizationManager.OptionsFetchedListener; -import com.android.customization.model.CustomizationOption; -import com.android.customization.model.theme.ThemeBundle; -import com.android.customization.model.theme.ThemeManager; -import com.android.customization.model.theme.custom.CustomTheme; -import com.android.customization.module.ThemesUserEventLogger; -import com.android.customization.picker.WallpaperPreviewer; -import com.android.customization.widget.OptionSelectorController; -import com.android.wallpaper.R; -import com.android.wallpaper.model.WallpaperInfo; -import com.android.wallpaper.module.CurrentWallpaperInfoFactory; -import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.picker.AppbarFragment; -import com.android.wallpaper.widget.BottomActionBar; -import com.android.wallpaper.widget.BottomActionBar.AccessibilityCallback; -import com.android.wallpaper.widget.BottomActionBar.BottomSheetContent; - -import java.util.List; - -/** - * Fragment that contains the main UI for selecting and applying a ThemeBundle. - */ -public class ThemeFragment extends AppbarFragment { - - private static final String TAG = "ThemeFragment"; - private static final String KEY_SELECTED_THEME = "ThemeFragment.SelectedThemeBundle"; - private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE = - "ThemeFragment.bottomActionBarVisible"; - private static final int FULL_PREVIEW_REQUEST_CODE = 1000; - - /** - * Interface to be implemented by an Activity hosting a {@link ThemeFragment} - */ - public interface ThemeFragmentHost { - ThemeManager getThemeManager(); - } - public static ThemeFragment newInstance(CharSequence title) { - ThemeFragment fragment = new ThemeFragment(); - fragment.setArguments(AppbarFragment.createArguments(title)); - return fragment; - } - - private RecyclerView mOptionsContainer; - private OptionSelectorController<ThemeBundle> mOptionsController; - private ThemeManager mThemeManager; - private ThemesUserEventLogger mEventLogger; - private ThemeBundle mSelectedTheme; - private ContentLoadingProgressBar mLoading; - private View mContent; - private View mError; - private WallpaperInfo mCurrentHomeWallpaper; - private CurrentWallpaperInfoFactory mCurrentWallpaperFactory; - private BottomActionBar mBottomActionBar; - private WallpaperPreviewer mWallpaperPreviewer; - private ImageView mWallpaperImage; - private ThemeOptionPreviewer mThemeOptionPreviewer; - private ThemeInfoView mThemeInfoView; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mThemeManager = ((ThemeFragmentHost) context).getThemeManager(); - mEventLogger = (ThemesUserEventLogger) - InjectorProvider.getInjector().getUserEventLogger(context); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate( - R.layout.fragment_theme_picker, container, /* attachToRoot */ false); - setUpToolbar(view); - - mContent = view.findViewById(R.id.content_section); - mLoading = view.findViewById(R.id.loading_indicator); - mError = view.findViewById(R.id.error_section); - mCurrentWallpaperFactory = InjectorProvider.getInjector() - .getCurrentWallpaperInfoFactory(getActivity().getApplicationContext()); - mOptionsContainer = view.findViewById(R.id.options_container); - - mThemeOptionPreviewer = new ThemeOptionPreviewer( - getLifecycle(), - getContext(), - view.findViewById(R.id.theme_preview_container)); - - // Set Wallpaper background. - mWallpaperImage = view.findViewById(R.id.wallpaper_preview_image); - mWallpaperPreviewer = new WallpaperPreviewer( - getLifecycle(), - getActivity(), - mWallpaperImage, - view.findViewById(R.id.wallpaper_preview_surface)); - mCurrentWallpaperFactory.createCurrentWallpaperInfos( - (homeWallpaper, lockWallpaper, presentationMode) -> { - mCurrentHomeWallpaper = homeWallpaper; - mWallpaperPreviewer.setWallpaper(mCurrentHomeWallpaper, - mThemeOptionPreviewer::updateColorForLauncherWidgets); - }, false); - return view; - } - - @Override - protected void onBottomActionBarReady(BottomActionBar bottomActionBar) { - super.onBottomActionBarReady(bottomActionBar); - mBottomActionBar = bottomActionBar; - mBottomActionBar.showActionsOnly(INFORMATION, APPLY); - mBottomActionBar.setActionClickListener(APPLY, v -> { - mBottomActionBar.disableActions(); - applyTheme(); - }); - - mBottomActionBar.bindBottomSheetContentWithAction( - new ThemeInfoContent(getContext()), INFORMATION); - mBottomActionBar.setActionClickListener(CUSTOMIZE, this::onCustomizeClicked); - - // Update target view's accessibility param since it will be blocked by the bottom sheet - // when expanded. - mBottomActionBar.setAccessibilityCallback(new AccessibilityCallback() { - @Override - public void onBottomSheetCollapsed() { - mOptionsContainer.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Override - public void onBottomSheetExpanded() { - mOptionsContainer.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } - }); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - // Setup options here when all views are ready(including BottomActionBar), since we need to - // update views after options are loaded. - setUpOptions(savedInstanceState); - } - - private void applyTheme() { - mThemeManager.apply(mSelectedTheme, new Callback() { - @Override - public void onSuccess() { - Toast.makeText(getContext(), R.string.applied_theme_msg, Toast.LENGTH_LONG).show(); - getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - getActivity().finish(); - } - - @Override - public void onError(@Nullable Throwable throwable) { - Log.w(TAG, "Error applying theme", throwable); - // Since we disabled it when clicked apply button. - mBottomActionBar.enableActions(); - mBottomActionBar.hide(); - Toast.makeText(getContext(), R.string.apply_theme_error_msg, - Toast.LENGTH_LONG).show(); - } - }); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - if (mSelectedTheme != null && !mSelectedTheme.isActive(mThemeManager)) { - outState.putString(KEY_SELECTED_THEME, mSelectedTheme.getSerializedPackages()); - } - if (mBottomActionBar != null) { - outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible()); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME) { - if (resultCode == CustomThemeActivity.RESULT_THEME_DELETED) { - mSelectedTheme = null; - reloadOptions(); - } else if (resultCode == CustomThemeActivity.RESULT_THEME_APPLIED) { - getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - getActivity().finish(); - } else { - if (mSelectedTheme != null) { - mOptionsController.setSelectedOption(mSelectedTheme); - // Set selected option above will show BottomActionBar, - // hide BottomActionBar for the mis-trigger. - mBottomActionBar.hide(); - } else { - reloadOptions(); - } - } - } else if (requestCode == FULL_PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) { - applyTheme(); - } - super.onActivityResult(requestCode, resultCode, data); - } - - private void onCustomizeClicked(View view) { - if (mSelectedTheme instanceof CustomTheme) { - navigateToCustomTheme((CustomTheme) mSelectedTheme); - } - } - - private void hideError() { - mContent.setVisibility(View.VISIBLE); - mError.setVisibility(View.GONE); - } - - private void showError() { - mLoading.hide(); - mContent.setVisibility(View.GONE); - mError.setVisibility(View.VISIBLE); - } - - private void setUpOptions(@Nullable Bundle savedInstanceState) { - hideError(); - mLoading.show(); - mThemeManager.fetchOptions(new OptionsFetchedListener<ThemeBundle>() { - @Override - public void onOptionsLoaded(List<ThemeBundle> options) { - mOptionsController = new OptionSelectorController<>(mOptionsContainer, options); - mOptionsController.initOptions(mThemeManager); - - // Find out the selected theme option. - // 1. Find previously selected theme. - String previouslySelected = savedInstanceState != null - ? savedInstanceState.getString(KEY_SELECTED_THEME) : null; - ThemeBundle previouslySelectedTheme = null; - ThemeBundle activeTheme = null; - for (ThemeBundle theme : options) { - if (previouslySelected != null - && previouslySelected.equals(theme.getSerializedPackages())) { - previouslySelectedTheme = theme; - } - if (theme.isActive(mThemeManager)) { - activeTheme = theme; - } - } - // 2. Use active theme if no previously selected theme. - mSelectedTheme = previouslySelectedTheme != null - ? previouslySelectedTheme - : activeTheme; - // 3. Select the first system theme(default theme currently) - // if there is no matching custom enabled theme. - if (mSelectedTheme == null) { - mSelectedTheme = findFirstSystemThemeBundle(options); - } - - mOptionsController.setSelectedOption(mSelectedTheme); - onOptionSelected(mSelectedTheme); - restoreBottomActionBarVisibility(savedInstanceState); - - mOptionsController.addListener(selectedOption -> { - onOptionSelected(selectedOption); - if (!isAddCustomThemeOption(selectedOption)) { - mBottomActionBar.show(); - } - }); - mLoading.hide(); - } - @Override - public void onError(@Nullable Throwable throwable) { - if (throwable != null) { - Log.e(TAG, "Error loading theme bundles", throwable); - } - showError(); - } - }, false); - } - - private void reloadOptions() { - mThemeManager.fetchOptions(options -> { - mOptionsController.resetOptions(options); - for (ThemeBundle theme : options) { - if (theme.isActive(mThemeManager)) { - mSelectedTheme = theme; - break; - } - } - if (mSelectedTheme == null) { - mSelectedTheme = findFirstSystemThemeBundle(options); - } - mOptionsController.setSelectedOption(mSelectedTheme); - // Set selected option above will show BottomActionBar, - // hide BottomActionBar for the mis-trigger. - mBottomActionBar.hide(); - }, true); - } - - private ThemeBundle findFirstSystemThemeBundle(List<ThemeBundle> options) { - for (ThemeBundle bundle : options) { - if (!(bundle instanceof CustomTheme)) { - return bundle; - } - } - return null; - } - - private void onOptionSelected(CustomizationOption selectedOption) { - if (isAddCustomThemeOption(selectedOption)) { - navigateToCustomTheme((CustomTheme) selectedOption); - } else { - mSelectedTheme = (ThemeBundle) selectedOption; - mSelectedTheme.setOverrideThemeWallpaper(mCurrentHomeWallpaper); - mEventLogger.logThemeSelected(mSelectedTheme, - selectedOption instanceof CustomTheme); - mThemeOptionPreviewer.setPreviewInfo(mSelectedTheme.getPreviewInfo()); - if (mThemeInfoView != null && mSelectedTheme != null) { - mThemeInfoView.populateThemeInfo(mSelectedTheme); - } - - if (selectedOption instanceof CustomTheme) { - mBottomActionBar.showActionsOnly(INFORMATION, CUSTOMIZE, APPLY); - } else { - mBottomActionBar.showActionsOnly(INFORMATION, APPLY); - } - } - } - - private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) { - boolean isBottomActionBarVisible = savedInstanceState != null - && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE); - if (isBottomActionBarVisible) { - mBottomActionBar.show(); - } else { - mBottomActionBar.hide(); - } - } - - private boolean isAddCustomThemeOption(CustomizationOption option) { - return option instanceof CustomTheme && !((CustomTheme) option).isDefined(); - } - - private void navigateToCustomTheme(CustomTheme themeToEdit) { - Intent intent = new Intent(getActivity(), CustomThemeActivity.class); - intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle()); - intent.putExtra(CustomThemeActivity.EXTRA_THEME_ID, themeToEdit.getId()); - intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES, - themeToEdit.getSerializedPackages()); - startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME); - } - - private final class ThemeInfoContent extends BottomSheetContent<ThemeInfoView> { - - private ThemeInfoContent(Context context) { - super(context); - } - - @Override - public int getViewId() { - return R.layout.theme_info_view; - } - - @Override - public void onViewCreated(ThemeInfoView view) { - mThemeInfoView = view; - if (mSelectedTheme != null) { - mThemeInfoView.populateThemeInfo(mSelectedTheme); - } - } - } -} diff --git a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java b/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java deleted file mode 100644 index 3ba64ecc..00000000 --- a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2020 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import static android.app.Activity.RESULT_OK; - -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY; -import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.customization.model.theme.DefaultThemeProvider; -import com.android.customization.model.theme.ThemeBundle; -import com.android.customization.model.theme.ThemeBundleProvider; -import com.android.customization.module.CustomizationInjector; -import com.android.customization.picker.WallpaperPreviewer; -import com.android.wallpaper.R; -import com.android.wallpaper.model.WallpaperInfo; -import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.picker.AppbarFragment; -import com.android.wallpaper.widget.BottomActionBar; -import com.android.wallpaper.widget.BottomActionBar.BottomSheetContent; - -import com.bumptech.glide.Glide; - -import org.json.JSONException; - -/** A Fragment for theme full preview page. */ -public class ThemeFullPreviewFragment extends AppbarFragment { - private static final String TAG = "ThemeFullPreviewFragment"; - - public static final String EXTRA_THEME_OPTION_TITLE = "theme_option_title"; - protected static final String EXTRA_THEME_OPTION = "theme_option"; - protected static final String EXTRA_WALLPAPER_INFO = "wallpaper_info"; - protected static final String EXTRA_CAN_APPLY_FROM_FULL_PREVIEW = "can_apply"; - - private WallpaperInfo mWallpaper; - private ThemeBundle mThemeBundle; - private boolean mCanApplyFromFullPreview; - - /** - * Returns a new {@link ThemeFullPreviewFragment} with the provided title and bundle arguments - * set. - */ - public static ThemeFullPreviewFragment newInstance(CharSequence title, Bundle intentBundle) { - ThemeFullPreviewFragment fragment = new ThemeFullPreviewFragment(); - Bundle bundle = new Bundle(); - bundle.putAll(AppbarFragment.createArguments(title)); - bundle.putAll(intentBundle); - fragment.setArguments(bundle); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mWallpaper = getArguments().getParcelable(EXTRA_WALLPAPER_INFO); - mCanApplyFromFullPreview = getArguments().getBoolean(EXTRA_CAN_APPLY_FROM_FULL_PREVIEW); - CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector(); - ThemeBundleProvider themeProvider = new DefaultThemeProvider( - getContext(), injector.getCustomizationPreferences(getContext())); - try { - ThemeBundle.Builder builder = themeProvider.parseThemeBundle( - getArguments().getString(EXTRA_THEME_OPTION)); - if (builder != null) { - builder.setTitle(getArguments().getString(EXTRA_THEME_OPTION_TITLE)); - mThemeBundle = builder.build(getContext()); - } - } catch (JSONException e) { - Log.w(TAG, "Couldn't parse provided custom theme, will override it"); - // TODO(chihhangchuang): Handle the error case. - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate( - R.layout.fragment_theme_full_preview, container, /* attachToRoot */ false); - setUpToolbar(view); - Glide.get(getContext()).clearMemory(); - - // Set theme option. - final ThemeOptionPreviewer themeOptionPreviewer = new ThemeOptionPreviewer( - getLifecycle(), - getContext(), - view.findViewById(R.id.theme_preview_container)); - themeOptionPreviewer.setPreviewInfo(mThemeBundle.getPreviewInfo()); - - // Set wallpaper background. - ImageView wallpaperImageView = view.findViewById(R.id.wallpaper_preview_image); - final WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer( - getLifecycle(), - getActivity(), - wallpaperImageView, - view.findViewById(R.id.wallpaper_preview_surface)); - wallpaperPreviewer.setWallpaper(mWallpaper, - themeOptionPreviewer::updateColorForLauncherWidgets); - return view; - } - - @Override - protected void onBottomActionBarReady(BottomActionBar bottomActionBar) { - super.onBottomActionBarReady(bottomActionBar); - if (mCanApplyFromFullPreview) { - bottomActionBar.showActionsOnly(INFORMATION, APPLY); - bottomActionBar.setActionClickListener(APPLY, v -> finishActivityWithResultOk()); - } else { - bottomActionBar.showActionsOnly(INFORMATION); - } - bottomActionBar.bindBottomSheetContentWithAction( - new ThemeInfoContent(getContext()), INFORMATION); - bottomActionBar.show(); - } - - private void finishActivityWithResultOk() { - Activity activity = requireActivity(); - activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - Intent intent = new Intent(); - activity.setResult(RESULT_OK, intent); - activity.finish(); - } - - private final class ThemeInfoContent extends BottomSheetContent<ThemeInfoView> { - - private ThemeInfoContent(Context context) { - super(context); - } - - @Override - public int getViewId() { - return R.layout.theme_info_view; - } - - @Override - public void onViewCreated(ThemeInfoView view) { - view.populateThemeInfo(mThemeBundle); - } - } -} diff --git a/src/com/android/customization/picker/theme/ThemeInfoView.java b/src/com/android/customization/picker/theme/ThemeInfoView.java deleted file mode 100644 index e929c4d2..00000000 --- a/src/com/android/customization/picker/theme/ThemeInfoView.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.customization.model.theme.ThemeBundle; -import com.android.wallpaper.R; - -/** A view for displaying style info. */ -public class ThemeInfoView extends LinearLayout { - private static final int WIFI_ICON_PREVIEW_INDEX = 0; - private static final int SHAPE_PREVIEW_INDEX = 0; - - private TextView mTitle; - private TextView mFontPreviewTextView; - private ImageView mIconPreviewImageView; - private ImageView mAppPreviewImageView; - private ImageView mShapePreviewImageView; - - public ThemeInfoView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mTitle = findViewById(R.id.style_info_title); - mFontPreviewTextView = findViewById(R.id.font_preview); - mIconPreviewImageView = findViewById(R.id.qs_preview_icon); - mAppPreviewImageView = findViewById(R.id.app_preview_icon); - mShapePreviewImageView = findViewById(R.id.shape_preview_icon); - } - - /** Populates theme info. */ - public void populateThemeInfo(@NonNull ThemeBundle selectedTheme) { - ThemeBundle.PreviewInfo previewInfo = selectedTheme.getPreviewInfo(); - - if (previewInfo != null) { - mTitle.setText(getContext().getString(R.string.style_info_description)); - if (previewInfo.headlineFontFamily != null) { - mTitle.setTypeface(previewInfo.headlineFontFamily); - mFontPreviewTextView.setTypeface(previewInfo.headlineFontFamily); - } - - if (previewInfo.icons.get(WIFI_ICON_PREVIEW_INDEX) != null) { - mIconPreviewImageView.setImageDrawable( - previewInfo.icons.get(WIFI_ICON_PREVIEW_INDEX)); - } - - if (previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX) != null) { - mAppPreviewImageView.setBackground( - previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX).getDrawableCopy()); - } - - if (previewInfo.shapeDrawable != null) { - mShapePreviewImageView.setImageDrawable(previewInfo.shapeDrawable); - mShapePreviewImageView.setImageTintList( - ColorStateList.valueOf(previewInfo.resolveAccentColor(getResources()))); - } - } - } -} diff --git a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java b/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java deleted file mode 100644 index 14b53ec4..00000000 --- a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2020 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.picker.theme; - -import static android.view.View.MeasureSpec.EXACTLY; -import static android.view.View.MeasureSpec.makeMeasureSpec; - -import android.app.WallpaperColors; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.text.format.DateFormat; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import androidx.cardview.widget.CardView; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; - -import com.android.customization.model.theme.ThemeBundle; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo; -import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon; -import com.android.wallpaper.R; -import com.android.wallpaper.util.ResourceUtils; -import com.android.wallpaper.util.ScreenSizeCalculator; -import com.android.wallpaper.util.TimeUtils; -import com.android.wallpaper.util.TimeUtils.TimeTicker; - -import java.util.Calendar; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; - -/** A class to load the {@link ThemeBundle} preview to the view. */ -class ThemeOptionPreviewer implements LifecycleObserver { - private static final String DATE_FORMAT = "EEEE, MMM d"; - - // Maps which icon from ResourceConstants#ICONS_FOR_PREVIEW. - private static final int ICON_WIFI = 0; - private static final int ICON_BLUETOOTH = 1; - private static final int ICON_FLASHLIGHT = 3; - private static final int ICON_AUTO_ROTATE = 4; - private static final int ICON_CELLULAR_SIGNAL = 6; - private static final int ICON_BATTERY = 7; - - // Icons in the top bar (fake "status bar") with the particular order. - private static final int [] sTopBarIconToPreviewIcon = new int [] { - ICON_WIFI, ICON_CELLULAR_SIGNAL, ICON_BATTERY }; - - // Ids of app icon shape preview. - private int[] mShapeAppIconIds = { - R.id.shape_preview_icon_0, R.id.shape_preview_icon_1, - R.id.shape_preview_icon_2, R.id.shape_preview_icon_3 - }; - private int[] mShapeIconAppNameIds = { - R.id.shape_preview_icon_app_name_0, R.id.shape_preview_icon_app_name_1, - R.id.shape_preview_icon_app_name_2, R.id.shape_preview_icon_app_name_3 - }; - - // Ids of color/icons section. - private int[][] mColorTileIconIds = { - new int[] { R.id.preview_color_qs_0_icon, ICON_WIFI}, - new int[] { R.id.preview_color_qs_1_icon, ICON_BLUETOOTH}, - new int[] { R.id.preview_color_qs_2_icon, ICON_FLASHLIGHT}, - new int[] { R.id.preview_color_qs_3_icon, ICON_AUTO_ROTATE}, - }; - private int[] mColorTileIds = { - R.id.preview_color_qs_0_bg, R.id.preview_color_qs_1_bg, - R.id.preview_color_qs_2_bg, R.id.preview_color_qs_3_bg - }; - private int[] mColorButtonIds = { - R.id.preview_check_selected, R.id.preview_radio_selected, R.id.preview_toggle_selected - }; - - private final Context mContext; - - private View mContentView; - private TextView mStatusBarClock; - private TextView mSmartSpaceDate; - private TimeTicker mTicker; - - private boolean mHasPreviewInfoSet; - private boolean mHasWallpaperColorSet; - - ThemeOptionPreviewer(Lifecycle lifecycle, Context context, ViewGroup previewContainer) { - lifecycle.addObserver(this); - - mContext = context; - mContentView = LayoutInflater.from(context).inflate( - R.layout.theme_preview_content, /* root= */ null); - mContentView.setVisibility(View.INVISIBLE); - mStatusBarClock = mContentView.findViewById(R.id.theme_preview_clock); - mSmartSpaceDate = mContentView.findViewById(R.id.smart_space_date); - updateTime(); - final float screenAspectRatio = - ScreenSizeCalculator.getInstance().getScreenAspectRatio(mContext); - Configuration config = mContext.getResources().getConfiguration(); - final boolean directionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - previewContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View view, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - // Calculate the full preview card height and width. - final int fullPreviewCardHeight = getFullPreviewCardHeight(); - final int fullPreviewCardWidth = (int) (fullPreviewCardHeight / screenAspectRatio); - - // Relayout the content view to match full preview card size. - mContentView.measure( - makeMeasureSpec(fullPreviewCardWidth, EXACTLY), - makeMeasureSpec(fullPreviewCardHeight, EXACTLY)); - mContentView.layout(0, 0, fullPreviewCardWidth, fullPreviewCardHeight); - - // Scale the content view from full preview size to the container size. For full - // preview, the scale value is 1. - float scale = (float) previewContainer.getMeasuredHeight() / fullPreviewCardHeight; - mContentView.setScaleX(scale); - mContentView.setScaleY(scale); - // The pivot point is centered by default, set to (0, 0). - mContentView.setPivotX(directionLTR ? 0f : mContentView.getMeasuredWidth()); - mContentView.setPivotY(0f); - - // Ensure there will be only one content view in the container. - previewContainer.removeAllViews(); - // Finally, add the content view to the container. - previewContainer.addView( - mContentView, - mContentView.getMeasuredWidth(), - mContentView.getMeasuredHeight()); - - previewContainer.removeOnLayoutChangeListener(this); - } - }); - } - - /** Loads the Theme option preview into the container view. */ - public void setPreviewInfo(PreviewInfo previewInfo) { - setHeadlineFont(previewInfo.headlineFontFamily); - setBodyFont(previewInfo.bodyFontFamily); - setTopBarIcons(previewInfo.icons); - setAppIconShape(previewInfo.shapeAppIcons); - setColorAndIconsSection(previewInfo.icons, previewInfo.shapeDrawable, - previewInfo.resolveAccentColor(mContext.getResources())); - setColorAndIconsBoxRadius(previewInfo.bottomSheeetCornerRadius); - setQsbRadius(previewInfo.bottomSheeetCornerRadius); - mHasPreviewInfoSet = true; - showPreviewIfHasAllConfigSet(); - } - - /** - * Updates the color of widgets in launcher (like top status bar, smart space, and app name - * text) which will change its content color according to different wallpapers. - * - * @param colors the {@link WallpaperColors} of the wallpaper, or {@code null} to use light - * color as default - */ - public void updateColorForLauncherWidgets(@Nullable WallpaperColors colors) { - boolean useLightTextColor = colors == null - || (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0; - int textColor = mContext.getColor(useLightTextColor - ? android.R.color.white - : android.R.color.black); - int textShadowColor = mContext.getColor(useLightTextColor - ? android.R.color.tertiary_text_dark - : android.R.color.transparent); - // Update the top status bar clock text color. - mStatusBarClock.setTextColor(textColor); - // Update the top status bar icon color. - ViewGroup iconsContainer = mContentView.findViewById(R.id.theme_preview_top_bar_icons); - for (int i = 0; i < iconsContainer.getChildCount(); i++) { - ((ImageView) iconsContainer.getChildAt(i)) - .setImageTintList(ColorStateList.valueOf(textColor)); - } - // Update smart space date color. - mSmartSpaceDate.setTextColor(textColor); - mSmartSpaceDate.setShadowLayer( - mContext.getResources().getDimension( - R.dimen.smartspace_preview_key_ambient_shadow_blur), - /* dx = */ 0, - /* dy = */ 0, - textShadowColor); - - // Update shape app icon name text color. - for (int id : mShapeIconAppNameIds) { - TextView appName = mContentView.findViewById(id); - appName.setTextColor(textColor); - appName.setShadowLayer( - mContext.getResources().getDimension( - R.dimen.preview_theme_app_name_key_ambient_shadow_blur), - /* dx = */ 0, - /* dy = */ 0, - textShadowColor); - } - - mHasWallpaperColorSet = true; - showPreviewIfHasAllConfigSet(); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - @MainThread - public void onResume() { - mTicker = TimeTicker.registerNewReceiver(mContext, this::updateTime); - updateTime(); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - @MainThread - public void onPause() { - if (mContext != null) { - mContext.unregisterReceiver(mTicker); - } - } - - private void showPreviewIfHasAllConfigSet() { - if (mHasPreviewInfoSet && mHasWallpaperColorSet - && mContentView.getVisibility() != View.VISIBLE) { - mContentView.setAlpha(0f); - mContentView.setVisibility(View.VISIBLE); - mContentView.animate().alpha(1f) - .setStartDelay(50) - .setDuration(200) - .setInterpolator(AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_linear_in)) - .start(); - } - } - - private void setHeadlineFont(Typeface headlineFont) { - mStatusBarClock.setTypeface(headlineFont); - mSmartSpaceDate.setTypeface(headlineFont); - - // Update font of color/icons section title. - TextView colorIconsSectionTitle = mContentView.findViewById(R.id.color_icons_section_title); - colorIconsSectionTitle.setTypeface(headlineFont); - } - - private void setBodyFont(Typeface bodyFont) { - // Update font of app names. - for (int id : mShapeIconAppNameIds) { - TextView appName = mContentView.findViewById(id); - appName.setTypeface(bodyFont); - } - } - - private void setTopBarIcons(List<Drawable> icons) { - ViewGroup iconsContainer = mContentView.findViewById(R.id.theme_preview_top_bar_icons); - for (int i = 0; i < iconsContainer.getChildCount(); i++) { - int iconIndex = sTopBarIconToPreviewIcon[i]; - if (iconIndex < icons.size()) { - ((ImageView) iconsContainer.getChildAt(i)) - .setImageDrawable(icons.get(iconIndex).getConstantState() - .newDrawable().mutate()); - } else { - iconsContainer.getChildAt(i).setVisibility(View.GONE); - } - } - } - - private void setAppIconShape(List<ShapeAppIcon> appIcons) { - for (int i = 0; i < mShapeAppIconIds.length && i < mShapeIconAppNameIds.length - && i < appIcons.size(); i++) { - ShapeAppIcon icon = appIcons.get(i); - // Set app icon. - ImageView iconView = mContentView.findViewById(mShapeAppIconIds[i]); - iconView.setBackground(icon.getDrawableCopy()); - // Set app name. - TextView appName = mContentView.findViewById(mShapeIconAppNameIds[i]); - appName.setText(icon.getAppName()); - } - } - - private void setColorAndIconsSection(List<Drawable> icons, Drawable shapeDrawable, - int accentColor) { - // Set QS icons and background. - for (int i = 0; i < mColorTileIconIds.length && i < icons.size(); i++) { - Drawable icon = icons.get(mColorTileIconIds[i][1]).getConstantState() - .newDrawable().mutate(); - Drawable bgShape = shapeDrawable.getConstantState().newDrawable(); - bgShape.setTint(accentColor); - - ImageView bg = mContentView.findViewById(mColorTileIds[i]); - bg.setImageDrawable(bgShape); - ImageView fg = mContentView.findViewById(mColorTileIconIds[i][0]); - fg.setImageDrawable(icon); - } - - // Set color for Buttons (CheckBox, RadioButton, and Switch). - ColorStateList tintList = getColorStateList(accentColor); - for (int mColorButtonId : mColorButtonIds) { - CompoundButton button = mContentView.findViewById(mColorButtonId); - button.setButtonTintList(tintList); - if (button instanceof Switch) { - ((Switch) button).setThumbTintList(tintList); - ((Switch) button).setTrackTintList(tintList); - } - } - } - - private void setColorAndIconsBoxRadius(int cornerRadius) { - ((CardView) mContentView.findViewById(R.id.color_icons_section)).setRadius(cornerRadius); - } - - private void setQsbRadius(int cornerRadius) { - View qsb = mContentView.findViewById(R.id.theme_qsb); - if (qsb != null && qsb.getVisibility() == View.VISIBLE) { - if (qsb.getBackground() instanceof GradientDrawable) { - GradientDrawable bg = (GradientDrawable) qsb.getBackground(); - float radius = useRoundedQSB(cornerRadius) - ? (float) qsb.getLayoutParams().height / 2 : cornerRadius; - bg.setCornerRadii(new float[]{ - radius, radius, radius, radius, - radius, radius, radius, radius}); - } - } - } - - private void updateTime() { - Calendar calendar = Calendar.getInstance(TimeZone.getDefault()); - if (mStatusBarClock != null) { - mStatusBarClock.setText(TimeUtils.getFormattedTime(mContext, calendar)); - } - if (mSmartSpaceDate != null) { - String datePattern = - DateFormat.getBestDateTimePattern(Locale.getDefault(), DATE_FORMAT); - mSmartSpaceDate.setText(DateFormat.format(datePattern, calendar)); - } - } - - private boolean useRoundedQSB(int cornerRadius) { - return cornerRadius >= mContext.getResources().getDimensionPixelSize( - R.dimen.roundCornerThreshold); - } - - private ColorStateList getColorStateList(int accentColor) { - int controlGreyColor = - ResourceUtils.getColorAttr(mContext, android.R.attr.textColorTertiary); - return new ColorStateList( - new int[][]{ - new int[]{android.R.attr.state_selected}, - new int[]{android.R.attr.state_checked}, - new int[]{-android.R.attr.state_enabled}, - }, - new int[] { - accentColor, - accentColor, - controlGreyColor - } - ); - } - - /** - * Gets the screen height which does not include the system status bar and bottom navigation - * bar. - */ - private int getDisplayHeight() { - final DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); - return dm.heightPixels; - } - - // The height of top tool bar (R.layout.section_header). - private int getTopToolBarHeight() { - final TypedValue typedValue = new TypedValue(); - return mContext.getTheme().resolveAttribute( - android.R.attr.actionBarSize, typedValue, true) - ? TypedValue.complexToDimensionPixelSize( - typedValue.data, mContext.getResources().getDisplayMetrics()) - : 0; - } - - private int getFullPreviewCardHeight() { - final Resources res = mContext.getResources(); - return getDisplayHeight() - - getTopToolBarHeight() - - res.getDimensionPixelSize(R.dimen.bottom_actions_height) - - res.getDimensionPixelSize(R.dimen.full_preview_page_default_padding_top) - - res.getDimensionPixelSize(R.dimen.full_preview_page_default_padding_bottom); - } -} diff --git a/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java index 3e03a41c..f83da8c1 100644 --- a/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java +++ b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java @@ -40,18 +40,16 @@ public class ThemedIconSectionView extends SectionView { protected void onFinishInflate() { super.onFinishInflate(); mSwitchView = findViewById(R.id.themed_icon_toggle); - setOnClickListener(v -> mSwitchView.toggle()); - mSwitchView.setOnCheckedChangeListener((buttonView, isChecked) -> viewActivated(isChecked)); + setOnClickListener(v -> { + mSwitchView.toggle(); + if (mSectionViewListener != null) { + mSectionViewListener.onViewActivated(getContext(), mSwitchView.isChecked()); + } + }); } /** Gets the switch view. */ public Switch getSwitch() { return mSwitchView; } - - private void viewActivated(boolean isChecked) { - if (mSectionViewListener != null) { - mSectionViewListener.onViewActivated(getContext(), isChecked); - } - } } diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java deleted file mode 100644 index 8c7af00b..00000000 --- a/src/com/android/customization/widget/OptionSelectorController.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.customization.widget; - -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.widget.TextView; - -import androidx.annotation.Dimension; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; - -import com.android.customization.model.CustomizationManager; -import com.android.customization.model.CustomizationOption; -import com.android.wallpaper.R; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Simple controller for a RecyclerView-based widget to hold the options for each customization - * section (eg, thumbnails for themes, clocks, grid sizes). - * To use, just pass the RV that will contain the tiles and the list of {@link CustomizationOption} - * representing each option, and call {@link #initOptions(CustomizationManager)} to populate the - * widget. - */ -public class OptionSelectorController<T extends CustomizationOption<T>> { - - /** - * Interface to be notified when an option is selected by the user. - */ - public interface OptionSelectedListener { - - /** - * Called when an option has been selected (and marked as such in the UI) - */ - void onOptionSelected(CustomizationOption selected); - } - - @IntDef({CheckmarkStyle.NONE, CheckmarkStyle.CORNER, CheckmarkStyle.CENTER, - CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED}) - public @interface CheckmarkStyle { - int NONE = 0; - int CORNER = 1; - int CENTER = 2; - int CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED = 3; - } - - private final float mLinearLayoutHorizontalDisplayOptionsMax; - - private final RecyclerView mContainer; - private final List<T> mOptions; - private final boolean mUseGrid; - @CheckmarkStyle - private final int mCheckmarkStyle; - - private final Set<OptionSelectedListener> mListeners = new HashSet<>(); - private RecyclerView.Adapter<TileViewHolder> mAdapter; - private T mSelectedOption; - private T mAppliedOption; - - public OptionSelectorController(RecyclerView container, List<T> options) { - this(container, options, true, CheckmarkStyle.CORNER); - } - - public OptionSelectorController(RecyclerView container, List<T> options, - boolean useGrid, @CheckmarkStyle int checkmarkStyle) { - mContainer = container; - mOptions = options; - mUseGrid = useGrid; - mCheckmarkStyle = checkmarkStyle; - TypedValue typedValue = new TypedValue(); - mContainer.getResources().getValue(R.dimen.linear_layout_horizontal_display_options_max, - typedValue, true); - mLinearLayoutHorizontalDisplayOptionsMax = typedValue.getFloat(); - } - - public void addListener(OptionSelectedListener listener) { - mListeners.add(listener); - } - - public void removeListener(OptionSelectedListener listener) { - mListeners.remove(listener); - } - - /** - * Mark the given option as selected - */ - public void setSelectedOption(T option) { - if (!mOptions.contains(option)) { - throw new IllegalArgumentException("Invalid option"); - } - T lastSelectedOption = mSelectedOption; - mSelectedOption = option; - mAdapter.notifyItemChanged(mOptions.indexOf(option)); - if (lastSelectedOption != null) { - mAdapter.notifyItemChanged(mOptions.indexOf(lastSelectedOption)); - } - notifyListeners(); - } - - /** - * @return whether this controller contains the given option - */ - public boolean containsOption(T option) { - return mOptions.contains(option); - } - - /** - * Mark an option as the one which is currently applied on the device. This will result in a - * check being displayed in the lower-right corner of the corresponding ViewHolder. - */ - public void setAppliedOption(T option) { - if (!mOptions.contains(option)) { - throw new IllegalArgumentException("Invalid option"); - } - T lastAppliedOption = mAppliedOption; - mAppliedOption = option; - mAdapter.notifyItemChanged(mOptions.indexOf(option)); - if (lastAppliedOption != null) { - mAdapter.notifyItemChanged(mOptions.indexOf(lastAppliedOption)); - } - } - - /** - * Notify that a given option has changed. - * - * @param option the option that changed - */ - public void optionChanged(T option) { - if (!mOptions.contains(option)) { - throw new IllegalArgumentException("Invalid option"); - } - mAdapter.notifyItemChanged(mOptions.indexOf(option)); - } - - /** - * Initializes the UI for the options passed in the constructor of this class. - */ - public void initOptions(final CustomizationManager<T> manager) { - mContainer.setAccessibilityDelegateCompat( - new OptionSelectorAccessibilityDelegate(mContainer)); - - mAdapter = new RecyclerView.Adapter<TileViewHolder>() { - @Override - public int getItemViewType(int position) { - return mOptions.get(position).getLayoutResId(); - } - - @NonNull - @Override - public TileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View v = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); - // Provide width constraint when a grid layout manager is not use and width is set - // to match parent - if (!mUseGrid - && v.getLayoutParams().width == RecyclerView.LayoutParams.MATCH_PARENT) { - Resources res = mContainer.getContext().getResources(); - RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams( - res.getDimensionPixelSize(R.dimen.option_tile_width), - RecyclerView.LayoutParams.WRAP_CONTENT); - v.setLayoutParams(layoutParams); - } - return new TileViewHolder(v); - } - - @Override - public void onBindViewHolder(@NonNull TileViewHolder holder, int position) { - T option = mOptions.get(position); - if (option.isActive(manager)) { - mAppliedOption = option; - if (mSelectedOption == null) { - mSelectedOption = option; - } - } - if (holder.labelView != null) { - holder.labelView.setText(option.getTitle()); - } - holder.itemView.setActivated(option.equals(mSelectedOption)); - option.bindThumbnailTile(holder.tileView); - holder.itemView.setOnClickListener(view -> setSelectedOption(option)); - - Resources res = mContainer.getContext().getResources(); - if (mCheckmarkStyle == CheckmarkStyle.CORNER && option.equals(mAppliedOption)) { - drawCheckmark(option, holder, - res.getDrawable(R.drawable.check_circle_accent_24dp, - mContainer.getContext().getTheme()), - Gravity.BOTTOM | Gravity.RIGHT, - res.getDimensionPixelSize(R.dimen.check_size), - res.getDimensionPixelOffset(R.dimen.check_offset), true); - } else if (mCheckmarkStyle == CheckmarkStyle.CENTER - && option.equals(mAppliedOption)) { - drawCheckmark(option, holder, - res.getDrawable(R.drawable.check_circle_grey_large, - mContainer.getContext().getTheme()), - Gravity.CENTER, res.getDimensionPixelSize(R.dimen.center_check_size), - 0, true); - } else if (mCheckmarkStyle == CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED - && option.equals(mAppliedOption)) { - int drawableRes = option.equals(mSelectedOption) - ? R.drawable.check_circle_grey_large - : R.drawable.check_circle_grey_large_not_select; - drawCheckmark(option, holder, - res.getDrawable(drawableRes, - mContainer.getContext().getTheme()), - Gravity.CENTER, res.getDimensionPixelSize(R.dimen.center_check_size), - 0, option.equals(mSelectedOption)); - } else if (option.equals(mAppliedOption)) { - // Initialize with "previewed" description if we don't show checkmark - holder.setContentDescription(mContainer.getContext(), option, - R.string.option_previewed_description); - } else if (mCheckmarkStyle != CheckmarkStyle.NONE) { - if (mCheckmarkStyle == CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED) { - if (option.equals(mSelectedOption)) { - holder.setContentDescription(mContainer.getContext(), option, - R.string.option_previewed_description); - } else { - holder.setContentDescription(mContainer.getContext(), option, - R.string.option_change_applied_previewed_description); - } - } - - holder.tileView.setForeground(null); - } - } - - @Override - public int getItemCount() { - return mOptions.size(); - } - - private void drawCheckmark(CustomizationOption<?> option, TileViewHolder holder, - Drawable checkmark, int gravity, @Dimension int checkSize, - @Dimension int checkOffset, boolean currentlyPreviewed) { - Drawable frame = holder.tileView.getForeground(); - Drawable[] layers = {frame, checkmark}; - if (frame == null) { - layers = new Drawable[]{checkmark}; - } - LayerDrawable checkedFrame = new LayerDrawable(layers); - - // Position according to the given gravity and offset - int idx = layers.length - 1; - checkedFrame.setLayerGravity(idx, gravity); - checkedFrame.setLayerWidth(idx, checkSize); - checkedFrame.setLayerHeight(idx, checkSize); - checkedFrame.setLayerInsetBottom(idx, checkOffset); - checkedFrame.setLayerInsetRight(idx, checkOffset); - holder.tileView.setForeground(checkedFrame); - - // Initialize the currently applied option - if (currentlyPreviewed) { - holder.setContentDescription(mContainer.getContext(), option, - R.string.option_applied_previewed_description); - } else { - holder.setContentDescription(mContainer.getContext(), option, - R.string.option_applied_description); - } - } - }; - - Resources res = mContainer.getContext().getResources(); - mContainer.setAdapter(mAdapter); - final DisplayMetrics metrics = new DisplayMetrics(); - mContainer.getContext().getSystemService(WindowManager.class) - .getDefaultDisplay().getMetrics(metrics); - final boolean hasDecoration = mContainer.getItemDecorationCount() != 0; - - if (mUseGrid) { - int numColumns = res.getInteger(R.integer.options_grid_num_columns); - GridLayoutManager gridLayoutManager = new GridLayoutManager(mContainer.getContext(), - numColumns); - mContainer.setLayoutManager(gridLayoutManager); - } else { - final int padding = res.getDimensionPixelSize( - R.dimen.option_tile_linear_padding_horizontal); - final int widthPerItem = res.getDimensionPixelSize(R.dimen.option_tile_width) + ( - hasDecoration ? 0 : 2 * padding); - mContainer.setLayoutManager(new LinearLayoutManager(mContainer.getContext(), - LinearLayoutManager.HORIZONTAL, false)); - mContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int availableWidth = metrics.widthPixels; - int extraSpace = availableWidth - mContainer.getMeasuredWidth(); - if (extraSpace >= 0) { - mContainer.setOverScrollMode(View.OVER_SCROLL_NEVER); - } - - if (mAdapter.getItemCount() >= mLinearLayoutHorizontalDisplayOptionsMax) { - int spaceBetweenItems = availableWidth - - Math.round(widthPerItem * mLinearLayoutHorizontalDisplayOptionsMax) - - mContainer.getPaddingLeft(); - int itemEndMargin = - spaceBetweenItems / (int) mLinearLayoutHorizontalDisplayOptionsMax; - itemEndMargin = Math.max(itemEndMargin, res.getDimensionPixelOffset( - R.dimen.option_tile_margin_horizontal)); - mContainer.addItemDecoration(new ItemEndHorizontalSpaceItemDecoration( - mContainer.getContext(), itemEndMargin)); - return; - } - - int spaceBetweenItems = extraSpace / (mAdapter.getItemCount() + 1); - int itemSideMargin = spaceBetweenItems / 2; - mContainer.addItemDecoration(new HorizontalSpacerItemDecoration(itemSideMargin)); - } - } - - public void resetOptions(List<T> options) { - mOptions.clear(); - mOptions.addAll(options); - mAdapter.notifyDataSetChanged(); - } - - private void notifyListeners() { - if (mListeners.isEmpty()) { - return; - } - T option = mSelectedOption; - Set<OptionSelectedListener> iterableListeners = new HashSet<>(mListeners); - for (OptionSelectedListener listener : iterableListeners) { - listener.onOptionSelected(option); - } - } - - private static class TileViewHolder extends RecyclerView.ViewHolder { - TextView labelView; - View tileView; - CharSequence title; - - TileViewHolder(@NonNull View itemView) { - super(itemView); - labelView = itemView.findViewById(R.id.option_label); - tileView = itemView.findViewById(R.id.option_tile); - title = null; - } - - /** - * Set the content description for this holder using the given string id. - * If the option does not have a label, the description will be set on the tile view. - * - * @param context The view's context - * @param option The customization option - * @param id Resource ID of the string to use for the content description - */ - public void setContentDescription(Context context, CustomizationOption<?> option, int id) { - title = option.getTitle(); - if (TextUtils.isEmpty(title) && tileView != null) { - title = tileView.getContentDescription(); - } - - CharSequence cd = context.getString(id, title); - if (labelView != null && !TextUtils.isEmpty(labelView.getText())) { - labelView.setAccessibilityPaneTitle(cd); - labelView.setContentDescription(cd); - } else if (tileView != null) { - tileView.setAccessibilityPaneTitle(cd); - tileView.setContentDescription(cd); - } - } - - public void resetContentDescription() { - if (labelView != null && !TextUtils.isEmpty(labelView.getText())) { - labelView.setAccessibilityPaneTitle(title); - labelView.setContentDescription(title); - } else if (tileView != null) { - tileView.setAccessibilityPaneTitle(title); - tileView.setContentDescription(title); - } - } - } - - private class OptionSelectorAccessibilityDelegate extends RecyclerViewAccessibilityDelegate { - - OptionSelectorAccessibilityDelegate(RecyclerView recyclerView) { - super(recyclerView); - } - - @Override - public boolean onRequestSendAccessibilityEvent( - ViewGroup host, View child, AccessibilityEvent event) { - - // Apply this workaround to horizontal recyclerview only, - // since the symptom is TalkBack will lose focus when navigating horizontal list items. - if (mContainer.getLayoutManager() != null - && mContainer.getLayoutManager().canScrollHorizontally() - && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { - int itemPos = mContainer.getChildLayoutPosition(child); - int itemWidth = mContainer.getContext().getResources() - .getDimensionPixelOffset(R.dimen.option_tile_width); - int itemMarginHorizontal = mContainer.getContext().getResources() - .getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal) * 2; - int scrollOffset = itemWidth + itemMarginHorizontal; - - // Make focusing item's previous/next item totally visible when changing focus, - // ensure TalkBack won't lose focus when recyclerview scrolling. - if (itemPos >= ((LinearLayoutManager) mContainer.getLayoutManager()) - .findLastCompletelyVisibleItemPosition()) { - mContainer.scrollBy(scrollOffset, 0); - } else if (itemPos <= ((LinearLayoutManager) mContainer.getLayoutManager()) - .findFirstCompletelyVisibleItemPosition() && itemPos != 0) { - mContainer.scrollBy(-scrollOffset, 0); - } - } - return super.onRequestSendAccessibilityEvent(host, child, event); - } - } - - /** Custom ItemDecorator to add specific spacing between items in the list. */ - private static final class ItemEndHorizontalSpaceItemDecoration - extends RecyclerView.ItemDecoration { - private final int mHorizontalSpacePx; - private final boolean mDirectionLTR; - - private ItemEndHorizontalSpaceItemDecoration(Context context, int horizontalSpacePx) { - mDirectionLTR = context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; - mHorizontalSpacePx = horizontalSpacePx; - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, - RecyclerView.State state) { - if (recyclerView.getAdapter() == null) { - return; - } - - if (recyclerView.getChildAdapterPosition(view) - != checkNotNull(recyclerView.getAdapter()).getItemCount() - 1) { - // Don't add spacing behind the last item - if (mDirectionLTR) { - outRect.right = mHorizontalSpacePx; - } else { - outRect.left = mHorizontalSpacePx; - } - } - } - } -} |