| /* |
| * 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); |
| } |
| } |
| } |
| |
| } |