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
17package com.android.systemui.recents.model;
18
19import android.app.ActivityManager;
20import android.content.ComponentName;
21import android.content.Intent;
22import android.content.pm.ActivityInfo;
23import android.graphics.Bitmap;
24import android.graphics.Color;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.view.ViewDebug;
28
29import com.android.systemui.recents.Recents;
30import com.android.systemui.recents.misc.SystemServicesProxy;
31import com.android.systemui.recents.misc.Utilities;
32
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Objects;
36
37
38/**
39 * A task represents the top most task in the system's task stack.
40 */
41public 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 */
48        public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo);
49        /* Notifies when a task has been unbound */
50        public void onTaskDataUnloaded();
51        /* Notifies when a task's stack id has changed. */
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
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
83        public void setStackId(int stackId) {
84            this.stackId = stackId;
85            updateHashCode();
86        }
87
88        public ComponentName getComponent() {
89            return this.baseIntent.getComponent();
90        }
91
92        @Override
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
102        public int hashCode() {
103            return mHashCode;
104        }
105
106        @Override
107        public String toString() {
108            return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
109                    lastActiveTime;
110        }
111
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
194    public Task() {
195        // Do nothing
196    }
197
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     */
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     */
259    public void addCallback(TaskCallbacks cb) {
260        if (!mCallbacks.contains(cb)) {
261            mCallbacks.add(cb);
262        }
263    }
264
265    /**
266     * Remove a callback.
267     */
268    public void removeCallback(TaskCallbacks cb) {
269        mCallbacks.remove(cb);
270    }
271
272    /** Set the grouping */
273    public void setGroup(TaskGrouping group) {
274        this.group = group;
275    }
276
277    /**
278     * Updates the stack id of this task.
279     */
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     */
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 */
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 */
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     */
319    public boolean isAffiliatedTask() {
320        return key.id != affiliationTaskId;
321    }
322
323    /**
324     * Returns the top activity component.
325     */
326    public ComponentName getTopComponent() {
327        return topActivity != null
328                ? topActivity
329                : key.baseIntent.getComponent();
330    }
331
332    @Override
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
340    public String toString() {
341        return "[" + key.toString() + "] " + title;
342    }
343
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