• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.recent;
18 
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 
25 import android.app.ActivityManager;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.ActivityInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.content.res.Resources;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.graphics.drawable.Drawable;
36 import android.os.AsyncTask;
37 import android.os.Handler;
38 import android.os.Process;
39 import android.os.SystemClock;
40 import android.util.DisplayMetrics;
41 import android.util.Log;
42 import android.util.LruCache;
43 
44 import com.android.systemui.R;
45 import com.android.systemui.statusbar.phone.PhoneStatusBar;
46 import com.android.systemui.statusbar.tablet.TabletStatusBar;
47 
48 public class RecentTasksLoader {
49     static final String TAG = "RecentTasksLoader";
50     static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
51 
52     private static final int DISPLAY_TASKS = 20;
53     private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
54 
55     private Context mContext;
56     private RecentsPanelView mRecentsPanel;
57 
58     private AsyncTask<Void, Integer, Void> mThumbnailLoader;
59     private final Handler mHandler;
60 
61     private int mIconDpi;
62     private Bitmap mDefaultThumbnailBackground;
63 
RecentTasksLoader(Context context)64     public RecentTasksLoader(Context context) {
65         mContext = context;
66 
67         final Resources res = context.getResources();
68 
69         // get the icon size we want -- on tablets, we use bigger icons
70         boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
71         int density = res.getDisplayMetrics().densityDpi;
72         if (isTablet) {
73             if (density == DisplayMetrics.DENSITY_LOW) {
74                 mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
75             } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
76                 mIconDpi = DisplayMetrics.DENSITY_HIGH;
77             } else if (density == DisplayMetrics.DENSITY_HIGH) {
78                 mIconDpi = DisplayMetrics.DENSITY_XHIGH;
79             } else if (density == DisplayMetrics.DENSITY_XHIGH) {
80                 // We'll need to use a denser icon, or some sort of a mipmap
81                 mIconDpi = DisplayMetrics.DENSITY_XHIGH;
82             }
83         } else {
84             mIconDpi = res.getDisplayMetrics().densityDpi;
85         }
86         mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
87 
88         // Render the default thumbnail background
89         int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
90         int height = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
91         int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
92 
93         mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
94         Canvas c = new Canvas(mDefaultThumbnailBackground);
95         c.drawColor(color);
96 
97         // If we're using the cache, begin listening to the activity manager for
98         // updated thumbnails
99         final ActivityManager am = (ActivityManager)
100                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
101 
102         mHandler = new Handler();
103     }
104 
setRecentsPanel(RecentsPanelView recentsPanel)105     public void setRecentsPanel(RecentsPanelView recentsPanel) {
106         mRecentsPanel = recentsPanel;
107     }
108 
getDefaultThumbnail()109     public Bitmap getDefaultThumbnail() {
110         return mDefaultThumbnailBackground;
111     }
112 
113     // Create an TaskDescription, returning null if the title or icon is null, or if it's the
114     // home activity
createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent, ComponentName origActivity, CharSequence description, ActivityInfo homeInfo)115     TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
116             ComponentName origActivity, CharSequence description, ActivityInfo homeInfo) {
117         Intent intent = new Intent(baseIntent);
118         if (origActivity != null) {
119             intent.setComponent(origActivity);
120         }
121         final PackageManager pm = mContext.getPackageManager();
122         if (homeInfo == null) {
123             homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
124             .resolveActivityInfo(pm, 0);
125         }
126 
127         intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
128                 | Intent.FLAG_ACTIVITY_NEW_TASK);
129         final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
130         if (resolveInfo != null) {
131             final ActivityInfo info = resolveInfo.activityInfo;
132             final String title = info.loadLabel(pm).toString();
133             Drawable icon = getFullResIcon(resolveInfo, pm);
134 
135             if (title != null && title.length() > 0 && icon != null) {
136                 if (DEBUG) Log.v(TAG, "creating activity desc for id="
137                         + persistentTaskId + ", label=" + title);
138 
139                 TaskDescription item = new TaskDescription(taskId,
140                         persistentTaskId, resolveInfo, baseIntent, info.packageName,
141                         description);
142                 item.setLabel(title);
143                 item.setIcon(icon);
144 
145                 // Don't load the current home activity.
146                 if (homeInfo != null
147                         && homeInfo.packageName.equals(intent.getComponent().getPackageName())
148                         && homeInfo.name.equals(intent.getComponent().getClassName())) {
149                     return null;
150                 }
151 
152                 return item;
153             } else {
154                 if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
155             }
156         }
157         return null;
158     }
159 
loadThumbnail(TaskDescription td)160     void loadThumbnail(TaskDescription td) {
161         final ActivityManager am = (ActivityManager)
162                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
163         ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId);
164 
165         if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
166                 + td + ": " + thumbs.mainThumbnail);
167         synchronized (td) {
168             if (thumbs != null && thumbs.mainThumbnail != null) {
169                 td.setThumbnail(thumbs.mainThumbnail);
170             } else {
171                 td.setThumbnail(mDefaultThumbnailBackground);
172             }
173         }
174     }
175 
getFullResDefaultActivityIcon()176     Drawable getFullResDefaultActivityIcon() {
177         return getFullResIcon(Resources.getSystem(),
178                 com.android.internal.R.mipmap.sym_def_app_icon);
179     }
180 
getFullResIcon(Resources resources, int iconId)181     Drawable getFullResIcon(Resources resources, int iconId) {
182         try {
183             return resources.getDrawableForDensity(iconId, mIconDpi);
184         } catch (Resources.NotFoundException e) {
185             return getFullResDefaultActivityIcon();
186         }
187     }
188 
getFullResIcon(ResolveInfo info, PackageManager packageManager)189     private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
190         Resources resources;
191         try {
192             resources = packageManager.getResourcesForApplication(
193                     info.activityInfo.applicationInfo);
194         } catch (PackageManager.NameNotFoundException e) {
195             resources = null;
196         }
197         if (resources != null) {
198             int iconId = info.activityInfo.getIconResource();
199             if (iconId != 0) {
200                 return getFullResIcon(resources, iconId);
201             }
202         }
203         return getFullResDefaultActivityIcon();
204     }
205 
cancelLoadingThumbnails()206     public void cancelLoadingThumbnails() {
207         if (mThumbnailLoader != null) {
208             mThumbnailLoader.cancel(false);
209             mThumbnailLoader = null;
210         }
211     }
212 
213     // return a snapshot of the current list of recent apps
getRecentTasks()214     ArrayList<TaskDescription> getRecentTasks() {
215         cancelLoadingThumbnails();
216 
217         ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
218         final PackageManager pm = mContext.getPackageManager();
219         final ActivityManager am = (ActivityManager)
220                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
221 
222         final List<ActivityManager.RecentTaskInfo> recentTasks =
223                 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
224 
225         ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
226                     .resolveActivityInfo(pm, 0);
227 
228         HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
229         int numTasks = recentTasks.size();
230 
231         // skip the first task - assume it's either the home screen or the current activity.
232         final int first = 1;
233         recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
234         for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
235             final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
236 
237             TaskDescription item = createTaskDescription(recentInfo.id,
238                     recentInfo.persistentId, recentInfo.baseIntent,
239                     recentInfo.origActivity, recentInfo.description, homeInfo);
240 
241             if (item != null) {
242                 tasks.add(item);
243                 ++index;
244             }
245         }
246 
247         // when we're not using the TaskDescription cache, we load the thumbnails in the
248         // background
249         loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
250         return tasks;
251     }
252 
loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions)253     private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) {
254         if (descriptions.size() > 0) {
255             if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks");
256             loadThumbnail(descriptions.get(0));
257             if (descriptions.size() > 1) {
258                 mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
259                     @Override
260                     protected void onProgressUpdate(Integer... values) {
261                         final TaskDescription td = descriptions.get(values[0]);
262                         if (!isCancelled()) {
263                             mRecentsPanel.onTaskThumbnailLoaded(td);
264                         }
265                         // This is to prevent the loader thread from getting ahead
266                         // of our UI updates.
267                         mHandler.post(new Runnable() {
268                             @Override public void run() {
269                                 synchronized (td) {
270                                     td.notifyAll();
271                                 }
272                             }
273                         });
274                     }
275 
276                     @Override
277                     protected Void doInBackground(Void... params) {
278                         final int origPri = Process.getThreadPriority(Process.myTid());
279                         Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
280                         long nextTime = SystemClock.uptimeMillis();
281                         for (int i=1; i<descriptions.size(); i++) {
282                             TaskDescription td = descriptions.get(i);
283                             loadThumbnail(td);
284                             long now = SystemClock.uptimeMillis();
285                             nextTime += 0;
286                             if (nextTime > now) {
287                                 try {
288                                     Thread.sleep(nextTime-now);
289                                 } catch (InterruptedException e) {
290                                 }
291                             }
292 
293                             if (isCancelled()) {
294                                 break;
295                             }
296                             synchronized (td) {
297                                 publishProgress(i);
298                                 try {
299                                     td.wait(500);
300                                 } catch (InterruptedException e) {
301                                 }
302                             }
303                         }
304                         Process.setThreadPriority(origPri);
305                         return null;
306                     }
307                 };
308                 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
309             }
310         }
311     }
312 
313 }
314