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