blob: a55fe9c0017ca59a33fe84923a40157768444ea3 [file] [log] [blame]
/*
* 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.List;
import android.animation.Animator;
import android.animation.LayoutTransition;
import android.app.ActivityManager;
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.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBar;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.tablet.StatusBarPanel;
import com.android.systemui.statusbar.tablet.TabletStatusBar;
public class RecentsPanelView extends RelativeLayout
implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
static final String TAG = "RecentsListView";
static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG;
private static final int DISPLAY_TASKS = 20;
private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
private StatusBar mBar;
private ArrayList<ActivityDescription> mActivityDescriptions;
private int mIconDpi;
private View mRecentsScrim;
private View mRecentsGlowView;
private View mRecentsContainer;
private Bitmap mGlowBitmap;
// TODO: add these widgets attributes to the layout file
private int mGlowBitmapPaddingLeftPx;
private int mGlowBitmapPaddingTopPx;
private int mGlowBitmapPaddingRightPx;
private int mGlowBitmapPaddingBottomPx;
private boolean mShowing;
private Choreographer mChoreo;
private View mRecentsDismissButton;
private ActivityDescriptionAdapter mListAdapter;
/* package */ final static class ActivityDescription {
int taskId; // application task id for curating apps
Bitmap thumbnail; // generated by Activity.onCreateThumbnail()
Drawable icon; // application package icon
String label; // application package label
CharSequence description; // generated by Activity.onCreateDescription()
Intent intent; // launch intent for application
Matrix matrix; // arbitrary rotation matrix to correct orientation
String packageName; // used to override animations (see onClick())
int position; // position in list
public ActivityDescription(Bitmap _thumbnail,
Drawable _icon, String _label, CharSequence _desc, Intent _intent,
int _id, int _pos, String _packageName)
{
thumbnail = _thumbnail;
icon = _icon;
label = _label;
description = _desc;
intent = _intent;
taskId = _id;
position = _pos;
packageName = _packageName;
}
};
/* package */ final static class ViewHolder {
ImageView thumbnailView;
ImageView iconView;
TextView labelView;
TextView descriptionView;
ActivityDescription activityDescription;
}
/* package */ final class ActivityDescriptionAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public ActivityDescriptionAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return mActivityDescriptions != null ? mActivityDescriptions.size() : 0;
}
public Object getItem(int position) {
return position; // we only need the index
}
public long getItemId(int position) {
return position; // we just need something unique for this position
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.status_bar_recent_item, null);
holder = new ViewHolder();
holder.thumbnailView = (ImageView) convertView.findViewById(R.id.app_thumbnail);
holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// activityId is reverse since most recent appears at the bottom...
final int activityId = mActivityDescriptions.size() - position - 1;
final ActivityDescription activityDescription = mActivityDescriptions.get(activityId);
final Bitmap thumb = activityDescription.thumbnail;
holder.thumbnailView.setImageBitmap(compositeBitmap(mGlowBitmap, thumb));
holder.iconView.setImageDrawable(activityDescription.icon);
holder.labelView.setText(activityDescription.label);
holder.descriptionView.setText(activityDescription.description);
holder.thumbnailView.setTag(activityDescription);
holder.activityDescription = activityDescription;
return convertView;
}
}
public boolean isInContentArea(int x, int y) {
// use mRecentsContainer's exact bounds to determine horizontal position
final int l = mRecentsContainer.getLeft();
final int r = mRecentsContainer.getRight();
// use surrounding mRecentsGlowView's position in parent determine vertical bounds
final int t = mRecentsGlowView.getTop();
final int b = mRecentsGlowView.getBottom();
return x >= l && x < r && y >= t && y < b;
}
public void show(boolean show, boolean animate) {
if (animate) {
if (mShowing != show) {
mShowing = show;
if (show) {
setVisibility(View.VISIBLE);
}
mChoreo.startAnimation(show);
}
} else {
mShowing = show;
setVisibility(show ? View.VISIBLE : View.GONE);
mChoreo.jumpTo(show);
}
}
public void onAnimationCancel(Animator animation) {
}
public void onAnimationEnd(Animator animation) {
if (mShowing) {
final LayoutTransition transitioner = new LayoutTransition();
((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
createCustomAnimations(transitioner);
} else {
((ViewGroup)mRecentsContainer).setLayoutTransition(null);
}
}
public void onAnimationRepeat(Animator animation) {
}
public void onAnimationStart(Animator animation) {
}
/**
* We need to be aligned at the bottom. LinearLayout can't do this, so instead,
* let LinearLayout do all the hard work, and then shift everything down to the bottom.
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mChoreo.setPanelHeight(mRecentsContainer.getHeight());
}
/**
* Whether the panel is showing, or, if it's animating, whether it will be
* when the animation is done.
*/
public boolean isShowing() {
return mShowing;
}
public void setBar(StatusBar bar) {
mBar = bar;
}
public RecentsPanelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources res = context.getResources();
boolean xlarge = (res.getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
mGlowBitmap = BitmapFactory.decodeResource(res, R.drawable.recents_thumbnail_bg);
mGlowBitmapPaddingLeftPx =
res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_left);
mGlowBitmapPaddingTopPx =
res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_top);
mGlowBitmapPaddingRightPx =
res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_right);
mGlowBitmapPaddingBottomPx =
res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_bottom);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRecentsContainer = findViewById(R.id.recents_container);
mListAdapter = new ActivityDescriptionAdapter(mContext);
if (mRecentsContainer instanceof RecentsListView) {
RecentsListView listView = (RecentsListView) mRecentsContainer;
listView.setAdapter(mListAdapter);
listView.setOnItemClickListener(this);
listView.setCallback(this);
} else if (mRecentsContainer instanceof RecentsHorizontalScrollView){
RecentsHorizontalScrollView scrollView
= (RecentsHorizontalScrollView) mRecentsContainer;
scrollView.setAdapter(mListAdapter);
scrollView.setCallback(this);
} else if (mRecentsContainer instanceof RecentsVerticalScrollView){
RecentsVerticalScrollView scrollView
= (RecentsVerticalScrollView) mRecentsContainer;
scrollView.setAdapter(mListAdapter);
scrollView.setCallback(this);
}
else {
throw new IllegalArgumentException("missing RecentsListView/RecentsScrollView");
}
mRecentsGlowView = findViewById(R.id.recents_glow);
mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
mRecentsDismissButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hide(true);
}
});
// In order to save space, we make the background texture repeat in the Y direction
if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) {
((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
}
}
private void createCustomAnimations(LayoutTransition transitioner) {
transitioner.setDuration(LayoutTransition.DISAPPEARING, 250);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")");
if (visibility == View.VISIBLE && changedView == this) {
refreshApplicationList();
}
}
private Drawable getFullResDefaultActivityIcon() {
return getFullResIcon(Resources.getSystem(),
com.android.internal.R.mipmap.sym_def_app_icon);
}
private 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();
}
private ArrayList<ActivityDescription> getRecentTasks() {
ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>();
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);
int numTasks = recentTasks.size();
// skip the first activity - assume it's either the home screen or the current app.
final int first = 1;
for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
Intent intent = new Intent(recentInfo.baseIntent);
if (recentInfo.origActivity != null) {
intent.setComponent(recentInfo.origActivity);
}
// Skip the current home activity.
if (homeInfo != null
&& homeInfo.packageName.equals(intent.getComponent().getPackageName())
&& homeInfo.name.equals(intent.getComponent().getClassName())) {
continue;
}
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 = info.loadIcon(pm);
Drawable icon = getFullResIcon(resolveInfo, pm);
int id = recentTasks.get(i).id;
if (title != null && title.length() > 0 && icon != null) {
if (DEBUG) Log.v(TAG, "creating activity desc for id=" + id + ", label=" + title);
ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
recentInfo.persistentId);
ActivityDescription item = new ActivityDescription(
thumbs != null ? thumbs.mainThumbnail : null,
icon, title, recentInfo.description, intent, id,
index, info.packageName);
activityDescriptions.add(item);
++index;
} else {
if (DEBUG) Log.v(TAG, "SKIPPING item " + id);
}
}
}
return activityDescriptions;
}
ActivityDescription findActivityDescription(int id)
{
ActivityDescription desc = null;
for (int i = 0; i < mActivityDescriptions.size(); i++) {
ActivityDescription item = mActivityDescriptions.get(i);
if (item != null && item.taskId == id) {
desc = item;
break;
}
}
return desc;
}
private void refreshApplicationList() {
mActivityDescriptions = getRecentTasks();
mListAdapter.notifyDataSetInvalidated();
if (mActivityDescriptions.size() > 0) {
if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
updateUiElements(getResources().getConfiguration());
} else {
// Immediately hide this panel
if (DEBUG) Log.v(TAG, "Nothing to show");
hide(false);
}
}
private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
Bitmap outBitmap = background.copy(background.getConfig(), true);
if (thumbnail != null) {
Canvas canvas = new Canvas(outBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setAlpha(255);
final int srcWidth = thumbnail.getWidth();
final int srcHeight = thumbnail.getHeight();
if (DEBUG) Log.v(TAG, "Source thumb: " + srcWidth + "x" + srcHeight);
canvas.drawBitmap(thumbnail,
new Rect(0, 0, srcWidth-1, srcHeight-1),
new RectF(mGlowBitmapPaddingLeftPx, mGlowBitmapPaddingTopPx,
outBitmap.getWidth() - mGlowBitmapPaddingRightPx,
outBitmap.getHeight() - mGlowBitmapPaddingBottomPx), paint);
}
return outBitmap;
}
private void updateUiElements(Configuration config) {
final int items = mActivityDescriptions.size();
mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
}
public void hide(boolean animate) {
if (!animate) {
setVisibility(View.GONE);
}
if (mBar != null) {
mBar.animateCollapse();
}
}
public void handleOnClick(View view) {
ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
final Context context = view.getContext();
final ActivityManager am = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
if (ad.taskId >= 0) {
// This is an active task; it should just go to the foreground.
am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
} else {
Intent intent = ad.intent;
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (DEBUG) Log.v(TAG, "Starting activity " + intent);
context.startActivity(intent);
}
hide(true);
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
handleOnClick(view);
}
public void handleSwipe(View view, int direction) {
ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
if (DEBUG) Log.v(TAG, "Jettison " + ad.label);
mActivityDescriptions.remove(ad);
// Handled by widget containers to enable LayoutTransitions properly
// mListAdapter.notifyDataSetChanged();
if (mActivityDescriptions.size() == 0) {
hide(false);
}
// Currently, either direction means the same thing, so ignore direction and remove
// the task.
final ActivityManager am = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
am.removeTask(ad.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
}
public void handleLongPress(View selectedView) {
// TODO show context menu : "Remove from list", "Show properties"
}
}