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 package com.android.launcher3.taskbar; 17 18 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR; 19 20 import android.animation.Animator; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.drawable.Drawable; 28 import android.util.AttributeSet; 29 import android.view.View; 30 import android.widget.ImageView; 31 32 import androidx.annotation.ColorInt; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.constraintlayout.widget.ConstraintLayout; 36 37 import com.android.launcher3.R; 38 import com.android.launcher3.util.Preconditions; 39 import com.android.quickstep.util.BorderAnimator; 40 import com.android.systemui.shared.recents.model.Task; 41 import com.android.systemui.shared.recents.model.ThumbnailData; 42 43 import java.util.function.Consumer; 44 45 import kotlin.Unit; 46 47 /** 48 * A view that displays a recent task during a keyboard quick switch. 49 */ 50 public class KeyboardQuickSwitchTaskView extends ConstraintLayout { 51 52 private static final float THUMBNAIL_BLUR_RADIUS = 1f; 53 54 @ColorInt private final int mBorderColor; 55 56 @Nullable private BorderAnimator mBorderAnimator; 57 58 @Nullable private ImageView mThumbnailView1; 59 @Nullable private ImageView mThumbnailView2; 60 @Nullable private ImageView mIcon1; 61 @Nullable private ImageView mIcon2; 62 @Nullable private View mContent; 63 KeyboardQuickSwitchTaskView(@onNull Context context)64 public KeyboardQuickSwitchTaskView(@NonNull Context context) { 65 this(context, null); 66 } 67 KeyboardQuickSwitchTaskView(@onNull Context context, @Nullable AttributeSet attrs)68 public KeyboardQuickSwitchTaskView(@NonNull Context context, @Nullable AttributeSet attrs) { 69 this(context, attrs, 0); 70 } 71 KeyboardQuickSwitchTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)72 public KeyboardQuickSwitchTaskView( 73 @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 74 this(context, attrs, defStyleAttr, 0); 75 } 76 KeyboardQuickSwitchTaskView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)77 public KeyboardQuickSwitchTaskView( 78 @NonNull Context context, 79 @Nullable AttributeSet attrs, 80 int defStyleAttr, 81 int defStyleRes) { 82 super(context, attrs, defStyleAttr, defStyleRes); 83 TypedArray ta = context.obtainStyledAttributes( 84 attrs, R.styleable.TaskView, defStyleAttr, defStyleRes); 85 86 setWillNotDraw(false); 87 88 mBorderColor = ta.getColor( 89 R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR); 90 ta.recycle(); 91 } 92 93 @Override onFinishInflate()94 protected void onFinishInflate() { 95 super.onFinishInflate(); 96 mThumbnailView1 = findViewById(R.id.thumbnail_1); 97 mThumbnailView2 = findViewById(R.id.thumbnail_2); 98 mIcon1 = findViewById(R.id.icon_1); 99 mIcon2 = findViewById(R.id.icon_2); 100 mContent = findViewById(R.id.content); 101 102 Resources resources = mContext.getResources(); 103 104 Preconditions.assertNotNull(mContent); 105 mBorderAnimator = BorderAnimator.createScalingBorderAnimator( 106 /* borderRadiusPx= */ resources.getDimensionPixelSize( 107 R.dimen.keyboard_quick_switch_task_view_radius), 108 /* borderWidthPx= */ resources.getDimensionPixelSize( 109 R.dimen.keyboard_quick_switch_border_width), 110 /* boundsBuilder= */ bounds -> { 111 bounds.set(0, 0, getWidth(), getHeight()); 112 return Unit.INSTANCE; 113 }, 114 /* targetView= */ this, 115 /* contentView= */ mContent, 116 /* borderColor= */ mBorderColor); 117 } 118 119 @Nullable getFocusAnimator(boolean focused)120 protected Animator getFocusAnimator(boolean focused) { 121 return mBorderAnimator == null ? null : mBorderAnimator.buildAnimator(focused); 122 } 123 124 @Override draw(Canvas canvas)125 public void draw(Canvas canvas) { 126 super.draw(canvas); 127 if (mBorderAnimator != null) { 128 mBorderAnimator.drawBorder(canvas); 129 } 130 } 131 setThumbnails( @onNull Task task1, @Nullable Task task2, @Nullable ThumbnailUpdateFunction thumbnailUpdateFunction, @Nullable IconUpdateFunction iconUpdateFunction)132 protected void setThumbnails( 133 @NonNull Task task1, 134 @Nullable Task task2, 135 @Nullable ThumbnailUpdateFunction thumbnailUpdateFunction, 136 @Nullable IconUpdateFunction iconUpdateFunction) { 137 applyThumbnail(mThumbnailView1, task1, thumbnailUpdateFunction); 138 applyThumbnail(mThumbnailView2, task2, thumbnailUpdateFunction); 139 140 if (iconUpdateFunction == null) { 141 applyIcon(mIcon1, task1); 142 applyIcon(mIcon2, task2); 143 setContentDescription(task2 == null 144 ? task1.titleDescription 145 : getContext().getString( 146 R.string.quick_switch_split_task, 147 task1.titleDescription, 148 task2.titleDescription)); 149 return; 150 } 151 iconUpdateFunction.updateIconInBackground(task1, t -> { 152 applyIcon(mIcon1, task1); 153 if (task2 != null) { 154 return; 155 } 156 setContentDescription(task1.titleDescription); 157 }); 158 if (task2 == null) { 159 return; 160 } 161 iconUpdateFunction.updateIconInBackground(task2, t -> { 162 applyIcon(mIcon2, task2); 163 setContentDescription(getContext().getString( 164 R.string.quick_switch_split_task, 165 task1.titleDescription, 166 task2.titleDescription)); 167 }); 168 } 169 applyThumbnail( @ullable ImageView thumbnailView, @Nullable Task task, @Nullable ThumbnailUpdateFunction updateFunction)170 private void applyThumbnail( 171 @Nullable ImageView thumbnailView, 172 @Nullable Task task, 173 @Nullable ThumbnailUpdateFunction updateFunction) { 174 if (thumbnailView == null || task == null) { 175 return; 176 } 177 if (updateFunction == null) { 178 applyThumbnail(thumbnailView, task.colorBackground, task.thumbnail); 179 return; 180 } 181 updateFunction.updateThumbnailInBackground(task, thumbnailData -> 182 applyThumbnail(thumbnailView, task.colorBackground, thumbnailData)); 183 } 184 applyThumbnail( @onNull ImageView thumbnailView, @ColorInt int backgroundColor, @Nullable ThumbnailData thumbnailData)185 private void applyThumbnail( 186 @NonNull ImageView thumbnailView, 187 @ColorInt int backgroundColor, 188 @Nullable ThumbnailData thumbnailData) { 189 Bitmap bm = thumbnailData == null ? null : thumbnailData.getThumbnail(); 190 191 if (thumbnailView.getVisibility() != VISIBLE) { 192 thumbnailView.setVisibility(VISIBLE); 193 } 194 thumbnailView.getBackground().setTint(bm == null ? backgroundColor : Color.TRANSPARENT); 195 thumbnailView.setImageDrawable(new BlurredBitmapDrawable(bm, THUMBNAIL_BLUR_RADIUS)); 196 } 197 applyIcon(@ullable ImageView iconView, @Nullable Task task)198 private void applyIcon(@Nullable ImageView iconView, @Nullable Task task) { 199 if (iconView == null || task == null || task.icon == null) { 200 return; 201 } 202 Drawable.ConstantState constantState = task.icon.getConstantState(); 203 if (constantState == null) { 204 return; 205 } 206 if (iconView.getVisibility() != VISIBLE) { 207 iconView.setVisibility(VISIBLE); 208 } 209 // Use the bitmap directly since the drawable's scale can change 210 iconView.setImageDrawable( 211 constantState.newDrawable(getResources(), getContext().getTheme())); 212 } 213 214 protected interface ThumbnailUpdateFunction { 215 updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback)216 void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback); 217 } 218 219 protected interface IconUpdateFunction { 220 updateIconInBackground(Task task, Consumer<Task> callback)221 void updateIconInBackground(Task task, Consumer<Task> callback); 222 } 223 } 224