Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc302640 authored by Catherine Liang's avatar Catherine Liang
Browse files

Create preview icon for Icons entry point (2/3)

Create the preview icon by retrieving the adaptive icon from the app to
be previewed, then wrapping it in a ShapeIconDrawable, which handles
drawing it in the given icon shape, and displaying the monochrome icon
with correct theme colors if themed. Make sure the colors are updated
also when the system color changes.

Flag: com.android.systemui.shared.new_customization_picker_ui
Bug: 402161932
Test: manually verified
Test: AppIconPickerViewModelTest
Change-Id: I2f7138de1f2b540df943f1306a6301b5d8922ce0
parent 247d15b2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@
        <package android:name="com.android.launcher3"/>
        <package android:name="com.android.settings"/>
        <package android:name="com.android.systemui"/>
        <!-- Package of camera for icon shape -->
        <package android:name="com.android.camera2" />

        <!-- Specific intents Wallpaper picker query for -->
        <!-- Package for theme stub -->
+3 −0
Original line number Diff line number Diff line
@@ -41,4 +41,7 @@
        <item>com.google.android.youtube</item>
        <item>com.android.vending</item>
    </array>

    <!-- Package name of the Camera app to be used when previewing icon shape and theme -->
    <string name="camera_package" translatable="false">com.android.camera2</string>
</resources>
+62 −1
Original line number Diff line number Diff line
@@ -16,12 +16,73 @@

package com.android.wallpaper.customization.ui.binder

import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import android.widget.ImageView
import androidx.lifecycle.LifecycleOwner
import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
import com.android.wallpaper.customization.ui.view.ShapeTileDrawable
import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
import kotlinx.coroutines.DisposableHandle

object ShapeIconViewBinder {
    const val TAG = "ShapeIconViewBinder"

    fun bind(view: ImageView, shapeIcon: ShapeIconViewModel) {
        view.setImageDrawable(ShapeTileDrawable(shapeIcon.path))
        view.setImageDrawable(ShapeTileDrawable(view.context, shapeIcon.path))
    }

    fun bindPreviewIcon(
        view: ImageView,
        appIconDrawable: AdaptiveIconDrawable?,
        shapeIcon: ShapeIconViewModel,
        isThemed: Boolean,
        colorUpdateViewModel: ColorUpdateViewModel,
        shouldAnimateColor: () -> Boolean,
        lifecycleOwner: LifecycleOwner,
    ): DisposableHandle {
        val shapeTileDrawable =
            ShapeTileDrawable(
                context = view.context,
                path = shapeIcon.path,
                icon = appIconDrawable,
                isThemed = isThemed,
            )
        view.setImageDrawable(shapeTileDrawable)
        val bindingForeground =
            if (isThemed) {
                ColorUpdateBinder.bind(
                    setColor = { color -> shapeTileDrawable.setThemedIconForegroundColor(color) },
                    color = colorUpdateViewModel.themedIconColor,
                    shouldAnimate = shouldAnimateColor,
                    lifecycleOwner = lifecycleOwner,
                )
            } else null
        val bindingBackground =
            if (isThemed) {
                ColorUpdateBinder.bind(
                    setColor = { color -> shapeTileDrawable.setThemedIconBackgroundColor(color) },
                    color = colorUpdateViewModel.themedIconBackgroundColor,
                    shouldAnimate = shouldAnimateColor,
                    lifecycleOwner = lifecycleOwner,
                )
            } else null
        return DisposableHandle {
            bindingForeground?.destroy()
            bindingBackground?.destroy()
        }
    }

    fun loadAppIcon(context: Context, packageName: String): Drawable? {
        return try {
            context.packageManager.getApplicationIcon(packageName)
        } catch (e: NameNotFoundException) {
            Log.d(TAG, "Couldn't find resource $packageName for app icon preview")
            null
        }
    }
}
+22 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.wallpaper.customization.ui.binder
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.AdaptiveIconDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -65,6 +66,7 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -322,14 +324,28 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
                }

                launch {
                    optionsViewModel.appIconPickerViewModel.summary.collect { description ->
                        // TODO(b/402161932): create and display app icon preview
                        optionAppIcons
                            .requireViewById<View>(R.id.option_entry_icon_container)
                            .visibility = View.INVISIBLE
                    var disposableHandle: DisposableHandle? = null
                    val previewIconPackageName =
                        view.context.resources.getString(R.string.camera_package)
                    val appIconDrawable =
                        ShapeIconViewBinder.loadAppIcon(view.context, previewIconPackageName)
                    optionsViewModel.appIconPickerViewModel.summary.collect { summary ->
                        disposableHandle?.dispose()
                        summary.iconShape?.let {
                            disposableHandle =
                                ShapeIconViewBinder.bindPreviewIcon(
                                    view = optionAppIconsIcon,
                                    appIconDrawable = appIconDrawable as? AdaptiveIconDrawable,
                                    shapeIcon = summary.iconShape,
                                    isThemed = summary.isThemed,
                                    colorUpdateViewModel = colorUpdateViewModel,
                                    shouldAnimateColor = isOnMainScreen,
                                    lifecycleOwner = lifecycleOwner,
                                )
                        }
                        TextViewBinder.bind(
                            view = optionAppIconsDescription,
                            viewModel = description,
                            viewModel = summary.description,
                        )
                    }
                }
+42 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.wallpaper.customization.ui.view

import android.annotation.ColorInt
import android.content.Context
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Matrix
@@ -23,30 +25,58 @@ import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import androidx.core.graphics.PathParser
import androidx.core.graphics.withSave
import com.android.wallpaper.R

/**
 * Drawable that draws a shape tile with a given path.
 * Drawable that draws a shape tile with a given path. If given an icon, it will also draw the icon
 * within the shape path.
 *
 * @param path Path of the shape assuming drawing on a 100x100 canvas.
 * @param icon The adaptive icon to draw within the path, or null if no icon should be drawn.
 * @param isThemed Whether the adaptive icon should be drawn in monochrome.
 */
class ShapeTileDrawable(path: String) : Drawable() {
class ShapeTileDrawable(
    context: Context,
    path: String,
    private val icon: AdaptiveIconDrawable? = null,
    private val isThemed: Boolean = false,
) : Drawable() {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val path = PathParser.createPathFromPathData(path)
    // The path scaled with regard to the update of drawable bounds
    private val scaledPath = Path(this.path)
    private val scaleMatrix = Matrix()
    private var backgroundColor = context.getColor(R.color.themed_icon_background_color)
    private var foregroundColor = context.getColor(R.color.themed_icon_color)

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        scaleMatrix.setScale(bounds.width() / PATH_SIZE, bounds.height() / PATH_SIZE)
        path.transform(scaleMatrix, scaledPath)
        icon?.bounds = bounds
    }

    override fun draw(canvas: Canvas) {
        canvas.clipPath(scaledPath)
        canvas.drawPath(scaledPath, paint)
        canvas.withSave {
            if (isThemed) {
                if (icon?.monochrome != null) {
                    canvas.drawColor(backgroundColor)
                    icon.monochrome?.setTint(foregroundColor)
                    icon.monochrome?.draw(this)
                }
                // TODO (b/402161932): explore whether we need to handle case of icon w/o monochrome
            } else {
                icon?.background?.draw(this)
                icon?.foreground?.draw(this)
            }
        }
    }

    override fun setAlpha(alpha: Int) {
@@ -57,6 +87,16 @@ class ShapeTileDrawable(path: String) : Drawable() {
        paint.setColorFilter(colorFilter)
    }

    fun setThemedIconBackgroundColor(@ColorInt backgroundColor: Int) {
        this.backgroundColor = backgroundColor
        invalidateSelf()
    }

    fun setThemedIconForegroundColor(@ColorInt foregroundColor: Int) {
        this.foregroundColor = foregroundColor
        invalidateSelf()
    }

    @Deprecated(
        "getOpacity() is deprecated",
        ReplaceWith("setAlpha(int)", "android.graphics.drawable.Drawable"),
Loading