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.views.ActivityContext 31 32 /** 33 * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of 34 * two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title. 35 */ 36 @OpenForTesting 37 open class AppPairIconGraphic 38 @JvmOverloads 39 constructor(context: Context, attrs: AttributeSet? = null) : 40 FrameLayout(context, attrs), OnDeviceProfileChangeListener { 41 private val TAG = "AppPairIconGraphic" 42 43 companion object { 44 /** Composes a drawable for this icon, consisting of a background and 2 app icons. */ 45 @JvmStatic composeDrawablenull46 fun composeDrawable( 47 appPairInfo: AppPairInfo, 48 p: AppPairIconDrawingParams, 49 ): AppPairIconDrawable { 50 // Generate new icons, using themed flag since the icon is drawn on homescreen 51 val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, BitmapInfo.FLAG_THEMED) 52 val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, BitmapInfo.FLAG_THEMED) 53 appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) 54 appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) 55 56 // If icons are unlaunchable due to screen size, manually override disabled appearance. 57 // (otherwise, leave disabled state alone; icons will naturally inherit the app's state) 58 val (isApp1Launchable, isApp2Launchable) = appPairInfo.isLaunchable(p.context) 59 if (!isApp1Launchable) appIcon1.setIsDisabled(true) 60 if (!isApp2Launchable) appIcon2.setIsDisabled(true) 61 62 // Create icon drawable. 63 val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2) 64 fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize) 65 66 return fullIconDrawable 67 } 68 } 69 70 private lateinit var parentIcon: AppPairIcon 71 private lateinit var drawParams: AppPairIconDrawingParams 72 lateinit var drawable: AppPairIconDrawable 73 initnull74 fun init(icon: AppPairIcon, container: Int) { 75 parentIcon = icon 76 drawParams = AppPairIconDrawingParams(context, container) 77 drawable = composeDrawable(icon.info, drawParams) 78 79 // Center the drawable area in the larger icon canvas 80 val lp: LayoutParams = layoutParams as LayoutParams 81 lp.gravity = Gravity.CENTER_HORIZONTAL 82 lp.height = drawParams.iconSize 83 lp.width = drawParams.iconSize 84 layoutParams = lp 85 } 86 onAttachedToWindownull87 override fun onAttachedToWindow() { 88 super.onAttachedToWindow() 89 getActivityContext().addOnDeviceProfileChangeListener(this) 90 } 91 onDetachedFromWindownull92 override fun onDetachedFromWindow() { 93 super.onDetachedFromWindow() 94 getActivityContext().removeOnDeviceProfileChangeListener(this) 95 } 96 getActivityContextnull97 private fun getActivityContext(): ActivityContext { 98 return ActivityContext.lookupContext(context) 99 } 100 101 /** When device profile changes, update orientation */ onDeviceProfileChangednull102 override fun onDeviceProfileChanged(dp: DeviceProfile) { 103 drawParams.updateOrientation(dp) 104 redraw() 105 } 106 107 /** 108 * When the icon is temporary moved to a different colored surface, update the background color. 109 * Calling this method with [null] reverts the icon back to its default color. 110 */ onTemporaryContainerChangenull111 fun onTemporaryContainerChange(newContainer: Int?) { 112 drawParams.updateBgColor(newContainer ?: parentIcon.container) 113 redraw() 114 } 115 116 /** 117 * Gets this icon graphic's visual bounds, with respect to the parent icon's coordinate system. 118 */ getIconBoundsnull119 fun getIconBounds(outBounds: Rect) { 120 outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt()) 121 outBounds.offset( 122 // x-coordinate in parent's coordinate system 123 ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(), 124 // y-coordinate in parent's coordinate system 125 (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding) 126 .toInt(), 127 ) 128 } 129 130 /** Updates the icon drawable and redraws it */ redrawnull131 fun redraw() { 132 drawable = composeDrawable(parentIcon.info, drawParams) 133 invalidate() 134 } 135 dispatchDrawnull136 override fun dispatchDraw(canvas: Canvas) { 137 super.dispatchDraw(canvas) 138 drawable.draw(canvas) 139 } 140 141 /** Sets the scale of the icon background while hovered. */ setHoverScalenull142 fun setHoverScale(scale: Float) { 143 drawParams.hoverScale = scale 144 redraw() 145 } 146 147 /** Gets the scale of the icon background while hovered. */ getHoverScalenull148 fun getHoverScale(): Float { 149 return drawParams.hoverScale 150 } 151 } 152