• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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