| /* |
| * 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.launcher3.apppairs |
| |
| import android.content.Context |
| import android.graphics.Canvas |
| import android.graphics.Rect |
| import android.util.AttributeSet |
| import android.view.Gravity |
| import android.widget.FrameLayout |
| import androidx.annotation.OpenForTesting |
| import com.android.launcher3.DeviceProfile |
| import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener |
| import com.android.launcher3.icons.BitmapInfo |
| import com.android.launcher3.model.data.AppPairInfo |
| import com.android.launcher3.util.Themes |
| import com.android.launcher3.views.ActivityContext |
| |
| /** |
| * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of |
| * two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title. |
| */ |
| @OpenForTesting |
| open class AppPairIconGraphic |
| @JvmOverloads |
| constructor(context: Context, attrs: AttributeSet? = null) : |
| FrameLayout(context, attrs), OnDeviceProfileChangeListener { |
| private val TAG = "AppPairIconGraphic" |
| |
| companion object { |
| /** Composes a drawable for this icon, consisting of a background and 2 app icons. */ |
| @JvmStatic |
| fun composeDrawable( |
| appPairInfo: AppPairInfo, |
| p: AppPairIconDrawingParams |
| ): AppPairIconDrawable { |
| // Generate new icons, using themed flag if needed. |
| val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0 |
| val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags) |
| val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags) |
| appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) |
| appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) |
| |
| // If icons are unlaunchable due to screen size, manually override disabled appearance. |
| // (otherwise, leave disabled state alone; icons will naturally inherit the app's state) |
| val (isApp1Launchable, isApp2Launchable) = appPairInfo.isLaunchable(p.context) |
| if (!isApp1Launchable) appIcon1.setIsDisabled(true) |
| if (!isApp2Launchable) appIcon2.setIsDisabled(true) |
| |
| // Create icon drawable. |
| val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2) |
| fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize) |
| |
| return fullIconDrawable |
| } |
| } |
| |
| private lateinit var parentIcon: AppPairIcon |
| private lateinit var drawParams: AppPairIconDrawingParams |
| lateinit var drawable: AppPairIconDrawable |
| |
| fun init(icon: AppPairIcon, container: Int) { |
| parentIcon = icon |
| drawParams = AppPairIconDrawingParams(context, container) |
| drawable = composeDrawable(icon.info, drawParams) |
| |
| // Center the drawable area in the larger icon canvas |
| val lp: LayoutParams = layoutParams as LayoutParams |
| lp.gravity = Gravity.CENTER_HORIZONTAL |
| lp.height = drawParams.iconSize |
| lp.width = drawParams.iconSize |
| layoutParams = lp |
| } |
| |
| override fun onAttachedToWindow() { |
| super.onAttachedToWindow() |
| getActivityContext().addOnDeviceProfileChangeListener(this) |
| } |
| |
| override fun onDetachedFromWindow() { |
| super.onDetachedFromWindow() |
| getActivityContext().removeOnDeviceProfileChangeListener(this) |
| } |
| |
| private fun getActivityContext(): ActivityContext { |
| return ActivityContext.lookupContext(context) |
| } |
| |
| /** When device profile changes, update orientation */ |
| override fun onDeviceProfileChanged(dp: DeviceProfile) { |
| drawParams.updateOrientation(dp) |
| redraw() |
| } |
| |
| /** |
| * When the icon is temporary moved to a different colored surface, update the background color. |
| * Calling this method with [null] reverts the icon back to its default color. |
| */ |
| fun onTemporaryContainerChange(newContainer: Int?) { |
| drawParams.updateBgColor(newContainer ?: parentIcon.container) |
| redraw() |
| } |
| |
| /** |
| * Gets this icon graphic's visual bounds, with respect to the parent icon's coordinate system. |
| */ |
| fun getIconBounds(outBounds: Rect) { |
| outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt()) |
| outBounds.offset( |
| // x-coordinate in parent's coordinate system |
| ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(), |
| // y-coordinate in parent's coordinate system |
| (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding) |
| .toInt() |
| ) |
| } |
| |
| /** Updates the icon drawable and redraws it */ |
| fun redraw() { |
| drawable = composeDrawable(parentIcon.info, drawParams) |
| invalidate() |
| } |
| |
| override fun dispatchDraw(canvas: Canvas) { |
| super.dispatchDraw(canvas) |
| drawable.draw(canvas) |
| } |
| } |