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