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