blob: 745f5a5a773e6a14c7f81d7288f4bdc90ce70130 [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,
232 AnimationProps animation, boolean fromDockGesture);
Winsona1ededd2016-03-25 12:23:12 -0700233
234 /**
Winson3b6ba1a2016-03-22 15:37:54 -0700235 * Notifies when all tasks have been removed from the stack.
236 */
237 void onStackTasksRemoved(TaskStack stack);
238
239 /**
Winsona1ededd2016-03-25 12:23:12 -0700240 * Notifies when tasks in the stack have been updated.
241 */
242 void onStackTasksUpdated(TaskStack stack);
Winson Chung04dfe0d2014-03-14 14:06:29 -0700243 }
244
Winson250608a2015-11-24 15:00:31 -0800245 /**
246 * The various possible dock states when dragging and dropping a task.
247 */
Winsonf0d1c442015-12-01 11:04:45 -0800248 public static class DockState implements DropTarget {
249
Winson99ef4582016-04-18 16:57:27 -0700250 // The rotation to apply to the hint text
251 @Retention(RetentionPolicy.SOURCE)
252 @IntDef({HORIZONTAL, VERTICAL})
253 public @interface TextOrientation {}
254 private static final int HORIZONTAL = 0;
255 private static final int VERTICAL = 1;
256
Winson3f32e7e2016-04-20 17:18:08 -0700257 private static final int DOCK_AREA_ALPHA = 80;
Winson99ef4582016-04-18 16:57:27 -0700258 public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
259 null, null, null);
Winson3e874742016-01-07 10:08:17 -0800260 public static final DockState LEFT = new DockState(DOCKED_LEFT,
Winson99ef4582016-04-18 16:57:27 -0700261 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
Winson3e874742016-01-07 10:08:17 -0800262 new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
263 new RectF(0, 0, 0.5f, 1));
264 public static final DockState TOP = new DockState(DOCKED_TOP,
Winson99ef4582016-04-18 16:57:27 -0700265 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
Winson3e874742016-01-07 10:08:17 -0800266 new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
267 new RectF(0, 0, 1, 0.5f));
268 public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
Winson99ef4582016-04-18 16:57:27 -0700269 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
Winson3e874742016-01-07 10:08:17 -0800270 new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
271 new RectF(0.5f, 0, 1, 1));
272 public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
Winson99ef4582016-04-18 16:57:27 -0700273 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
Winson3e874742016-01-07 10:08:17 -0800274 new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
275 new RectF(0, 0.5f, 1, 1));
Winsoneca4ab62015-11-04 10:50:28 -0800276
277 @Override
Winson08deff02016-08-05 13:58:31 -0700278 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
279 boolean isCurrentTarget) {
280 if (isCurrentTarget) {
281 getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
282 return mTmpRect.contains(x, y);
283 } else {
284 getMappedRect(touchArea, width, height, mTmpRect);
285 updateBoundsWithSystemInsets(mTmpRect, insets);
286 return mTmpRect.contains(x, y);
287 }
Winsoneca4ab62015-11-04 10:50:28 -0800288 }
Winsonbe7607a2015-10-01 17:24:51 -0700289
Winson882072b2015-10-12 11:26:33 -0700290 // Represents the view state of this dock state
Winson99ef4582016-04-18 16:57:27 -0700291 public static class ViewState {
292 private static final IntProperty<ViewState> HINT_ALPHA =
293 new IntProperty<ViewState>("drawableAlpha") {
294 @Override
295 public void setValue(ViewState object, int alpha) {
296 object.mHintTextAlpha = alpha;
297 object.dockAreaOverlay.invalidateSelf();
298 }
299
300 @Override
301 public Integer get(ViewState object) {
302 return object.mHintTextAlpha;
303 }
304 };
305
Winson882072b2015-10-12 11:26:33 -0700306 public final int dockAreaAlpha;
307 public final ColorDrawable dockAreaOverlay;
Winson99ef4582016-04-18 16:57:27 -0700308 public final int hintTextAlpha;
309 public final int hintTextOrientation;
Winson882072b2015-10-12 11:26:33 -0700310
Winson99ef4582016-04-18 16:57:27 -0700311 private final int mHintTextResId;
312 private String mHintText;
313 private Paint mHintTextPaint;
314 private Point mHintTextBounds = new Point();
315 private int mHintTextAlpha = 255;
316 private AnimatorSet mDockAreaOverlayAnimator;
317 private Rect mTmpRect = new Rect();
318
319 private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
320 int hintTextResId) {
321 dockAreaAlpha = areaAlpha;
Winson882072b2015-10-12 11:26:33 -0700322 dockAreaOverlay = new ColorDrawable(0xFFffffff);
323 dockAreaOverlay.setAlpha(0);
Winson99ef4582016-04-18 16:57:27 -0700324 hintTextAlpha = hintAlpha;
325 hintTextOrientation = hintOrientation;
326 mHintTextResId = hintTextResId;
327 mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
328 mHintTextPaint.setColor(Color.WHITE);
329 }
330
331 /**
332 * Updates the view state with the given context.
333 */
334 public void update(Context context) {
335 Resources res = context.getResources();
336 mHintText = context.getString(mHintTextResId);
337 mHintTextPaint.setTextSize(res.getDimensionPixelSize(
338 R.dimen.recents_drag_hint_text_size));
339 mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
340 mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
341 }
342
343 /**
344 * Draws the current view state.
345 */
346 public void draw(Canvas canvas) {
347 // Draw the overlay background
348 if (dockAreaOverlay.getAlpha() > 0) {
349 dockAreaOverlay.draw(canvas);
350 }
351
352 // Draw the hint text
353 if (mHintTextAlpha > 0) {
354 Rect bounds = dockAreaOverlay.getBounds();
355 int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
356 int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
357 mHintTextPaint.setAlpha(mHintTextAlpha);
358 if (hintTextOrientation == VERTICAL) {
359 canvas.save();
360 canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
361 }
362 canvas.drawText(mHintText, x, y, mHintTextPaint);
363 if (hintTextOrientation == VERTICAL) {
364 canvas.restore();
365 }
366 }
Winson882072b2015-10-12 11:26:33 -0700367 }
368
369 /**
Winson3e874742016-01-07 10:08:17 -0800370 * Creates a new bounds and alpha animation.
Winson882072b2015-10-12 11:26:33 -0700371 */
Winson99ef4582016-04-18 16:57:27 -0700372 public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
Winson3e874742016-01-07 10:08:17 -0800373 Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
Winson99ef4582016-04-18 16:57:27 -0700374 if (mDockAreaOverlayAnimator != null) {
375 mDockAreaOverlayAnimator.cancel();
Winson3e874742016-01-07 10:08:17 -0800376 }
377
Winson3f32e7e2016-04-20 17:18:08 -0700378 ObjectAnimator anim;
Winson3e874742016-01-07 10:08:17 -0800379 ArrayList<Animator> animators = new ArrayList<>();
Winson99ef4582016-04-18 16:57:27 -0700380 if (dockAreaOverlay.getAlpha() != areaAlpha) {
Winson3e874742016-01-07 10:08:17 -0800381 if (animateAlpha) {
Winson3f32e7e2016-04-20 17:18:08 -0700382 anim = ObjectAnimator.ofInt(dockAreaOverlay,
383 Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
384 anim.setDuration(duration);
385 anim.setInterpolator(interpolator);
386 animators.add(anim);
Winson3e874742016-01-07 10:08:17 -0800387 } else {
Winson99ef4582016-04-18 16:57:27 -0700388 dockAreaOverlay.setAlpha(areaAlpha);
389 }
390 }
391 if (mHintTextAlpha != hintAlpha) {
392 if (animateAlpha) {
Winson3f32e7e2016-04-20 17:18:08 -0700393 anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
394 hintAlpha);
395 anim.setDuration(150);
396 anim.setInterpolator(hintAlpha > mHintTextAlpha
397 ? Interpolators.ALPHA_IN
398 : Interpolators.ALPHA_OUT);
399 animators.add(anim);
Winson99ef4582016-04-18 16:57:27 -0700400 } else {
401 mHintTextAlpha = hintAlpha;
402 dockAreaOverlay.invalidateSelf();
Winson882072b2015-10-12 11:26:33 -0700403 }
Winson3e874742016-01-07 10:08:17 -0800404 }
405 if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
406 if (animateBounds) {
407 PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
Winson67c79572016-04-13 14:02:18 -0700408 Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
Winson3f32e7e2016-04-20 17:18:08 -0700409 new Rect(dockAreaOverlay.getBounds()), bounds);
410 anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
411 anim.setDuration(duration);
412 anim.setInterpolator(interpolator);
413 animators.add(anim);
Winson3e874742016-01-07 10:08:17 -0800414 } else {
415 dockAreaOverlay.setBounds(bounds);
416 }
417 }
418 if (!animators.isEmpty()) {
Winson99ef4582016-04-18 16:57:27 -0700419 mDockAreaOverlayAnimator = new AnimatorSet();
420 mDockAreaOverlayAnimator.playTogether(animators);
Winson99ef4582016-04-18 16:57:27 -0700421 mDockAreaOverlayAnimator.start();
Winson882072b2015-10-12 11:26:33 -0700422 }
423 }
424 }
425
Winson3e874742016-01-07 10:08:17 -0800426 public final int dockSide;
Winsonbe7607a2015-10-01 17:24:51 -0700427 public final int createMode;
Winson882072b2015-10-12 11:26:33 -0700428 public final ViewState viewState;
Winson882072b2015-10-12 11:26:33 -0700429 private final RectF touchArea;
Winson3e874742016-01-07 10:08:17 -0800430 private final RectF dockArea;
431 private final RectF expandedTouchDockArea;
Winson08deff02016-08-05 13:58:31 -0700432 private static final Rect mTmpRect = new Rect();
Winsonbe7607a2015-10-01 17:24:51 -0700433
434 /**
435 * @param createMode used to pass to ActivityManager to dock the task
436 * @param touchArea the area in which touch will initiate this dock state
Winsoneca4ab62015-11-04 10:50:28 -0800437 * @param dockArea the visible dock area
Winson3e874742016-01-07 10:08:17 -0800438 * @param expandedTouchDockArea the areain which touch will continue to dock after entering
439 * the initial touch area. This is also the new dock area to
440 * draw.
Winsonbe7607a2015-10-01 17:24:51 -0700441 */
Winson99ef4582016-04-18 16:57:27 -0700442 DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
443 @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
Winson3e874742016-01-07 10:08:17 -0800444 RectF expandedTouchDockArea) {
445 this.dockSide = dockSide;
Winsonbe7607a2015-10-01 17:24:51 -0700446 this.createMode = createMode;
Winson99ef4582016-04-18 16:57:27 -0700447 this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
448 R.string.recents_drag_hint_message);
Winsonbe7607a2015-10-01 17:24:51 -0700449 this.dockArea = dockArea;
Winson882072b2015-10-12 11:26:33 -0700450 this.touchArea = touchArea;
Winson3e874742016-01-07 10:08:17 -0800451 this.expandedTouchDockArea = expandedTouchDockArea;
Winsonbe7607a2015-10-01 17:24:51 -0700452 }
453
454 /**
Winson99ef4582016-04-18 16:57:27 -0700455 * Updates the dock state with the given context.
456 */
457 public void update(Context context) {
458 viewState.update(context);
459 }
460
461 /**
Winsonbe7607a2015-10-01 17:24:51 -0700462 * Returns the docked task bounds with the given {@param width} and {@param height}.
463 */
Winson08deff02016-08-05 13:58:31 -0700464 public Rect getPreDockedBounds(int width, int height, Rect insets) {
465 getMappedRect(dockArea, width, height, mTmpRect);
466 return updateBoundsWithSystemInsets(mTmpRect, insets);
Winsonbe7607a2015-10-01 17:24:51 -0700467 }
Winson3e874742016-01-07 10:08:17 -0800468
469 /**
470 * Returns the expanded docked task bounds with the given {@param width} and
471 * {@param height}.
472 */
473 public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
474 Resources res) {
475 // Calculate the docked task bounds
476 boolean isHorizontalDivision =
477 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
478 int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
479 insets, width, height, dividerSize);
480 Rect newWindowBounds = new Rect();
481 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
482 width, height, dividerSize);
483 return newWindowBounds;
484 }
485
486 /**
487 * Returns the task stack bounds with the given {@param width} and
488 * {@param height}.
489 */
Winsonfc48b072016-04-21 11:20:11 -0700490 public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
491 int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
492 Resources res, Rect windowRectOut) {
Winson3e874742016-01-07 10:08:17 -0800493 // Calculate the inverse docked task bounds
494 boolean isHorizontalDivision =
495 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
496 int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
497 insets, width, height, dividerSize);
Winson3e874742016-01-07 10:08:17 -0800498 DockedDividerUtils.calculateBoundsForPosition(position,
Winson59924fe2016-03-17 14:13:18 -0700499 DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
Winson3e874742016-01-07 10:08:17 -0800500 dividerSize);
501
502 // Calculate the task stack bounds from the new window bounds
Winson3e874742016-01-07 10:08:17 -0800503 Rect taskStackBounds = new Rect();
Winsond9529612016-01-28 13:29:49 -0800504 // If the task stack bounds is specifically under the dock area, then ignore the top
505 // inset
506 int top = dockArea.bottom < 1f
507 ? 0
508 : insets.top;
Winson08deff02016-08-05 13:58:31 -0700509 // For now, ignore the left insets since we always dock on the left and show Recents
510 // on the right
511 layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
512 taskStackBounds);
Winson3e874742016-01-07 10:08:17 -0800513 return taskStackBounds;
514 }
Winson08deff02016-08-05 13:58:31 -0700515
516 /**
517 * Returns the expanded bounds in certain dock sides such that the bounds account for the
518 * system insets (namely the vertical nav bar). This call modifies and returns the given
519 * {@param bounds}.
520 */
521 private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
522 if (dockSide == DOCKED_LEFT) {
523 bounds.right += insets.left;
524 } else if (dockSide == DOCKED_RIGHT) {
525 bounds.left -= insets.right;
526 }
527 return bounds;
528 }
529
530 /**
531 * Returns the mapped rect to the given dimensions.
532 */
533 private void getMappedRect(RectF bounds, int width, int height, Rect out) {
534 out.set((int) (bounds.left * width), (int) (bounds.top * height),
535 (int) (bounds.right * width), (int) (bounds.bottom * height));
536 }
Winsonbe7607a2015-10-01 17:24:51 -0700537 }
538
Winson6976f7b2016-05-03 14:58:12 -0700539 // A comparator that sorts tasks by their freeform state
540 private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
Winson Chung509d0d02015-12-16 15:43:12 -0500541 @Override
542 public int compare(Task o1, Task o2) {
543 if (o1.isFreeformTask() && !o2.isFreeformTask()) {
544 return 1;
545 } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
546 return -1;
547 }
Winson6976f7b2016-05-03 14:58:12 -0700548 return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
Winson Chung509d0d02015-12-16 15:43:12 -0500549 }
550 };
551
552
Winson Chung083baf92014-07-11 10:32:42 -0700553 // The task offset to apply to a task id as a group affiliation
554 static final int IndividualTaskIdOffset = 1 << 16;
555
Winson Chung06266772015-12-11 10:24:21 -0500556 ArrayList<Task> mRawTaskList = new ArrayList<>();
Winson250608a2015-11-24 15:00:31 -0800557 FilteredTaskList mStackTaskList = new FilteredTaskList();
Winson Chung303e1ff2014-03-07 15:06:19 -0800558 TaskStackCallbacks mCb;
559
Winson8f0e3a62015-11-23 09:15:08 -0800560 ArrayList<TaskGrouping> mGroups = new ArrayList<>();
Winson55003902016-01-12 12:00:37 -0800561 ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
Winson8f0e3a62015-11-23 09:15:08 -0800562
563 public TaskStack() {
564 // Ensure that we only show non-docked tasks
Winson250608a2015-11-24 15:00:31 -0800565 mStackTaskList.setFilter(new TaskFilter() {
Winson8f0e3a62015-11-23 09:15:08 -0800566 @Override
Winson Chung2b9ef652015-12-11 10:23:59 -0500567 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
Winsond8f74312016-03-23 20:39:24 -0700568 if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
569 if (t.isAffiliatedTask()) {
570 // If this task is affiliated with another parent in the stack, then the
571 // historical state of this task depends on the state of the parent task
572 Task parentTask = taskIdMap.get(t.affiliationTaskId);
573 if (parentTask != null) {
574 t = parentTask;
575 }
Winson Chung2b9ef652015-12-11 10:23:59 -0500576 }
577 }
Winson8f6ee482016-03-18 17:51:48 -0700578 return t.isStackTask;
Winson8f0e3a62015-11-23 09:15:08 -0800579 }
580 });
581 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800582
Winson Chungd16c5652015-01-26 16:11:07 -0800583 /** Sets the callbacks for this task stack. */
Winson Chung303e1ff2014-03-07 15:06:19 -0800584 public void setCallbacks(TaskStackCallbacks cb) {
585 mCb = cb;
586 }
587
Winsoneca4ab62015-11-04 10:50:28 -0800588 /**
589 * Moves the given task to either the front of the freeform workspace or the stack.
590 */
591 public void moveTaskToStack(Task task, int newStackId) {
592 // Find the index to insert into
Winson250608a2015-11-24 15:00:31 -0800593 ArrayList<Task> taskList = mStackTaskList.getTasks();
Winsoneca4ab62015-11-04 10:50:28 -0800594 int taskCount = taskList.size();
595 if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
596 // Insert freeform tasks at the front
Winson250608a2015-11-24 15:00:31 -0800597 mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
Winsoneca4ab62015-11-04 10:50:28 -0800598 } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
599 // Insert after the first stacked task
600 int insertIndex = 0;
601 for (int i = taskCount - 1; i >= 0; i--) {
602 if (!taskList.get(i).isFreeformTask()) {
603 insertIndex = i + 1;
604 break;
605 }
606 }
Winson250608a2015-11-24 15:00:31 -0800607 mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
Winsoneca4ab62015-11-04 10:50:28 -0800608 }
609 }
610
Winson Chung6ac8bd62015-01-07 16:38:35 -0800611 /** Does the actual work associated with removing the task. */
Winson250608a2015-11-24 15:00:31 -0800612 void removeTaskImpl(FilteredTaskList taskList, Task t) {
Winson Chung6ac8bd62015-01-07 16:38:35 -0800613 // Remove the task from the list
Winson250608a2015-11-24 15:00:31 -0800614 taskList.remove(t);
Winson Chung6ac8bd62015-01-07 16:38:35 -0800615 // Remove it from the group as well, and if it is empty, remove the group
616 TaskGrouping group = t.group;
Winson Chung4e5fb2f2015-12-15 14:46:23 -0500617 if (group != null) {
618 group.removeTask(t);
619 if (group.getTaskCount() == 0) {
620 removeGroup(group);
621 }
Winson Chung6ac8bd62015-01-07 16:38:35 -0800622 }
Winson Chung6ac8bd62015-01-07 16:38:35 -0800623 }
624
Winson8aa99592016-01-19 15:07:07 -0800625 /**
626 * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
627 * how they should update themselves.
628 */
Winson20684082016-03-16 17:13:34 -0700629 public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
Winson250608a2015-11-24 15:00:31 -0800630 if (mStackTaskList.contains(t)) {
Winson250608a2015-11-24 15:00:31 -0800631 removeTaskImpl(mStackTaskList, t);
Winson35a8b042016-01-22 09:41:09 -0800632 Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
Winson Chung303e1ff2014-03-07 15:06:19 -0800633 if (mCb != null) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700634 // Notify that a task has been removed
Winson6c8217a2016-05-25 10:53:53 -0700635 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
Winson20684082016-03-16 17:13:34 -0700636 fromDockGesture);
Winson Chung303e1ff2014-03-07 15:06:19 -0800637 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800638 }
Winson3e874742016-01-07 10:08:17 -0800639 mRawTaskList.remove(t);
Winson250608a2015-11-24 15:00:31 -0800640 }
641
642 /**
Winson3b6ba1a2016-03-22 15:37:54 -0700643 * Removes all tasks from the stack.
644 */
645 public void removeAllTasks() {
646 ArrayList<Task> tasks = mStackTaskList.getTasks();
647 for (int i = tasks.size() - 1; i >= 0; i--) {
648 Task t = tasks.get(i);
649 removeTaskImpl(mStackTaskList, t);
650 mRawTaskList.remove(t);
651 }
652 if (mCb != null) {
653 // Notify that all tasks have been removed
654 mCb.onStackTasksRemoved(this);
655 }
656 }
657
658 /**
Winson250608a2015-11-24 15:00:31 -0800659 * Sets a few tasks in one go, without calling any callbacks.
Winson Chung06266772015-12-11 10:24:21 -0500660 *
661 * @param tasks the new set of tasks to replace the current set.
662 * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
Winson250608a2015-11-24 15:00:31 -0800663 */
Winson88737542016-02-17 13:27:33 -0800664 public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
Winson Chung06266772015-12-11 10:24:21 -0500665 // Compute a has set for each of the tasks
Winson55003902016-01-12 12:00:37 -0800666 ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
667 ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
Winson88737542016-02-17 13:27:33 -0800668 ArrayList<Task> addedTasks = new ArrayList<>();
Winson6c8217a2016-05-25 10:53:53 -0700669 ArrayList<Task> removedTasks = new ArrayList<>();
Winson88737542016-02-17 13:27:33 -0800670 ArrayList<Task> allTasks = new ArrayList<>();
Winson Chung06266772015-12-11 10:24:21 -0500671
672 // Disable notifications if there are no callbacks
673 if (mCb == null) {
674 notifyStackChanges = false;
675 }
676
677 // Remove any tasks that no longer exist
678 int taskCount = mRawTaskList.size();
Winson88737542016-02-17 13:27:33 -0800679 for (int i = taskCount - 1; i >= 0; i--) {
Winson Chung06266772015-12-11 10:24:21 -0500680 Task task = mRawTaskList.get(i);
681 if (!newTasksMap.containsKey(task.key)) {
682 if (notifyStackChanges) {
Winson6c8217a2016-05-25 10:53:53 -0700683 removedTasks.add(task);
Winson Chung06266772015-12-11 10:24:21 -0500684 }
Winson Chung06266772015-12-11 10:24:21 -0500685 }
Winson Chung97567552015-12-16 17:07:19 -0500686 task.setGroup(null);
Winson Chung06266772015-12-11 10:24:21 -0500687 }
688
689 // Add any new tasks
690 taskCount = tasks.size();
691 for (int i = 0; i < taskCount; i++) {
Winson88737542016-02-17 13:27:33 -0800692 Task newTask = tasks.get(i);
693 Task currentTask = currentTasksMap.get(newTask.key);
694 if (currentTask == null && notifyStackChanges) {
695 addedTasks.add(newTask);
696 } else if (currentTask != null) {
697 // The current task has bound callbacks, so just copy the data from the new task
698 // state and add it back into the list
699 currentTask.copyFrom(newTask);
700 newTask = currentTask;
Winson Chung06266772015-12-11 10:24:21 -0500701 }
Winson88737542016-02-17 13:27:33 -0800702 allTasks.add(newTask);
Winson Chung06266772015-12-11 10:24:21 -0500703 }
704
705 // Sort all the tasks to ensure they are ordered correctly
Winson6976f7b2016-05-03 14:58:12 -0700706 for (int i = allTasks.size() - 1; i >= 0; i--) {
707 allTasks.get(i).temporarySortIndexInStack = i;
708 }
709 Collections.sort(allTasks, FREEFORM_COMPARATOR);
Winson Chung06266772015-12-11 10:24:21 -0500710
Winson8f6ee482016-03-18 17:51:48 -0700711 mStackTaskList.set(allTasks);
Winson88737542016-02-17 13:27:33 -0800712 mRawTaskList = allTasks;
713
Winsona1ededd2016-03-25 12:23:12 -0700714 // Update the affiliated groupings
715 createAffiliatedGroupings(context);
716
Winson6c8217a2016-05-25 10:53:53 -0700717 // Only callback for the removed tasks after the stack has updated
718 int removedTaskCount = removedTasks.size();
719 Task newFrontMostTask = getStackFrontMostTask(false);
720 for (int i = 0; i < removedTaskCount; i++) {
721 mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
722 AnimationProps.IMMEDIATE, false /* fromDockGesture */);
723 }
724
Winson88737542016-02-17 13:27:33 -0800725 // Only callback for the newly added tasks after this stack has been updated
726 int addedTaskCount = addedTasks.size();
727 for (int i = 0; i < addedTaskCount; i++) {
728 mCb.onStackTaskAdded(this, addedTasks.get(i));
729 }
730
Winsona1ededd2016-03-25 12:23:12 -0700731 // Notify that the task stack has been updated
732 if (notifyStackChanges) {
733 mCb.onStackTasksUpdated(this);
734 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800735 }
736
Winson Chung931c51f2015-12-17 17:08:55 -0500737 /**
738 * Gets the front-most task in the stack.
739 */
Winson35a8b042016-01-22 09:41:09 -0800740 public Task getStackFrontMostTask(boolean includeFreeformTasks) {
Winson Chung931c51f2015-12-17 17:08:55 -0500741 ArrayList<Task> stackTasks = mStackTaskList.getTasks();
742 if (stackTasks.isEmpty()) {
743 return null;
744 }
745 for (int i = stackTasks.size() - 1; i >= 0; i--) {
746 Task task = stackTasks.get(i);
Winson35a8b042016-01-22 09:41:09 -0800747 if (!task.isFreeformTask() || includeFreeformTasks) {
Winson Chung931c51f2015-12-17 17:08:55 -0500748 return task;
749 }
750 }
751 return null;
Winson Chungffa2ec62014-07-03 15:54:42 -0700752 }
753
Winson Chung04400672014-10-17 14:53:30 -0700754 /** Gets the task keys */
755 public ArrayList<Task.TaskKey> getTaskKeys() {
Winson8f0e3a62015-11-23 09:15:08 -0800756 ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
Winson250608a2015-11-24 15:00:31 -0800757 ArrayList<Task> tasks = computeAllTasksList();
Winson Chung04400672014-10-17 14:53:30 -0700758 int taskCount = tasks.size();
759 for (int i = 0; i < taskCount; i++) {
Winson250608a2015-11-24 15:00:31 -0800760 Task task = tasks.get(i);
761 taskKeys.add(task.key);
Winson Chung04400672014-10-17 14:53:30 -0700762 }
763 return taskKeys;
764 }
765
Winson250608a2015-11-24 15:00:31 -0800766 /**
767 * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
768 */
769 public ArrayList<Task> getStackTasks() {
770 return mStackTaskList.getTasks();
Winson Chung303e1ff2014-03-07 15:06:19 -0800771 }
772
Winson36a5a2c2015-10-29 18:04:39 -0700773 /**
Winsonf24f2162016-01-05 12:11:55 -0800774 * Returns the set of "freeform" tasks in the stack.
775 */
776 public ArrayList<Task> getFreeformTasks() {
777 ArrayList<Task> freeformTasks = new ArrayList<>();
778 ArrayList<Task> tasks = mStackTaskList.getTasks();
779 int taskCount = tasks.size();
780 for (int i = 0; i < taskCount; i++) {
781 Task task = tasks.get(i);
782 if (task.isFreeformTask()) {
783 freeformTasks.add(task);
784 }
785 }
786 return freeformTasks;
787 }
788
789 /**
Winson6976f7b2016-05-03 14:58:12 -0700790 * Computes a set of all the active and historical tasks.
Winson250608a2015-11-24 15:00:31 -0800791 */
792 public ArrayList<Task> computeAllTasksList() {
793 ArrayList<Task> tasks = new ArrayList<>();
794 tasks.addAll(mStackTaskList.getTasks());
Winson250608a2015-11-24 15:00:31 -0800795 return tasks;
796 }
797
798 /**
Winson4b057c62016-01-12 15:01:52 -0800799 * Returns the number of stack and freeform tasks.
Winson250608a2015-11-24 15:00:31 -0800800 */
Winson4b057c62016-01-12 15:01:52 -0800801 public int getTaskCount() {
Winson250608a2015-11-24 15:00:31 -0800802 return mStackTaskList.size();
803 }
804
805 /**
Winson4b057c62016-01-12 15:01:52 -0800806 * Returns the number of stack tasks.
Winsonf0d1c442015-12-01 11:04:45 -0800807 */
Winson4b057c62016-01-12 15:01:52 -0800808 public int getStackTaskCount() {
809 ArrayList<Task> tasks = mStackTaskList.getTasks();
810 int stackCount = 0;
811 int taskCount = tasks.size();
812 for (int i = 0; i < taskCount; i++) {
813 Task task = tasks.get(i);
814 if (!task.isFreeformTask()) {
815 stackCount++;
816 }
817 }
818 return stackCount;
819 }
820
821 /**
822 * Returns the number of freeform tasks.
823 */
824 public int getFreeformTaskCount() {
Winsonf0d1c442015-12-01 11:04:45 -0800825 ArrayList<Task> tasks = mStackTaskList.getTasks();
826 int freeformCount = 0;
827 int taskCount = tasks.size();
828 for (int i = 0; i < taskCount; i++) {
829 Task task = tasks.get(i);
830 if (task.isFreeformTask()) {
831 freeformCount++;
832 }
833 }
834 return freeformCount;
835 }
836
837 /**
Winson250608a2015-11-24 15:00:31 -0800838 * Returns the task in stack tasks which is the launch target.
Winson36a5a2c2015-10-29 18:04:39 -0700839 */
840 public Task getLaunchTarget() {
Winson250608a2015-11-24 15:00:31 -0800841 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson36a5a2c2015-10-29 18:04:39 -0700842 int taskCount = tasks.size();
843 for (int i = 0; i < taskCount; i++) {
844 Task task = tasks.get(i);
845 if (task.isLaunchTarget) {
846 return task;
847 }
848 }
849 return null;
850 }
851
Winson Chung303e1ff2014-03-07 15:06:19 -0800852 /** Returns the index of this task in this current task stack */
Winson250608a2015-11-24 15:00:31 -0800853 public int indexOfStackTask(Task t) {
854 return mStackTaskList.indexOf(t);
Winson Chung303e1ff2014-03-07 15:06:19 -0800855 }
856
Winson Chungb1f74992014-08-08 12:53:09 -0700857 /** Finds the task with the specified task id. */
858 public Task findTaskWithId(int taskId) {
Winson250608a2015-11-24 15:00:31 -0800859 ArrayList<Task> tasks = computeAllTasksList();
Winsond72c3152016-04-05 15:33:35 -0700860 int taskCount = tasks.size();
861 for (int i = 0; i < taskCount; i++) {
862 Task task = tasks.get(i);
Winson Chungb1f74992014-08-08 12:53:09 -0700863 if (task.key.id == taskId) {
864 return task;
865 }
866 }
867 return null;
868 }
869
Winson Chungffa2ec62014-07-03 15:54:42 -0700870 /******** Grouping ********/
871
872 /** Adds a group to the set */
873 public void addGroup(TaskGrouping group) {
874 mGroups.add(group);
875 mAffinitiesGroups.put(group.affiliation, group);
Winson Chungffa2ec62014-07-03 15:54:42 -0700876 }
877
878 public void removeGroup(TaskGrouping group) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700879 mGroups.remove(group);
880 mAffinitiesGroups.remove(group.affiliation);
Winson Chungffa2ec62014-07-03 15:54:42 -0700881 }
882
883 /** Returns the group with the specified affiliation. */
Winson Chung083baf92014-07-11 10:32:42 -0700884 public TaskGrouping getGroupWithAffiliation(int affiliation) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700885 return mAffinitiesGroups.get(affiliation);
886 }
887
Winson Chungffa2ec62014-07-03 15:54:42 -0700888 /**
Winson88737542016-02-17 13:27:33 -0800889 * Temporary: This method will simulate affiliation groups
Winson Chungffa2ec62014-07-03 15:54:42 -0700890 */
Winson88737542016-02-17 13:27:33 -0800891 void createAffiliatedGroupings(Context context) {
892 mGroups.clear();
893 mAffinitiesGroups.clear();
894
Winson6e6bd8772016-01-25 10:41:40 -0800895 if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
Winson55003902016-01-12 12:00:37 -0800896 ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
Winson Chungffa2ec62014-07-03 15:54:42 -0700897 // Sort all tasks by increasing firstActiveTime of the task
Winson250608a2015-11-24 15:00:31 -0800898 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson Chungffa2ec62014-07-03 15:54:42 -0700899 Collections.sort(tasks, new Comparator<Task>() {
900 @Override
901 public int compare(Task task, Task task2) {
Winson Chung16bc2a72015-12-15 10:27:04 -0500902 return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
Winson Chungffa2ec62014-07-03 15:54:42 -0700903 }
904 });
905 // Create groups when sequential packages are the same
906 NamedCounter counter = new NamedCounter("task-group", "");
907 int taskCount = tasks.size();
908 String prevPackage = "";
Winson Chung083baf92014-07-11 10:32:42 -0700909 int prevAffiliation = -1;
Winson Chunga433fa92014-07-08 21:50:31 -0700910 Random r = new Random();
Winson6e6bd8772016-01-25 10:41:40 -0800911 int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
Winson Chungffa2ec62014-07-03 15:54:42 -0700912 for (int i = 0; i < taskCount; i++) {
913 Task t = tasks.get(i);
Winsone7f138c2015-10-22 16:15:21 -0700914 String packageName = t.key.getComponent().getPackageName();
Winson Chunga433fa92014-07-08 21:50:31 -0700915 packageName = "pkg";
Winson Chungffa2ec62014-07-03 15:54:42 -0700916 TaskGrouping group;
Winson Chunga433fa92014-07-08 21:50:31 -0700917 if (packageName.equals(prevPackage) && groupCountDown > 0) {
Winson Chungffa2ec62014-07-03 15:54:42 -0700918 group = getGroupWithAffiliation(prevAffiliation);
Winson Chunga433fa92014-07-08 21:50:31 -0700919 groupCountDown--;
Winson Chungffa2ec62014-07-03 15:54:42 -0700920 } else {
Winson Chung083baf92014-07-11 10:32:42 -0700921 int affiliation = IndividualTaskIdOffset + t.key.id;
Winson Chungffa2ec62014-07-03 15:54:42 -0700922 group = new TaskGrouping(affiliation);
923 addGroup(group);
924 prevAffiliation = affiliation;
925 prevPackage = packageName;
Winson6e6bd8772016-01-25 10:41:40 -0800926 groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
Winson Chungffa2ec62014-07-03 15:54:42 -0700927 }
928 group.addTask(t);
929 taskMap.put(t.key, t);
930 }
931 // Sort groups by increasing latestActiveTime of the group
932 Collections.sort(mGroups, new Comparator<TaskGrouping>() {
933 @Override
934 public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
Winson Chung509d0d02015-12-16 15:43:12 -0500935 return Long.compare(taskGrouping.latestActiveTimeInGroup,
Winson Chungffa2ec62014-07-03 15:54:42 -0700936 taskGrouping2.latestActiveTimeInGroup);
937 }
938 });
Winson Chung2b9ef652015-12-11 10:23:59 -0500939 // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
940 // of tasks
Winson Chungffa2ec62014-07-03 15:54:42 -0700941 int taskIndex = 0;
942 int groupCount = mGroups.size();
943 for (int i = 0; i < groupCount; i++) {
944 TaskGrouping group = mGroups.get(i);
Winson Chung083baf92014-07-11 10:32:42 -0700945 Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
Winson Chungffa2ec62014-07-03 15:54:42 -0700946 @Override
947 public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
Winson Chung509d0d02015-12-16 15:43:12 -0500948 return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
Winson Chungffa2ec62014-07-03 15:54:42 -0700949 }
950 });
Winson Chung083baf92014-07-11 10:32:42 -0700951 ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
Winson Chungffa2ec62014-07-03 15:54:42 -0700952 int groupTaskCount = groupTasks.size();
953 for (int j = 0; j < groupTaskCount; j++) {
954 tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
955 taskIndex++;
956 }
957 }
Winson250608a2015-11-24 15:00:31 -0800958 mStackTaskList.set(tasks);
Winson Chungffa2ec62014-07-03 15:54:42 -0700959 } else {
Winson Chung083baf92014-07-11 10:32:42 -0700960 // Create the task groups
Winson55003902016-01-12 12:00:37 -0800961 ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
Winson250608a2015-11-24 15:00:31 -0800962 ArrayList<Task> tasks = mStackTaskList.getTasks();
Winson Chungffa2ec62014-07-03 15:54:42 -0700963 int taskCount = tasks.size();
964 for (int i = 0; i < taskCount; i++) {
965 Task t = tasks.get(i);
Winson Chung083baf92014-07-11 10:32:42 -0700966 TaskGrouping group;
Winson65c851e2016-01-20 12:43:35 -0800967 if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
968 int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
969 IndividualTaskIdOffset + t.key.id;
970 if (mAffinitiesGroups.containsKey(affiliation)) {
971 group = getGroupWithAffiliation(affiliation);
972 } else {
973 group = new TaskGrouping(affiliation);
974 addGroup(group);
975 }
Winson Chung083baf92014-07-11 10:32:42 -0700976 } else {
Winson65c851e2016-01-20 12:43:35 -0800977 group = new TaskGrouping(t.key.id);
Winson Chung083baf92014-07-11 10:32:42 -0700978 addGroup(group);
979 }
Winson Chungffa2ec62014-07-03 15:54:42 -0700980 group.addTask(t);
Winson Chungec396d62014-08-06 17:08:00 -0700981 tasksMap.put(t.key, t);
982 }
983 // Update the task colors for each of the groups
Winson35f30502015-09-28 11:24:36 -0700984 float minAlpha = context.getResources().getFloat(
985 R.dimen.recents_task_affiliation_color_min_alpha_percentage);
Winson Chungec396d62014-08-06 17:08:00 -0700986 int taskGroupCount = mGroups.size();
987 for (int i = 0; i < taskGroupCount; i++) {
988 TaskGrouping group = mGroups.get(i);
989 taskCount = group.getTaskCount();
990 // Ignore the groups that only have one task
991 if (taskCount <= 1) continue;
992 // Calculate the group color distribution
Winson Chung296278a2015-12-17 12:09:02 -0500993 int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
Winson Chungec396d62014-08-06 17:08:00 -0700994 float alphaStep = (1f - minAlpha) / taskCount;
995 float alpha = 1f;
996 for (int j = 0; j < taskCount; j++) {
997 Task t = tasksMap.get(group.mTaskKeys.get(j));
Winson Chunga0e88b52014-08-11 19:25:42 -0700998 t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
999 alpha);
Winson Chungec396d62014-08-06 17:08:00 -07001000 alpha -= alphaStep;
1001 }
Winson Chungffa2ec62014-07-03 15:54:42 -07001002 }
1003 }
1004 }
1005
Winsone7f138c2015-10-22 16:15:21 -07001006 /**
1007 * Computes the components of tasks in this stack that have been removed as a result of a change
1008 * in the specified package.
1009 */
Winson55003902016-01-12 12:00:37 -08001010 public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
Winsone7f138c2015-10-22 16:15:21 -07001011 // Identify all the tasks that should be removed as a result of the package being removed.
1012 // Using a set to ensure that we callback once per unique component.
1013 SystemServicesProxy ssp = Recents.getSystemServices();
Winson55003902016-01-12 12:00:37 -08001014 ArraySet<ComponentName> existingComponents = new ArraySet<>();
1015 ArraySet<ComponentName> removedComponents = new ArraySet<>();
Winsone7f138c2015-10-22 16:15:21 -07001016 ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
Winsond72c3152016-04-05 15:33:35 -07001017 int taskKeyCount = taskKeys.size();
1018 for (int i = 0; i < taskKeyCount; i++) {
1019 Task.TaskKey t = taskKeys.get(i);
1020
Winsone7f138c2015-10-22 16:15:21 -07001021 // Skip if this doesn't apply to the current user
1022 if (t.userId != userId) continue;
1023
1024 ComponentName cn = t.getComponent();
1025 if (cn.getPackageName().equals(packageName)) {
1026 if (existingComponents.contains(cn)) {
1027 // If we know that the component still exists in the package, then skip
1028 continue;
1029 }
1030 if (ssp.getActivityInfo(cn, userId) != null) {
1031 existingComponents.add(cn);
1032 } else {
1033 removedComponents.add(cn);
1034 }
1035 }
1036 }
1037 return removedComponents;
1038 }
1039
Winson Chung303e1ff2014-03-07 15:06:19 -08001040 @Override
1041 public String toString() {
Winson88737542016-02-17 13:27:33 -08001042 String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
Winsond72c3152016-04-05 15:33:35 -07001043 ArrayList<Task> tasks = mStackTaskList.getTasks();
1044 int taskCount = tasks.size();
1045 for (int i = 0; i < taskCount; i++) {
1046 str += " " + tasks.get(i).toString() + "\n";
Winson250608a2015-11-24 15:00:31 -08001047 }
Winson Chung303e1ff2014-03-07 15:06:19 -08001048 return str;
1049 }
Winson Chung06266772015-12-11 10:24:21 -05001050
1051 /**
1052 * Given a list of tasks, returns a map of each task's key to the task.
1053 */
Winson55003902016-01-12 12:00:37 -08001054 private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
1055 ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
Winson Chung06266772015-12-11 10:24:21 -05001056 int taskCount = tasks.size();
1057 for (int i = 0; i < taskCount; i++) {
1058 Task task = tasks.get(i);
1059 map.put(task.key, task);
1060 }
1061 return map;
1062 }
Winsond72c3152016-04-05 15:33:35 -07001063
1064 public void dump(String prefix, PrintWriter writer) {
1065 String innerPrefix = prefix + " ";
1066
1067 writer.print(prefix); writer.print(TAG);
1068 writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
1069 writer.println();
1070 ArrayList<Task> tasks = mStackTaskList.getTasks();
1071 int taskCount = tasks.size();
1072 for (int i = 0; i < taskCount; i++) {
1073 tasks.get(i).dump(innerPrefix, writer);
1074 }
1075 }
Winson Chung06266772015-12-11 10:24:21 -05001076}