/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.recent; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Handler; import android.os.Process; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; import com.android.systemui.R; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.tablet.TabletStatusBar; public class RecentTasksLoader { static final String TAG = "RecentTasksLoader"; static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; private static final int DISPLAY_TASKS = 20; private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps private Context mContext; private RecentsPanelView mRecentsPanel; private AsyncTask<Void, Integer, Void> mThumbnailLoader; private final Handler mHandler; private int mIconDpi; private Bitmap mDefaultThumbnailBackground; public RecentTasksLoader(Context context) { mContext = context; final Resources res = context.getResources(); // get the icon size we want -- on tablets, we use bigger icons boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets); int density = res.getDisplayMetrics().densityDpi; if (isTablet) { if (density == DisplayMetrics.DENSITY_LOW) { mIconDpi = DisplayMetrics.DENSITY_MEDIUM; } else if (density == DisplayMetrics.DENSITY_MEDIUM) { mIconDpi = DisplayMetrics.DENSITY_HIGH; } else if (density == DisplayMetrics.DENSITY_HIGH) { mIconDpi = DisplayMetrics.DENSITY_XHIGH; } else if (density == DisplayMetrics.DENSITY_XHIGH) { // We'll need to use a denser icon, or some sort of a mipmap mIconDpi = DisplayMetrics.DENSITY_XHIGH; } } else { mIconDpi = res.getDisplayMetrics().densityDpi; } mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi; // Render the default thumbnail background int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); int height = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background); mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(mDefaultThumbnailBackground); c.drawColor(color); // If we're using the cache, begin listening to the activity manager for // updated thumbnails final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mHandler = new Handler(); } public void setRecentsPanel(RecentsPanelView recentsPanel) { mRecentsPanel = recentsPanel; } public Bitmap getDefaultThumbnail() { return mDefaultThumbnailBackground; } // Create an TaskDescription, returning null if the title or icon is null, or if it's the // home activity TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent, ComponentName origActivity, CharSequence description, ActivityInfo homeInfo) { Intent intent = new Intent(baseIntent); if (origActivity != null) { intent.setComponent(origActivity); } final PackageManager pm = mContext.getPackageManager(); if (homeInfo == null) { homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) .resolveActivityInfo(pm, 0); } intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) | Intent.FLAG_ACTIVITY_NEW_TASK); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); if (resolveInfo != null) { final ActivityInfo info = resolveInfo.activityInfo; final String title = info.loadLabel(pm).toString(); Drawable icon = getFullResIcon(resolveInfo, pm); if (title != null && title.length() > 0 && icon != null) { if (DEBUG) Log.v(TAG, "creating activity desc for id=" + persistentTaskId + ", label=" + title); TaskDescription item = new TaskDescription(taskId, persistentTaskId, resolveInfo, baseIntent, info.packageName, description); item.setLabel(title); item.setIcon(icon); // Don't load the current home activity. if (homeInfo != null && homeInfo.packageName.equals(intent.getComponent().getPackageName()) && homeInfo.name.equals(intent.getComponent().getClassName())) { return null; } return item; } else { if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId); } } return null; } void loadThumbnail(TaskDescription td) { final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId); if (DEBUG) Log.v(TAG, "Loaded bitmap for task " + td + ": " + thumbs.mainThumbnail); synchronized (td) { if (thumbs != null && thumbs.mainThumbnail != null) { td.setThumbnail(thumbs.mainThumbnail); } else { td.setThumbnail(mDefaultThumbnailBackground); } } } Drawable getFullResDefaultActivityIcon() { return getFullResIcon(Resources.getSystem(), com.android.internal.R.mipmap.sym_def_app_icon); } Drawable getFullResIcon(Resources resources, int iconId) { try { return resources.getDrawableForDensity(iconId, mIconDpi); } catch (Resources.NotFoundException e) { return getFullResDefaultActivityIcon(); } } private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { Resources resources; try { resources = packageManager.getResourcesForApplication( info.activityInfo.applicationInfo); } catch (PackageManager.NameNotFoundException e) { resources = null; } if (resources != null) { int iconId = info.activityInfo.getIconResource(); if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } public void cancelLoadingThumbnails() { if (mThumbnailLoader != null) { mThumbnailLoader.cancel(false); mThumbnailLoader = null; } } // return a snapshot of the current list of recent apps ArrayList<TaskDescription> getRecentTasks() { cancelLoadingThumbnails(); ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>(); final PackageManager pm = mContext.getPackageManager(); final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) .resolveActivityInfo(pm, 0); HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>(); int numTasks = recentTasks.size(); // skip the first task - assume it's either the home screen or the current activity. final int first = 1; recentTasksToKeepInCache.add(recentTasks.get(0).persistentId); for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) { final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i); TaskDescription item = createTaskDescription(recentInfo.id, recentInfo.persistentId, recentInfo.baseIntent, recentInfo.origActivity, recentInfo.description, homeInfo); if (item != null) { tasks.add(item); ++index; } } // when we're not using the TaskDescription cache, we load the thumbnails in the // background loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks)); return tasks; } private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) { if (descriptions.size() > 0) { if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks"); loadThumbnail(descriptions.get(0)); if (descriptions.size() > 1) { mThumbnailLoader = new AsyncTask<Void, Integer, Void>() { @Override protected void onProgressUpdate(Integer... values) { final TaskDescription td = descriptions.get(values[0]); if (!isCancelled()) { mRecentsPanel.onTaskThumbnailLoaded(td); } // This is to prevent the loader thread from getting ahead // of our UI updates. mHandler.post(new Runnable() { @Override public void run() { synchronized (td) { td.notifyAll(); } } }); } @Override protected Void doInBackground(Void... params) { final int origPri = Process.getThreadPriority(Process.myTid()); Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE); long nextTime = SystemClock.uptimeMillis(); for (int i=1; i<descriptions.size(); i++) { TaskDescription td = descriptions.get(i); loadThumbnail(td); long now = SystemClock.uptimeMillis(); nextTime += 0; if (nextTime > now) { try { Thread.sleep(nextTime-now); } catch (InterruptedException e) { } } if (isCancelled()) { break; } synchronized (td) { publishProgress(i); try { td.wait(500); } catch (InterruptedException e) { } } } Process.setThreadPriority(origPri); return null; } }; mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } } }