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