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