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