1 /*
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 
17 package com.android.systemui.recents.model;
18 
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Intent;
22 import android.content.pm.ActivityInfo;
23 import android.graphics.Bitmap;
24 import android.graphics.Color;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.view.ViewDebug;
28 
29 import com.android.systemui.recents.Recents;
30 import com.android.systemui.recents.misc.SystemServicesProxy;
31 import com.android.systemui.recents.misc.Utilities;
32 
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Objects;
36 
37 
38 /**
39  * A task represents the top most task in the system's task stack.
40  */
41 public class Task {
42 
43     public static final String TAG = "Task";
44 
45     /* Task callbacks */
46     public interface TaskCallbacks {
47         /* Notifies when a task has been bound */
onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo)48         public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo);
49         /* Notifies when a task has been unbound */
onTaskDataUnloaded()50         public void onTaskDataUnloaded();
51         /* Notifies when a task's stack id has changed. */
onTaskStackIdChanged()52         public void onTaskStackIdChanged();
53     }
54 
55     /* The Task Key represents the unique primary key for the task */
56     public static class TaskKey {
57         @ViewDebug.ExportedProperty(category="recents")
58         public final int id;
59         @ViewDebug.ExportedProperty(category="recents")
60         public int stackId;
61         @ViewDebug.ExportedProperty(category="recents")
62         public final Intent baseIntent;
63         @ViewDebug.ExportedProperty(category="recents")
64         public final int userId;
65         @ViewDebug.ExportedProperty(category="recents")
66         public long firstActiveTime;
67         @ViewDebug.ExportedProperty(category="recents")
68         public long lastActiveTime;
69 
70         private int mHashCode;
71 
TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, long lastActiveTime)72         public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
73                 long lastActiveTime) {
74             this.id = id;
75             this.stackId = stackId;
76             this.baseIntent = intent;
77             this.userId = userId;
78             this.firstActiveTime = firstActiveTime;
79             this.lastActiveTime = lastActiveTime;
80             updateHashCode();
81         }
82 
setStackId(int stackId)83         public void setStackId(int stackId) {
84             this.stackId = stackId;
85             updateHashCode();
86         }
87 
getComponent()88         public ComponentName getComponent() {
89             return this.baseIntent.getComponent();
90         }
91 
92         @Override
equals(Object o)93         public boolean equals(Object o) {
94             if (!(o instanceof TaskKey)) {
95                 return false;
96             }
97             TaskKey otherKey = (TaskKey) o;
98             return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
99         }
100 
101         @Override
hashCode()102         public int hashCode() {
103             return mHashCode;
104         }
105 
106         @Override
toString()107         public String toString() {
108             return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
109                     lastActiveTime;
110         }
111 
updateHashCode()112         private void updateHashCode() {
113             mHashCode = Objects.hash(id, stackId, userId);
114         }
115     }
116 
117     @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
118     public TaskKey key;
119 
120     /**
121      * The temporary sort index in the stack, used when ordering the stack.
122      */
123     public int temporarySortIndexInStack;
124 
125     /**
126      * The group will be computed separately from the initialization of the task
127      */
128     @ViewDebug.ExportedProperty(deepExport=true, prefix="group_")
129     public TaskGrouping group;
130     /**
131      * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated
132      * with any task.
133      */
134     @ViewDebug.ExportedProperty(category="recents")
135     public int affiliationTaskId;
136     @ViewDebug.ExportedProperty(category="recents")
137     public int affiliationColor;
138 
139     /**
140      * The icon is the task description icon (if provided), which falls back to the activity icon,
141      * which can then fall back to the application icon.
142      */
143     public Drawable icon;
144     public Bitmap thumbnail;
145     @ViewDebug.ExportedProperty(category="recents")
146     public String title;
147     @ViewDebug.ExportedProperty(category="recents")
148     public String titleDescription;
149     @ViewDebug.ExportedProperty(category="recents")
150     public String dismissDescription;
151     @ViewDebug.ExportedProperty(category="recents")
152     public String appInfoDescription;
153     @ViewDebug.ExportedProperty(category="recents")
154     public int colorPrimary;
155     @ViewDebug.ExportedProperty(category="recents")
156     public int colorBackground;
157     @ViewDebug.ExportedProperty(category="recents")
158     public boolean useLightOnPrimaryColor;
159 
160     /**
161      * The bounds of the task, used only if it is a freeform task.
162      */
163     @ViewDebug.ExportedProperty(category="recents")
164     public Rect bounds;
165 
166     /**
167      * The task description for this task, only used to reload task icons.
168      */
169     public ActivityManager.TaskDescription taskDescription;
170 
171     /**
172      * The state isLaunchTarget will be set for the correct task upon launching Recents.
173      */
174     @ViewDebug.ExportedProperty(category="recents")
175     public boolean isLaunchTarget;
176     @ViewDebug.ExportedProperty(category="recents")
177     public boolean isStackTask;
178     @ViewDebug.ExportedProperty(category="recents")
179     public boolean isSystemApp;
180     @ViewDebug.ExportedProperty(category="recents")
181     public boolean isDockable;
182 
183     /**
184      * Resize mode. See {@link ActivityInfo#resizeMode}.
185      */
186     @ViewDebug.ExportedProperty(category="recents")
187     public int resizeMode;
188 
189     @ViewDebug.ExportedProperty(category="recents")
190     public ComponentName topActivity;
191 
192     private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
193 
Task()194     public Task() {
195         // Do nothing
196     }
197 
Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, Bitmap thumbnail, String title, String titleDescription, String dismissDescription, String appInfoDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription, int resizeMode, ComponentName topActivity)198     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
199                 Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
200                 String appInfoDescription, int colorPrimary, int colorBackground,
201                 boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
202                 boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
203                 int resizeMode, ComponentName topActivity) {
204         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
205         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
206         this.key = key;
207         this.affiliationTaskId = affiliationTaskId;
208         this.affiliationColor = affiliationColor;
209         this.icon = icon;
210         this.thumbnail = thumbnail;
211         this.title = title;
212         this.titleDescription = titleDescription;
213         this.dismissDescription = dismissDescription;
214         this.appInfoDescription = appInfoDescription;
215         this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
216         this.colorBackground = colorBackground;
217         this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
218                 Color.WHITE) > 3f;
219         this.bounds = bounds;
220         this.taskDescription = taskDescription;
221         this.isLaunchTarget = isLaunchTarget;
222         this.isStackTask = isStackTask;
223         this.isSystemApp = isSystemApp;
224         this.isDockable = isDockable;
225         this.resizeMode = resizeMode;
226         this.topActivity = topActivity;
227     }
228 
229     /**
230      * Copies the metadata from another task, but retains the current callbacks.
231      */
copyFrom(Task o)232     public void copyFrom(Task o) {
233         this.key = o.key;
234         this.group = o.group;
235         this.affiliationTaskId = o.affiliationTaskId;
236         this.affiliationColor = o.affiliationColor;
237         this.icon = o.icon;
238         this.thumbnail = o.thumbnail;
239         this.title = o.title;
240         this.titleDescription = o.titleDescription;
241         this.dismissDescription = o.dismissDescription;
242         this.appInfoDescription = o.appInfoDescription;
243         this.colorPrimary = o.colorPrimary;
244         this.colorBackground = o.colorBackground;
245         this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
246         this.bounds = o.bounds;
247         this.taskDescription = o.taskDescription;
248         this.isLaunchTarget = o.isLaunchTarget;
249         this.isStackTask = o.isStackTask;
250         this.isSystemApp = o.isSystemApp;
251         this.isDockable = o.isDockable;
252         this.resizeMode = o.resizeMode;
253         this.topActivity = o.topActivity;
254     }
255 
256     /**
257      * Add a callback.
258      */
addCallback(TaskCallbacks cb)259     public void addCallback(TaskCallbacks cb) {
260         if (!mCallbacks.contains(cb)) {
261             mCallbacks.add(cb);
262         }
263     }
264 
265     /**
266      * Remove a callback.
267      */
removeCallback(TaskCallbacks cb)268     public void removeCallback(TaskCallbacks cb) {
269         mCallbacks.remove(cb);
270     }
271 
272     /** Set the grouping */
setGroup(TaskGrouping group)273     public void setGroup(TaskGrouping group) {
274         this.group = group;
275     }
276 
277     /**
278      * Updates the stack id of this task.
279      */
setStackId(int stackId)280     public void setStackId(int stackId) {
281         key.setStackId(stackId);
282         int callbackCount = mCallbacks.size();
283         for (int i = 0; i < callbackCount; i++) {
284             mCallbacks.get(i).onTaskStackIdChanged();
285         }
286     }
287 
288     /**
289      * Returns whether this task is on the freeform task stack.
290      */
isFreeformTask()291     public boolean isFreeformTask() {
292         SystemServicesProxy ssp = Recents.getSystemServices();
293         return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
294     }
295 
296     /** Notifies the callback listeners that this task has been loaded */
notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon, ActivityManager.TaskThumbnailInfo thumbnailInfo)297     public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon,
298             ActivityManager.TaskThumbnailInfo thumbnailInfo) {
299         this.icon = applicationIcon;
300         this.thumbnail = thumbnail;
301         int callbackCount = mCallbacks.size();
302         for (int i = 0; i < callbackCount; i++) {
303             mCallbacks.get(i).onTaskDataLoaded(this, thumbnailInfo);
304         }
305     }
306 
307     /** Notifies the callback listeners that this task has been unloaded */
notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon)308     public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
309         icon = defaultApplicationIcon;
310         thumbnail = defaultThumbnail;
311         for (int i = mCallbacks.size() - 1; i >= 0; i--) {
312             mCallbacks.get(i).onTaskDataUnloaded();
313         }
314     }
315 
316     /**
317      * Returns whether this task is affiliated with another task.
318      */
isAffiliatedTask()319     public boolean isAffiliatedTask() {
320         return key.id != affiliationTaskId;
321     }
322 
323     /**
324      * Returns the top activity component.
325      */
getTopComponent()326     public ComponentName getTopComponent() {
327         return topActivity != null
328                 ? topActivity
329                 : key.baseIntent.getComponent();
330     }
331 
332     @Override
equals(Object o)333     public boolean equals(Object o) {
334         // Check that the id matches
335         Task t = (Task) o;
336         return key.equals(t.key);
337     }
338 
339     @Override
toString()340     public String toString() {
341         return "[" + key.toString() + "] " + title;
342     }
343 
dump(String prefix, PrintWriter writer)344     public void dump(String prefix, PrintWriter writer) {
345         writer.print(prefix); writer.print(key);
346         if (isAffiliatedTask()) {
347             writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
348         }
349         if (!isDockable) {
350             writer.print(" dockable=N");
351         }
352         if (isLaunchTarget) {
353             writer.print(" launchTarget=Y");
354         }
355         if (isFreeformTask()) {
356             writer.print(" freeform=Y");
357         }
358         writer.print(" "); writer.print(title);
359         writer.println();
360     }
361 }
362