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