blob: d19b98a20cb156f6af8c6a5f44d337eba2a9bd39 [file] [log] [blame]
Jim Miller44c66fe2010-10-20 18:32:52 -07001/*
Jim Miller9f0f0e02011-05-17 20:06:29 -07002 * Copyright (C) 2011 The Android Open Source Project
Jim Miller44c66fe2010-10-20 18:32:52 -07003 *
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
Jim Miller9f0f0e02011-05-17 20:06:29 -070017package com.android.systemui.recent;
Jim Miller44c66fe2010-10-20 18:32:52 -070018
19import java.util.ArrayList;
20import java.util.List;
21
Michael Jurka3b1fc472011-06-13 10:54:40 -070022import android.animation.Animator;
Jim Miller9f0f0e02011-05-17 20:06:29 -070023import android.animation.LayoutTransition;
Jim Miller44c66fe2010-10-20 18:32:52 -070024import android.app.ActivityManager;
Jim Miller44c66fe2010-10-20 18:32:52 -070025import android.content.Context;
26import android.content.Intent;
Jim Miller44c66fe2010-10-20 18:32:52 -070027import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
Jim Millere5c16ab2010-12-03 19:18:38 -080030import android.content.res.Configuration;
31import android.content.res.Resources;
Jim Miller44c66fe2010-10-20 18:32:52 -070032import android.graphics.Bitmap;
Jim Millerf9905b42010-12-22 20:37:17 -080033import android.graphics.BitmapFactory;
Jim Millere5c16ab2010-12-03 19:18:38 -080034import android.graphics.Canvas;
Jim Millerfefef312010-11-09 14:08:39 -080035import android.graphics.Matrix;
Jim Millere5c16ab2010-12-03 19:18:38 -080036import android.graphics.Paint;
37import android.graphics.Rect;
Jim Millerf9905b42010-12-22 20:37:17 -080038import android.graphics.RectF;
Jim Millere5c16ab2010-12-03 19:18:38 -080039import android.graphics.Shader.TileMode;
40import android.graphics.drawable.BitmapDrawable;
Jim Miller44c66fe2010-10-20 18:32:52 -070041import android.graphics.drawable.Drawable;
Jim Millerc0d27312011-07-14 18:54:01 -070042import android.net.Uri;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070043import android.os.AsyncTask;
44import android.os.Handler;
45import android.os.Process;
46import android.os.SystemClock;
Jim Millerc0d27312011-07-14 18:54:01 -070047import android.provider.Settings;
Jim Miller44c66fe2010-10-20 18:32:52 -070048import android.util.AttributeSet;
Jim Millere5c16ab2010-12-03 19:18:38 -080049import android.util.DisplayMetrics;
Jim Miller44c66fe2010-10-20 18:32:52 -070050import android.util.Log;
Michael Jurkae9166ed2011-07-27 18:23:18 -070051import android.view.KeyEvent;
Jim Miller5fda69c2011-02-16 15:17:11 -080052import android.view.LayoutInflater;
Jim Millerc0d27312011-07-14 18:54:01 -070053import android.view.MenuItem;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070054import android.view.MotionEvent;
Jim Miller44c66fe2010-10-20 18:32:52 -070055import android.view.View;
Jim Miller5fda69c2011-02-16 15:17:11 -080056import android.view.ViewGroup;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070057import android.view.animation.AnimationUtils;
Jim Miller5fda69c2011-02-16 15:17:11 -080058import android.widget.AdapterView;
Jim Miller5fda69c2011-02-16 15:17:11 -080059import android.widget.BaseAdapter;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070060import android.widget.HorizontalScrollView;
Jim Miller44c66fe2010-10-20 18:32:52 -070061import android.widget.ImageView;
Jim Millerc0d27312011-07-14 18:54:01 -070062import android.widget.PopupMenu;
Jim Miller85babff2011-01-11 14:26:03 -080063import android.widget.RelativeLayout;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070064import android.widget.ScrollView;
Jim Miller44c66fe2010-10-20 18:32:52 -070065import android.widget.TextView;
Michael Jurka3cd0a592011-08-16 12:40:30 -070066import android.widget.AdapterView.OnItemClickListener;
Jim Miller44c66fe2010-10-20 18:32:52 -070067
68import com.android.systemui.R;
Michael Jurka3b1fc472011-06-13 10:54:40 -070069import com.android.systemui.statusbar.StatusBar;
70import com.android.systemui.statusbar.phone.PhoneStatusBar;
Jim Miller9f0f0e02011-05-17 20:06:29 -070071import com.android.systemui.statusbar.tablet.StatusBarPanel;
72import com.android.systemui.statusbar.tablet.TabletStatusBar;
Jim Miller44c66fe2010-10-20 18:32:52 -070073
Jim Miller9f0f0e02011-05-17 20:06:29 -070074public class RecentsPanelView extends RelativeLayout
Michael Jurka3b1fc472011-06-13 10:54:40 -070075 implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
Michael Jurka3cd0a592011-08-16 12:40:30 -070076 static final String TAG = "RecentsPanelView";
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070077 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
Jim Miller5fda69c2011-02-16 15:17:11 -080078 private static final int DISPLAY_TASKS = 20;
79 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
Michael Jurka3b1fc472011-06-13 10:54:40 -070080 private StatusBar mBar;
Jim Millerfefef312010-11-09 14:08:39 -080081 private ArrayList<ActivityDescription> mActivityDescriptions;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070082 private AsyncTask<Void, Integer, Void> mThumbnailLoader;
Jim Millere5c16ab2010-12-03 19:18:38 -080083 private int mIconDpi;
Jim Miller85babff2011-01-11 14:26:03 -080084 private View mRecentsScrim;
85 private View mRecentsGlowView;
Jim Millerc0d27312011-07-14 18:54:01 -070086 private ViewGroup mRecentsContainer;
Peter Ng622a9762011-08-29 10:56:53 -070087 private Bitmap mAppThumbnailBackground;
88
Jim Miller85babff2011-01-11 14:26:03 -080089 private boolean mShowing;
90 private Choreographer mChoreo;
Jim Millerf1e22e52011-01-26 15:39:51 -080091 private View mRecentsDismissButton;
Michael Jurka3b1fc472011-06-13 10:54:40 -070092 private ActivityDescriptionAdapter mListAdapter;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070093 private final Handler mHandler = new Handler();
Jim Millerfefef312010-11-09 14:08:39 -080094
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070095 /* package */ final class ActivityDescription {
96 final ActivityManager.RecentTaskInfo recentTaskInfo;
97 final ResolveInfo resolveInfo;
Jim Miller9f0f0e02011-05-17 20:06:29 -070098 int taskId; // application task id for curating apps
Jim Millerfefef312010-11-09 14:08:39 -080099 Intent intent; // launch intent for application
100 Matrix matrix; // arbitrary rotation matrix to correct orientation
Jim Millere5c16ab2010-12-03 19:18:38 -0800101 String packageName; // used to override animations (see onClick())
Jim Millerfefef312010-11-09 14:08:39 -0800102 int position; // position in list
103
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700104 private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail()
105 private Drawable mIcon; // application package icon
106 private CharSequence mLabel; // application package label
107
108 public ActivityDescription(ActivityManager.RecentTaskInfo _recentInfo,
109 ResolveInfo _resolveInfo, Intent _intent,
110 int _id, int _pos, String _packageName) {
111 recentTaskInfo = _recentInfo;
112 resolveInfo = _resolveInfo;
Jim Millerfefef312010-11-09 14:08:39 -0800113 intent = _intent;
Jim Miller9f0f0e02011-05-17 20:06:29 -0700114 taskId = _id;
Jim Millerfefef312010-11-09 14:08:39 -0800115 position = _pos;
Jim Millere5c16ab2010-12-03 19:18:38 -0800116 packageName = _packageName;
Jim Millerfefef312010-11-09 14:08:39 -0800117 }
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700118
119 public CharSequence getLabel() {
120 return mLabel;
121 }
122
123 public Drawable getIcon() {
124 return mIcon;
125 }
126
127 public void setThumbnail(Bitmap thumbnail) {
Peter Ng622a9762011-08-29 10:56:53 -0700128 mThumbnail = compositeBitmap(mAppThumbnailBackground, thumbnail);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700129 }
130
131 public Bitmap getThumbnail() {
132 return mThumbnail;
133 }
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700134 }
Jim Millerfefef312010-11-09 14:08:39 -0800135
Jim Millerc0d27312011-07-14 18:54:01 -0700136 private final class OnLongClickDelegate implements View.OnLongClickListener {
137 View mOtherView;
138 OnLongClickDelegate(View other) {
139 mOtherView = other;
140 }
141 public boolean onLongClick(View v) {
142 return mOtherView.performLongClick();
143 }
144 }
145
Jim Millerb5a2b292011-03-01 16:14:59 -0800146 /* package */ final static class ViewHolder {
Jim Millerc0d27312011-07-14 18:54:01 -0700147 View thumbnailView;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700148 ImageView thumbnailViewImage;
Jim Miller9f0f0e02011-05-17 20:06:29 -0700149 ImageView iconView;
150 TextView labelView;
151 TextView descriptionView;
152 ActivityDescription activityDescription;
Jim Miller5fda69c2011-02-16 15:17:11 -0800153 }
154
Michael Jurka3b1fc472011-06-13 10:54:40 -0700155 /* package */ final class ActivityDescriptionAdapter extends BaseAdapter {
Jim Miller5fda69c2011-02-16 15:17:11 -0800156 private LayoutInflater mInflater;
157
Michael Jurka3b1fc472011-06-13 10:54:40 -0700158 public ActivityDescriptionAdapter(Context context) {
Jim Miller5fda69c2011-02-16 15:17:11 -0800159 mInflater = LayoutInflater.from(context);
160 }
161
162 public int getCount() {
163 return mActivityDescriptions != null ? mActivityDescriptions.size() : 0;
164 }
165
166 public Object getItem(int position) {
167 return position; // we only need the index
168 }
169
170 public long getItemId(int position) {
171 return position; // we just need something unique for this position
172 }
173
174 public View getView(int position, View convertView, ViewGroup parent) {
175 ViewHolder holder;
176 if (convertView == null) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700177 convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false);
Jim Miller5fda69c2011-02-16 15:17:11 -0800178 holder = new ViewHolder();
Jim Millerc0d27312011-07-14 18:54:01 -0700179 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700180 holder.thumbnailViewImage = (ImageView) convertView.findViewById(
181 R.id.app_thumbnail_image);
Jim Miller5fda69c2011-02-16 15:17:11 -0800182 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
183 holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
184 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
185 convertView.setTag(holder);
186 } else {
187 holder = (ViewHolder) convertView.getTag();
188 }
189
190 // activityId is reverse since most recent appears at the bottom...
191 final int activityId = mActivityDescriptions.size() - position - 1;
192
193 final ActivityDescription activityDescription = mActivityDescriptions.get(activityId);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700194 holder.thumbnailViewImage.setImageBitmap(activityDescription.getThumbnail());
195 holder.iconView.setImageDrawable(activityDescription.getIcon());
196 holder.labelView.setText(activityDescription.getLabel());
197 holder.descriptionView.setText(activityDescription.recentTaskInfo.description);
Jim Miller5fda69c2011-02-16 15:17:11 -0800198 holder.thumbnailView.setTag(activityDescription);
Jim Millerc0d27312011-07-14 18:54:01 -0700199 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
Jim Miller5fda69c2011-02-16 15:17:11 -0800200 holder.activityDescription = activityDescription;
201
202 return convertView;
203 }
204 }
205
Michael Jurkae9166ed2011-07-27 18:23:18 -0700206 @Override
207 public boolean onKeyUp(int keyCode, KeyEvent event) {
208 if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) {
209 show(false, true);
210 return true;
211 }
212 return super.onKeyUp(keyCode, event);
213 }
214
Jim Miller44c66fe2010-10-20 18:32:52 -0700215 public boolean isInContentArea(int x, int y) {
Jim Miller6f5bd3c2011-01-25 17:03:27 -0800216 // use mRecentsContainer's exact bounds to determine horizontal position
217 final int l = mRecentsContainer.getLeft();
218 final int r = mRecentsContainer.getRight();
219 // use surrounding mRecentsGlowView's position in parent determine vertical bounds
220 final int t = mRecentsGlowView.getTop();
221 final int b = mRecentsGlowView.getBottom();
Jim Miller44c66fe2010-10-20 18:32:52 -0700222 return x >= l && x < r && y >= t && y < b;
223 }
224
Jim Miller85babff2011-01-11 14:26:03 -0800225 public void show(boolean show, boolean animate) {
226 if (animate) {
227 if (mShowing != show) {
228 mShowing = show;
229 if (show) {
230 setVisibility(View.VISIBLE);
231 }
232 mChoreo.startAnimation(show);
233 }
234 } else {
235 mShowing = show;
236 setVisibility(show ? View.VISIBLE : View.GONE);
237 mChoreo.jumpTo(show);
238 }
Michael Jurkae9166ed2011-07-27 18:23:18 -0700239 if (show) {
240 setFocusable(true);
241 setFocusableInTouchMode(true);
242 requestFocus();
243 }
Jim Miller85babff2011-01-11 14:26:03 -0800244 }
245
Michael Jurka3cd0a592011-08-16 12:40:30 -0700246 public void handleShowBackground(boolean show) {
247 if (show) {
248 mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background);
249 } else {
250 mRecentsScrim.setBackgroundDrawable(null);
251 }
252 }
253
254 public boolean isRecentsVisible() {
255 return getVisibility() == VISIBLE;
256 }
257
Michael Jurka3b1fc472011-06-13 10:54:40 -0700258 public void onAnimationCancel(Animator animation) {
259 }
260
261 public void onAnimationEnd(Animator animation) {
262 if (mShowing) {
263 final LayoutTransition transitioner = new LayoutTransition();
264 ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
265 createCustomAnimations(transitioner);
266 } else {
267 ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
268 }
269 }
270
271 public void onAnimationRepeat(Animator animation) {
272 }
273
274 public void onAnimationStart(Animator animation) {
275 }
276
277
Jim Miller85babff2011-01-11 14:26:03 -0800278 /**
279 * We need to be aligned at the bottom. LinearLayout can't do this, so instead,
280 * let LinearLayout do all the hard work, and then shift everything down to the bottom.
281 */
282 @Override
283 protected void onLayout(boolean changed, int l, int t, int r, int b) {
284 super.onLayout(changed, l, t, r, b);
285 mChoreo.setPanelHeight(mRecentsContainer.getHeight());
286 }
287
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700288 @Override
289 public boolean dispatchHoverEvent(MotionEvent event) {
290 // Ignore hover events outside of this panel bounds since such events
291 // generate spurious accessibility events with the panel content when
292 // tapping outside of it, thus confusing the user.
293 final int x = (int) event.getX();
294 final int y = (int) event.getY();
295 if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
296 return super.dispatchHoverEvent(event);
297 }
298 return true;
299 }
300
Jim Miller85babff2011-01-11 14:26:03 -0800301 /**
302 * Whether the panel is showing, or, if it's animating, whether it will be
303 * when the animation is done.
304 */
305 public boolean isShowing() {
306 return mShowing;
307 }
308
Michael Jurka3b1fc472011-06-13 10:54:40 -0700309 public void setBar(StatusBar bar) {
Jim Miller44c66fe2010-10-20 18:32:52 -0700310 mBar = bar;
311 }
312
Jim Miller9f0f0e02011-05-17 20:06:29 -0700313 public RecentsPanelView(Context context, AttributeSet attrs) {
Jim Miller44c66fe2010-10-20 18:32:52 -0700314 this(context, attrs, 0);
315 }
316
Jim Miller9f0f0e02011-05-17 20:06:29 -0700317 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
Jim Miller44c66fe2010-10-20 18:32:52 -0700318 super(context, attrs, defStyle);
Jim Millere5c16ab2010-12-03 19:18:38 -0800319
320 Resources res = context.getResources();
321 boolean xlarge = (res.getConfiguration().screenLayout
322 & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
323
324 mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
Michael Jurka3b1fc472011-06-13 10:54:40 -0700325
Peter Ng622a9762011-08-29 10:56:53 -0700326 int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width);
327 int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height);
328 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
329 mAppThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
330 Canvas c = new Canvas(mAppThumbnailBackground);
331 c.drawColor(color);
Jim Miller44c66fe2010-10-20 18:32:52 -0700332 }
333
334 @Override
335 protected void onFinishInflate() {
336 super.onFinishInflate();
Jim Miller5fda69c2011-02-16 15:17:11 -0800337 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Jim Millerc0d27312011-07-14 18:54:01 -0700338 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
Michael Jurka3b1fc472011-06-13 10:54:40 -0700339 mListAdapter = new ActivityDescriptionAdapter(mContext);
Michael Jurka3cd0a592011-08-16 12:40:30 -0700340 if (mRecentsContainer instanceof RecentsHorizontalScrollView){
Jim Miller9f0f0e02011-05-17 20:06:29 -0700341 RecentsHorizontalScrollView scrollView
342 = (RecentsHorizontalScrollView) mRecentsContainer;
343 scrollView.setAdapter(mListAdapter);
344 scrollView.setCallback(this);
345 } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
346 RecentsVerticalScrollView scrollView
347 = (RecentsVerticalScrollView) mRecentsContainer;
348 scrollView.setAdapter(mListAdapter);
349 scrollView.setCallback(this);
350 }
351 else {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700352 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
Jim Miller9f0f0e02011-05-17 20:06:29 -0700353 }
Jim Miller5fda69c2011-02-16 15:17:11 -0800354
Jim Miller5fda69c2011-02-16 15:17:11 -0800355
Jim Miller169a2112010-12-10 18:30:07 -0800356 mRecentsGlowView = findViewById(R.id.recents_glow);
Michael Jurkad4ae1de2011-07-21 17:10:21 -0700357 mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
Michael Jurka3b1fc472011-06-13 10:54:40 -0700358 mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
Jim Millerf1e22e52011-01-26 15:39:51 -0800359 mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
360 mRecentsDismissButton.setOnClickListener(new OnClickListener() {
361 public void onClick(View v) {
362 hide(true);
363 }
364 });
Jim Millere5c16ab2010-12-03 19:18:38 -0800365
366 // In order to save space, we make the background texture repeat in the Y direction
Jim Miller85babff2011-01-11 14:26:03 -0800367 if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) {
368 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
Jim Millere5c16ab2010-12-03 19:18:38 -0800369 }
370 }
371
Jim Miller9f0f0e02011-05-17 20:06:29 -0700372 private void createCustomAnimations(LayoutTransition transitioner) {
Chet Haase212f6252011-08-12 09:19:10 -0700373 transitioner.setDuration(200);
374 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
375 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700376 }
377
Jim Millere5c16ab2010-12-03 19:18:38 -0800378 @Override
Jim Miller44c66fe2010-10-20 18:32:52 -0700379 protected void onVisibilityChanged(View changedView, int visibility) {
380 super.onVisibilityChanged(changedView, visibility);
Jim Millere5c16ab2010-12-03 19:18:38 -0800381 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")");
Jim Miller44c66fe2010-10-20 18:32:52 -0700382 if (visibility == View.VISIBLE && changedView == this) {
Jim Millerfefef312010-11-09 14:08:39 -0800383 refreshApplicationList();
Jim Miller44c66fe2010-10-20 18:32:52 -0700384 }
Michael Jurka3cd0a592011-08-16 12:40:30 -0700385
386 if (mRecentsContainer instanceof RecentsHorizontalScrollView) {
387 ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
388 } else if (mRecentsContainer instanceof RecentsVerticalScrollView) {
389 ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
390 } else {
391 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
392 }
Jim Miller44c66fe2010-10-20 18:32:52 -0700393 }
394
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700395 Drawable getFullResDefaultActivityIcon() {
Jim Millere5c16ab2010-12-03 19:18:38 -0800396 return getFullResIcon(Resources.getSystem(),
Michael Jurka573068c2011-01-17 13:29:53 -0800397 com.android.internal.R.mipmap.sym_def_app_icon);
Jim Millere5c16ab2010-12-03 19:18:38 -0800398 }
399
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700400 Drawable getFullResIcon(Resources resources, int iconId) {
Michael Jurka4917b0d2011-07-07 15:42:30 -0700401 try {
402 return resources.getDrawableForDensity(iconId, mIconDpi);
403 } catch (Resources.NotFoundException e) {
404 return getFullResDefaultActivityIcon();
405 }
Jim Millere5c16ab2010-12-03 19:18:38 -0800406 }
407
408 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
409 Resources resources;
410 try {
411 resources = packageManager.getResourcesForApplication(
412 info.activityInfo.applicationInfo);
413 } catch (PackageManager.NameNotFoundException e) {
414 resources = null;
415 }
416 if (resources != null) {
417 int iconId = info.activityInfo.getIconResource();
418 if (iconId != 0) {
419 return getFullResIcon(resources, iconId);
420 }
421 }
422 return getFullResDefaultActivityIcon();
423 }
424
Jim Millerfefef312010-11-09 14:08:39 -0800425 private ArrayList<ActivityDescription> getRecentTasks() {
426 ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>();
427 final PackageManager pm = mContext.getPackageManager();
Jim Miller44c66fe2010-10-20 18:32:52 -0700428 final ActivityManager am = (ActivityManager)
Jim Millerfefef312010-11-09 14:08:39 -0800429 mContext.getSystemService(Context.ACTIVITY_SERVICE);
Jim Miller44c66fe2010-10-20 18:32:52 -0700430
Jim Millerfefef312010-11-09 14:08:39 -0800431 final List<ActivityManager.RecentTaskInfo> recentTasks =
Dianne Hackborn0c5001d2011-04-12 18:16:08 -0700432 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
Jim Millerfefef312010-11-09 14:08:39 -0800433
434 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
435 .resolveActivityInfo(pm, 0);
Jim Miller44c66fe2010-10-20 18:32:52 -0700436
437 int numTasks = recentTasks.size();
Jim Millerd19bede2010-12-14 17:17:05 -0800438
439 // skip the first activity - assume it's either the home screen or the current app.
440 final int first = 1;
441 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
Jim Millerfefef312010-11-09 14:08:39 -0800442 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
Jim Miller44c66fe2010-10-20 18:32:52 -0700443
Jim Millerfefef312010-11-09 14:08:39 -0800444 Intent intent = new Intent(recentInfo.baseIntent);
445 if (recentInfo.origActivity != null) {
446 intent.setComponent(recentInfo.origActivity);
Jim Miller44c66fe2010-10-20 18:32:52 -0700447 }
448
Jim Millerfefef312010-11-09 14:08:39 -0800449 // Skip the current home activity.
Jim Miller44c66fe2010-10-20 18:32:52 -0700450 if (homeInfo != null
451 && homeInfo.packageName.equals(intent.getComponent().getPackageName())
452 && homeInfo.name.equals(intent.getComponent().getClassName())) {
Jim Millerfefef312010-11-09 14:08:39 -0800453 continue;
Jim Miller44c66fe2010-10-20 18:32:52 -0700454 }
455
456 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
457 | Intent.FLAG_ACTIVITY_NEW_TASK);
458 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
459 if (resolveInfo != null) {
Jim Millerfefef312010-11-09 14:08:39 -0800460 final ActivityInfo info = resolveInfo.activityInfo;
461 final String title = info.loadLabel(pm).toString();
Jim Millere5c16ab2010-12-03 19:18:38 -0800462 // Drawable icon = info.loadIcon(pm);
463 Drawable icon = getFullResIcon(resolveInfo, pm);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700464 int id = recentInfo.id;
Jim Miller44c66fe2010-10-20 18:32:52 -0700465 if (title != null && title.length() > 0 && icon != null) {
Jim Millere5c16ab2010-12-03 19:18:38 -0800466 if (DEBUG) Log.v(TAG, "creating activity desc for id=" + id + ", label=" + title);
Dianne Hackbornf26fd992011-04-08 18:14:09 -0700467 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
468 recentInfo.persistentId);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700469 ActivityDescription item = new ActivityDescription(recentInfo,
470 resolveInfo, intent, id, index, info.packageName);
Jim Millerfefef312010-11-09 14:08:39 -0800471 activityDescriptions.add(item);
472 ++index;
473 } else {
Jim Millere5c16ab2010-12-03 19:18:38 -0800474 if (DEBUG) Log.v(TAG, "SKIPPING item " + id);
Jim Miller44c66fe2010-10-20 18:32:52 -0700475 }
476 }
477 }
Jim Millerfefef312010-11-09 14:08:39 -0800478 return activityDescriptions;
479 }
480
481 ActivityDescription findActivityDescription(int id)
482 {
483 ActivityDescription desc = null;
484 for (int i = 0; i < mActivityDescriptions.size(); i++) {
485 ActivityDescription item = mActivityDescriptions.get(i);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700486 if (item != null && item.taskId == id) {
Jim Millerfefef312010-11-09 14:08:39 -0800487 desc = item;
488 break;
489 }
490 }
491 return desc;
492 }
493
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700494 void loadActivityDescription(ActivityDescription ad, int index) {
495 final ActivityManager am = (ActivityManager)
496 mContext.getSystemService(Context.ACTIVITY_SERVICE);
497 final PackageManager pm = mContext.getPackageManager();
498 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
499 ad.recentTaskInfo.persistentId);
500 CharSequence label = ad.resolveInfo.activityInfo.loadLabel(pm);
501 Drawable icon = getFullResIcon(ad.resolveInfo, pm);
502 if (DEBUG) Log.v(TAG, "Loaded bitmap for #" + index + " in "
503 + ad + ": " + thumbs.mainThumbnail);
504 synchronized (ad) {
505 ad.mLabel = label;
506 ad.mIcon = icon;
Dianne Hackborncfb9f2b2011-08-24 10:51:49 -0700507 ad.setThumbnail(thumbs != null ? thumbs.mainThumbnail : null);
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700508 }
509 }
510
511 void applyActivityDescription(ActivityDescription ad, int index, boolean anim) {
512 synchronized (ad) {
513 if (mRecentsContainer != null) {
514 ViewGroup container = mRecentsContainer;
515 if (container instanceof HorizontalScrollView
516 || container instanceof ScrollView) {
517 container = (ViewGroup)container.findViewById(
518 R.id.recents_linear_layout);
519 }
520 // Look for a view showing this thumbnail, to update.
521 for (int i=0; i<container.getChildCount(); i++) {
522 View v = container.getChildAt(i);
523 if (v.getTag() instanceof ViewHolder) {
524 ViewHolder h = (ViewHolder)v.getTag();
525 if (h.activityDescription == ad) {
526 if (DEBUG) Log.v(TAG, "Updatating thumbnail #" + index + " in "
527 + h.activityDescription
528 + ": " + ad.getThumbnail());
529 h.iconView.setImageDrawable(ad.getIcon());
530 if (anim) {
531 h.iconView.setAnimation(AnimationUtils.loadAnimation(
532 mContext, R.anim.recent_appear));
533 }
534 h.iconView.setVisibility(View.VISIBLE);
535 h.labelView.setText(ad.getLabel());
536 if (anim) {
537 h.labelView.setAnimation(AnimationUtils.loadAnimation(
538 mContext, R.anim.recent_appear));
539 }
540 h.labelView.setVisibility(View.VISIBLE);
541 Bitmap thumbnail = ad.getThumbnail();
542 if (thumbnail != null) {
543 // Should remove the default image in the frame
544 // that this now covers, to improve scrolling speed.
545 // That can't be done until the anim is complete though.
546 h.thumbnailViewImage.setImageBitmap(thumbnail);
547 if (anim) {
548 h.thumbnailViewImage.setAnimation(AnimationUtils.loadAnimation(
549 mContext, R.anim.recent_appear));
550 }
551 h.thumbnailViewImage.setVisibility(View.VISIBLE);
552 }
553 }
554 }
555 }
556 }
557 }
558 }
559
Jim Millerfefef312010-11-09 14:08:39 -0800560 private void refreshApplicationList() {
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700561 if (mThumbnailLoader != null) {
562 mThumbnailLoader.cancel(false);
563 mThumbnailLoader = null;
564 }
Jim Millerfefef312010-11-09 14:08:39 -0800565 mActivityDescriptions = getRecentTasks();
Peter Ng622a9762011-08-29 10:56:53 -0700566 for (ActivityDescription ad : mActivityDescriptions) {
567 ad.setThumbnail(mAppThumbnailBackground);
568 }
Jim Miller5fda69c2011-02-16 15:17:11 -0800569 mListAdapter.notifyDataSetInvalidated();
Jim Miller01f99442011-01-10 21:02:25 -0800570 if (mActivityDescriptions.size() > 0) {
Jim Miller3c728fe2011-07-07 20:53:52 -0700571 if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
Jim Miller5fda69c2011-02-16 15:17:11 -0800572 updateUiElements(getResources().getConfiguration());
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700573 final ArrayList<ActivityDescription> descriptions = mActivityDescriptions;
574 loadActivityDescription(descriptions.get(0), 0);
575 applyActivityDescription(descriptions.get(0), 0, false);
576 if (descriptions.size() > 1) {
577 mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
578 @Override
579 protected void onProgressUpdate(Integer... values) {
580 final ActivityDescription ad = descriptions.get(values[0]);
581 if (!isCancelled()) {
582 applyActivityDescription(ad, values[0], true);
583 }
584 // This is to prevent the loader thread from getting ahead
585 // of our UI updates.
586 mHandler.post(new Runnable() {
587 @Override public void run() {
588 synchronized (ad) {
589 ad.notifyAll();
590 }
591 }
592 });
593 }
594
595 @Override
596 protected Void doInBackground(Void... params) {
597 final int origPri = Process.getThreadPriority(Process.myTid());
598 Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
599 long nextTime = SystemClock.uptimeMillis();
600 for (int i=1; i<descriptions.size(); i++) {
601 ActivityDescription ad = descriptions.get(i);
602 loadActivityDescription(ad, i);
603 long now = SystemClock.uptimeMillis();
Dianne Hackborncfb9f2b2011-08-24 10:51:49 -0700604 nextTime += 150;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700605 if (nextTime > now) {
606 try {
607 Thread.sleep(nextTime-now);
608 } catch (InterruptedException e) {
609 }
610 }
611 if (isCancelled()) {
612 break;
613 }
614 synchronized (ad) {
615 publishProgress(i);
616 try {
617 ad.wait(500);
618 } catch (InterruptedException e) {
619 }
620 }
621 }
622 Process.setThreadPriority(origPri);
623 return null;
624 }
625 };
626 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
627 }
Jim Miller01f99442011-01-10 21:02:25 -0800628 } else {
Jim Miller85babff2011-01-11 14:26:03 -0800629 // Immediately hide this panel
Jim Miller3c728fe2011-07-07 20:53:52 -0700630 if (DEBUG) Log.v(TAG, "Nothing to show");
Jim Millerf1e22e52011-01-26 15:39:51 -0800631 hide(false);
Jim Miller01f99442011-01-10 21:02:25 -0800632 }
Jim Millerfefef312010-11-09 14:08:39 -0800633 }
634
Jim Millerf9905b42010-12-22 20:37:17 -0800635 private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
636 Bitmap outBitmap = background.copy(background.getConfig(), true);
Jim Miller25eef892011-01-04 13:52:22 -0800637 if (thumbnail != null) {
638 Canvas canvas = new Canvas(outBitmap);
639 Paint paint = new Paint();
640 paint.setAntiAlias(true);
641 paint.setFilterBitmap(true);
642 paint.setAlpha(255);
Peter Ng622a9762011-08-29 10:56:53 -0700643 canvas.drawBitmap(thumbnail, null,
644 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700645 canvas.setBitmap(null);
Jim Miller25eef892011-01-04 13:52:22 -0800646 }
Jim Millere5c16ab2010-12-03 19:18:38 -0800647 return outBitmap;
648 }
649
Jim Miller5fda69c2011-02-16 15:17:11 -0800650 private void updateUiElements(Configuration config) {
651 final int items = mActivityDescriptions.size();
Jim Millere5c16ab2010-12-03 19:18:38 -0800652
Jim Miller5fda69c2011-02-16 15:17:11 -0800653 mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
654 mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
Jim Miller44c66fe2010-10-20 18:32:52 -0700655 }
656
Jim Miller9f0f0e02011-05-17 20:06:29 -0700657 public void hide(boolean animate) {
Jim Miller5fda69c2011-02-16 15:17:11 -0800658 if (!animate) {
659 setVisibility(View.GONE);
660 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700661 if (mBar != null) {
662 mBar.animateCollapse();
663 }
Jim Miller5fda69c2011-02-16 15:17:11 -0800664 }
665
Jim Miller9f0f0e02011-05-17 20:06:29 -0700666 public void handleOnClick(View view) {
Jim Miller5fda69c2011-02-16 15:17:11 -0800667 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
Jim Miller9f0f0e02011-05-17 20:06:29 -0700668 final Context context = view.getContext();
Jim Millere5c16ab2010-12-03 19:18:38 -0800669 final ActivityManager am = (ActivityManager)
Jim Miller9f0f0e02011-05-17 20:06:29 -0700670 context.getSystemService(Context.ACTIVITY_SERVICE);
671 if (ad.taskId >= 0) {
Dianne Hackbornb6777462010-11-12 12:43:22 -0800672 // This is an active task; it should just go to the foreground.
Jim Miller9f0f0e02011-05-17 20:06:29 -0700673 am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
Dianne Hackbornb6777462010-11-12 12:43:22 -0800674 } else {
675 Intent intent = ad.intent;
Dianne Hackborn621e17d2010-11-22 15:59:56 -0800676 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
677 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Dianne Hackbornb6777462010-11-12 12:43:22 -0800678 if (DEBUG) Log.v(TAG, "Starting activity " + intent);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700679 context.startActivity(intent);
Dianne Hackbornb6777462010-11-12 12:43:22 -0800680 }
Jim Millerf1e22e52011-01-26 15:39:51 -0800681 hide(true);
682 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700683
684 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
685 handleOnClick(view);
686 }
687
Michael Jurka07d40462011-07-19 10:54:38 -0700688 public void handleSwipe(View view) {
Jim Miller9f0f0e02011-05-17 20:06:29 -0700689 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700690 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
Jim Miller9f0f0e02011-05-17 20:06:29 -0700691 mActivityDescriptions.remove(ad);
692
693 // Handled by widget containers to enable LayoutTransitions properly
694 // mListAdapter.notifyDataSetChanged();
695
696 if (mActivityDescriptions.size() == 0) {
697 hide(false);
698 }
699
700 // Currently, either direction means the same thing, so ignore direction and remove
701 // the task.
702 final ActivityManager am = (ActivityManager)
703 mContext.getSystemService(Context.ACTIVITY_SERVICE);
Dianne Hackborn8894cc52011-07-01 16:28:17 -0700704 am.removeTask(ad.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700705 }
706
Jim Millerc0d27312011-07-14 18:54:01 -0700707 private void startApplicationDetailsActivity(String packageName) {
708 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
709 Uri.fromParts("package", packageName, null));
710 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
711 getContext().startActivity(intent);
712 }
713
714 public void handleLongPress(final View selectedView, final View anchorView) {
715 PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
716 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
717 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
718 public boolean onMenuItemClick(MenuItem item) {
719 if (item.getItemId() == R.id.recent_remove_item) {
720 mRecentsContainer.removeViewInLayout(selectedView);
721 } else if (item.getItemId() == R.id.recent_inspect_item) {
722 ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
723 if (viewHolder != null) {
724 final ActivityDescription ad = viewHolder.activityDescription;
725 startApplicationDetailsActivity(ad.packageName);
726 mBar.animateCollapse();
727 } else {
728 throw new IllegalStateException("Oops, no tag on view " + selectedView);
729 }
730 } else {
731 return false;
732 }
733 return true;
734 }
735 });
736 popup.show();
Jim Miller9f0f0e02011-05-17 20:06:29 -0700737 }
Jim Miller44c66fe2010-10-20 18:32:52 -0700738}