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