1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.apppairs
18 
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Rect
22 import android.util.AttributeSet
23 import android.view.Gravity
24 import android.widget.FrameLayout
25 import androidx.annotation.OpenForTesting
26 import com.android.launcher3.DeviceProfile
27 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
28 import com.android.launcher3.icons.BitmapInfo
29 import com.android.launcher3.model.data.AppPairInfo
30 import com.android.launcher3.util.Themes
31 import com.android.launcher3.views.ActivityContext
32 
33 /**
34  * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
35  * two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
36  */
37 @OpenForTesting
38 open class AppPairIconGraphic
39 @JvmOverloads
40 constructor(context: Context, attrs: AttributeSet? = null) :
41     FrameLayout(context, attrs), OnDeviceProfileChangeListener {
42     private val TAG = "AppPairIconGraphic"
43 
44     companion object {
45         /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
46         @JvmStatic
composeDrawablenull47         fun composeDrawable(
48             appPairInfo: AppPairInfo,
49             p: AppPairIconDrawingParams
50         ): AppPairIconDrawable {
51             // Generate new icons, using themed flag if needed.
52             val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
53             val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
54             val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags)
55             appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
56             appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
57 
58             // If icons are unlaunchable due to screen size, manually override disabled appearance.
59             // (otherwise, leave disabled state alone; icons will naturally inherit the app's state)
60             val (isApp1Launchable, isApp2Launchable) = appPairInfo.isLaunchable(p.context)
61             if (!isApp1Launchable) appIcon1.setIsDisabled(true)
62             if (!isApp2Launchable) appIcon2.setIsDisabled(true)
63 
64             // Create icon drawable.
65             val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2)
66             fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize)
67 
68             return fullIconDrawable
69         }
70     }
71 
72     private lateinit var parentIcon: AppPairIcon
73     private lateinit var drawParams: AppPairIconDrawingParams
74     lateinit var drawable: AppPairIconDrawable
75 
initnull76     fun init(icon: AppPairIcon, container: Int) {
77         parentIcon = icon
78         drawParams = AppPairIconDrawingParams(context, container)
79         drawable = composeDrawable(icon.info, drawParams)
80 
81         // Center the drawable area in the larger icon canvas
82         val lp: LayoutParams = layoutParams as LayoutParams
83         lp.gravity = Gravity.CENTER_HORIZONTAL
84         lp.height = drawParams.iconSize
85         lp.width = drawParams.iconSize
86         layoutParams = lp
87     }
88 
onAttachedToWindownull89     override fun onAttachedToWindow() {
90         super.onAttachedToWindow()
91         getActivityContext().addOnDeviceProfileChangeListener(this)
92     }
93 
onDetachedFromWindownull94     override fun onDetachedFromWindow() {
95         super.onDetachedFromWindow()
96         getActivityContext().removeOnDeviceProfileChangeListener(this)
97     }
98 
getActivityContextnull99     private fun getActivityContext(): ActivityContext {
100         return ActivityContext.lookupContext(context)
101     }
102 
103     /** When device profile changes, update orientation */
onDeviceProfileChangednull104     override fun onDeviceProfileChanged(dp: DeviceProfile) {
105         drawParams.updateOrientation(dp)
106         redraw()
107     }
108 
109     /**
110      * When the icon is temporary moved to a different colored surface, update the background color.
111      * Calling this method with [null] reverts the icon back to its default color.
112      */
onTemporaryContainerChangenull113     fun onTemporaryContainerChange(newContainer: Int?) {
114         drawParams.updateBgColor(newContainer ?: parentIcon.container)
115         redraw()
116     }
117 
118     /**
119      * Gets this icon graphic's visual bounds, with respect to the parent icon's coordinate system.
120      */
getIconBoundsnull121     fun getIconBounds(outBounds: Rect) {
122         outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt())
123         outBounds.offset(
124             // x-coordinate in parent's coordinate system
125             ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(),
126             // y-coordinate in parent's coordinate system
127             (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding)
128                 .toInt()
129         )
130     }
131 
132     /** Updates the icon drawable and redraws it */
redrawnull133     fun redraw() {
134         drawable = composeDrawable(parentIcon.info, drawParams)
135         invalidate()
136     }
137 
dispatchDrawnull138     override fun dispatchDraw(canvas: Canvas) {
139         super.dispatchDraw(canvas)
140         drawable.draw(canvas)
141     }
142 }
143