AppsCustomizePagedView.java revision 9e05a5ea951b4d5ffce324da8dd0656353306e6f
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.launcher2;
18
19import android.animation.AnimatorSet;
20import android.animation.ValueAnimator;
21import android.appwidget.AppWidgetHostView;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.content.res.Configuration;
30import android.content.res.Resources;
31import android.content.res.TypedArray;
32import android.graphics.Bitmap;
33import android.graphics.Bitmap.Config;
34import android.graphics.Canvas;
35import android.graphics.ColorMatrix;
36import android.graphics.ColorMatrixColorFilter;
37import android.graphics.Matrix;
38import android.graphics.Paint;
39import android.graphics.PorterDuff;
40import android.graphics.Rect;
41import android.graphics.RectF;
42import android.graphics.Shader;
43import android.graphics.drawable.BitmapDrawable;
44import android.graphics.drawable.Drawable;
45import android.os.AsyncTask;
46import android.os.Build;
47import android.os.Bundle;
48import android.os.Process;
49import android.util.AttributeSet;
50import android.util.Log;
51import android.view.Gravity;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.MotionEvent;
55import android.view.View;
56import android.view.ViewGroup;
57import android.view.animation.AccelerateInterpolator;
58import android.view.animation.DecelerateInterpolator;
59import android.widget.GridLayout;
60import android.widget.ImageView;
61import android.widget.Toast;
62
63import com.android.launcher.R;
64import com.android.launcher2.DropTarget.DragObject;
65
66import java.lang.ref.WeakReference;
67import java.util.ArrayList;
68import java.util.Collections;
69import java.util.Iterator;
70import java.util.List;
71
72/**
73 * A simple callback interface which also provides the results of the task.
74 */
75interface AsyncTaskCallback {
76    void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
77}
78
79/**
80 * The data needed to perform either of the custom AsyncTasks.
81 */
82class AsyncTaskPageData {
83    enum Type {
84        LoadWidgetPreviewData
85    }
86
87    AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
88            AsyncTaskCallback postR) {
89        page = p;
90        items = l;
91        sourceImages = si;
92        generatedImages = new ArrayList<Bitmap>();
93        maxImageWidth = maxImageHeight = -1;
94        doInBackgroundCallback = bgR;
95        postExecuteCallback = postR;
96    }
97    AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
98            AsyncTaskCallback postR) {
99        page = p;
100        items = l;
101        generatedImages = new ArrayList<Bitmap>();
102        maxImageWidth = cw;
103        maxImageHeight = ch;
104        doInBackgroundCallback = bgR;
105        postExecuteCallback = postR;
106    }
107    void cleanup(boolean cancelled) {
108        // Clean up any references to source/generated bitmaps
109        if (sourceImages != null) {
110            if (cancelled) {
111                for (Bitmap b : sourceImages) {
112                    b.recycle();
113                }
114            }
115            sourceImages.clear();
116        }
117        if (generatedImages != null) {
118            if (cancelled) {
119                for (Bitmap b : generatedImages) {
120                    b.recycle();
121                }
122            }
123            generatedImages.clear();
124        }
125    }
126    int page;
127    ArrayList<Object> items;
128    ArrayList<Bitmap> sourceImages;
129    ArrayList<Bitmap> generatedImages;
130    int maxImageWidth;
131    int maxImageHeight;
132    AsyncTaskCallback doInBackgroundCallback;
133    AsyncTaskCallback postExecuteCallback;
134}
135
136/**
137 * A generic template for an async task used in AppsCustomize.
138 */
139class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
140    AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
141        page = p;
142        threadPriority = Process.THREAD_PRIORITY_DEFAULT;
143        dataType = ty;
144    }
145    @Override
146    protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
147        if (params.length != 1) return null;
148        // Load each of the widget previews in the background
149        params[0].doInBackgroundCallback.run(this, params[0]);
150        return params[0];
151    }
152    @Override
153    protected void onPostExecute(AsyncTaskPageData result) {
154        // All the widget previews are loaded, so we can just callback to inflate the page
155        result.postExecuteCallback.run(this, result);
156    }
157
158    void setThreadPriority(int p) {
159        threadPriority = p;
160    }
161    void syncThreadPriority() {
162        Process.setThreadPriority(threadPriority);
163    }
164
165    // The page that this async task is associated with
166    AsyncTaskPageData.Type dataType;
167    int page;
168    int threadPriority;
169}
170
171abstract class WeakReferenceThreadLocal<T> {
172    private ThreadLocal<WeakReference<T>> mThreadLocal;
173    public WeakReferenceThreadLocal() {
174        mThreadLocal = new ThreadLocal<WeakReference<T>>();
175    }
176
177    abstract T initialValue();
178
179    public void set(T t) {
180        mThreadLocal.set(new WeakReference<T>(t));
181    }
182
183    public T get() {
184        WeakReference<T> reference = mThreadLocal.get();
185        T obj;
186        if (reference == null) {
187            obj = initialValue();
188            mThreadLocal.set(new WeakReference<T>(obj));
189            return obj;
190        } else {
191            obj = reference.get();
192            if (obj == null) {
193                obj = initialValue();
194                mThreadLocal.set(new WeakReference<T>(obj));
195            }
196            return obj;
197        }
198    }
199}
200
201class CanvasCache extends WeakReferenceThreadLocal<Canvas> {
202    @Override
203    protected Canvas initialValue() {
204        return new Canvas();
205    }
206}
207
208class PaintCache extends WeakReferenceThreadLocal<Paint> {
209    @Override
210    protected Paint initialValue() {
211        return null;
212    }
213}
214
215class BitmapCache extends WeakReferenceThreadLocal<Bitmap> {
216    @Override
217    protected Bitmap initialValue() {
218        return null;
219    }
220}
221
222class RectCache extends WeakReferenceThreadLocal<Rect> {
223    @Override
224    protected Rect initialValue() {
225        return new Rect();
226    }
227}
228
229/**
230 * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
231 */
232public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
233        View.OnClickListener, View.OnKeyListener, DragSource,
234        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
235        LauncherTransitionable {
236    static final String TAG = "AppsCustomizePagedView";
237
238    /**
239     * The different content types that this paged view can show.
240     */
241    public enum ContentType {
242        Applications,
243        Widgets
244    }
245
246    // Refs
247    private Launcher mLauncher;
248    private DragController mDragController;
249    private final LayoutInflater mLayoutInflater;
250    private final PackageManager mPackageManager;
251
252    // Save and Restore
253    private int mSaveInstanceStateItemIndex = -1;
254    private PagedViewIcon mPressedIcon;
255
256    // Content
257    private ArrayList<ApplicationInfo> mApps;
258    private ArrayList<Object> mWidgets;
259
260    // Cling
261    private boolean mHasShownAllAppsCling;
262    private int mClingFocusedX;
263    private int mClingFocusedY;
264
265    // Caching
266    private Canvas mCanvas;
267    private IconCache mIconCache;
268
269    // Dimens
270    private int mContentWidth;
271    private int mAppIconSize;
272    private int mMaxAppCellCountX, mMaxAppCellCountY;
273    private int mWidgetCountX, mWidgetCountY;
274    private int mWidgetWidthGap, mWidgetHeightGap;
275    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
276    private PagedViewCellLayout mWidgetSpacingLayout;
277    private int mNumAppsPages;
278    private int mNumWidgetPages;
279
280    // Relating to the scroll and overscroll effects
281    Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
282    private static float CAMERA_DISTANCE = 6500;
283    private static float TRANSITION_SCALE_FACTOR = 0.74f;
284    private static float TRANSITION_PIVOT = 0.65f;
285    private static float TRANSITION_MAX_ROTATION = 22;
286    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
287    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
288    private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
289
290    // Previews & outlines
291    ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
292    private static final int sPageSleepDelay = 200;
293
294    private Runnable mInflateWidgetRunnable = null;
295    private Runnable mBindWidgetRunnable = null;
296    static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
297    static final int WIDGET_PRELOAD_PENDING = 0;
298    static final int WIDGET_BOUND = 1;
299    static final int WIDGET_INFLATED = 2;
300    int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
301    int mWidgetLoadingId = -1;
302    PendingAddWidgetInfo mCreateWidgetInfo = null;
303    private boolean mDraggingWidget = false;
304
305    private Toast mWidgetInstructionToast;
306
307    // Deferral of loading widget previews during launcher transitions
308    private boolean mInTransition;
309    private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
310        new ArrayList<AsyncTaskPageData>();
311    private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
312        new ArrayList<Runnable>();
313
314    private Rect mTmpRect = new Rect();
315
316    // Used for drawing shortcut previews
317    BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
318    PaintCache mCachedShortcutPreviewPaint = new PaintCache();
319    CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
320
321    // Used for drawing widget previews
322    CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
323    RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
324    RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
325    PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
326
327    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
328        super(context, attrs);
329        mLayoutInflater = LayoutInflater.from(context);
330        mPackageManager = context.getPackageManager();
331        mApps = new ArrayList<ApplicationInfo>();
332        mWidgets = new ArrayList<Object>();
333        mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
334        mCanvas = new Canvas();
335        mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
336
337        // Save the default widget preview background
338        Resources resources = context.getResources();
339        mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
340
341        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
342        mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
343        mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1);
344        mWidgetWidthGap =
345            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
346        mWidgetHeightGap =
347            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
348        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
349        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
350        mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
351        mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
352        a.recycle();
353        mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
354
355        // The padding on the non-matched dimension for the default widget preview icons
356        // (top + bottom)
357        mFadeInAdjacentScreens = false;
358
359        // Unless otherwise specified this view is important for accessibility.
360        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
361            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
362        }
363    }
364
365    @Override
366    protected void init() {
367        super.init();
368        mCenterPagesVertically = false;
369
370        Context context = getContext();
371        Resources r = context.getResources();
372        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
373    }
374
375    @Override
376    protected void onUnhandledTap(MotionEvent ev) {
377        if (LauncherApplication.isScreenLarge()) {
378            // Dismiss AppsCustomize if we tap
379            mLauncher.showWorkspace(true);
380        }
381    }
382
383    /** Returns the item index of the center item on this page so that we can restore to this
384     *  item index when we rotate. */
385    private int getMiddleComponentIndexOnCurrentPage() {
386        int i = -1;
387        if (getPageCount() > 0) {
388            int currentPage = getCurrentPage();
389            if (currentPage < mNumAppsPages) {
390                PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage);
391                PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout();
392                int numItemsPerPage = mCellCountX * mCellCountY;
393                int childCount = childrenLayout.getChildCount();
394                if (childCount > 0) {
395                    i = (currentPage * numItemsPerPage) + (childCount / 2);
396                }
397            } else {
398                int numApps = mApps.size();
399                PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
400                int numItemsPerPage = mWidgetCountX * mWidgetCountY;
401                int childCount = layout.getChildCount();
402                if (childCount > 0) {
403                    i = numApps +
404                        ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2);
405                }
406            }
407        }
408        return i;
409    }
410
411    /** Get the index of the item to restore to if we need to restore the current page. */
412    int getSaveInstanceStateIndex() {
413        if (mSaveInstanceStateItemIndex == -1) {
414            mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
415        }
416        return mSaveInstanceStateItemIndex;
417    }
418
419    /** Returns the page in the current orientation which is expected to contain the specified
420     *  item index. */
421    int getPageForComponent(int index) {
422        if (index < 0) return 0;
423
424        if (index < mApps.size()) {
425            int numItemsPerPage = mCellCountX * mCellCountY;
426            return (index / numItemsPerPage);
427        } else {
428            int numItemsPerPage = mWidgetCountX * mWidgetCountY;
429            return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage);
430        }
431    }
432
433    /** Restores the page for an item at the specified index */
434    void restorePageForIndex(int index) {
435        if (index < 0) return;
436        mSaveInstanceStateItemIndex = index;
437    }
438
439    private void updatePageCounts() {
440        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
441                (float) (mWidgetCountX * mWidgetCountY));
442        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
443    }
444
445    protected void onDataReady(int width, int height) {
446        // Note that we transpose the counts in portrait so that we get a similar layout
447        boolean isLandscape = getResources().getConfiguration().orientation ==
448            Configuration.ORIENTATION_LANDSCAPE;
449        int maxCellCountX = Integer.MAX_VALUE;
450        int maxCellCountY = Integer.MAX_VALUE;
451        if (LauncherApplication.isScreenLarge()) {
452            maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
453                LauncherModel.getCellCountY());
454            maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
455                LauncherModel.getCellCountX());
456        }
457        if (mMaxAppCellCountX > -1) {
458            maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX);
459        }
460        if (mMaxAppCellCountY > -1) {
461            maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY);
462        }
463
464        // Now that the data is ready, we can calculate the content width, the number of cells to
465        // use for each page
466        mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
467        mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
468                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
469        mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
470        mCellCountX = mWidgetSpacingLayout.getCellCountX();
471        mCellCountY = mWidgetSpacingLayout.getCellCountY();
472        updatePageCounts();
473
474        // Force a measure to update recalculate the gaps
475        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
476        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
477        mWidgetSpacingLayout.measure(widthSpec, heightSpec);
478        mContentWidth = mWidgetSpacingLayout.getContentWidth();
479
480        AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost();
481        final boolean hostIsTransitioning = host.isTransitioning();
482
483        // Restore the page
484        int page = getPageForComponent(mSaveInstanceStateItemIndex);
485        invalidatePageData(Math.max(0, page), hostIsTransitioning);
486
487        // Show All Apps cling if we are finished transitioning, otherwise, we will try again when
488        // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be
489        // returned while animating)
490        if (!hostIsTransitioning) {
491            post(new Runnable() {
492                @Override
493                public void run() {
494                    showAllAppsCling();
495                }
496            });
497        }
498    }
499
500    void showAllAppsCling() {
501        if (!mHasShownAllAppsCling && isDataReady()) {
502            mHasShownAllAppsCling = true;
503            // Calculate the position for the cling punch through
504            int[] offset = new int[2];
505            int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
506            mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
507            // PagedViews are centered horizontally but top aligned
508            pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
509                    offset[0];
510            pos[1] += offset[1];
511            mLauncher.showFirstRunAllAppsCling(pos);
512        }
513    }
514
515    @Override
516    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
517        int width = MeasureSpec.getSize(widthMeasureSpec);
518        int height = MeasureSpec.getSize(heightMeasureSpec);
519        if (!isDataReady()) {
520            if (!mApps.isEmpty() && !mWidgets.isEmpty()) {
521                setDataIsReady();
522                setMeasuredDimension(width, height);
523                onDataReady(width, height);
524            }
525        }
526
527        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
528    }
529
530    public void onPackagesUpdated() {
531        // Get the list of widgets and shortcuts
532        mWidgets.clear();
533        List<AppWidgetProviderInfo> widgets =
534            AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
535        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
536        List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
537        for (AppWidgetProviderInfo widget : widgets) {
538            if (widget.minWidth > 0 && widget.minHeight > 0) {
539                // Ensure that all widgets we show can be added on a workspace of this size
540                int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
541                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
542                int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
543                int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
544                if (minSpanX <= LauncherModel.getCellCountX() &&
545                        minSpanY <= LauncherModel.getCellCountY()) {
546                    mWidgets.add(widget);
547                } else {
548                    Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
549                            widget.minWidth + ", " + widget.minHeight + ")");
550                }
551            } else {
552                Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
553                        widget.minWidth + ", " + widget.minHeight + ")");
554            }
555        }
556        mWidgets.addAll(shortcuts);
557        Collections.sort(mWidgets,
558                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
559        updatePageCounts();
560        invalidateOnDataChange();
561    }
562
563    @Override
564    public void onClick(View v) {
565        // When we have exited all apps or are in transition, disregard clicks
566        if (!mLauncher.isAllAppsVisible() ||
567                mLauncher.getWorkspace().isSwitchingState()) return;
568
569        if (v instanceof PagedViewIcon) {
570            // Animate some feedback to the click
571            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
572
573            // Lock the drawable state to pressed until we return to Launcher
574            if (mPressedIcon != null) {
575                mPressedIcon.lockDrawableState();
576            }
577
578            // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
579            // to be consistent.  So re-enable the flag here, and we will re-disable it as necessary
580            // when Launcher resumes and we are still in AllApps.
581            mLauncher.updateWallpaperVisibility(true);
582            mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
583
584        } else if (v instanceof PagedViewWidget) {
585            // Let the user know that they have to long press to add a widget
586            if (mWidgetInstructionToast != null) {
587                mWidgetInstructionToast.cancel();
588            }
589            mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
590                Toast.LENGTH_SHORT);
591            mWidgetInstructionToast.show();
592
593            // Create a little animation to show that the widget can move
594            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
595            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
596            AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
597            ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
598            tyuAnim.setDuration(125);
599            ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
600            tydAnim.setDuration(100);
601            bounce.play(tyuAnim).before(tydAnim);
602            bounce.setInterpolator(new AccelerateInterpolator());
603            bounce.start();
604        }
605    }
606
607    public boolean onKey(View v, int keyCode, KeyEvent event) {
608        return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
609    }
610
611    /*
612     * PagedViewWithDraggableItems implementation
613     */
614    @Override
615    protected void determineDraggingStart(android.view.MotionEvent ev) {
616        // Disable dragging by pulling an app down for now.
617    }
618
619    private void beginDraggingApplication(View v) {
620        mLauncher.getWorkspace().onDragStartedWithItem(v);
621        mLauncher.getWorkspace().beginDragShared(v, this);
622    }
623
624    Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
625        Bundle options = null;
626        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
627            AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect);
628            options = new Bundle();
629            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, mTmpRect.left);
630            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, mTmpRect.top);
631            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, mTmpRect.right);
632            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, mTmpRect.bottom);
633        }
634        return options;
635    }
636
637    private void preloadWidget(final PendingAddWidgetInfo info) {
638        final AppWidgetProviderInfo pInfo = info.info;
639        if (pInfo.configure != null) {
640            return;
641        }
642
643        mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
644        mBindWidgetRunnable = new Runnable() {
645            @Override
646            public void run() {
647                mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
648
649                Bundle options = getDefaultOptionsForWidget(mLauncher, info);
650                // Options will be null for platforms with JB or lower, so this serves as an
651                // SDK level check.
652                if (options == null) {
653                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
654                            mWidgetLoadingId, info.componentName)) {
655                        mWidgetCleanupState = WIDGET_BOUND;
656                    }
657                } else {
658                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
659                            mWidgetLoadingId, info.componentName, options)) {
660                        mWidgetCleanupState = WIDGET_BOUND;
661                    }
662                }
663            }
664        };
665        post(mBindWidgetRunnable);
666
667        mInflateWidgetRunnable = new Runnable() {
668            @Override
669            public void run() {
670                if (mWidgetCleanupState != WIDGET_BOUND) {
671                    return;
672                }
673                AppWidgetHostView hostView = mLauncher.
674                        getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
675                info.boundWidget = hostView;
676                mWidgetCleanupState = WIDGET_INFLATED;
677                hostView.setVisibility(INVISIBLE);
678                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
679                        info.spanY, info, false);
680
681                // We want the first widget layout to be the correct size. This will be important
682                // for width size reporting to the AppWidgetManager.
683                DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
684                        unScaledSize[1]);
685                lp.x = lp.y = 0;
686                lp.customPosition = true;
687                hostView.setLayoutParams(lp);
688                mLauncher.getDragLayer().addView(hostView);
689            }
690        };
691        post(mInflateWidgetRunnable);
692    }
693
694    @Override
695    public void onShortPress(View v) {
696        // We are anticipating a long press, and we use this time to load bind and instantiate
697        // the widget. This will need to be cleaned up if it turns out no long press occurs.
698        if (mCreateWidgetInfo != null) {
699            // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
700            cleanupWidgetPreloading(false);
701        }
702        mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
703        preloadWidget(mCreateWidgetInfo);
704    }
705
706    private void cleanupWidgetPreloading(boolean widgetWasAdded) {
707        if (!widgetWasAdded) {
708            // If the widget was not added, we may need to do further cleanup.
709            PendingAddWidgetInfo info = mCreateWidgetInfo;
710            mCreateWidgetInfo = null;
711
712            if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
713                // We never did any preloading, so just remove pending callbacks to do so
714                removeCallbacks(mBindWidgetRunnable);
715                removeCallbacks(mInflateWidgetRunnable);
716            } else if (mWidgetCleanupState == WIDGET_BOUND) {
717                 // Delete the widget id which was allocated
718                if (mWidgetLoadingId != -1) {
719                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
720                }
721
722                // We never got around to inflating the widget, so remove the callback to do so.
723                removeCallbacks(mInflateWidgetRunnable);
724            } else if (mWidgetCleanupState == WIDGET_INFLATED) {
725                // Delete the widget id which was allocated
726                if (mWidgetLoadingId != -1) {
727                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
728                }
729
730                // The widget was inflated and added to the DragLayer -- remove it.
731                AppWidgetHostView widget = info.boundWidget;
732                mLauncher.getDragLayer().removeView(widget);
733            }
734        }
735        mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
736        mWidgetLoadingId = -1;
737        mCreateWidgetInfo = null;
738        PagedViewWidget.resetShortPressTarget();
739    }
740
741    @Override
742    public void cleanUpShortPress(View v) {
743        if (!mDraggingWidget) {
744            cleanupWidgetPreloading(false);
745        }
746    }
747
748    private boolean beginDraggingWidget(View v) {
749        mDraggingWidget = true;
750        // Get the widget preview as the drag representation
751        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
752        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
753
754        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
755        // we abort the drag.
756        if (image.getDrawable() == null) {
757            mDraggingWidget = false;
758            return false;
759        }
760
761        // Compose the drag image
762        Bitmap preview;
763        Bitmap outline;
764        float scale = 1f;
765        if (createItemInfo instanceof PendingAddWidgetInfo) {
766            // This can happen in some weird cases involving multi-touch. We can't start dragging
767            // the widget if this is null, so we break out.
768            if (mCreateWidgetInfo == null) {
769                return false;
770            }
771
772            PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
773            createItemInfo = createWidgetInfo;
774            int spanX = createItemInfo.spanX;
775            int spanY = createItemInfo.spanY;
776            int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
777                    createWidgetInfo, true);
778
779            FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
780            float minScale = 1.25f;
781            int maxWidth, maxHeight;
782            maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
783            maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
784            preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
785                    createWidgetInfo.icon, spanX, spanY, maxWidth, maxHeight);
786
787            // Determine the image view drawable scale relative to the preview
788            float[] mv = new float[9];
789            Matrix m = new Matrix();
790            m.setRectToRect(
791                    new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()),
792                    new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(),
793                            (float) previewDrawable.getIntrinsicHeight()),
794                    Matrix.ScaleToFit.START);
795            m.getValues(mv);
796            scale = (float) mv[0];
797        } else {
798            PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
799            Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
800            preview = Bitmap.createBitmap(icon.getIntrinsicWidth(),
801                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
802
803            mCanvas.setBitmap(preview);
804            mCanvas.save();
805            renderDrawableToBitmap(icon, preview, 0, 0,
806                    icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
807            mCanvas.restore();
808            mCanvas.setBitmap(null);
809            createItemInfo.spanX = createItemInfo.spanY = 1;
810        }
811
812        // Don't clip alpha values for the drag outline if we're using the default widget preview
813        boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
814                (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
815
816        // Save the preview for the outline generation, then dim the preview
817        outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
818                false);
819
820        // Start the drag
821        mLauncher.lockScreenOrientation();
822        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
823        mDragController.startDrag(image, preview, this, createItemInfo,
824                DragController.DRAG_ACTION_COPY, null, scale);
825        outline.recycle();
826        preview.recycle();
827        return true;
828    }
829
830    @Override
831    protected boolean beginDragging(final View v) {
832        if (!super.beginDragging(v)) return false;
833
834        if (v instanceof PagedViewIcon) {
835            beginDraggingApplication(v);
836        } else if (v instanceof PagedViewWidget) {
837            if (!beginDraggingWidget(v)) {
838                return false;
839            }
840        }
841
842        // We delay entering spring-loaded mode slightly to make sure the UI
843        // thready is free of any work.
844        postDelayed(new Runnable() {
845            @Override
846            public void run() {
847                // We don't enter spring-loaded mode if the drag has been cancelled
848                if (mLauncher.getDragController().isDragging()) {
849                    // Dismiss the cling
850                    mLauncher.dismissAllAppsCling(null);
851
852                    // Reset the alpha on the dragged icon before we drag
853                    resetDrawableState();
854
855                    // Go into spring loaded mode (must happen before we startDrag())
856                    mLauncher.enterSpringLoadedDragMode();
857                }
858            }
859        }, 150);
860
861        return true;
862    }
863
864    /**
865     * Clean up after dragging.
866     *
867     * @param target where the item was dragged to (can be null if the item was flung)
868     */
869    private void endDragging(View target, boolean isFlingToDelete, boolean success) {
870        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
871                !(target instanceof DeleteDropTarget))) {
872            // Exit spring loaded mode if we have not successfully dropped or have not handled the
873            // drop in Workspace
874            mLauncher.exitSpringLoadedDragMode();
875        }
876        mLauncher.unlockScreenOrientation(false);
877    }
878
879    @Override
880    public View getContent() {
881        return null;
882    }
883
884    @Override
885    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
886        mInTransition = true;
887        if (toWorkspace) {
888            cancelAllTasks();
889        }
890    }
891
892    @Override
893    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
894    }
895
896    @Override
897    public void onLauncherTransitionStep(Launcher l, float t) {
898    }
899
900    @Override
901    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
902        mInTransition = false;
903        for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
904            onSyncWidgetPageItems(d);
905        }
906        mDeferredSyncWidgetPageItems.clear();
907        for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
908            r.run();
909        }
910        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
911        mForceDrawAllChildrenNextFrame = !toWorkspace;
912    }
913
914    @Override
915    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
916            boolean success) {
917        // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
918        if (isFlingToDelete) return;
919
920        endDragging(target, false, success);
921
922        // Display an error message if the drag failed due to there not being enough space on the
923        // target layout we were dropping on.
924        if (!success) {
925            boolean showOutOfSpaceMessage = false;
926            if (target instanceof Workspace) {
927                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
928                Workspace workspace = (Workspace) target;
929                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
930                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
931                if (layout != null) {
932                    layout.calculateSpans(itemInfo);
933                    showOutOfSpaceMessage =
934                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
935                }
936            }
937            if (showOutOfSpaceMessage) {
938                mLauncher.showOutOfSpaceMessage(false);
939            }
940
941            d.deferDragViewCleanupPostAnimation = false;
942        }
943        cleanupWidgetPreloading(success);
944        mDraggingWidget = false;
945    }
946
947    @Override
948    public void onFlingToDeleteCompleted() {
949        // We just dismiss the drag when we fling, so cleanup here
950        endDragging(null, true, true);
951        cleanupWidgetPreloading(false);
952        mDraggingWidget = false;
953    }
954
955    @Override
956    public boolean supportsFlingToDelete() {
957        return true;
958    }
959
960    @Override
961    protected void onDetachedFromWindow() {
962        super.onDetachedFromWindow();
963        cancelAllTasks();
964    }
965
966    public void clearAllWidgetPages() {
967        cancelAllTasks();
968        int count = getChildCount();
969        for (int i = 0; i < count; i++) {
970            View v = getPageAt(i);
971            if (v instanceof PagedViewGridLayout) {
972                ((PagedViewGridLayout) v).removeAllViewsOnPage();
973                mDirtyPageContent.set(i, true);
974            }
975        }
976    }
977
978    private void cancelAllTasks() {
979        // Clean up all the async tasks
980        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
981        while (iter.hasNext()) {
982            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
983            task.cancel(false);
984            iter.remove();
985            mDirtyPageContent.set(task.page, true);
986
987            // We've already preallocated the views for the data to load into, so clear them as well
988            View v = getPageAt(task.page);
989            if (v instanceof PagedViewGridLayout) {
990                ((PagedViewGridLayout) v).removeAllViewsOnPage();
991            }
992        }
993        mDeferredSyncWidgetPageItems.clear();
994        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
995    }
996
997    public void setContentType(ContentType type) {
998        if (type == ContentType.Widgets) {
999            invalidatePageData(mNumAppsPages, true);
1000        } else if (type == ContentType.Applications) {
1001            invalidatePageData(0, true);
1002        }
1003    }
1004
1005    protected void snapToPage(int whichPage, int delta, int duration) {
1006        super.snapToPage(whichPage, delta, duration);
1007        updateCurrentTab(whichPage);
1008
1009        // Update the thread priorities given the direction lookahead
1010        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1011        while (iter.hasNext()) {
1012            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1013            int pageIndex = task.page;
1014            if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
1015                (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
1016                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
1017            } else {
1018                task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
1019            }
1020        }
1021    }
1022
1023    private void updateCurrentTab(int currentPage) {
1024        AppsCustomizeTabHost tabHost = getTabHost();
1025        if (tabHost != null) {
1026            String tag = tabHost.getCurrentTabTag();
1027            if (tag != null) {
1028                if (currentPage >= mNumAppsPages &&
1029                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
1030                    tabHost.setCurrentTabFromContent(ContentType.Widgets);
1031                } else if (currentPage < mNumAppsPages &&
1032                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
1033                    tabHost.setCurrentTabFromContent(ContentType.Applications);
1034                }
1035            }
1036        }
1037    }
1038
1039    /*
1040     * Apps PagedView implementation
1041     */
1042    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
1043        int childCount = layout.getChildCount();
1044        for (int i = 0; i < childCount; ++i) {
1045            layout.getChildAt(i).setVisibility(visibility);
1046        }
1047    }
1048    private void setupPage(PagedViewCellLayout layout) {
1049        layout.setCellCount(mCellCountX, mCellCountY);
1050        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
1051        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
1052                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
1053
1054        // Note: We force a measure here to get around the fact that when we do layout calculations
1055        // immediately after syncing, we don't have a proper width.  That said, we already know the
1056        // expected page width, so we can actually optimize by hiding all the TextView-based
1057        // children that are expensive to measure, and let that happen naturally later.
1058        setVisibilityOnChildren(layout, View.GONE);
1059        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
1060        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
1061        layout.setMinimumWidth(getPageContentWidth());
1062        layout.measure(widthSpec, heightSpec);
1063        setVisibilityOnChildren(layout, View.VISIBLE);
1064    }
1065
1066    public void syncAppsPageItems(int page, boolean immediate) {
1067        // ensure that we have the right number of items on the pages
1068        int numCells = mCellCountX * mCellCountY;
1069        int startIndex = page * numCells;
1070        int endIndex = Math.min(startIndex + numCells, mApps.size());
1071        PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
1072
1073        layout.removeAllViewsOnPage();
1074        ArrayList<Object> items = new ArrayList<Object>();
1075        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
1076        for (int i = startIndex; i < endIndex; ++i) {
1077            ApplicationInfo info = mApps.get(i);
1078            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
1079                    R.layout.apps_customize_application, layout, false);
1080            icon.applyFromApplicationInfo(info, true, this);
1081            icon.setOnClickListener(this);
1082            icon.setOnLongClickListener(this);
1083            icon.setOnTouchListener(this);
1084            icon.setOnKeyListener(this);
1085
1086            int index = i - startIndex;
1087            int x = index % mCellCountX;
1088            int y = index / mCellCountX;
1089            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
1090
1091            items.add(info);
1092            images.add(info.iconBitmap);
1093        }
1094
1095        layout.createHardwareLayers();
1096    }
1097
1098    /**
1099     * A helper to return the priority for loading of the specified widget page.
1100     */
1101    private int getWidgetPageLoadPriority(int page) {
1102        // If we are snapping to another page, use that index as the target page index
1103        int toPage = mCurrentPage;
1104        if (mNextPage > -1) {
1105            toPage = mNextPage;
1106        }
1107
1108        // We use the distance from the target page as an initial guess of priority, but if there
1109        // are no pages of higher priority than the page specified, then bump up the priority of
1110        // the specified page.
1111        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1112        int minPageDiff = Integer.MAX_VALUE;
1113        while (iter.hasNext()) {
1114            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1115            minPageDiff = Math.abs(task.page - toPage);
1116        }
1117
1118        int rawPageDiff = Math.abs(page - toPage);
1119        return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
1120    }
1121    /**
1122     * Return the appropriate thread priority for loading for a given page (we give the current
1123     * page much higher priority)
1124     */
1125    private int getThreadPriorityForPage(int page) {
1126        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
1127        int pageDiff = getWidgetPageLoadPriority(page);
1128        if (pageDiff <= 0) {
1129            return Process.THREAD_PRIORITY_LESS_FAVORABLE;
1130        } else if (pageDiff <= 1) {
1131            return Process.THREAD_PRIORITY_LOWEST;
1132        } else {
1133            return Process.THREAD_PRIORITY_LOWEST;
1134        }
1135    }
1136    private int getSleepForPage(int page) {
1137        int pageDiff = getWidgetPageLoadPriority(page);
1138        return Math.max(0, pageDiff * sPageSleepDelay);
1139    }
1140    /**
1141     * Creates and executes a new AsyncTask to load a page of widget previews.
1142     */
1143    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
1144            int cellWidth, int cellHeight, int cellCountX) {
1145
1146        // Prune all tasks that are no longer needed
1147        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1148        while (iter.hasNext()) {
1149            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1150            int taskPage = task.page;
1151            if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
1152                    taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
1153                task.cancel(false);
1154                iter.remove();
1155            } else {
1156                task.setThreadPriority(getThreadPriorityForPage(taskPage));
1157            }
1158        }
1159
1160        // We introduce a slight delay to order the loading of side pages so that we don't thrash
1161        final int sleepMs = getSleepForPage(page);
1162        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
1163            new AsyncTaskCallback() {
1164                @Override
1165                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1166                    try {
1167                        try {
1168                            Thread.sleep(sleepMs);
1169                        } catch (Exception e) {}
1170                        loadWidgetPreviewsInBackground(task, data);
1171                    } finally {
1172                        if (task.isCancelled()) {
1173                            data.cleanup(true);
1174                        }
1175                    }
1176                }
1177            },
1178            new AsyncTaskCallback() {
1179                @Override
1180                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1181                    mRunningTasks.remove(task);
1182                    if (task.isCancelled()) return;
1183                    // do cleanup inside onSyncWidgetPageItems
1184                    onSyncWidgetPageItems(data);
1185                }
1186            });
1187
1188        // Ensure that the task is appropriately prioritized and runs in parallel
1189        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
1190                AsyncTaskPageData.Type.LoadWidgetPreviewData);
1191        t.setThreadPriority(getThreadPriorityForPage(page));
1192        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
1193        mRunningTasks.add(t);
1194    }
1195
1196    /*
1197     * Widgets PagedView implementation
1198     */
1199    private void setupPage(PagedViewGridLayout layout) {
1200        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
1201                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
1202
1203        // Note: We force a measure here to get around the fact that when we do layout calculations
1204        // immediately after syncing, we don't have a proper width.
1205        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
1206        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
1207        layout.setMinimumWidth(getPageContentWidth());
1208        layout.measure(widthSpec, heightSpec);
1209    }
1210
1211    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
1212        renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
1213    }
1214
1215    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
1216            float scale) {
1217        if (bitmap != null) {
1218            Canvas c = new Canvas(bitmap);
1219            c.scale(scale, scale);
1220            Rect oldBounds = d.copyBounds();
1221            d.setBounds(x, y, x + w, y + h);
1222            d.draw(c);
1223            d.setBounds(oldBounds); // Restore the bounds
1224            c.setBitmap(null);
1225        }
1226    }
1227
1228    private Bitmap getShortcutPreview(ResolveInfo info, int maxWidth, int maxHeight) {
1229        Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
1230        final Canvas c = mCachedShortcutPreviewCanvas.get();
1231        if (tempBitmap == null ||
1232                tempBitmap.getWidth() != maxWidth ||
1233                tempBitmap.getHeight() != maxHeight) {
1234            tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
1235            mCachedShortcutPreviewBitmap.set(tempBitmap);
1236        } else {
1237            c.setBitmap(tempBitmap);
1238            c.drawColor(0, PorterDuff.Mode.CLEAR);
1239            c.setBitmap(null);
1240        }
1241        // Render the icon
1242        Drawable icon = mIconCache.getFullResIcon(info);
1243
1244        int paddingTop =
1245                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
1246        int paddingLeft =
1247                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
1248        int paddingRight =
1249                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
1250
1251        int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
1252
1253        renderDrawableToBitmap(
1254                icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
1255
1256        Bitmap preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
1257        c.setBitmap(preview);
1258        Paint p = mCachedShortcutPreviewPaint.get();
1259        if (p == null) {
1260            p = new Paint();
1261            ColorMatrix colorMatrix = new ColorMatrix();
1262            colorMatrix.setSaturation(0);
1263            p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
1264            p.setAlpha((int) (255 * 0.06f));
1265            //float density = 1f;
1266            //p.setMaskFilter(new BlurMaskFilter(15*density, BlurMaskFilter.Blur.NORMAL));
1267            mCachedShortcutPreviewPaint.set(p);
1268        }
1269        c.drawBitmap(tempBitmap, 0, 0, p);
1270        c.setBitmap(null);
1271
1272        renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
1273
1274        return preview;
1275    }
1276
1277    private Bitmap getWidgetPreview(ComponentName provider, int previewImage,
1278            int iconId, int cellHSpan, int cellVSpan, int maxWidth,
1279            int maxHeight) {
1280        // Load the preview image if possible
1281        String packageName = provider.getPackageName();
1282        if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
1283        if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
1284
1285        Drawable drawable = null;
1286        if (previewImage != 0) {
1287            drawable = mPackageManager.getDrawable(packageName, previewImage, null);
1288            if (drawable == null) {
1289                Log.w(TAG, "Can't load widget preview drawable 0x" +
1290                        Integer.toHexString(previewImage) + " for provider: " + provider);
1291            }
1292        }
1293
1294        int bitmapWidth;
1295        int bitmapHeight;
1296        Bitmap defaultPreview = null;
1297        boolean widgetPreviewExists = (drawable != null);
1298        if (widgetPreviewExists) {
1299            bitmapWidth = drawable.getIntrinsicWidth();
1300            bitmapHeight = drawable.getIntrinsicHeight();
1301        } else {
1302            // Generate a preview image if we couldn't load one
1303            if (cellHSpan < 1) cellHSpan = 1;
1304            if (cellVSpan < 1) cellVSpan = 1;
1305
1306            BitmapDrawable previewDrawable = (BitmapDrawable) getResources()
1307                    .getDrawable(R.drawable.widget_preview_tile);
1308            final int previewDrawableWidth = previewDrawable
1309                    .getIntrinsicWidth();
1310            final int previewDrawableHeight = previewDrawable
1311                    .getIntrinsicHeight();
1312            bitmapWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips
1313            bitmapHeight = previewDrawableHeight * cellVSpan;
1314
1315            defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
1316                    Config.ARGB_8888);
1317            final Canvas c = mCachedAppWidgetPreviewCanvas.get();
1318            c.setBitmap(defaultPreview);
1319            previewDrawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
1320            previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
1321                    Shader.TileMode.REPEAT);
1322            previewDrawable.draw(c);
1323            c.setBitmap(null);
1324
1325            // Draw the icon in the top left corner
1326            int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
1327            int smallestSide = Math.min(bitmapWidth, bitmapHeight);
1328            float iconScale = Math.min((float) smallestSide
1329                    / (mAppIconSize + 2 * minOffset), 1f);
1330
1331            try {
1332                Drawable icon = null;
1333                int hoffset =
1334                        (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
1335                int yoffset =
1336                        (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
1337                if (iconId > 0)
1338                    icon = mIconCache.getFullResIcon(packageName, iconId);
1339                if (icon != null) {
1340                    renderDrawableToBitmap(icon, defaultPreview, hoffset,
1341                            yoffset, (int) (mAppIconSize * iconScale),
1342                            (int) (mAppIconSize * iconScale));
1343                }
1344            } catch (Resources.NotFoundException e) {
1345            }
1346        }
1347
1348        // Scale to fit width only - let the widget preview be clipped in the
1349        // vertical dimension
1350        float scale = 1f;
1351        if (bitmapWidth > maxWidth) {
1352            scale = maxWidth / (float) bitmapWidth;
1353        }
1354        if (scale != 1f) {
1355            bitmapWidth = (int) (scale * bitmapWidth);
1356            bitmapHeight = (int) (scale * bitmapHeight);
1357        }
1358
1359        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
1360                Config.ARGB_8888);
1361
1362        // Draw the scaled preview into the final bitmap
1363        if (widgetPreviewExists) {
1364            renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth,
1365                    bitmapHeight);
1366        } else {
1367            final Canvas c = mCachedAppWidgetPreviewCanvas.get();
1368            final Rect src = mCachedAppWidgetPreviewSrcRect.get();
1369            final Rect dest = mCachedAppWidgetPreviewDestRect.get();
1370            c.setBitmap(preview);
1371            src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
1372            dest.set(0, 0, preview.getWidth(), preview.getHeight());
1373
1374            Paint p = mCachedAppWidgetPreviewPaint.get();
1375            if (p == null) {
1376                p = new Paint();
1377                p.setFilterBitmap(true);
1378                mCachedAppWidgetPreviewPaint.set(p);
1379            }
1380            c.drawBitmap(defaultPreview, src, dest, p);
1381            c.setBitmap(null);
1382        }
1383        return preview;
1384    }
1385
1386    public void syncWidgetPageItems(final int page, final boolean immediate) {
1387        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1388
1389        // Calculate the dimensions of each cell we are giving to each widget
1390        final ArrayList<Object> items = new ArrayList<Object>();
1391        int contentWidth = mWidgetSpacingLayout.getContentWidth();
1392        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
1393                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
1394        int contentHeight = mWidgetSpacingLayout.getContentHeight();
1395        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
1396                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
1397
1398        // Prepare the set of widgets to load previews for in the background
1399        int offset = (page - mNumAppsPages) * numItemsPerPage;
1400        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1401            items.add(mWidgets.get(i));
1402        }
1403
1404        // Prepopulate the pages with the other widget info, and fill in the previews later
1405        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1406        layout.setColumnCount(layout.getCellCountX());
1407        for (int i = 0; i < items.size(); ++i) {
1408            Object rawInfo = items.get(i);
1409            PendingAddItemInfo createItemInfo = null;
1410            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1411                    R.layout.apps_customize_widget, layout, false);
1412            if (rawInfo instanceof AppWidgetProviderInfo) {
1413                // Fill in the widget information
1414                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1415                createItemInfo = new PendingAddWidgetInfo(info, null, null);
1416
1417                // Determine the widget spans and min resize spans.
1418                int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
1419                createItemInfo.spanX = spanXY[0];
1420                createItemInfo.spanY = spanXY[1];
1421                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
1422                createItemInfo.minSpanX = minSpanXY[0];
1423                createItemInfo.minSpanY = minSpanXY[1];
1424
1425                widget.applyFromAppWidgetProviderInfo(info, -1, spanXY);
1426                widget.setTag(createItemInfo);
1427                widget.setShortPressListener(this);
1428            } else if (rawInfo instanceof ResolveInfo) {
1429                // Fill in the shortcuts information
1430                ResolveInfo info = (ResolveInfo) rawInfo;
1431                createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
1432                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1433                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1434                        info.activityInfo.name);
1435                widget.applyFromResolveInfo(mPackageManager, info);
1436                widget.setTag(createItemInfo);
1437            }
1438            widget.setOnClickListener(this);
1439            widget.setOnLongClickListener(this);
1440            widget.setOnTouchListener(this);
1441            widget.setOnKeyListener(this);
1442
1443            // Layout each widget
1444            int ix = i % mWidgetCountX;
1445            int iy = i / mWidgetCountX;
1446            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1447                    GridLayout.spec(iy, GridLayout.LEFT),
1448                    GridLayout.spec(ix, GridLayout.TOP));
1449            lp.width = cellWidth;
1450            lp.height = cellHeight;
1451            lp.setGravity(Gravity.TOP | Gravity.LEFT);
1452            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
1453            if (iy > 0) lp.topMargin = mWidgetHeightGap;
1454            layout.addView(widget, lp);
1455        }
1456
1457        // wait until a call on onLayout to start loading, because
1458        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1459        // TODO: can we do a measure/layout immediately?
1460        layout.setOnLayoutListener(new Runnable() {
1461            public void run() {
1462                // Load the widget previews
1463                int maxPreviewWidth = cellWidth;
1464                int maxPreviewHeight = cellHeight;
1465                if (layout.getChildCount() > 0) {
1466                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1467                    int[] maxSize = w.getPreviewSize();
1468                    maxPreviewWidth = maxSize[0];
1469                    maxPreviewHeight = maxSize[1];
1470                }
1471                if (immediate) {
1472                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1473                            maxPreviewWidth, maxPreviewHeight, null, null);
1474                    loadWidgetPreviewsInBackground(null, data);
1475                    onSyncWidgetPageItems(data);
1476                } else {
1477                    if (mInTransition) {
1478                        mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
1479                    } else {
1480                        prepareLoadWidgetPreviewsTask(page, items,
1481                                maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1482                    }
1483                }
1484            }
1485        });
1486    }
1487    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1488            AsyncTaskPageData data) {
1489        // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
1490        // previews synchronously
1491        if (task != null) {
1492            // Ensure that this task starts running at the correct priority
1493            task.syncThreadPriority();
1494        }
1495
1496        // Load each of the widget/shortcut previews
1497        ArrayList<Object> items = data.items;
1498        ArrayList<Bitmap> images = data.generatedImages;
1499        int count = items.size();
1500        for (int i = 0; i < count; ++i) {
1501            if (task != null) {
1502                // Ensure we haven't been cancelled yet
1503                if (task.isCancelled()) break;
1504                // Before work on each item, ensure that this task is running at the correct
1505                // priority
1506                task.syncThreadPriority();
1507            }
1508
1509            Object rawInfo = items.get(i);
1510            if (rawInfo instanceof AppWidgetProviderInfo) {
1511                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1512                int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info);
1513
1514                int maxWidth = Math.min(data.maxImageWidth,
1515                        mWidgetSpacingLayout.estimateCellWidth(cellSpans[0]));
1516                int maxHeight = Math.min(data.maxImageHeight,
1517                        mWidgetSpacingLayout.estimateCellHeight(cellSpans[1]));
1518                Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
1519                        cellSpans[0], cellSpans[1], maxWidth, maxHeight);
1520                images.add(b);
1521            } else if (rawInfo instanceof ResolveInfo) {
1522                // Fill in the shortcuts information
1523                ResolveInfo info = (ResolveInfo) rawInfo;
1524                images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight));
1525            }
1526        }
1527    }
1528
1529    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
1530        if (mInTransition) {
1531            mDeferredSyncWidgetPageItems.add(data);
1532            return;
1533        }
1534        try {
1535            int page = data.page;
1536            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1537
1538            ArrayList<Object> items = data.items;
1539            int count = items.size();
1540            for (int i = 0; i < count; ++i) {
1541                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1542                if (widget != null) {
1543                    Bitmap preview = data.generatedImages.get(i);
1544                    widget.applyPreview(new FastBitmapDrawable(preview), i);
1545                }
1546            }
1547
1548            layout.createHardwareLayer();
1549            invalidate();
1550
1551            // Update all thread priorities
1552            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1553            while (iter.hasNext()) {
1554                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1555                int pageIndex = task.page;
1556                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
1557            }
1558        } finally {
1559            data.cleanup(false);
1560        }
1561    }
1562
1563    @Override
1564    public void syncPages() {
1565        removeAllViews();
1566        cancelAllTasks();
1567
1568        Context context = getContext();
1569        for (int j = 0; j < mNumWidgetPages; ++j) {
1570            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1571                    mWidgetCountY);
1572            setupPage(layout);
1573            addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
1574                    LayoutParams.MATCH_PARENT));
1575        }
1576
1577        for (int i = 0; i < mNumAppsPages; ++i) {
1578            PagedViewCellLayout layout = new PagedViewCellLayout(context);
1579            setupPage(layout);
1580            addView(layout);
1581        }
1582    }
1583
1584    @Override
1585    public void syncPageItems(int page, boolean immediate) {
1586        if (page < mNumAppsPages) {
1587            syncAppsPageItems(page, immediate);
1588        } else {
1589            syncWidgetPageItems(page, immediate);
1590        }
1591    }
1592
1593    // We want our pages to be z-ordered such that the further a page is to the left, the higher
1594    // it is in the z-order. This is important to insure touch events are handled correctly.
1595    View getPageAt(int index) {
1596        return getChildAt(indexToPage(index));
1597    }
1598
1599    @Override
1600    protected int indexToPage(int index) {
1601        return getChildCount() - index - 1;
1602    }
1603
1604    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1605    @Override
1606    protected void screenScrolled(int screenCenter) {
1607        super.screenScrolled(screenCenter);
1608
1609        for (int i = 0; i < getChildCount(); i++) {
1610            View v = getPageAt(i);
1611            if (v != null) {
1612                float scrollProgress = getScrollProgress(screenCenter, v, i);
1613
1614                float interpolatedProgress =
1615                        mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
1616                float scale = (1 - interpolatedProgress) +
1617                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
1618                float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
1619
1620                float alpha;
1621
1622                if (scrollProgress < 0) {
1623                    alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1624                        1 - Math.abs(scrollProgress)) : 1.0f;
1625                } else {
1626                    // On large screens we need to fade the page as it nears its leftmost position
1627                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
1628                }
1629
1630                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
1631                int pageWidth = v.getMeasuredWidth();
1632                int pageHeight = v.getMeasuredHeight();
1633
1634                if (PERFORM_OVERSCROLL_ROTATION) {
1635                    if (i == 0 && scrollProgress < 0) {
1636                        // Overscroll to the left
1637                        v.setPivotX(TRANSITION_PIVOT * pageWidth);
1638                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1639                        scale = 1.0f;
1640                        alpha = 1.0f;
1641                        // On the first page, we don't want the page to have any lateral motion
1642                        translationX = 0;
1643                    } else if (i == getChildCount() - 1 && scrollProgress > 0) {
1644                        // Overscroll to the right
1645                        v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
1646                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1647                        scale = 1.0f;
1648                        alpha = 1.0f;
1649                        // On the last page, we don't want the page to have any lateral motion.
1650                        translationX = 0;
1651                    } else {
1652                        v.setPivotY(pageHeight / 2.0f);
1653                        v.setPivotX(pageWidth / 2.0f);
1654                        v.setRotationY(0f);
1655                    }
1656                }
1657
1658                v.setTranslationX(translationX);
1659                v.setScaleX(scale);
1660                v.setScaleY(scale);
1661                v.setAlpha(alpha);
1662
1663                // If the view has 0 alpha, we set it to be invisible so as to prevent
1664                // it from accepting touches
1665                if (alpha == 0) {
1666                    v.setVisibility(INVISIBLE);
1667                } else if (v.getVisibility() != VISIBLE) {
1668                    v.setVisibility(VISIBLE);
1669                }
1670            }
1671        }
1672    }
1673
1674    protected void overScroll(float amount) {
1675        acceleratedOverScroll(amount);
1676    }
1677
1678    /**
1679     * Used by the parent to get the content width to set the tab bar to
1680     * @return
1681     */
1682    public int getPageContentWidth() {
1683        return mContentWidth;
1684    }
1685
1686    @Override
1687    protected void onPageEndMoving() {
1688        super.onPageEndMoving();
1689        mForceDrawAllChildrenNextFrame = true;
1690        // We reset the save index when we change pages so that it will be recalculated on next
1691        // rotation
1692        mSaveInstanceStateItemIndex = -1;
1693    }
1694
1695    /*
1696     * AllAppsView implementation
1697     */
1698    public void setup(Launcher launcher, DragController dragController) {
1699        mLauncher = launcher;
1700        mDragController = dragController;
1701    }
1702
1703    /**
1704     * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
1705     * appropriately determine when to invalidate the PagedView page data.  In cases where the data
1706     * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
1707     * next onMeasure() pass, which will trigger an invalidatePageData() itself.
1708     */
1709    private void invalidateOnDataChange() {
1710        if (!isDataReady()) {
1711            // The next layout pass will trigger data-ready if both widgets and apps are set, so
1712            // request a layout to trigger the page data when ready.
1713            requestLayout();
1714        } else {
1715            cancelAllTasks();
1716            invalidatePageData();
1717        }
1718    }
1719
1720    public void setApps(ArrayList<ApplicationInfo> list) {
1721        mApps = list;
1722        Collections.sort(mApps, LauncherModel.getAppNameComparator());
1723        updatePageCounts();
1724        invalidateOnDataChange();
1725    }
1726    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1727        // We add it in place, in alphabetical order
1728        int count = list.size();
1729        for (int i = 0; i < count; ++i) {
1730            ApplicationInfo info = list.get(i);
1731            int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
1732            if (index < 0) {
1733                mApps.add(-(index + 1), info);
1734            }
1735        }
1736    }
1737    public void addApps(ArrayList<ApplicationInfo> list) {
1738        addAppsWithoutInvalidate(list);
1739        updatePageCounts();
1740        invalidateOnDataChange();
1741    }
1742    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1743        ComponentName removeComponent = item.intent.getComponent();
1744        int length = list.size();
1745        for (int i = 0; i < length; ++i) {
1746            ApplicationInfo info = list.get(i);
1747            if (info.intent.getComponent().equals(removeComponent)) {
1748                return i;
1749            }
1750        }
1751        return -1;
1752    }
1753    private int findAppByPackage(List<ApplicationInfo> list, String packageName) {
1754        int length = list.size();
1755        for (int i = 0; i < length; ++i) {
1756            ApplicationInfo info = list.get(i);
1757            if (ItemInfo.getPackageName(info.intent).equals(packageName)) {
1758                return i;
1759            }
1760        }
1761        return -1;
1762    }
1763    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1764        // loop through all the apps and remove apps that have the same component
1765        int length = list.size();
1766        for (int i = 0; i < length; ++i) {
1767            ApplicationInfo info = list.get(i);
1768            int removeIndex = findAppByComponent(mApps, info);
1769            if (removeIndex > -1) {
1770                mApps.remove(removeIndex);
1771            }
1772        }
1773    }
1774    private void removeAppsWithPackageNameWithoutInvalidate(ArrayList<String> packageNames) {
1775        // loop through all the package names and remove apps that have the same package name
1776        for (String pn : packageNames) {
1777            int removeIndex = findAppByPackage(mApps, pn);
1778            while (removeIndex > -1) {
1779                mApps.remove(removeIndex);
1780                removeIndex = findAppByPackage(mApps, pn);
1781            }
1782        }
1783    }
1784    public void removeApps(ArrayList<String> packageNames) {
1785        removeAppsWithPackageNameWithoutInvalidate(packageNames);
1786        updatePageCounts();
1787        invalidateOnDataChange();
1788    }
1789    public void updateApps(ArrayList<ApplicationInfo> list) {
1790        // We remove and re-add the updated applications list because it's properties may have
1791        // changed (ie. the title), and this will ensure that the items will be in their proper
1792        // place in the list.
1793        removeAppsWithoutInvalidate(list);
1794        addAppsWithoutInvalidate(list);
1795        updatePageCounts();
1796        invalidateOnDataChange();
1797    }
1798
1799    public void reset() {
1800        // If we have reset, then we should not continue to restore the previous state
1801        mSaveInstanceStateItemIndex = -1;
1802
1803        AppsCustomizeTabHost tabHost = getTabHost();
1804        String tag = tabHost.getCurrentTabTag();
1805        if (tag != null) {
1806            if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
1807                tabHost.setCurrentTabFromContent(ContentType.Applications);
1808            }
1809        }
1810
1811        if (mCurrentPage != 0) {
1812            invalidatePageData(0);
1813        }
1814    }
1815
1816    private AppsCustomizeTabHost getTabHost() {
1817        return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
1818    }
1819
1820    public void dumpState() {
1821        // TODO: Dump information related to current list of Applications, Widgets, etc.
1822        ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
1823        dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
1824    }
1825
1826    private void dumpAppWidgetProviderInfoList(String tag, String label,
1827            ArrayList<Object> list) {
1828        Log.d(tag, label + " size=" + list.size());
1829        for (Object i: list) {
1830            if (i instanceof AppWidgetProviderInfo) {
1831                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1832                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1833                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1834                        + " initialLayout=" + info.initialLayout
1835                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1836            } else if (i instanceof ResolveInfo) {
1837                ResolveInfo info = (ResolveInfo) i;
1838                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1839                        + info.icon);
1840            }
1841        }
1842    }
1843
1844    public void surrender() {
1845        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1846        // should stop this now.
1847
1848        // Stop all background tasks
1849        cancelAllTasks();
1850    }
1851
1852    @Override
1853    public void iconPressed(PagedViewIcon icon) {
1854        // Reset the previously pressed icon and store a reference to the pressed icon so that
1855        // we can reset it on return to Launcher (in Launcher.onResume())
1856        if (mPressedIcon != null) {
1857            mPressedIcon.resetDrawableState();
1858        }
1859        mPressedIcon = icon;
1860    }
1861
1862    public void resetDrawableState() {
1863        if (mPressedIcon != null) {
1864            mPressedIcon.resetDrawableState();
1865            mPressedIcon = null;
1866        }
1867    }
1868
1869    /*
1870     * We load an extra page on each side to prevent flashes from scrolling and loading of the
1871     * widget previews in the background with the AsyncTasks.
1872     */
1873    final static int sLookBehindPageCount = 2;
1874    final static int sLookAheadPageCount = 2;
1875    protected int getAssociatedLowerPageBound(int page) {
1876        final int count = getChildCount();
1877        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1878        int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
1879        return windowMinIndex;
1880    }
1881    protected int getAssociatedUpperPageBound(int page) {
1882        final int count = getChildCount();
1883        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1884        int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
1885                count - 1);
1886        return windowMaxIndex;
1887    }
1888
1889    @Override
1890    protected String getCurrentPageDescription() {
1891        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
1892        int stringId = R.string.default_scroll_format;
1893        int count = 0;
1894
1895        if (page < mNumAppsPages) {
1896            stringId = R.string.apps_customize_apps_scroll_format;
1897            count = mNumAppsPages;
1898        } else {
1899            page -= mNumAppsPages;
1900            stringId = R.string.apps_customize_widgets_scroll_format;
1901            count = mNumWidgetPages;
1902        }
1903
1904        return String.format(getContext().getString(stringId), page + 1, count);
1905    }
1906}
1907