RecentsPanelView.java revision 6f90f2a1aab5c4e56012b65b788b84370e231bc6
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
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
17package com.android.systemui.recent;
18
19import java.util.ArrayList;
20import java.util.List;
21
22import android.animation.Animator;
23import android.animation.LayoutTransition;
24import android.app.ActivityManager;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.graphics.Bitmap;
33import android.graphics.Canvas;
34import android.graphics.Matrix;
35import android.graphics.Paint;
36import android.graphics.RectF;
37import android.graphics.Shader.TileMode;
38import android.graphics.drawable.BitmapDrawable;
39import android.graphics.drawable.Drawable;
40import android.graphics.drawable.StateListDrawable;
41import android.net.Uri;
42import android.os.AsyncTask;
43import android.os.Handler;
44import android.os.Process;
45import android.os.SystemClock;
46import android.provider.Settings;
47import android.util.AttributeSet;
48import android.util.DisplayMetrics;
49import android.util.Log;
50import android.view.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.MenuItem;
53import android.view.MotionEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.animation.AnimationUtils;
57import android.widget.AdapterView;
58import android.widget.BaseAdapter;
59import android.widget.FrameLayout;
60import android.widget.HorizontalScrollView;
61import android.widget.ImageView;
62import android.widget.PopupMenu;
63import android.widget.RelativeLayout;
64import android.widget.ScrollView;
65import android.widget.TextView;
66import android.widget.AdapterView.OnItemClickListener;
67
68import com.android.systemui.R;
69import com.android.systemui.statusbar.StatusBar;
70import com.android.systemui.statusbar.phone.PhoneStatusBar;
71import com.android.systemui.statusbar.tablet.StatusBarPanel;
72import com.android.systemui.statusbar.tablet.TabletStatusBar;
73
74public class RecentsPanelView extends RelativeLayout
75        implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
76    static final String TAG = "RecentsPanelView";
77    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
78    private static final int DISPLAY_TASKS = 20;
79    private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
80    private StatusBar mBar;
81    private ArrayList<ActivityDescription> mActivityDescriptions;
82    private AsyncTask<Void, Integer, Void> mThumbnailLoader;
83    private int mIconDpi;
84    private View mRecentsScrim;
85    private View mRecentsGlowView;
86    private ViewGroup mRecentsContainer;
87    private Bitmap mDefaultThumbnailBackground;
88
89    private boolean mShowing;
90    private Choreographer mChoreo;
91    private View mRecentsDismissButton;
92    private ActivityDescriptionAdapter mListAdapter;
93    private final Handler mHandler = new Handler();
94
95    /* package */ final class ActivityDescription {
96        final ActivityManager.RecentTaskInfo recentTaskInfo;
97        final ResolveInfo resolveInfo;
98        final int taskId; // application task id for curating apps
99        final int persistentTaskId; // persistent id
100        final Intent intent; // launch intent for application
101        final String packageName; // used to override animations (see onClick())
102        final int position; // position in list
103
104        Matrix matrix; // arbitrary rotation matrix to correct orientation
105
106        private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail()
107        private Drawable mIcon; // application package icon
108        private CharSequence mLabel; // application package label
109
110        public ActivityDescription(ActivityManager.RecentTaskInfo _recentInfo,
111                ResolveInfo _resolveInfo, Intent _intent,
112                int _pos, String _packageName) {
113            recentTaskInfo = _recentInfo;
114            resolveInfo = _resolveInfo;
115            intent = _intent;
116            taskId = _recentInfo.id;
117            persistentTaskId = _recentInfo.persistentId;
118            position = _pos;
119            packageName = _packageName;
120        }
121
122        public CharSequence getLabel() {
123            return mLabel;
124        }
125
126        public Drawable getIcon() {
127            return mIcon;
128        }
129
130        public void setThumbnail(Bitmap thumbnail) {
131            mThumbnail = compositeBitmap(mDefaultThumbnailBackground, thumbnail);
132        }
133
134        public Bitmap getThumbnail() {
135            return mThumbnail;
136        }
137    }
138
139    private final class OnLongClickDelegate implements View.OnLongClickListener {
140        View mOtherView;
141        OnLongClickDelegate(View other) {
142            mOtherView = other;
143        }
144        public boolean onLongClick(View v) {
145            return mOtherView.performLongClick();
146        }
147    }
148
149    /* package */ final static class ViewHolder {
150        View thumbnailView;
151        ImageView thumbnailViewImage;
152        ImageView iconView;
153        TextView labelView;
154        TextView descriptionView;
155        ActivityDescription activityDescription;
156    }
157
158    /* package */ final class ActivityDescriptionAdapter extends BaseAdapter {
159        private LayoutInflater mInflater;
160
161        public ActivityDescriptionAdapter(Context context) {
162            mInflater = LayoutInflater.from(context);
163        }
164
165        public int getCount() {
166            return mActivityDescriptions != null ? mActivityDescriptions.size() : 0;
167        }
168
169        public Object getItem(int position) {
170            return position; // we only need the index
171        }
172
173        public long getItemId(int position) {
174            return position; // we just need something unique for this position
175        }
176
177        public View getView(int position, View convertView, ViewGroup parent) {
178            ViewHolder holder;
179            if (convertView == null) {
180                convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false);
181                holder = new ViewHolder();
182                holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
183                holder.thumbnailViewImage = (ImageView) convertView.findViewById(
184                        R.id.app_thumbnail_image);
185                holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
186                holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
187                holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
188
189		/*                StateListDrawable thumbnailForegroundDrawable = new StateListDrawable();
190                thumbnailForegroundDrawable.addState(new int[] { android.R.attr.state_pressed },
191                        mPressedDrawable);
192                thumbnailForegroundDrawable.addState(new int[] { android.R.attr.state_selected },
193                        mPressedDrawable);
194			((FrameLayout)holder.thumbnailView).setForeground(thumbnailForegroundDrawable);*/
195                convertView.setTag(holder);
196            } else {
197                holder = (ViewHolder) convertView.getTag();
198            }
199
200            // activityId is reverse since most recent appears at the bottom...
201            final int activityId = mActivityDescriptions.size() - position - 1;
202
203            final ActivityDescription activityDescription = mActivityDescriptions.get(activityId);
204            holder.thumbnailViewImage.setImageBitmap(activityDescription.getThumbnail());
205            holder.iconView.setImageDrawable(activityDescription.getIcon());
206            holder.labelView.setText(activityDescription.getLabel());
207            holder.descriptionView.setText(activityDescription.recentTaskInfo.description);
208            holder.thumbnailView.setTag(activityDescription);
209            holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
210            holder.activityDescription = activityDescription;
211
212            return convertView;
213        }
214    }
215
216    @Override
217    public boolean onKeyUp(int keyCode, KeyEvent event) {
218        if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) {
219            show(false, true);
220            return true;
221        }
222        return super.onKeyUp(keyCode, event);
223    }
224
225    public boolean isInContentArea(int x, int y) {
226        // use mRecentsContainer's exact bounds to determine horizontal position
227        final int l = mRecentsContainer.getLeft();
228        final int r = mRecentsContainer.getRight();
229        // use surrounding mRecentsGlowView's position in parent determine vertical bounds
230        final int t = mRecentsGlowView.getTop();
231        final int b = mRecentsGlowView.getBottom();
232        return x >= l && x < r && y >= t && y < b;
233    }
234
235    public void show(boolean show, boolean animate) {
236        if (animate) {
237            if (mShowing != show) {
238                mShowing = show;
239                if (show) {
240                    setVisibility(View.VISIBLE);
241                }
242                mChoreo.startAnimation(show);
243            }
244        } else {
245            mShowing = show;
246            setVisibility(show ? View.VISIBLE : View.GONE);
247            mChoreo.jumpTo(show);
248        }
249        if (show) {
250            setFocusable(true);
251            setFocusableInTouchMode(true);
252            requestFocus();
253        }
254    }
255
256    public void dismiss() {
257        hide(true);
258    }
259
260    public void hide(boolean animate) {
261        if (!animate) {
262            setVisibility(View.GONE);
263        }
264        if (mBar != null) {
265            mBar.animateCollapse();
266        }
267    }
268
269    public void handleShowBackground(boolean show) {
270        if (show) {
271            mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background);
272        } else {
273            mRecentsScrim.setBackgroundDrawable(null);
274        }
275    }
276
277    public boolean isRecentsVisible() {
278        return getVisibility() == VISIBLE;
279    }
280
281    public void onAnimationCancel(Animator animation) {
282    }
283
284    public void onAnimationEnd(Animator animation) {
285        if (mShowing) {
286            final LayoutTransition transitioner = new LayoutTransition();
287            ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
288            createCustomAnimations(transitioner);
289        } else {
290            ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
291        }
292    }
293
294    public void onAnimationRepeat(Animator animation) {
295    }
296
297    public void onAnimationStart(Animator animation) {
298    }
299
300
301    /**
302     * We need to be aligned at the bottom.  LinearLayout can't do this, so instead,
303     * let LinearLayout do all the hard work, and then shift everything down to the bottom.
304     */
305    @Override
306    protected void onLayout(boolean changed, int l, int t, int r, int b) {
307        super.onLayout(changed, l, t, r, b);
308        mChoreo.setPanelHeight(mRecentsContainer.getHeight());
309    }
310
311    @Override
312    public boolean dispatchHoverEvent(MotionEvent event) {
313        // Ignore hover events outside of this panel bounds since such events
314        // generate spurious accessibility events with the panel content when
315        // tapping outside of it, thus confusing the user.
316        final int x = (int) event.getX();
317        final int y = (int) event.getY();
318        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
319            return super.dispatchHoverEvent(event);
320        }
321        return true;
322    }
323
324    /**
325     * Whether the panel is showing, or, if it's animating, whether it will be
326     * when the animation is done.
327     */
328    public boolean isShowing() {
329        return mShowing;
330    }
331
332    public void setBar(StatusBar bar) {
333        mBar = bar;
334    }
335
336    public RecentsPanelView(Context context, AttributeSet attrs) {
337        this(context, attrs, 0);
338    }
339
340    public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
341        super(context, attrs, defStyle);
342
343        Resources res = context.getResources();
344        boolean xlarge = (res.getConfiguration().screenLayout
345                & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
346
347        mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
348
349        int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width);
350        int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height);
351        int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
352
353        // Render the default thumbnail background
354        mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
355        Canvas c = new Canvas(mDefaultThumbnailBackground);
356        c.drawColor(color);
357    }
358
359    @Override
360    protected void onFinishInflate() {
361        super.onFinishInflate();
362        mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
363        mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
364        mListAdapter = new ActivityDescriptionAdapter(mContext);
365        if (mRecentsContainer instanceof RecentsHorizontalScrollView){
366            RecentsHorizontalScrollView scrollView
367                    = (RecentsHorizontalScrollView) mRecentsContainer;
368            scrollView.setAdapter(mListAdapter);
369            scrollView.setCallback(this);
370        } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
371            RecentsVerticalScrollView scrollView
372                    = (RecentsVerticalScrollView) mRecentsContainer;
373            scrollView.setAdapter(mListAdapter);
374            scrollView.setCallback(this);
375        }
376        else {
377            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
378        }
379
380
381        mRecentsGlowView = findViewById(R.id.recents_glow);
382        mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
383        mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
384        mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
385        mRecentsDismissButton.setOnClickListener(new OnClickListener() {
386            public void onClick(View v) {
387                hide(true);
388            }
389        });
390
391        // In order to save space, we make the background texture repeat in the Y direction
392        if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) {
393            ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
394        }
395    }
396
397    private void createCustomAnimations(LayoutTransition transitioner) {
398        transitioner.setDuration(200);
399        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
400        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
401    }
402
403    @Override
404    protected void onVisibilityChanged(View changedView, int visibility) {
405        super.onVisibilityChanged(changedView, visibility);
406        if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")");
407        if (visibility == View.VISIBLE && changedView == this) {
408            refreshApplicationList();
409        }
410
411        if (mRecentsContainer instanceof RecentsHorizontalScrollView) {
412            ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
413        } else if (mRecentsContainer instanceof RecentsVerticalScrollView) {
414            ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
415        } else {
416            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
417        }
418    }
419
420    Drawable getFullResDefaultActivityIcon() {
421        return getFullResIcon(Resources.getSystem(),
422                com.android.internal.R.mipmap.sym_def_app_icon);
423    }
424
425    Drawable getFullResIcon(Resources resources, int iconId) {
426        try {
427            return resources.getDrawableForDensity(iconId, mIconDpi);
428        } catch (Resources.NotFoundException e) {
429            return getFullResDefaultActivityIcon();
430        }
431    }
432
433    private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
434        Resources resources;
435        try {
436            resources = packageManager.getResourcesForApplication(
437                    info.activityInfo.applicationInfo);
438        } catch (PackageManager.NameNotFoundException e) {
439            resources = null;
440        }
441        if (resources != null) {
442            int iconId = info.activityInfo.getIconResource();
443            if (iconId != 0) {
444                return getFullResIcon(resources, iconId);
445            }
446        }
447        return getFullResDefaultActivityIcon();
448    }
449
450    private ArrayList<ActivityDescription> getRecentTasks() {
451        ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>();
452        final PackageManager pm = mContext.getPackageManager();
453        final ActivityManager am = (ActivityManager)
454                mContext.getSystemService(Context.ACTIVITY_SERVICE);
455
456        final List<ActivityManager.RecentTaskInfo> recentTasks =
457                am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
458
459        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
460                    .resolveActivityInfo(pm, 0);
461
462        int numTasks = recentTasks.size();
463
464        // skip the first activity - assume it's either the home screen or the current app.
465        final int first = 1;
466        for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
467            final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
468
469            Intent intent = new Intent(recentInfo.baseIntent);
470            if (recentInfo.origActivity != null) {
471                intent.setComponent(recentInfo.origActivity);
472            }
473
474            // Skip the current home activity.
475            if (homeInfo != null
476                    && homeInfo.packageName.equals(intent.getComponent().getPackageName())
477                    && homeInfo.name.equals(intent.getComponent().getClassName())) {
478                continue;
479            }
480
481            intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
482                    | Intent.FLAG_ACTIVITY_NEW_TASK);
483            final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
484            if (resolveInfo != null) {
485                final ActivityInfo info = resolveInfo.activityInfo;
486                final String title = info.loadLabel(pm).toString();
487                // Drawable icon = info.loadIcon(pm);
488                Drawable icon = getFullResIcon(resolveInfo, pm);
489                if (title != null && title.length() > 0 && icon != null) {
490                    if (DEBUG) Log.v(TAG, "creating activity desc for id="
491                            + recentInfo.id + ", label=" + title);
492                    ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
493                            recentInfo.persistentId);
494                    ActivityDescription item = new ActivityDescription(recentInfo,
495                            resolveInfo, intent, index, info.packageName);
496                    activityDescriptions.add(item);
497                    ++index;
498                } else {
499                    if (DEBUG) Log.v(TAG, "SKIPPING item " + recentInfo.id);
500                }
501            }
502        }
503        return activityDescriptions;
504    }
505
506    ActivityDescription findActivityDescription(int id)
507    {
508        ActivityDescription desc = null;
509        for (int i = 0; i < mActivityDescriptions.size(); i++) {
510            ActivityDescription item = mActivityDescriptions.get(i);
511            if (item != null && item.taskId == id) {
512                desc = item;
513                break;
514            }
515        }
516        return desc;
517    }
518
519    void loadActivityDescription(ActivityDescription ad, int index) {
520        final ActivityManager am = (ActivityManager)
521                mContext.getSystemService(Context.ACTIVITY_SERVICE);
522        final PackageManager pm = mContext.getPackageManager();
523        ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
524                ad.recentTaskInfo.persistentId);
525        CharSequence label = ad.resolveInfo.activityInfo.loadLabel(pm);
526        Drawable icon = getFullResIcon(ad.resolveInfo, pm);
527        if (DEBUG) Log.v(TAG, "Loaded bitmap for #" + index + " in "
528                + ad + ": " + thumbs.mainThumbnail);
529        synchronized (ad) {
530            ad.mLabel = label;
531            ad.mIcon = icon;
532            ad.setThumbnail(thumbs != null ? thumbs.mainThumbnail : mDefaultThumbnailBackground);
533        }
534    }
535
536    void applyActivityDescription(ActivityDescription ad, int index, boolean anim) {
537        synchronized (ad) {
538            if (mRecentsContainer != null) {
539                ViewGroup container = mRecentsContainer;
540                if (container instanceof HorizontalScrollView
541                        || container instanceof ScrollView) {
542                    container = (ViewGroup)container.findViewById(
543                            R.id.recents_linear_layout);
544                }
545                // Look for a view showing this thumbnail, to update.
546                for (int i=0; i<container.getChildCount(); i++) {
547                    View v = container.getChildAt(i);
548                    if (v.getTag() instanceof ViewHolder) {
549                        ViewHolder h = (ViewHolder)v.getTag();
550                        if (h.activityDescription == ad) {
551                            if (DEBUG) Log.v(TAG, "Updatating thumbnail #" + index + " in "
552                                    + h.activityDescription
553                                    + ": " + ad.getThumbnail());
554                            h.iconView.setImageDrawable(ad.getIcon());
555                            if (anim) {
556                                h.iconView.setAnimation(AnimationUtils.loadAnimation(
557                                        mContext, R.anim.recent_appear));
558                            }
559                            h.iconView.setVisibility(View.VISIBLE);
560                            h.labelView.setText(ad.getLabel());
561                            if (anim) {
562                                h.labelView.setAnimation(AnimationUtils.loadAnimation(
563                                        mContext, R.anim.recent_appear));
564                            }
565                            h.labelView.setVisibility(View.VISIBLE);
566                            Bitmap thumbnail = ad.getThumbnail();
567                            if (thumbnail != null) {
568                                // Should remove the default image in the frame
569                                // that this now covers, to improve scrolling speed.
570                                // That can't be done until the anim is complete though.
571                                h.thumbnailViewImage.setImageBitmap(thumbnail);
572                                if (anim) {
573                                    h.thumbnailViewImage.setAnimation(AnimationUtils.loadAnimation(
574                                            mContext, R.anim.recent_appear));
575                                }
576                                h.thumbnailViewImage.setVisibility(View.VISIBLE);
577                            }
578                        }
579                    }
580                }
581            }
582        }
583    }
584
585    private void refreshApplicationList() {
586        if (mThumbnailLoader != null) {
587            mThumbnailLoader.cancel(false);
588            mThumbnailLoader = null;
589        }
590        mActivityDescriptions = getRecentTasks();
591        for (ActivityDescription ad : mActivityDescriptions) {
592            ad.setThumbnail(mDefaultThumbnailBackground);
593        }
594        mListAdapter.notifyDataSetInvalidated();
595        if (mActivityDescriptions.size() > 0) {
596            if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
597            updateUiElements(getResources().getConfiguration());
598            final ArrayList<ActivityDescription> descriptions = mActivityDescriptions;
599            loadActivityDescription(descriptions.get(0), 0);
600            applyActivityDescription(descriptions.get(0), 0, false);
601            if (descriptions.size() > 1) {
602                mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
603                    @Override
604                    protected void onProgressUpdate(Integer... values) {
605                        final ActivityDescription ad = descriptions.get(values[0]);
606                        if (!isCancelled()) {
607                            applyActivityDescription(ad, values[0], true);
608                        }
609                        // This is to prevent the loader thread from getting ahead
610                        // of our UI updates.
611                        mHandler.post(new Runnable() {
612                            @Override public void run() {
613                                synchronized (ad) {
614                                    ad.notifyAll();
615                                }
616                            }
617                        });
618                    }
619
620                    @Override
621                    protected Void doInBackground(Void... params) {
622                        final int origPri = Process.getThreadPriority(Process.myTid());
623                        Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
624                        long nextTime = SystemClock.uptimeMillis();
625                        for (int i=1; i<descriptions.size(); i++) {
626                            ActivityDescription ad = descriptions.get(i);
627                            loadActivityDescription(ad, i);
628                            long now = SystemClock.uptimeMillis();
629                            nextTime += 150;
630                            if (nextTime > now) {
631                                try {
632                                    Thread.sleep(nextTime-now);
633                                } catch (InterruptedException e) {
634                                }
635                            }
636                            if (isCancelled()) {
637                                break;
638                            }
639                            synchronized (ad) {
640                                publishProgress(i);
641                                try {
642                                    ad.wait(500);
643                                } catch (InterruptedException e) {
644                                }
645                            }
646                        }
647                        Process.setThreadPriority(origPri);
648                        return null;
649                    }
650                };
651                mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
652            }
653        } else {
654            // Immediately hide this panel
655            if (DEBUG) Log.v(TAG, "Nothing to show");
656            hide(false);
657        }
658    }
659
660    private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
661        Bitmap outBitmap = background.copy(background.getConfig(), true);
662        if (thumbnail != null) {
663            Canvas canvas = new Canvas(outBitmap);
664            Paint paint = new Paint();
665            paint.setAntiAlias(true);
666            paint.setFilterBitmap(true);
667            paint.setAlpha(255);
668            canvas.drawBitmap(thumbnail, null,
669                    new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
670            canvas.setBitmap(null);
671        }
672        return outBitmap;
673    }
674
675    private void updateUiElements(Configuration config) {
676        final int items = mActivityDescriptions.size();
677
678        mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
679        mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
680    }
681
682    public void handleOnClick(View view) {
683        ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
684        final Context context = view.getContext();
685        final ActivityManager am = (ActivityManager)
686                context.getSystemService(Context.ACTIVITY_SERVICE);
687        if (ad.taskId >= 0) {
688            // This is an active task; it should just go to the foreground.
689            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
690        } else {
691            Intent intent = ad.intent;
692            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
693                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
694            if (DEBUG) Log.v(TAG, "Starting activity " + intent);
695            context.startActivity(intent);
696        }
697        hide(true);
698    }
699
700    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
701        handleOnClick(view);
702    }
703
704    public void handleSwipe(View view) {
705        ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
706        if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
707        mActivityDescriptions.remove(ad);
708
709        // Handled by widget containers to enable LayoutTransitions properly
710        // mListAdapter.notifyDataSetChanged();
711
712        if (mActivityDescriptions.size() == 0) {
713            hide(false);
714        }
715
716        // Currently, either direction means the same thing, so ignore direction and remove
717        // the task.
718        final ActivityManager am = (ActivityManager)
719                mContext.getSystemService(Context.ACTIVITY_SERVICE);
720        am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
721    }
722
723    private void startApplicationDetailsActivity(String packageName) {
724        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
725                Uri.fromParts("package", packageName, null));
726        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
727        getContext().startActivity(intent);
728    }
729
730    public void handleLongPress(
731            final View selectedView, final View anchorView, final View thumbnailView) {
732        thumbnailView.setSelected(true);
733        PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
734        popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
735        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
736            public boolean onMenuItemClick(MenuItem item) {
737                if (item.getItemId() == R.id.recent_remove_item) {
738                    mRecentsContainer.removeViewInLayout(selectedView);
739                } else if (item.getItemId() == R.id.recent_inspect_item) {
740                    ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
741                    if (viewHolder != null) {
742                        final ActivityDescription ad = viewHolder.activityDescription;
743                        startApplicationDetailsActivity(ad.packageName);
744                        mBar.animateCollapse();
745                    } else {
746                        throw new IllegalStateException("Oops, no tag on view " + selectedView);
747                    }
748                } else {
749                    return false;
750                }
751                return true;
752            }
753        });
754        popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
755            public void onDismiss(PopupMenu menu) {
756                thumbnailView.setSelected(false);
757            }
758        });
759        popup.show();
760    }
761}
762