blob: 6e3be09b2ae9c425623e663ae8f5d219e67f85c5 [file] [log] [blame]
Winson Chung303e1ff2014-03-07 15:06:19 -08001/*
2 * Copyright (C) 2014 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
17package com.android.systemui.recents.model;
18
Winson59924fe2016-03-17 14:13:18 -070019import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
20import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
21import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
22import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
23import static android.view.WindowManager.DOCKED_BOTTOM;
24import static android.view.WindowManager.DOCKED_INVALID;
25import static android.view.WindowManager.DOCKED_LEFT;
26import static android.view.WindowManager.DOCKED_RIGHT;
27import static android.view.WindowManager.DOCKED_TOP;
28
Winson3e874742016-01-07 10:08:17 -080029import android.animation.Animator;
30import android.animation.AnimatorSet;
Winson882072b2015-10-12 11:26:33 -070031import android.animation.ObjectAnimator;
Winson3e874742016-01-07 10:08:17 -080032import android.animation.PropertyValuesHolder;
Winson99ef4582016-04-18 16:57:27 -070033import android.annotation.IntDef;
Winsone7f138c2015-10-22 16:15:21 -070034import android.content.ComponentName;
Winson35f30502015-09-28 11:24:36 -070035import android.content.Context;
Winson3e874742016-01-07 10:08:17 -080036import android.content.res.Configuration;
37import android.content.res.Resources;
Winson99ef4582016-04-18 16:57:27 -070038import android.graphics.Canvas;
Winson Chungec396d62014-08-06 17:08:00 -070039import android.graphics.Color;
Winson99ef4582016-04-18 16:57:27 -070040import android.graphics.Paint;
41import android.graphics.Point;
Winsonbe7607a2015-10-01 17:24:51 -070042import android.graphics.Rect;
43import android.graphics.RectF;
Winson882072b2015-10-12 11:26:33 -070044import android.graphics.drawable.ColorDrawable;
Winson55003902016-01-12 12:00:37 -080045import android.util.ArrayMap;
46import android.util.ArraySet;
Winson99ef4582016-04-18 16:57:27 -070047import android.util.IntProperty;
Winson Chung2b9ef652015-12-11 10:23:59 -050048import android.util.SparseArray;
Winson3e874742016-01-07 10:08:17 -080049import android.view.animation.Interpolator;
Winsonc0d70582016-01-29 10:24:39 -080050
Winson3e874742016-01-07 10:08:17 -080051import com.android.internal.policy.DockedDividerUtils;
Winson3f32e7e2016-04-20 17:18:08 -070052import com.android.systemui.Interpolators;
Winson2536c7e2015-10-01 15:49:31 -070053import com.android.systemui.R;
Winsone7f138c2015-10-22 16:15:21 -070054import com.android.systemui.recents.Recents;
Winsonc742f972015-11-12 11:32:21 -080055import com.android.systemui.recents.RecentsDebugFlags;
Winson Chungffa2ec62014-07-03 15:54:42 -070056import com.android.systemui.recents.misc.NamedCounter;
Winsone7f138c2015-10-22 16:15:21 -070057import com.android.systemui.recents.misc.SystemServicesProxy;
Winson Chunga0e88b52014-08-11 19:25:42 -070058import com.android.systemui.recents.misc.Utilities;
Winsonbe8e6962016-02-01 14:27:52 -080059import com.android.systemui.recents.views.AnimationProps;
Winson59924fe2016-03-17 14:13:18 -070060import com.android.systemui.recents.views.DropTarget;
61import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
Winson Chung303e1ff2014-03-07 15:06:19 -080062
Winsond72c3152016-04-05 15:33:35 -070063import java.io.PrintWriter;
Winson99ef4582016-04-18 16:57:27 -070064import java.lang.annotation.Retention;
65import java.lang.annotation.RetentionPolicy;
Winson Chung303e1ff2014-03-07 15:06:19 -080066import java.util.ArrayList;
Winson Chungffa2ec62014-07-03 15:54:42 -070067import java.util.Collections;
68import java.util.Comparator;
Winson Chung303e1ff2014-03-07 15:06:19 -080069import java.util.List;
Winson Chunga433fa92014-07-08 21:50:31 -070070import java.util.Random;
Winson Chung303e1ff2014-03-07 15:06:19 -080071
72
73/**
74 * An interface for a task filter to query whether a particular task should show in a stack.
75 */
76interface TaskFilter {
77 /** Returns whether the filter accepts the specified task */
Winson Chung2b9ef652015-12-11 10:23:59 -050078 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
Winson Chung303e1ff2014-03-07 15:06:19 -080079}
80
81/**
82 * A list of filtered tasks.
83 */
84class FilteredTaskList {
Winsoneca4ab62015-11-04 10:50:28 -080085
Winsoneca4ab62015-11-04 10:50:28 -080086 ArrayList<Task> mTasks = new ArrayList<>();
87 ArrayList<Task> mFilteredTasks = new ArrayList<>();
Winson55003902016-01-12 12:00:37 -080088 ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
Winson Chung303e1ff2014-03-07 15:06:19 -080089 TaskFilter mFilter;
90
91 /** Sets the task filter, saving the current touch state */
Winson Chungc6a16232014-04-01 14:04:48 -070092 boolean setFilter(TaskFilter filter) {
Winson55003902016-01-12 12:00:37 -080093 ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
Winson Chung303e1ff2014-03-07 15:06:19 -080094 mFilter = filter;
95 updateFilteredTasks();
Winson Chungb44c24f2014-04-09 15:17:43 -070096 if (!prevFilteredTasks.equals(mFilteredTasks)) {
97 return true;
98 } else {
Winson Chungb44c24f2014-04-09 15:17:43 -070099 return false;
100 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800101 }
102
103 /** Removes the task filter and returns the previous touch state */
104 void removeFilter() {
105 mFilter = null;
106 updateFilteredTasks();
107 }
108
109 /** Adds a new task to the task list */
110 void add(Task t) {
111 mTasks.add(t);
112 updateFilteredTasks();
113 }
114
Winsoneca4ab62015-11-04 10:50:28 -0800115 /**
116 * Moves the given task.
117 */
118 public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
119 int taskIndex = indexOf(task);
120 if (taskIndex != insertIndex) {
121 mTasks.remove(taskIndex);
122 if (taskIndex < insertIndex) {
123 insertIndex--;
124 }
125 mTasks.add(insertIndex, task);
126 }
127
128 // Update the stack id now, after we've moved the task, and before we update the
129 // filtered tasks
130 task.setStackId(newStackId);
131 updateFilteredTasks();
132 }
133
Winson Chung303e1ff2014-03-07 15:06:19 -0800134 /** Sets the list of tasks */
135 void set(List<Task> tasks) {
136 mTasks.clear();
137 mTasks.addAll(tasks);
138 updateFilteredTasks();
139 }
140
141 /** Removes a task from the base list only if it is in the filtered list */
142 boolean remove(Task t) {
143 if (mFilteredTasks.contains(t)) {
144 boolean removed = mTasks.remove(t);
145 updateFilteredTasks();
146 return removed;
147 }
148 return false;
149 }
150
151 /** Returns the index of this task in the list of filtered tasks */
152 int indexOf(Task t) {
Winson23746d52015-12-03 16:13:07 -0800153 if (t != null && mTaskIndices.containsKey(t.key)) {
Winson Chunga4ccb862014-08-22 15:26:27 -0700154 return mTaskIndices.get(t.key);
155 }
156 return -1;
Winson Chung303e1ff2014-03-07 15:06:19 -0800157 }
158
159 /** Returns the size of the list of filtered tasks */
160 int size() {
161 return mFilteredTasks.size();
162 }
163
164 /** Returns whether the filtered list contains this task */
165 boolean contains(Task t) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700166 return mTaskIndices.containsKey(t.key);
Winson Chung303e1ff2014-03-07 15:06:19 -0800167 }
168
169 /** Updates the list of filtered tasks whenever the base task list changes */
170 private void updateFilteredTasks() {
171 mFilteredTasks.clear();
172 if (mFilter != null) {
Winson Chung2b9ef652015-12-11 10:23:59 -0500173 // Create a sparse array from task id to Task
174 SparseArray<Task> taskIdMap = new SparseArray<>();
Winson Chung303e1ff2014-03-07 15:06:19 -0800175 int taskCount = mTasks.size();
176 for (int i = 0; i < taskCount; i++) {
177 Task t = mTasks.get(i);
Winson Chung2b9ef652015-12-11 10:23:59 -0500178 taskIdMap.put(t.key.id, t);
179 }
180
181 for (int i = 0; i < taskCount; i++) {
182 Task t = mTasks.get(i);
183 if (mFilter.acceptTask(taskIdMap, t, i)) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800184 mFilteredTasks.add(t);
185 }
186 }
187 } else {
188 mFilteredTasks.addAll(mTasks);
189 }
Winson Chungffa2ec62014-07-03 15:54:42 -0700190 updateFilteredTaskIndices();
191 }
192
193 /** Updates the mapping of tasks to indices. */
194 private void updateFilteredTaskIndices() {
Winson Chungffa2ec62014-07-03 15:54:42 -0700195 int taskCount = mFilteredTasks.size();
Winson55003902016-01-12 12:00:37 -0800196 mTaskIndices.clear();
Winson Chungffa2ec62014-07-03 15:54:42 -0700197 for (int i = 0; i < taskCount; i++) {
198 Task t = mFilteredTasks.get(i);
199 mTaskIndices.put(t.key, i);
200 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800201 }
202
203 /** Returns whether this task list is filtered */
204 boolean hasFilter() {
205 return (mFilter != null);
206 }
207
208 /** Returns the list of filtered tasks */
209 ArrayList<Task> getTasks() {
210 return mFilteredTasks;
211 }
212}
213
214/**
215 * The task stack contains a list of multiple tasks.
216 */
217public class TaskStack {
Winson Chungffa2ec62014-07-03 15:54:42 -0700218
Winsond72c3152016-04-05 15:33:35 -0700219 private static final String TAG = "TaskStack";
220
Winson Chungffa2ec62014-07-03 15:54:42 -0700221 /** Task stack callbacks */
Winson Chung04dfe0d2014-03-14 14:06:29 -0700222 public interface TaskStackCallbacks {
Winson Chung06266772015-12-11 10:24:21 -0500223 /**
224 * Notifies when a new task has been added to the stack.
225 */
226 void onStackTaskAdded(TaskStack stack, Task newTask);
227
228 /**
229 * Notifies when a task has been removed from the stack.
230 */
Winson6c8217a2016-05-25 10:53:53 -0700231 void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
Winson Chungb5026902017-05-03 12:45:13 -0700232 AnimationProps animation, boolean fromDockGesture,
233 boolean dismissRecentsIfAllRemoved);
Winsona1ededd2016-03-25 12:23:12 -0700234
235 /**
Winson3b6ba1a2016-03-22 15:37:54 -0700236 * Notifies when all tasks have been removed from the stack.
237 */
238 void onStackTasksRemoved(TaskStack stack);
239
240 /**
Winsona1ededd2016-03-25 12:23:12 -0700241 * Notifies when tasks in the stack have been updated.
242 */
243 void onStackTasksUpdated(TaskStack stack);
Winson Chung04dfe0d2014-03-14 14:06:29 -0700244 }
245
Winson250608a2015-11-24 15:00:31 -0800246 /**
247 * The various possible dock states when dragging and dropping a task.
248 */
Winsonf0d1c442015-12-01 11:04:45 -0800249 public static class DockState implements DropTarget {
250
Manu Cornet47dd7c52017-01-10 13:26:25 -0800251 public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
252 public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
253
Winson99ef4582016-04-18 16:57:27 -0700254 // The rotation to apply to the hint text
255 @Retention(RetentionPolicy.SOURCE)
256 @IntDef({HORIZONTAL, VERTICAL})
257 public @interface TextOrientation {}
258 private static final int HORIZONTAL = 0;
259 private static final int VERTICAL = 1;
260
Winson3f32e7e2016-04-20 17:18:08 -0700261 private static final int DOCK_AREA_ALPHA = 80;
Winson99ef4582016-04-18 16:57:27 -0700262 public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
263 null, null, null);
Winson3e874742016-01-07 10:08:17 -0800264 public static final DockState LEFT = new DockState(DOCKED_LEFT,
Winson99ef4582016-04-18 16:57:27 -0700265 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
Winson3e874742016-01-07 10:08:17 -0800266 new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
267 new RectF(0, 0, 0.5f, 1));
268 public static final DockState TOP = new DockState(DOCKED_TOP,
Winson99ef4582016-04-18 16:57:27 -0700269 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
Winson3e874742016-01-07 10:08:17 -0800270 new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
271 new RectF(0, 0, 1, 0.5f));
272 public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
Winson99ef4582016-04-18 16:57:27 -0700273 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
Winson3e874742016-01-07 10:08:17 -0800274 new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
275 new RectF(0.5f, 0, 1, 1));
276 public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
Winson99ef4582016-04-18 16:57:27 -0700277 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
Winson3e874742016-01-07 10:08:17 -0800278 new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
279 new RectF(0, 0.5f, 1, 1));
Winsoneca4ab62015-11-04 10:50:28 -0800280
281 @Override
Winson08deff02016-08-05 13:58:31 -0700282 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
283 boolean isCurrentTarget) {
284 if (isCurrentTarget) {
285 getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
286 return mTmpRect.contains(x, y);
287 } else {
288 getMappedRect(touchArea, width, height, mTmpRect);
289 updateBoundsWithSystemInsets(mTmpRect, insets);
290 return mTmpRect.contains(x, y);
291 }
Winsoneca4ab62015-11-04 10:50:28 -0800292 }
Winsonbe7607a2015-10-01 17:24:51 -0700293
Winson882072b2015-10-12 11:26:33 -0700294 // Represents the view state of this dock state
Winson99ef4582016-04-18 16:57:27 -0700295 public static class ViewState {
296 private static final IntProperty<ViewState> HINT_ALPHA =
297 new IntProperty<ViewState>("drawableAlpha") {
298 @Override
299 public void setValue(ViewState object, int alpha) {
300 object.mHintTextAlpha = alpha;
301 object.dockAreaOverlay.invalidateSelf();
302 }
303
304 @Override
305 public Integer get(ViewState object) {
306 return object.mHintTextAlpha;
307 }
308 };
309
Winson882072b2015-10-12 11:26:33 -0700310 public final int dockAreaAlpha;
311 public final ColorDrawable dockAreaOverlay;
Winson99ef4582016-04-18 16:57:27 -0700312 public final int hintTextAlpha;
313 public final int hintTextOrientation;
Winson882072b2015-10-12 11:26:33 -0700314
Winson99ef4582016-04-18 16:57:27 -0700315 private final int mHintTextResId;
316 private String mHintText;
317 private Paint mHintTextPaint;
318 private Point mHintTextBounds = new Point();
319 private int mHintTextAlpha = 255;
320 private AnimatorSet mDockAreaOverlayAnimator;
321 private Rect mTmpRect = new Rect();
322
323 private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
324 int hintTextResId) {
325 dockAreaAlpha = areaAlpha;
Manu Cornet47dd7c52017-01-10 13:26:25 -0800326 dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
327 ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
Winson882072b2015-10-12 11:26:33 -0700328 dockAreaOverlay.setAlpha(0);
Winson99ef4582016-04-18 16:57:27 -0700329 hintTextAlpha = hintAlpha;
330 hintTextOrientation = hintOrientation;
331 mHintTextResId = hintTextResId;
332 mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
333 mHintTextPaint.setColor(Color.WHITE);
334 }
335
336 /**
337 * Updates the view state with the given context.
338 */
339 public void update(Context context) {
340 Resources res = context.getResources();
341 mHintText = context.getString(mHintTextResId);
342 mHintTextPaint.setTextSize(res.getDimensionPixelSize(
343 R.dimen.recents_drag_hint_text_size));
344 mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
345 mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
346 }
347
348 /**
349 * Draws the current view state.
350 */
351 public void draw(Canvas canvas) {
352 // Draw the overlay background
353 if (dockAreaOverlay.getAlpha() > 0) {
354 dockAreaOverlay.draw(canvas);
355 }
356
357 // Draw the hint text
358 if (mHintTextAlpha > 0) {
359 Rect bounds = dockAreaOverlay.getBounds();
360 int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
361 int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
362 mHintTextPaint.setAlpha(mHintTextAlpha);
363 if (hintTextOrientation == VERTICAL) {
364 canvas.save();
365 canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
366 }
367 canvas.drawText(mHintText, x, y, mHintTextPaint);
368 if (hintTextOrientation == VERTICAL) {
369 canvas.restore();
370 }
371 }
Winson882072b2015-10-12 11:26:33 -0700372 }
373
374 /**
Winson3e874742016-01-07 10:08:17 -0800375 * Creates a new bounds and alpha animation.
Winson882072b2015-10-12 11:26:33 -0700376 */
Winson99ef4582016-04-18 16:57:27 -0700377 public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
Winson3e874742016-01-07 10:08:17 -0800378 Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
Winson99ef4582016-04-18 16:57:27 -0700379 if (mDockAreaOverlayAnimator != null) {
380 mDockAreaOverlayAnimator.cancel();
Winson3e874742016-01-07 10:08:17 -0800381 }
382
Winson3f32e7e2016-04-20 17:18:08 -0700383 ObjectAnimator anim;
Winson3e874742016-01-07 10:08:17 -0800384 ArrayList<Animator> animators = new ArrayList<>();
Winson99ef4582016-04-18 16:57:27 -0700385 if (dockAreaOverlay.getAlpha() != areaAlpha) {
Winson3e874742016-01-07 10:08:17 -0800386 if (animateAlpha) {
Winson3f32e7e2016-04-20 17:18:08 -0700387 anim = ObjectAnimator.ofInt(dockAreaOverlay,
388 Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
389 anim.setDuration(duration);
390 anim.setInterpolator(interpolator);
391 animators.add(anim);
Winson3e874742016-01-07 10:08:17 -0800392 } else {
Winson99ef4582016-04-18 16:57:27 -0700393 dockAreaOverlay.setAlpha(areaAlpha);
394 }
395 }
396 if (mHintTextAlpha != hintAlpha) {
397 if (animateAlpha) {
Winson3f32e7e2016-04-20 17:18:08 -0700398 anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
399 hintAlpha);
400 anim.setDuration(150);
401 anim.setInterpolator(hintAlpha > mHintTextAlpha
402 ? Interpolators.ALPHA_IN
403 : Interpolators.ALPHA_OUT);
404 animators.add(anim);
Winson99ef4582016-04-18 16:57:27 -0700405 } else {
406 mHintTextAlpha = hintAlpha;
407 dockAreaOverlay.invalidateSelf();
Winson882072b2015-10-12 11:26:33 -0700408 }
Winson3e874742016-01-07 10:08:17 -0800409 }
410 if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
411 if (animateBounds) {
412 PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
Winson67c79572016-04-13 14:02:18 -0700413 Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
Winson3f32e7e2016-04-20 17:18:08 -0700414 new Rect(dockAreaOverlay.getBounds()), bounds);
415 anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
416 anim.setDuration(duration);
417 anim.setInterpolator(interpolator);
418 animators.add(anim);
Winson3e874742016-01-07 10:08:17 -0800419 } else {
420 dockAreaOverlay.setBounds(bounds);
421 }
422 }
423 if (!animators.isEmpty()) {
Winson99ef4582016-04-18 16:57:27 -0700424 mDockAreaOverlayAnimator = new AnimatorSet();
425 mDockAreaOverlayAnimator.playTogether(animators);
Winson99ef4582016-04-18 16:57:27 -0700426 mDockAreaOverlayAnimator.start();
Winson882072b2015-10-12 11:26:33 -0700427 }
428 }
429 }
430
Winson3e874742016-01-07 10:08:17 -0800431 public final int dockSide;
Winsonbe7607a2015-10-01 17:24:51 -0700432 public final int createMode;
Winson882072b2015-10-12 11:26:33 -0700433 public final ViewState viewState;
Winson882072b2015-10-12 11:26:33 -0700434 private final RectF touchArea;
Winson3e874742016-01-07 10:08:17 -0800435 private final RectF dockArea;
436 private final RectF expandedTouchDockArea;
Winson08deff02016-08-05 13:58:31 -0700437 private static final Rect mTmpRect = new Rect();
Winsonbe7607a2015-10-01 17:24:51 -0700438
439 /**
440 * @param createMode used to pass to ActivityManager to dock the task
441 * @param touchArea the area in which touch will initiate this dock state
Winsoneca4ab62015-11-04 10:50:28 -0800442 * @param dockArea the visible dock area
Manu Cornet47dd7c52017-01-10 13:26:25 -0800443 * @param expandedTouchDockArea the area in which touch will continue to dock after entering
Winson3e874742016-01-07 10:08:17 -0800444 * the initial touch area. This is also the new dock area to
445 * draw.
Winsonbe7607a2015-10-01 17:24:51 -0700446 */
Winson99ef4582016-04-18 16:57:27 -0700447 DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
448 @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
Winson3e874742016-01-07 10:08:17 -0800449 RectF expandedTouchDockArea) {
450 this.dockSide = dockSide;
Winsonbe7607a2015-10-01 17:24:51 -0700451 this.createMode = createMode;
Winson99ef4582016-04-18 16:57:27 -0700452 this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
453 R.string.recents_drag_hint_message);
Winsonbe7607a2015-10-01 17:24:51 -0700454 this.dockArea = dockArea;
Winson882072b2015-10-12 11:26:33 -0700455 this.touchArea = touchArea;
Winson3e874742016-01-07 10:08:17 -0800456 this.expandedTouchDockArea = expandedTouchDockArea;
Winsonbe7607a2015-10-01 17:24:51 -0700457 }
458
459 /**
Winson99ef4582016-04-18 16:57:27 -0700460 * Updates the dock state with the given context.
461 */
462 public void update(Context context) {
463 viewState.update(context);
464 }
465
466 /**
Winsonbe7607a2015-10-01 17:24:51 -0700467 * Returns the docked task bounds with the given {@param width} and {@param height}.
468 */
Winson08deff02016-08-05 13:58:31 -0700469 public Rect getPreDockedBounds(int width, int height, Rect insets) {
470 getMappedRect(dockArea, width, height, mTmpRect);
471 return updateBoundsWithSystemInsets(mTmpRect, insets);
Winsonbe7607a2015-10-01 17:24:51 -0700472 }
Winson3e874742016-01-07 10:08:17 -0800473
474 /**
475 * Returns the expanded docked task bounds with the given {@param width} and
476 * {@param height}.
477 */
478 public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
479 Resources res) {
480 // Calculate the docked task bounds
481 boolean isHorizontalDivision =
482 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
483 int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
484 insets, width, height, dividerSize);
485 Rect newWindowBounds = new Rect();
486 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
487 width, height, dividerSize);
488 return newWindowBounds;
489 }
490
491 /**
492 * Returns the task stack bounds with the given {@param width} and
493 * {@param height}.
494 */
Winsonfc48b072016-04-21 11:20:11 -0700495 public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
496 int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
497 Resources res, Rect windowRectOut) {
Winson3e874742016-01-07 10:08:17 -0800498 // Calculate the inverse docked task bounds
499 boolean isHorizontalDivision =
500 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
501 int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
502 insets, width, height, dividerSize);
Winson3e874742016-01-07 10:08:17 -0800503 DockedDividerUtils.calculateBoundsForPosition(position,
Winson59924fe2016-03-17 14:13:18 -0700504 DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
Winson3e874742016-01-07 10:08:17 -0800505 dividerSize);
506
507 // Calculate the task stack bounds from the new window bounds
Winson3e874742016-01-07 10:08:17 -0800508 Rect taskStackBounds = new Rect();
Winsond9529612016-01-28 13:29:49 -0800509 // If the task stack bounds is specifically under the dock area, then ignore the top
510 // inset
511 int top = dockArea.bottom < 1f
512 ? 0
513 : insets.top;
Winson08deff02016-08-05 13:58:31 -0700514 // For now, ignore the left insets since we always dock on the left and show Recents
515 // on the right
516 layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
517 taskStackBounds);
Winson3e874742016-01-07 10:08:17 -0800518 return taskStackBounds;
519 }
Winson08deff02016-08-05 13:58:31 -0700520
521 /**
522 * Returns the expanded bounds in certain dock sides such that the bounds account for the
523 * system insets (namely the vertical nav bar). This call modifies and returns the given
524 * {@param bounds}.
525 */
526 private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
527 if (dockSide == DOCKED_LEFT) {
528 bounds.right += insets.left;
529 } else if (dockSide == DOCKED_RIGHT) {
530 bounds.left -= insets.right;
531 }
532 return bounds;
533 }
534
535 /**
536 * Returns the mapped rect to the given dimensions.
537 */
538 private void getMappedRect(RectF bounds, int width, int height, Rect out) {
539 out.set((int) (bounds.left * width), (int) (bounds.top * height),
540 (int) (bounds.right * width), (int) (bounds.bottom * height));
541 }
Winsonbe7607a2015-10-01 17:24:51 -0700542 }
543
Winson6976f7b2016-05-03 14:58:12 -0700544 // A comparator that sorts tasks by their freeform state
545 private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
Winson Chung509d0d02015-12-16 15:43:12 -0500546 @Override
547 public int compare(Task o1, Task o2) {
548 if (o1.isFreeformTask() && !o2.isFreeformTask()) {
549 return 1;
550 } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
551 return -1;
552 }
Winson6976f7b2016-05-03 14:58:12 -0700553 return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
Winson Chung509d0d02015-12-16 15:43:12 -0500554 }
555 };
556
557
Winson Chung083baf92014-07-11 10:32:42 -0700558 // The task offset to apply to a task id as a group affiliation
559 static final int IndividualTaskIdOffset = 1 << 16;
560
Winson Chung06266772015-12-11 10:24:21 -0500561 ArrayList<Task> mRawTaskList = new ArrayList<>();
Winson250608a2015-11-24 15:00:31 -0800562 FilteredTaskList mStackTaskList = new FilteredTaskList();
Winson Chung303e1ff2014-03-07 15:06:19 -0800563 TaskStackCallbacks mCb;
564
Winson8f0e3a62015-11-23 09:15:08 -0800565 ArrayList<TaskGrouping> mGroups = new ArrayList<>();
Winson55003902016-01-12 12:00:37 -0800566 ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
Winson8f0e3a62015-11-23 09:15:08 -0800567
568 public TaskStack() {
569 // Ensure that we only show non-docked tasks
Winson250608a2015-11-24 15:00:31 -0800570 mStackTaskList.setFilter(new TaskFilter() {
Winson8f0e3a62015-11-23 09:15:08 -0800571 @Override
Winson Chung2b9ef652015-12-11 10:23:59 -0500572 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
Winsond8f74312016-03-23 20:39:24 -0700573 if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
574 if (t.isAffiliatedTask()) {
575 // If this task is affiliated with another parent in the stack, then the
576 // historical state of this task depends on the state of the parent task
577 Task parentTask = taskIdMap.get(t.affiliationTaskId);
578 if (parentTask != null) {
579 t = parentTask;
580 }
Winson Chung2b9ef652015-12-11 10:23:59 -0500581 }
582 }
Winson8f6ee482016-03-18 17:51:48 -0700583 return t.isStackTask;
Winson8f0e3a62015-11-23 09:15:08 -0800584 }
585 });
586 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800587
Winson Chungd16c5652015-01-26 16:11:07 -0800588 /** Sets the callbacks for this task stack. */
Winson Chung303e1ff2014-03-07 15:06:19 -0800589 public void setCallbacks(TaskStackCallbacks cb) {
590 mCb = cb;
591 }
592
Winsoneca4ab62015-11-04 10:50:28 -0800593 /**
594 * Moves the given task to either the front of the freeform workspace or the stack.
595 */
596 public void moveTaskToStack(Task task, int newStackId) {
597 // Find the index to insert into
Winson250608a2015-11-24 15:00:31 -0800598 ArrayList<Task> taskList = mStackTaskList.getTasks();
Winsoneca4ab62015-11-04 10:50:28 -0800599 int taskCount = taskList.size();
600 if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
601 // Insert freeform tasks at the front
Winson250608a2015-11-24 15:00:31 -0800602 mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
Winsoneca4ab62015-11-04 10:50:28 -0800603 } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
604 // Insert after the first stacked task
605 int insertIndex = 0;
606 for (int i = taskCount - 1; i >= 0; i--) {
607 if (!taskList.get(i).isFreeformTask()) {
608 insertIndex = i + 1;
609 break;
610 }
611 }
Winson250608a2015-11-24 15:00:31 -0800612 mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
Winsoneca4ab62015-11-04 10:50:28 -0800613 }
614 }
615
Winson Chung6ac8bd62015-01-07 16:38:35 -0800616 /** Does the actual work associated with removing the task. */
Winson250608a2015-11-24 15:00:31 -0800617 void removeTaskImpl(FilteredTaskList taskList, Task t) {
Winson Chung6ac8bd62015-01-07 16:38:35 -0800618 // Remove the task from the list
Winson250608a2015-11-24 15:00:31 -0800619 taskList.remove(t);
Winson Chung6ac8bd62015-01-07 16:38:35 -0800620 // Remove it from the group as well, and if it is empty, remove the group
621 TaskGrouping group = t.group;
Winson Chung4e5fb2f2015-12-15 14:46:23 -0500622 if (group != null) {
623 group.removeTask(t);
624 if (group.getTaskCount() == 0) {
625 removeGroup(group);
626 }
Winson Chung6ac8bd62015-01-07 16:38:35 -0800627 }
Winson Chung6ac8bd62015-01-07 16:38:35 -0800628 }
629
Winson8aa99592016-01-19 15:07:07 -0800630 /**
631 * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
632 * how they should update themselves.
633 */
Winson20684082016-03-16 17:13:34 -0700634 public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
Winson Chungb5026902017-05-03 12:45:13 -0700635 removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
636 }
637
638 /**
639 * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
640 * how they should update themselves.
641 */
642 public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
643 boolean dismissRecentsIfAllRemoved) {
Winson250608a2015-11-24 15:00:31 -0800644 if (mStackTaskList.contains(t)) {
Winson250608a2015-11-24 15:00:31 -0800645 removeTaskImpl(mStackTaskList, t);
Winson35a8b042016-01-22 09:41:09 -0800646 Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
Winson Chung303e1ff2014-03-07 15:06:19 -0800647 if (mCb != null) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700648 // Notify that a task has been removed
Winson6c8217a2016-05-25 10:53:53 -0700649 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
Winson Chungb5026902017-05-03 12:45:13 -0700650 fromDockGesture, dismissRecentsIfAllRemoved);
Winson Chung303e1ff2014-03-07 15:06:19 -0800651 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800652 }
Winson3e874742016-01-07 10:08:17 -0800653 mRawTaskList.remove(t);
Winson250608a2015-11-24 15:00:31 -0800654 }
655
656 /**
Winson3b6ba1a2016-03-22 15:37:54 -0700657 * Removes all tasks from the stack.
658 */
Winson Chungb5026902017-05-03 12:45:13 -0700659 public void removeAllTasks(boolean notifyStackChanges) {
Winson3b6ba1a2016-03-22 15:37:54 -0700660 ArrayList<Task> tasks = mStackTaskList.getTasks();
661 for (int i = tasks.size() - 1; i >= 0; i--) {
662 Task t = tasks.get(i);
663 removeTaskImpl(mStackTaskList, t);
664 mRawTaskList.remove(t);
665 }
Winson Chungb5026902017-05-03 12:45:13 -0700666 if (mCb != null && notifyStackChanges) {
Winson3b6ba1a2016-03-22 15:37:54 -0700667 // Notify that all tasks have been removed
668 mCb.onStackTasksRemoved(this);
669 }
670 }
671
Winson Chungb5026902017-05-03 12:45:13 -0700672
673 /**
674 * @see #setTasks(Context, List, boolean, boolean)
675 */
676 public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
677 setTasks(context, stack.mRawTaskList, notifyStackChanges);
678 }
679
Winson3b6ba1a2016-03-22 15:37:54 -0700680 /**
Winson250608a2015-11-24 15:00:31 -0800681 * Sets a few tasks in one go, without calling any callbacks.
Winson Chung06266772015-12-11 10:24:21 -0500682 *
683 * @param tasks the new set of tasks to replace the current set.
684 * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
Winson250608a2015-11-24 15:00:31 -0800685 */
Winson88737542016-02-17 13:27:33 -0800686 public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
Winson Chung06266772015-12-11 10:24:21 -0500687 // Compute a has set for each of the tasks
Winson55003902016-01-12 12:00:37 -0800688 ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
689 ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
Winson88737542016-02-17 13:27:33 -0800690 ArrayList<Task> addedTasks = new ArrayList<>();
Winson6c8217a2016-05-25 10:53:53 -0700691 ArrayList<Task> removedTasks = new ArrayList<>();
Winson88737542016-02-17 13:27:33 -0800692 ArrayList<Task> allTasks = new ArrayList<>();
Winson Chung06266772015-12-11 10:24:21 -0500693
694 // Disable notifications if there are no callbacks
695 if (mCb == null) {
696 notifyStackChanges = false;
697 }
698
699 // Remove any tasks that no longer exist
700 int taskCount = mRawTaskList.size();
Winson88737542016-02-17 13:27:33 -0800701 for (int i = taskCount - 1; i >= 0; i--) {
Winson Chung06266772015-12-11 10:24:21 -0500702 Task task = mRawTaskList.get(i);
703 if (!newTasksMap.containsKey(task.key)) {
704 if (notifyStackChanges) {
Winson6c8217a2016-05-25 10:53:53 -0700705 removedTasks.add(task);
Winson Chung06266772015-12-11 10:24:21 -0500706 }
Winson Chung06266772015-12-11 10:24:21 -0500707 }
Winson Chung97567552015-12-16 17:07:19 -0500708 task.setGroup(null);
Winson Chung06266772015-12-11 10:24:21 -0500709 }
710
711 // Add any new tasks
712 taskCount = tasks.size();
713 for (int i = 0; i < taskCount; i++) {
Winson88737542016-02-17 13:27:33 -0800714 Task newTask = tasks.get(i);
715 Task currentTask = currentTasksMap.get(newTask.key);
716 if (currentTask == null && notifyStackChanges) {
717 addedTasks.add(newTask);
718 } else if (currentTask != null) {
719 // The current task has bound callbacks, so just copy the data from the new task
720 // state and add it back into the list
721 currentTask.copyFrom(newTask);
722 newTask = currentTask;
Winson Chung06266772015-12-11 10:24:21 -0500723 }
Winson88737542016-02-17 13:27:33 -0800724 allTasks.add(newTask);
Winson Chung06266772015-12-11 10:24:21 -0500725 }
726
727 // Sort all the tasks to ensure they are ordered correctly
Winson6976f7b2016-05-03 14:58:12 -0700728 for (int i = allTasks.size() - 1; i >= 0; i--) {
729 allTasks.get(i).temporarySortIndexInStack = i;
730 }
731 Collections.sort(allTasks, FREEFORM_COMPARATOR);
Winson Chung06266772015-12-11 10:24:21 -0500732
Winson8f6ee482016-03-18 17:51:48 -0700733 mStackTaskList.set(allTasks);
Winson88737542016-02-17 13:27:33 -0800734 mRawTaskList = allTasks;
735
Winsona1ededd2016-03-25 12:23:12 -0700736 // Update the affiliated groupings
737 createAffiliatedGroupings(context);
738
Winson6c8217a2016-05-25 10:53:53 -0700739 // Only callback for the removed tasks after the stack has updated
740 int removedTaskCount = removedTasks.size();
741 Task newFrontMostTask = getStackFrontMostTask(false);
742 for (int i = 0; i < removedTaskCount; i++) {
743 mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
Winson Chungb5026902017-05-03 12:45:13 -0700744 AnimationProps.IMMEDIATE, false /* fromDockGesture */,
745 true /* dismissRecentsIfAllRemoved */);
Winson6c8217a2016-05-25 10:53:53 -0700746 }
747
Winson88737542016-02-17 13:27:33 -0800748 // Only callback for the newly added tasks after this stack has been updated
749 int addedTaskCount = addedTasks.size();
750 for (int i = 0; i < addedTaskCount; i++) {
751 mCb.onStackTaskAdded(this, addedTasks.get(i));
752 }
753
Winsona1ededd2016-03-25 12:23:12 -0700754 // Notify that the task stack has been updated
755 if (notifyStackChanges) {
756 mCb.onStackTasksUpdated(this);
757 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800758 }
759
Winson Chung931c51f2015-12-17 17:08:55 -0500760 /**
761 * Gets the front-most task in the stack.
762 */
Winson35a8b042016-01-22 09:41:09 -0800763 public Task getStackFrontMostTask(boolean includeFreeformTasks) {
Winson Chung931c51f2015-12-17 17:08:55 -0500764 ArrayList<Task> stackTasks = mStackTaskList.getTasks();
765 if (stackTasks.isEmpty()) {
766 return null;
767 }
768 for (int i = stackTasks.size() - 1; i >= 0; i--) {
769 Task task = stackTasks.get(i);
Winson35a8b042016-01-22 09:41:09 -0800770 if (!task.isFreeformTask() || includeFreeformTasks) {
Winson Chung931c51f2015-12-17 17:08:55 -0500771 return task;
772 }
773 }
774 return null;
Winson Chungffa2ec62014-07-03 15:54:42 -0700775 }
776
Winson Chung04400672014-10-17 14:53:30 -0700777 /** Gets the task keys */
778 public ArrayList<Task.TaskKey> getTaskKeys() {
Winson8f0e3a62015-11-23 09:15:08 -0800779 ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
Winson250608a2015-11-24 15:00:31 -0800780 ArrayList<Task> tasks = computeAllTasksList();
Winson Chung04400672014-10-17 14:53:30 -0700781 int taskCount = tasks.size();
782 for (int i = 0; i < taskCount; i++) {
Winson250608a2015-11-24 15:00:31 -0800783 Task task = tasks.get(i);
784 taskKeys.add(task.key);
Winson Chung04400672014-10-17 14:53:30 -0700785 }
786 return taskKeys;
787 }
788
Winson250608a2015-11-24 15:00:31 -0800789 /**
790 * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
791 */
792 public ArrayList<Task> getStackTasks() {
793 return mStackTaskList.getTasks();
Winson Chung303e1ff2014-03-07 15:06:19 -0800794 }
795
Winson36a5a2c2015-10-29 18:04:39 -0700796 /**
Winsonf24f2162016-01-05 12:11:55 -0800797 * Returns the set of "freeform" tasks in the stack.
798 */
799 public ArrayList<Task> getFreeformTasks() {
800 ArrayList<Task> freeformTasks = new ArrayList<>();
801 ArrayList<Task> tasks = mStackTaskList.getTasks();
802 int taskCount = tasks.size();
803 for (int i = 0; i < taskCount; i++) {
804 Task task = tasks.get(i);
805 if (task.isFreeformTask()) {
806 freeformTasks.add(task);
807 }
808 }
809 return freeformTasks;
810 }
811
812 /**
Winson6976f7b2016-05-03 14:58:12 -0700813 * Computes a set of all the active and historical tasks.
Winson250608a2015-11-24 15:00:31 -0800814 */
815 public ArrayList<Task> computeAllTasksList() {
816 ArrayList<Task> tasks = new ArrayList<>();
817 tasks.addAll(mStackTaskList.getTasks());
Winson250608a2015-11-24 15:00:31 -0800818 return tasks;
819 }
820
821 /**
Winson4b057c62016-01-12 15:01:52 -0800822 * Returns the number of stack and freeform tasks.
Winson250608a2015-11-24 15:00:31 -0800823 */
Winson4b057c62016-01-12 15:01:52 -0800824 public int getTaskCount() {
Winson250608a2015-11-24 15:00:31 -0800825 return mStackTaskList.size();
826 }
827
828 /**
Winson4b057c62016-01-12 15:01:52 -0800829 * Returns the number of stack tasks.
Winsonf0d1c442015-12-01 11:04:45 -0800830 */
Winson4b057c62016-01-12 15:01:52 -0800831 public int getStackTaskCount() {
832 ArrayList<Task> tasks = mStackTaskList.getTasks();
833 int stackCount = 0;
834 int taskCount = tasks.size();
835 for (int i = 0; i < taskCount; i++) {
836 Task task = tasks.get(i);
837 if (!task.isFreeformTask()) {
838 stackCount++;
839 }
840 }
841 return stackCount;
842 }
843
844 /**
845 * Returns the number of freeform tasks.
846 */
847 public int getFreeformTaskCount() {
Winsonf0d1c442015-12-01 11:04:45 -0800848 ArrayList<Task> tasks = mStackTaskList.getTasks();
849 int freeformCount = 0;
850 int taskCount = tasks.size();
851 for (int i = 0; i < taskCount; i++) {
852 Task task = tasks.get(i);
853 if (task.isFreeformTask()) {
854 freeformCount++;
855 }
856 }
857 return freeformCount;
858 }
859
860 /**
Winson250608a2015-11-24 15:00:31 -0800861 * Returns the task in stack tasks which is the launch target.
Winson36a5a2c2015-10-29 18:04:39 -0700862 */
863 public Task getLaunchTarget() {
Winson250608a2015-11-24 15:00:31 -0800864 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson36a5a2c2015-10-29 18:04:39 -0700865 int taskCount = tasks.size();
866 for (int i = 0; i < taskCount; i++) {
867 Task task = tasks.get(i);
868 if (task.isLaunchTarget) {
869 return task;
870 }
871 }
872 return null;
873 }
874
Manu Cornetbf8e2902016-12-20 08:29:33 -0800875 /**
Winson Chungb5026902017-05-03 12:45:13 -0700876 * Returns whether the next launch target should actually be the PiP task.
877 */
878 public boolean isNextLaunchTargetPip(long lastPipTime) {
879 Task launchTarget = getLaunchTarget();
880 Task nextLaunchTarget = getNextLaunchTargetRaw();
881 if (nextLaunchTarget != null && lastPipTime > 0) {
882 // If the PiP time is more recent than the next launch target, then launch the PiP task
883 return lastPipTime > nextLaunchTarget.key.lastActiveTime;
884 } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
885 // Otherwise, if there is no next launch target, but there is a PiP, then launch
886 // the PiP task
887 return true;
888 }
889 return false;
890 }
891
892 /**
Manu Cornetbf8e2902016-12-20 08:29:33 -0800893 * Returns the task in stack tasks which should be launched next if Recents are toggled
Winson Chungb5026902017-05-03 12:45:13 -0700894 * again, or null if there is no task to be launched. Callers should check
895 * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
896 * stack.
Manu Cornetbf8e2902016-12-20 08:29:33 -0800897 */
898 public Task getNextLaunchTarget() {
Winson Chungb5026902017-05-03 12:45:13 -0700899 Task nextLaunchTarget = getNextLaunchTargetRaw();
900 if (nextLaunchTarget != null) {
901 return nextLaunchTarget;
902 }
903 return getStackTasks().get(getTaskCount() - 1);
904 }
905
906 private Task getNextLaunchTargetRaw() {
Manu Cornetbf8e2902016-12-20 08:29:33 -0800907 int taskCount = getTaskCount();
908 if (taskCount == 0) {
909 return null;
910 }
911 int launchTaskIndex = indexOfStackTask(getLaunchTarget());
Winson Chungb5026902017-05-03 12:45:13 -0700912 if (launchTaskIndex != -1 && launchTaskIndex > 0) {
913 return getStackTasks().get(launchTaskIndex - 1);
Manu Cornetbf8e2902016-12-20 08:29:33 -0800914 }
Winson Chungb5026902017-05-03 12:45:13 -0700915 return null;
Manu Cornetbf8e2902016-12-20 08:29:33 -0800916 }
917
Winson Chung303e1ff2014-03-07 15:06:19 -0800918 /** Returns the index of this task in this current task stack */
Winson250608a2015-11-24 15:00:31 -0800919 public int indexOfStackTask(Task t) {
920 return mStackTaskList.indexOf(t);
Winson Chung303e1ff2014-03-07 15:06:19 -0800921 }
922
Winson Chungb1f74992014-08-08 12:53:09 -0700923 /** Finds the task with the specified task id. */
924 public Task findTaskWithId(int taskId) {
Winson250608a2015-11-24 15:00:31 -0800925 ArrayList<Task> tasks = computeAllTasksList();
Winsond72c3152016-04-05 15:33:35 -0700926 int taskCount = tasks.size();
927 for (int i = 0; i < taskCount; i++) {
928 Task task = tasks.get(i);
Winson Chungb1f74992014-08-08 12:53:09 -0700929 if (task.key.id == taskId) {
930 return task;
931 }
932 }
933 return null;
934 }
935
Winson Chungffa2ec62014-07-03 15:54:42 -0700936 /******** Grouping ********/
937
938 /** Adds a group to the set */
939 public void addGroup(TaskGrouping group) {
940 mGroups.add(group);
941 mAffinitiesGroups.put(group.affiliation, group);
Winson Chungffa2ec62014-07-03 15:54:42 -0700942 }
943
944 public void removeGroup(TaskGrouping group) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700945 mGroups.remove(group);
946 mAffinitiesGroups.remove(group.affiliation);
Winson Chungffa2ec62014-07-03 15:54:42 -0700947 }
948
949 /** Returns the group with the specified affiliation. */
Winson Chung083baf92014-07-11 10:32:42 -0700950 public TaskGrouping getGroupWithAffiliation(int affiliation) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700951 return mAffinitiesGroups.get(affiliation);
952 }
953
Winson Chungffa2ec62014-07-03 15:54:42 -0700954 /**
Winson88737542016-02-17 13:27:33 -0800955 * Temporary: This method will simulate affiliation groups
Winson Chungffa2ec62014-07-03 15:54:42 -0700956 */
Winson88737542016-02-17 13:27:33 -0800957 void createAffiliatedGroupings(Context context) {
958 mGroups.clear();
959 mAffinitiesGroups.clear();
960
Winson6e6bd8772016-01-25 10:41:40 -0800961 if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
Winson55003902016-01-12 12:00:37 -0800962 ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
Winson Chungffa2ec62014-07-03 15:54:42 -0700963 // Sort all tasks by increasing firstActiveTime of the task
Winson250608a2015-11-24 15:00:31 -0800964 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson Chungffa2ec62014-07-03 15:54:42 -0700965 Collections.sort(tasks, new Comparator<Task>() {
966 @Override
967 public int compare(Task task, Task task2) {
Winson Chung16bc2a72015-12-15 10:27:04 -0500968 return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
Winson Chungffa2ec62014-07-03 15:54:42 -0700969 }
970 });
971 // Create groups when sequential packages are the same
972 NamedCounter counter = new NamedCounter("task-group", "");
973 int taskCount = tasks.size();
974 String prevPackage = "";
Winson Chung083baf92014-07-11 10:32:42 -0700975 int prevAffiliation = -1;
Winson Chunga433fa92014-07-08 21:50:31 -0700976 Random r = new Random();
Winson6e6bd8772016-01-25 10:41:40 -0800977 int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
Winson Chungffa2ec62014-07-03 15:54:42 -0700978 for (int i = 0; i < taskCount; i++) {
979 Task t = tasks.get(i);
Winsone7f138c2015-10-22 16:15:21 -0700980 String packageName = t.key.getComponent().getPackageName();
Winson Chunga433fa92014-07-08 21:50:31 -0700981 packageName = "pkg";
Winson Chungffa2ec62014-07-03 15:54:42 -0700982 TaskGrouping group;
Winson Chunga433fa92014-07-08 21:50:31 -0700983 if (packageName.equals(prevPackage) && groupCountDown > 0) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700984 group = getGroupWithAffiliation(prevAffiliation);
Winson Chunga433fa92014-07-08 21:50:31 -0700985 groupCountDown--;
Winson Chungffa2ec62014-07-03 15:54:42 -0700986 } else {
Winson Chung083baf92014-07-11 10:32:42 -0700987 int affiliation = IndividualTaskIdOffset + t.key.id;
Winson Chungffa2ec62014-07-03 15:54:42 -0700988 group = new TaskGrouping(affiliation);
989 addGroup(group);
990 prevAffiliation = affiliation;
991 prevPackage = packageName;
Winson6e6bd8772016-01-25 10:41:40 -0800992 groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
Winson Chungffa2ec62014-07-03 15:54:42 -0700993 }
994 group.addTask(t);
995 taskMap.put(t.key, t);
996 }
997 // Sort groups by increasing latestActiveTime of the group
998 Collections.sort(mGroups, new Comparator<TaskGrouping>() {
999 @Override
1000 public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
Winson Chung509d0d02015-12-16 15:43:12 -05001001 return Long.compare(taskGrouping.latestActiveTimeInGroup,
Winson Chungffa2ec62014-07-03 15:54:42 -07001002 taskGrouping2.latestActiveTimeInGroup);
1003 }
1004 });
Winson Chung2b9ef652015-12-11 10:23:59 -05001005 // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
1006 // of tasks
Winson Chungffa2ec62014-07-03 15:54:42 -07001007 int taskIndex = 0;
1008 int groupCount = mGroups.size();
1009 for (int i = 0; i < groupCount; i++) {
1010 TaskGrouping group = mGroups.get(i);
Winson Chung083baf92014-07-11 10:32:42 -07001011 Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
Winson Chungffa2ec62014-07-03 15:54:42 -07001012 @Override
1013 public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
Winson Chung509d0d02015-12-16 15:43:12 -05001014 return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
Winson Chungffa2ec62014-07-03 15:54:42 -07001015 }
1016 });
Winson Chung083baf92014-07-11 10:32:42 -07001017 ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
Winson Chungffa2ec62014-07-03 15:54:42 -07001018 int groupTaskCount = groupTasks.size();
1019 for (int j = 0; j < groupTaskCount; j++) {
1020 tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
1021 taskIndex++;
1022 }
1023 }
Winson250608a2015-11-24 15:00:31 -08001024 mStackTaskList.set(tasks);
Winson Chungffa2ec62014-07-03 15:54:42 -07001025 } else {
Winson Chung083baf92014-07-11 10:32:42 -07001026 // Create the task groups
Winson55003902016-01-12 12:00:37 -08001027 ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
Winson250608a2015-11-24 15:00:31 -08001028 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson Chungffa2ec62014-07-03 15:54:42 -07001029 int taskCount = tasks.size();
1030 for (int i = 0; i < taskCount; i++) {
1031 Task t = tasks.get(i);
Winson Chung083baf92014-07-11 10:32:42 -07001032 TaskGrouping group;
Winson65c851e2016-01-20 12:43:35 -08001033 if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
1034 int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
1035 IndividualTaskIdOffset + t.key.id;
1036 if (mAffinitiesGroups.containsKey(affiliation)) {
1037 group = getGroupWithAffiliation(affiliation);
1038 } else {
1039 group = new TaskGrouping(affiliation);
1040 addGroup(group);
1041 }
Winson Chung083baf92014-07-11 10:32:42 -07001042 } else {
Winson65c851e2016-01-20 12:43:35 -08001043 group = new TaskGrouping(t.key.id);
Winson Chung083baf92014-07-11 10:32:42 -07001044 addGroup(group);
1045 }
Winson Chungffa2ec62014-07-03 15:54:42 -07001046 group.addTask(t);
Winson Chungec396d62014-08-06 17:08:00 -07001047 tasksMap.put(t.key, t);
1048 }
1049 // Update the task colors for each of the groups
Winson35f30502015-09-28 11:24:36 -07001050 float minAlpha = context.getResources().getFloat(
1051 R.dimen.recents_task_affiliation_color_min_alpha_percentage);
Winson Chungec396d62014-08-06 17:08:00 -07001052 int taskGroupCount = mGroups.size();
1053 for (int i = 0; i < taskGroupCount; i++) {
1054 TaskGrouping group = mGroups.get(i);
1055 taskCount = group.getTaskCount();
1056 // Ignore the groups that only have one task
1057 if (taskCount <= 1) continue;
1058 // Calculate the group color distribution
Winson Chung296278a2015-12-17 12:09:02 -05001059 int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
Winson Chungec396d62014-08-06 17:08:00 -07001060 float alphaStep = (1f - minAlpha) / taskCount;
1061 float alpha = 1f;
1062 for (int j = 0; j < taskCount; j++) {
1063 Task t = tasksMap.get(group.mTaskKeys.get(j));
Winson Chunga0e88b52014-08-11 19:25:42 -07001064 t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
1065 alpha);
Winson Chungec396d62014-08-06 17:08:00 -07001066 alpha -= alphaStep;
1067 }
Winson Chungffa2ec62014-07-03 15:54:42 -07001068 }
1069 }
1070 }
1071
Winsone7f138c2015-10-22 16:15:21 -07001072 /**
1073 * Computes the components of tasks in this stack that have been removed as a result of a change
1074 * in the specified package.
1075 */
Winson55003902016-01-12 12:00:37 -08001076 public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
Winsone7f138c2015-10-22 16:15:21 -07001077 // Identify all the tasks that should be removed as a result of the package being removed.
1078 // Using a set to ensure that we callback once per unique component.
1079 SystemServicesProxy ssp = Recents.getSystemServices();
Winson55003902016-01-12 12:00:37 -08001080 ArraySet<ComponentName> existingComponents = new ArraySet<>();
1081 ArraySet<ComponentName> removedComponents = new ArraySet<>();
Winsone7f138c2015-10-22 16:15:21 -07001082 ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
Winsond72c3152016-04-05 15:33:35 -07001083 int taskKeyCount = taskKeys.size();
1084 for (int i = 0; i < taskKeyCount; i++) {
1085 Task.TaskKey t = taskKeys.get(i);
1086
Winsone7f138c2015-10-22 16:15:21 -07001087 // Skip if this doesn't apply to the current user
1088 if (t.userId != userId) continue;
1089
1090 ComponentName cn = t.getComponent();
1091 if (cn.getPackageName().equals(packageName)) {
1092 if (existingComponents.contains(cn)) {
1093 // If we know that the component still exists in the package, then skip
1094 continue;
1095 }
1096 if (ssp.getActivityInfo(cn, userId) != null) {
1097 existingComponents.add(cn);
1098 } else {
1099 removedComponents.add(cn);
1100 }
1101 }
1102 }
1103 return removedComponents;
1104 }
1105
Winson Chung303e1ff2014-03-07 15:06:19 -08001106 @Override
1107 public String toString() {
Winson88737542016-02-17 13:27:33 -08001108 String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
Winsond72c3152016-04-05 15:33:35 -07001109 ArrayList<Task> tasks = mStackTaskList.getTasks();
1110 int taskCount = tasks.size();
1111 for (int i = 0; i < taskCount; i++) {
1112 str += " " + tasks.get(i).toString() + "\n";
Winson250608a2015-11-24 15:00:31 -08001113 }
Winson Chung303e1ff2014-03-07 15:06:19 -08001114 return str;
1115 }
Winson Chung06266772015-12-11 10:24:21 -05001116
1117 /**
1118 * Given a list of tasks, returns a map of each task's key to the task.
1119 */
Winson55003902016-01-12 12:00:37 -08001120 private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
1121 ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
Winson Chung06266772015-12-11 10:24:21 -05001122 int taskCount = tasks.size();
1123 for (int i = 0; i < taskCount; i++) {
1124 Task task = tasks.get(i);
1125 map.put(task.key, task);
1126 }
1127 return map;
1128 }
Winsond72c3152016-04-05 15:33:35 -07001129
1130 public void dump(String prefix, PrintWriter writer) {
1131 String innerPrefix = prefix + " ";
1132
1133 writer.print(prefix); writer.print(TAG);
1134 writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
1135 writer.println();
1136 ArrayList<Task> tasks = mStackTaskList.getTasks();
1137 int taskCount = tasks.size();
1138 for (int i = 0; i < taskCount; i++) {
1139 tasks.get(i).dump(innerPrefix, writer);
1140 }
1141 }
Winson Chung06266772015-12-11 10:24:21 -05001142}