AppsCustomizePagedView.java revision c6f10b906fc9c120cafc81046a42b2434ff635a9
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.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ComponentName;
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.content.res.TypedArray;
33import android.graphics.Bitmap;
34import android.graphics.Bitmap.Config;
35import android.graphics.Canvas;
36import android.graphics.MaskFilter;
37import android.graphics.Paint;
38import android.graphics.PorterDuff;
39import android.graphics.Rect;
40import android.graphics.TableMaskFilter;
41import android.graphics.drawable.Drawable;
42import android.os.AsyncTask;
43import android.os.Process;
44import android.util.AttributeSet;
45import android.util.Log;
46import android.view.Gravity;
47import android.view.KeyEvent;
48import android.view.LayoutInflater;
49import android.view.MotionEvent;
50import android.view.View;
51import android.view.ViewConfiguration;
52import android.view.ViewGroup;
53import android.view.animation.AccelerateInterpolator;
54import android.view.animation.DecelerateInterpolator;
55import android.widget.GridLayout;
56import android.widget.ImageView;
57import android.widget.Toast;
58
59import com.android.launcher.R;
60import com.android.launcher2.DropTarget.DragObject;
61
62import java.util.ArrayList;
63import java.util.Collections;
64import java.util.Iterator;
65import java.util.List;
66
67/**
68 * A simple callback interface which also provides the results of the task.
69 */
70interface AsyncTaskCallback {
71    void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
72}
73
74/**
75 * The data needed to perform either of the custom AsyncTasks.
76 */
77class AsyncTaskPageData {
78    enum Type {
79        LoadWidgetPreviewData,
80        LoadHolographicIconsData
81    }
82
83    AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
84            AsyncTaskCallback postR) {
85        page = p;
86        items = l;
87        sourceImages = si;
88        generatedImages = new ArrayList<Bitmap>();
89        maxImageWidth = maxImageHeight = -1;
90        doInBackgroundCallback = bgR;
91        postExecuteCallback = postR;
92    }
93    AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
94            AsyncTaskCallback postR) {
95        page = p;
96        items = l;
97        generatedImages = new ArrayList<Bitmap>();
98        maxImageWidth = cw;
99        maxImageHeight = ch;
100        doInBackgroundCallback = bgR;
101        postExecuteCallback = postR;
102    }
103    void cleanup(boolean cancelled) {
104        // Clean up any references to source/generated bitmaps
105        if (sourceImages != null) {
106            if (cancelled) {
107                for (Bitmap b : sourceImages) {
108                    b.recycle();
109                }
110            }
111            sourceImages.clear();
112        }
113        if (generatedImages != null) {
114            if (cancelled) {
115                for (Bitmap b : generatedImages) {
116                    b.recycle();
117                }
118            }
119            generatedImages.clear();
120        }
121    }
122    int page;
123    ArrayList<Object> items;
124    ArrayList<Bitmap> sourceImages;
125    ArrayList<Bitmap> generatedImages;
126    int maxImageWidth;
127    int maxImageHeight;
128    AsyncTaskCallback doInBackgroundCallback;
129    AsyncTaskCallback postExecuteCallback;
130}
131
132/**
133 * A generic template for an async task used in AppsCustomize.
134 */
135class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
136    AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
137        page = p;
138        threadPriority = Process.THREAD_PRIORITY_DEFAULT;
139        dataType = ty;
140    }
141    @Override
142    protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
143        if (params.length != 1) return null;
144        // Load each of the widget previews in the background
145        params[0].doInBackgroundCallback.run(this, params[0]);
146        return params[0];
147    }
148    @Override
149    protected void onPostExecute(AsyncTaskPageData result) {
150        // All the widget previews are loaded, so we can just callback to inflate the page
151        result.postExecuteCallback.run(this, result);
152    }
153
154    void setThreadPriority(int p) {
155        threadPriority = p;
156    }
157    void syncThreadPriority() {
158        Process.setThreadPriority(threadPriority);
159    }
160
161    // The page that this async task is associated with
162    AsyncTaskPageData.Type dataType;
163    int page;
164    int threadPriority;
165}
166
167/**
168 * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
169 */
170public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
171        AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource {
172    static final String LOG_TAG = "AppsCustomizePagedView";
173
174    /**
175     * The different content types that this paged view can show.
176     */
177    public enum ContentType {
178        Applications,
179        Widgets
180    }
181
182    // Refs
183    private Launcher mLauncher;
184    private DragController mDragController;
185    private final LayoutInflater mLayoutInflater;
186    private final PackageManager mPackageManager;
187
188    // Save and Restore
189    private int mSaveInstanceStateItemIndex = -1;
190
191    // Content
192    private ArrayList<ApplicationInfo> mApps;
193    private ArrayList<Object> mWidgets;
194
195    // Cling
196    private int mClingFocusedX;
197    private int mClingFocusedY;
198
199    // Caching
200    private Canvas mCanvas;
201    private Drawable mDefaultWidgetBackground;
202    private IconCache mIconCache;
203    private int mDragViewMultiplyColor;
204
205    // Dimens
206    private int mContentWidth;
207    private int mAppIconSize;
208    private int mMaxAppCellCountX, mMaxAppCellCountY;
209    private int mWidgetCountX, mWidgetCountY;
210    private int mWidgetWidthGap, mWidgetHeightGap;
211    private final int mWidgetPreviewIconPaddedDimension;
212    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
213    private PagedViewCellLayout mWidgetSpacingLayout;
214    private int mNumAppsPages;
215    private int mNumWidgetPages;
216
217    // Relating to the scroll and overscroll effects
218    Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
219    private static float CAMERA_DISTANCE = 6500;
220    private static float TRANSITION_SCALE_FACTOR = 0.74f;
221    private static float TRANSITION_PIVOT = 0.65f;
222    private static float TRANSITION_MAX_ROTATION = 22;
223    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
224    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
225    private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
226
227    // Previews & outlines
228    ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
229    private HolographicOutlineHelper mHolographicOutlineHelper;
230    private static final int sPageSleepDelay = 150;
231
232    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
233        super(context, attrs);
234        mLayoutInflater = LayoutInflater.from(context);
235        mPackageManager = context.getPackageManager();
236        mApps = new ArrayList<ApplicationInfo>();
237        mWidgets = new ArrayList<Object>();
238        mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
239        mHolographicOutlineHelper = new HolographicOutlineHelper();
240        mCanvas = new Canvas();
241        mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
242
243        // Save the default widget preview background
244        Resources resources = context.getResources();
245        mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
246        mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
247        mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color);
248
249        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
250        mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
251        mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1);
252        mWidgetWidthGap =
253            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
254        mWidgetHeightGap =
255            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
256        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
257        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
258        mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
259        mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
260        a.recycle();
261        mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
262
263        // The padding on the non-matched dimension for the default widget preview icons
264        // (top + bottom)
265        mWidgetPreviewIconPaddedDimension =
266            (int) (mAppIconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage)));
267        mFadeInAdjacentScreens = false;
268    }
269
270    @Override
271    protected void init() {
272        super.init();
273        mCenterPagesVertically = false;
274
275        Context context = getContext();
276        Resources r = context.getResources();
277        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
278    }
279
280    @Override
281    protected void onUnhandledTap(MotionEvent ev) {
282        if (LauncherApplication.isScreenLarge()) {
283            // Dismiss AppsCustomize if we tap
284            mLauncher.showWorkspace(true);
285        }
286    }
287
288    /** Returns the item index of the center item on this page so that we can restore to this
289     *  item index when we rotate. */
290    private int getMiddleComponentIndexOnCurrentPage() {
291        int i = -1;
292        if (getPageCount() > 0) {
293            int currentPage = getCurrentPage();
294            if (currentPage < mNumAppsPages) {
295                PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage);
296                PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout();
297                int numItemsPerPage = mCellCountX * mCellCountY;
298                int childCount = childrenLayout.getChildCount();
299                if (childCount > 0) {
300                    i = (currentPage * numItemsPerPage) + (childCount / 2);
301                }
302            } else {
303                int numApps = mApps.size();
304                PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
305                int numItemsPerPage = mWidgetCountX * mWidgetCountY;
306                int childCount = layout.getChildCount();
307                if (childCount > 0) {
308                    i = numApps +
309                        ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2);
310                }
311            }
312        }
313        return i;
314    }
315
316    /** Get the index of the item to restore to if we need to restore the current page. */
317    int getSaveInstanceStateIndex() {
318        if (mSaveInstanceStateItemIndex == -1) {
319            mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
320        }
321        return mSaveInstanceStateItemIndex;
322    }
323
324    /** Returns the page in the current orientation which is expected to contain the specified
325     *  item index. */
326    int getPageForComponent(int index) {
327        if (index < 0) return 0;
328
329        if (index < mApps.size()) {
330            int numItemsPerPage = mCellCountX * mCellCountY;
331            return (index / numItemsPerPage);
332        } else {
333            int numItemsPerPage = mWidgetCountX * mWidgetCountY;
334            return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage);
335        }
336    }
337
338    /**
339     * This differs from isDataReady as this is the test done if isDataReady is not set.
340     */
341    private boolean testDataReady() {
342        // We only do this test once, and we default to the Applications page, so we only really
343        // have to wait for there to be apps.
344        // TODO: What if one of them is validly empty
345        return !mApps.isEmpty() && !mWidgets.isEmpty();
346    }
347
348    /** Restores the page for an item at the specified index */
349    void restorePageForIndex(int index) {
350        if (index < 0) return;
351        mSaveInstanceStateItemIndex = index;
352    }
353
354    private void updatePageCounts() {
355        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
356                (float) (mWidgetCountX * mWidgetCountY));
357        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
358    }
359
360    protected void onDataReady(int width, int height) {
361        // Note that we transpose the counts in portrait so that we get a similar layout
362        boolean isLandscape = getResources().getConfiguration().orientation ==
363            Configuration.ORIENTATION_LANDSCAPE;
364        int maxCellCountX = Integer.MAX_VALUE;
365        int maxCellCountY = Integer.MAX_VALUE;
366        if (LauncherApplication.isScreenLarge()) {
367            maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
368                LauncherModel.getCellCountY());
369            maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
370                LauncherModel.getCellCountX());
371        }
372        if (mMaxAppCellCountX > -1) {
373            maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX);
374        }
375        if (mMaxAppCellCountY > -1) {
376            maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY);
377        }
378
379        // Now that the data is ready, we can calculate the content width, the number of cells to
380        // use for each page
381        mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
382        mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
383                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
384        mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
385        mCellCountX = mWidgetSpacingLayout.getCellCountX();
386        mCellCountY = mWidgetSpacingLayout.getCellCountY();
387        updatePageCounts();
388
389        // Force a measure to update recalculate the gaps
390        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
391        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
392        mWidgetSpacingLayout.measure(widthSpec, heightSpec);
393        mContentWidth = mWidgetSpacingLayout.getContentWidth();
394
395        // Restore the page
396        int page = getPageForComponent(mSaveInstanceStateItemIndex);
397        invalidatePageData(Math.max(0, page));
398
399        // Calculate the position for the cling punch through
400        int[] offset = new int[2];
401        int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
402        mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
403        pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + offset[0];
404        pos[1] += (getMeasuredHeight() - mWidgetSpacingLayout.getMeasuredHeight()) / 2 + offset[1];
405        mLauncher.showFirstRunAllAppsCling(pos);
406
407    }
408
409    @Override
410    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
411        int width = MeasureSpec.getSize(widthMeasureSpec);
412        int height = MeasureSpec.getSize(heightMeasureSpec);
413        if (!isDataReady()) {
414            if (testDataReady()) {
415                setDataIsReady();
416                setMeasuredDimension(width, height);
417                onDataReady(width, height);
418            }
419        }
420
421        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
422    }
423
424    /** Removes and returns the ResolveInfo with the specified ComponentName */
425    private ResolveInfo removeResolveInfoWithComponentName(List<ResolveInfo> list,
426            ComponentName cn) {
427        Iterator<ResolveInfo> iter = list.iterator();
428        while (iter.hasNext()) {
429            ResolveInfo rinfo = iter.next();
430            ActivityInfo info = rinfo.activityInfo;
431            ComponentName c = new ComponentName(info.packageName, info.name);
432            if (c.equals(cn)) {
433                iter.remove();
434                return rinfo;
435            }
436        }
437        return null;
438    }
439
440    public void onPackagesUpdated() {
441        // TODO: this isn't ideal, but we actually need to delay here. This call is triggered
442        // by a broadcast receiver, and in order for it to work correctly, we need to know that
443        // the AppWidgetService has already received and processed the same broadcast. Since there
444        // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally,
445        // we should have a more precise way of ensuring the AppWidgetService is up to date.
446        postDelayed(new Runnable() {
447           public void run() {
448               updatePackages();
449           }
450        }, 500);
451    }
452
453    public void updatePackages() {
454        // Get the list of widgets and shortcuts
455        boolean wasEmpty = mWidgets.isEmpty();
456        mWidgets.clear();
457        List<AppWidgetProviderInfo> widgets =
458            AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
459        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
460        List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
461        for (AppWidgetProviderInfo widget : widgets) {
462            if (widget.minWidth > 0 && widget.minHeight > 0) {
463                mWidgets.add(widget);
464            } else {
465                Log.e(LOG_TAG, "Widget " + widget.provider + " has invalid dimensions (" +
466                        widget.minWidth + ", " + widget.minHeight + ")");
467            }
468        }
469        mWidgets.addAll(shortcuts);
470        Collections.sort(mWidgets,
471                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
472        updatePageCounts();
473
474        if (wasEmpty) {
475            // The next layout pass will trigger data-ready if both widgets and apps are set, so request
476            // a layout to do this test and invalidate the page data when ready.
477            if (testDataReady()) requestLayout();
478        } else {
479            cancelAllTasks();
480            invalidatePageData();
481        }
482    }
483
484    @Override
485    public void onClick(View v) {
486        // When we have exited all apps or are in transition, disregard clicks
487        if (!mLauncher.isAllAppsCustomizeOpen() ||
488                mLauncher.getWorkspace().isSwitchingState()) return;
489
490        if (v instanceof PagedViewIcon) {
491            // Animate some feedback to the click
492            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
493            animateClickFeedback(v, new Runnable() {
494                @Override
495                public void run() {
496                    mLauncher.startActivitySafely(appInfo.intent, appInfo);
497                }
498            });
499        } else if (v instanceof PagedViewWidget) {
500            // Let the user know that they have to long press to add a widget
501            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
502                    Toast.LENGTH_SHORT).show();
503
504            // Create a little animation to show that the widget can move
505            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
506            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
507            AnimatorSet bounce = new AnimatorSet();
508            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
509            tyuAnim.setDuration(125);
510            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
511            tydAnim.setDuration(100);
512            bounce.play(tyuAnim).before(tydAnim);
513            bounce.setInterpolator(new AccelerateInterpolator());
514            bounce.start();
515        }
516    }
517
518    public boolean onKey(View v, int keyCode, KeyEvent event) {
519        return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
520    }
521
522    /*
523     * PagedViewWithDraggableItems implementation
524     */
525    @Override
526    protected void determineDraggingStart(android.view.MotionEvent ev) {
527        // Disable dragging by pulling an app down for now.
528    }
529
530    private void beginDraggingApplication(View v) {
531        mLauncher.getWorkspace().onDragStartedWithItem(v);
532        mLauncher.getWorkspace().beginDragShared(v, this);
533    }
534
535    private void beginDraggingWidget(View v) {
536        // Get the widget preview as the drag representation
537        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
538        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
539
540        // Compose the drag image
541        Bitmap b;
542        if (createItemInfo instanceof PendingAddWidgetInfo) {
543            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
544            int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null);
545            createItemInfo.spanX = spanXY[0];
546            createItemInfo.spanY = spanXY[1];
547
548            int[] maxSize = mLauncher.getWorkspace().estimateItemSize(spanXY[0], spanXY[1],
549                    createWidgetInfo, true);
550            b = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
551                    createWidgetInfo.icon, spanXY[0], spanXY[1], maxSize[0], maxSize[1]);
552        } else {
553            // Workaround for the fact that we don't keep the original ResolveInfo associated with
554            // the shortcut around.  To get the icon, we just render the preview image (which has
555            // the shortcut icon) to a new drag bitmap that clips the non-icon space.
556            b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension,
557                    mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888);
558            Drawable preview = image.getDrawable();
559            mCanvas.setBitmap(b);
560            mCanvas.save();
561            preview.draw(mCanvas);
562            mCanvas.restore();
563            mCanvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
564            mCanvas.setBitmap(null);
565            createItemInfo.spanX = createItemInfo.spanY = 1;
566        }
567
568        // We use a custom alpha clip table for the default widget previews
569        Paint alphaClipPaint = null;
570        if (createItemInfo instanceof PendingAddWidgetInfo) {
571            if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) {
572                MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255);
573                alphaClipPaint = new Paint();
574                alphaClipPaint.setMaskFilter(alphaClipTable);
575            }
576        }
577
578        // Start the drag
579        mLauncher.lockScreenOrientationOnLargeUI();
580        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, b, alphaClipPaint);
581        mDragController.startDrag(image, b, this, createItemInfo,
582                DragController.DRAG_ACTION_COPY, null);
583        b.recycle();
584    }
585    @Override
586    protected boolean beginDragging(View v) {
587        // Dismiss the cling
588        mLauncher.dismissAllAppsCling(null);
589
590        if (!super.beginDragging(v)) return false;
591
592        // Go into spring loaded mode (must happen before we startDrag())
593        mLauncher.enterSpringLoadedDragMode();
594
595        if (v instanceof PagedViewIcon) {
596            beginDraggingApplication(v);
597        } else if (v instanceof PagedViewWidget) {
598            beginDraggingWidget(v);
599        }
600        return true;
601    }
602    private void endDragging(View target, boolean success) {
603        mLauncher.getWorkspace().onDragStopped(success);
604        if (!success || (target != mLauncher.getWorkspace() &&
605                !(target instanceof DeleteDropTarget))) {
606            // Exit spring loaded mode if we have not successfully dropped or have not handled the
607            // drop in Workspace
608            mLauncher.exitSpringLoadedDragMode();
609        }
610        mLauncher.unlockScreenOrientationOnLargeUI();
611
612    }
613
614    @Override
615    public void onDropCompleted(View target, DragObject d, boolean success) {
616        endDragging(target, success);
617
618        // Display an error message if the drag failed due to there not being enough space on the
619        // target layout we were dropping on.
620        if (!success) {
621            boolean showOutOfSpaceMessage = false;
622            if (target instanceof Workspace) {
623                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
624                Workspace workspace = (Workspace) target;
625                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
626                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
627                if (layout != null) {
628                    layout.calculateSpans(itemInfo);
629                    showOutOfSpaceMessage =
630                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
631                }
632            }
633            if (showOutOfSpaceMessage) {
634                mLauncher.showOutOfSpaceMessage();
635            }
636        }
637    }
638
639    @Override
640    protected void onDetachedFromWindow() {
641        super.onDetachedFromWindow();
642        cancelAllTasks();
643    }
644
645    private void cancelAllTasks() {
646        // Clean up all the async tasks
647        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
648        while (iter.hasNext()) {
649            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
650            task.cancel(false);
651            iter.remove();
652        }
653    }
654
655    public void setContentType(ContentType type) {
656        if (type == ContentType.Widgets) {
657            invalidatePageData(mNumAppsPages, true);
658        } else if (type == ContentType.Applications) {
659            invalidatePageData(0, true);
660        }
661    }
662
663    protected void snapToPage(int whichPage, int delta, int duration) {
664        super.snapToPage(whichPage, delta, duration);
665        updateCurrentTab(whichPage);
666    }
667
668    private void updateCurrentTab(int currentPage) {
669        AppsCustomizeTabHost tabHost = getTabHost();
670        if (tabHost != null) {
671            String tag = tabHost.getCurrentTabTag();
672            if (tag != null) {
673                if (currentPage >= mNumAppsPages &&
674                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
675                    tabHost.setCurrentTabFromContent(ContentType.Widgets);
676                } else if (currentPage < mNumAppsPages &&
677                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
678                    tabHost.setCurrentTabFromContent(ContentType.Applications);
679                }
680            }
681        }
682    }
683
684    /*
685     * Apps PagedView implementation
686     */
687    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
688        int childCount = layout.getChildCount();
689        for (int i = 0; i < childCount; ++i) {
690            layout.getChildAt(i).setVisibility(visibility);
691        }
692    }
693    private void setupPage(PagedViewCellLayout layout) {
694        layout.setCellCount(mCellCountX, mCellCountY);
695        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
696        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
697                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
698
699        // Note: We force a measure here to get around the fact that when we do layout calculations
700        // immediately after syncing, we don't have a proper width.  That said, we already know the
701        // expected page width, so we can actually optimize by hiding all the TextView-based
702        // children that are expensive to measure, and let that happen naturally later.
703        setVisibilityOnChildren(layout, View.GONE);
704        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
705        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
706        layout.setMinimumWidth(getPageContentWidth());
707        layout.measure(widthSpec, heightSpec);
708        setVisibilityOnChildren(layout, View.VISIBLE);
709    }
710
711    public void syncAppsPageItems(int page, boolean immediate) {
712        // ensure that we have the right number of items on the pages
713        int numCells = mCellCountX * mCellCountY;
714        int startIndex = page * numCells;
715        int endIndex = Math.min(startIndex + numCells, mApps.size());
716        PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
717
718        layout.removeAllViewsOnPage();
719        ArrayList<Object> items = new ArrayList<Object>();
720        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
721        for (int i = startIndex; i < endIndex; ++i) {
722            ApplicationInfo info = mApps.get(i);
723            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
724                    R.layout.apps_customize_application, layout, false);
725            icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper);
726            icon.setOnClickListener(this);
727            icon.setOnLongClickListener(this);
728            icon.setOnTouchListener(this);
729            icon.setOnKeyListener(this);
730
731            int index = i - startIndex;
732            int x = index % mCellCountX;
733            int y = index / mCellCountX;
734            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
735
736            items.add(info);
737            images.add(info.iconBitmap);
738        }
739
740        layout.createHardwareLayers();
741
742        /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
743        if (mFadeInAdjacentScreens) {
744            prepareGenerateHoloOutlinesTask(page, items, images);
745        }
746        */
747    }
748
749    /**
750     * Return the appropriate thread priority for loading for a given page (we give the current
751     * page much higher priority)
752     */
753    private int getThreadPriorityForPage(int page) {
754        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
755        int pageDiff = Math.abs(page - mCurrentPage);
756        if (pageDiff <= 0) {
757            // return Process.THREAD_PRIORITY_DEFAULT;
758            return Process.THREAD_PRIORITY_MORE_FAVORABLE;
759        } else if (pageDiff <= 1) {
760            // return Process.THREAD_PRIORITY_BACKGROUND;
761            return Process.THREAD_PRIORITY_DEFAULT;
762        } else {
763            // return Process.THREAD_PRIORITY_LOWEST;
764            return Process.THREAD_PRIORITY_DEFAULT;
765        }
766    }
767    private int getSleepForPage(int page) {
768        int pageDiff = Math.abs(page - mCurrentPage) - 1;
769        return Math.max(0, pageDiff * sPageSleepDelay);
770    }
771    /**
772     * Creates and executes a new AsyncTask to load a page of widget previews.
773     */
774    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
775            int cellWidth, int cellHeight, int cellCountX) {
776        // Prune all tasks that are no longer needed
777        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
778        while (iter.hasNext()) {
779            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
780            int taskPage = task.page;
781            if ((taskPage == page) ||
782                    taskPage < getAssociatedLowerPageBound(mCurrentPage - mNumAppsPages) ||
783                    taskPage > getAssociatedUpperPageBound(mCurrentPage - mNumAppsPages)) {
784                task.cancel(false);
785                iter.remove();
786            } else {
787                task.setThreadPriority(getThreadPriorityForPage(taskPage + mNumAppsPages));
788            }
789        }
790
791        // We introduce a slight delay to order the loading of side pages so that we don't thrash
792        final int sleepMs = getSleepForPage(page + mNumAppsPages);
793        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
794            new AsyncTaskCallback() {
795                @Override
796                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
797                    try {
798                        try {
799                            Thread.sleep(sleepMs);
800                        } catch (Exception e) {}
801                        loadWidgetPreviewsInBackground(task, data);
802                    } finally {
803                        if (task.isCancelled()) {
804                            data.cleanup(true);
805                        }
806                    }
807                }
808            },
809            new AsyncTaskCallback() {
810                @Override
811                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
812                    try {
813                        mRunningTasks.remove(task);
814                        if (task.isCancelled()) return;
815                        onSyncWidgetPageItems(data);
816                    } finally {
817                        data.cleanup(task.isCancelled());
818                    }
819                }
820            });
821
822        // Ensure that the task is appropriately prioritized and runs in parallel
823        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
824                AsyncTaskPageData.Type.LoadWidgetPreviewData);
825        t.setThreadPriority(getThreadPriorityForPage(page));
826        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
827        mRunningTasks.add(t);
828    }
829    /**
830     * Creates and executes a new AsyncTask to load the outlines for a page of content.
831     */
832    private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items,
833            ArrayList<Bitmap> images) {
834        // Prune old tasks for this page
835        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
836        while (iter.hasNext()) {
837            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
838            int taskPage = task.page;
839            if ((taskPage == page) &&
840                    (task.dataType == AsyncTaskPageData.Type.LoadHolographicIconsData)) {
841                task.cancel(false);
842                iter.remove();
843            }
844        }
845
846        AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images,
847            new AsyncTaskCallback() {
848                @Override
849                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
850                    try {
851                        // Ensure that this task starts running at the correct priority
852                        task.syncThreadPriority();
853
854                        ArrayList<Bitmap> images = data.generatedImages;
855                        ArrayList<Bitmap> srcImages = data.sourceImages;
856                        int count = srcImages.size();
857                        Canvas c = new Canvas();
858                        for (int i = 0; i < count && !task.isCancelled(); ++i) {
859                            // Before work on each item, ensure that this task is running at the correct
860                            // priority
861                            task.syncThreadPriority();
862
863                            Bitmap b = srcImages.get(i);
864                            Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(),
865                                    Bitmap.Config.ARGB_8888);
866
867                            c.setBitmap(outline);
868                            c.save();
869                            c.drawBitmap(b, 0, 0, null);
870                            c.restore();
871                            c.setBitmap(null);
872
873                            images.add(outline);
874                        }
875                    } finally {
876                        if (task.isCancelled()) {
877                            data.cleanup(true);
878                        }
879                    }
880                }
881            },
882            new AsyncTaskCallback() {
883                @Override
884                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
885                    try {
886                        mRunningTasks.remove(task);
887                        if (task.isCancelled()) return;
888                        onHolographicPageItemsLoaded(data);
889                    } finally {
890                        data.cleanup(task.isCancelled());
891                    }
892                }
893            });
894
895        // Ensure that the outline task always runs in the background, serially
896        AppsCustomizeAsyncTask t =
897            new AppsCustomizeAsyncTask(page, AsyncTaskPageData.Type.LoadHolographicIconsData);
898        t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
899        t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData);
900        mRunningTasks.add(t);
901    }
902
903    /*
904     * Widgets PagedView implementation
905     */
906    private void setupPage(PagedViewGridLayout layout) {
907        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
908                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
909
910        // Note: We force a measure here to get around the fact that when we do layout calculations
911        // immediately after syncing, we don't have a proper width.
912        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
913        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
914        layout.setMinimumWidth(getPageContentWidth());
915        layout.measure(widthSpec, heightSpec);
916    }
917
918    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
919        renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f, 0xFFFFFFFF);
920    }
921    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
922            float scale) {
923        renderDrawableToBitmap(d, bitmap, x, y, w, h, scale, 0xFFFFFFFF);
924    }
925    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
926            float scale, int multiplyColor) {
927        if (bitmap != null) {
928            Canvas c = new Canvas(bitmap);
929            c.scale(scale, scale);
930            Rect oldBounds = d.copyBounds();
931            d.setBounds(x, y, x + w, y + h);
932            d.draw(c);
933            d.setBounds(oldBounds); // Restore the bounds
934            if (multiplyColor != 0xFFFFFFFF) {
935                c.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
936            }
937            c.setBitmap(null);
938        }
939    }
940    private Bitmap getShortcutPreview(ResolveInfo info) {
941        // Render the background
942        int offset = 0;
943        int bitmapSize = mAppIconSize;
944        Bitmap preview = Bitmap.createBitmap(bitmapSize, bitmapSize, Config.ARGB_8888);
945
946        // Render the icon
947        Drawable icon = mIconCache.getFullResIcon(info);
948        renderDrawableToBitmap(icon, preview, offset, offset, mAppIconSize, mAppIconSize);
949        return preview;
950    }
951
952    private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId,
953            int cellHSpan, int cellVSpan, int maxWidth, int maxHeight) {
954        // Load the preview image if possible
955        String packageName = provider.getPackageName();
956        if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
957        if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
958
959        Drawable drawable = null;
960        if (previewImage != 0) {
961            drawable = mPackageManager.getDrawable(packageName, previewImage, null);
962            if (drawable == null) {
963                Log.w(LOG_TAG, "Can't load widget preview drawable 0x" +
964                        Integer.toHexString(previewImage) + " for provider: " + provider);
965            }
966        }
967
968        int bitmapWidth;
969        int bitmapHeight;
970        boolean widgetPreviewExists = (drawable != null);
971        if (widgetPreviewExists) {
972            bitmapWidth = drawable.getIntrinsicWidth();
973            bitmapHeight = drawable.getIntrinsicHeight();
974
975            // Cap the size so widget previews don't appear larger than the actual widget
976            maxWidth = Math.min(maxWidth, mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
977            maxHeight = Math.min(maxHeight, mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
978        } else {
979            // Determine the size of the bitmap for the preview image we will generate
980            // TODO: This actually uses the apps customize cell layout params, where as we make want
981            // the Workspace params for more accuracy.
982            bitmapWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
983            bitmapHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
984            if (cellHSpan == cellVSpan) {
985                // For square widgets, we just have a fixed size for 1x1 and larger-than-1x1
986                int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
987                if (cellHSpan <= 1) {
988                    bitmapWidth = bitmapHeight = mAppIconSize + 2 * minOffset;
989                } else {
990                    bitmapWidth = bitmapHeight = mAppIconSize + 4 * minOffset;
991                }
992            }
993        }
994
995        float scale = 1f;
996        if (bitmapWidth > maxWidth) {
997            scale = maxWidth / (float) bitmapWidth;
998        }
999        if (bitmapHeight * scale > maxHeight) {
1000            scale = maxHeight / (float) bitmapHeight;
1001        }
1002        if (scale != 1f) {
1003            bitmapWidth = (int) (scale * bitmapWidth);
1004            bitmapHeight = (int) (scale * bitmapHeight);
1005        }
1006
1007        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888);
1008
1009        if (widgetPreviewExists) {
1010            renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, bitmapHeight);
1011        } else {
1012            // Generate a preview image if we couldn't load one
1013            int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
1014            int smallestSide = Math.min(bitmapWidth, bitmapHeight);
1015            float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), 1f);
1016            if (cellHSpan != 1 || cellVSpan != 1) {
1017                renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, bitmapWidth,
1018                        bitmapHeight);
1019            }
1020
1021            // Draw the icon in the top left corner
1022            try {
1023                Drawable icon = null;
1024                int hoffset = (int) (bitmapWidth / 2 - mAppIconSize * iconScale / 2);
1025                int yoffset = (int) (bitmapHeight / 2 - mAppIconSize * iconScale / 2);
1026                if (iconId > 0) icon = mIconCache.getFullResIcon(packageName, iconId);
1027                Resources resources = mLauncher.getResources();
1028                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
1029
1030                renderDrawableToBitmap(icon, preview, hoffset, yoffset,
1031                        (int) (mAppIconSize * iconScale),
1032                        (int) (mAppIconSize * iconScale));
1033            } catch (Resources.NotFoundException e) {}
1034        }
1035        return preview;
1036    }
1037
1038    public void syncWidgetPageItems(final int page, final boolean immediate) {
1039        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1040
1041        // Calculate the dimensions of each cell we are giving to each widget
1042        final ArrayList<Object> items = new ArrayList<Object>();
1043        int contentWidth = mWidgetSpacingLayout.getContentWidth();
1044        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
1045                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
1046        int contentHeight = mWidgetSpacingLayout.getContentHeight();
1047        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
1048                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
1049
1050        // Prepare the set of widgets to load previews for in the background
1051        int offset = page * numItemsPerPage;
1052        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1053            items.add(mWidgets.get(i));
1054        }
1055
1056        // Prepopulate the pages with the other widget info, and fill in the previews later
1057        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
1058        layout.setColumnCount(layout.getCellCountX());
1059        for (int i = 0; i < items.size(); ++i) {
1060            Object rawInfo = items.get(i);
1061            PendingAddItemInfo createItemInfo = null;
1062            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1063                    R.layout.apps_customize_widget, layout, false);
1064            if (rawInfo instanceof AppWidgetProviderInfo) {
1065                // Fill in the widget information
1066                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1067                createItemInfo = new PendingAddWidgetInfo(info, null, null);
1068                int[] cellSpans = mLauncher.getSpanForWidget(info, null);
1069                widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans,
1070                        mHolographicOutlineHelper);
1071                widget.setTag(createItemInfo);
1072            } else if (rawInfo instanceof ResolveInfo) {
1073                // Fill in the shortcuts information
1074                ResolveInfo info = (ResolveInfo) rawInfo;
1075                createItemInfo = new PendingAddItemInfo();
1076                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1077                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1078                        info.activityInfo.name);
1079                widget.applyFromResolveInfo(mPackageManager, info, mHolographicOutlineHelper);
1080                widget.setTag(createItemInfo);
1081            }
1082            widget.setOnClickListener(this);
1083            widget.setOnLongClickListener(this);
1084            widget.setOnTouchListener(this);
1085            widget.setOnKeyListener(this);
1086
1087            // Layout each widget
1088            int ix = i % mWidgetCountX;
1089            int iy = i / mWidgetCountX;
1090            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1091                    GridLayout.spec(iy, GridLayout.LEFT),
1092                    GridLayout.spec(ix, GridLayout.TOP));
1093            lp.width = cellWidth;
1094            lp.height = cellHeight;
1095            lp.setGravity(Gravity.TOP | Gravity.LEFT);
1096            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
1097            if (iy > 0) lp.topMargin = mWidgetHeightGap;
1098            layout.addView(widget, lp);
1099        }
1100
1101        // wait until a call on onLayout to start loading, because
1102        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1103        // TODO: can we do a measure/layout immediately?
1104        layout.setOnLayoutListener(new Runnable() {
1105            public void run() {
1106                // Load the widget previews
1107                int maxPreviewWidth = cellWidth;
1108                int maxPreviewHeight = cellHeight;
1109                if (layout.getChildCount() > 0) {
1110                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1111                    int[] maxSize = w.getPreviewSize();
1112                    maxPreviewWidth = maxSize[0];
1113                    maxPreviewHeight = maxSize[1];
1114                }
1115                if (immediate) {
1116                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1117                            maxPreviewWidth, maxPreviewHeight, null, null);
1118                    loadWidgetPreviewsInBackground(null, data);
1119                    onSyncWidgetPageItems(data);
1120                } else {
1121                    prepareLoadWidgetPreviewsTask(page, items,
1122                            maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1123                }
1124            }
1125        });
1126    }
1127    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1128            AsyncTaskPageData data) {
1129        if (task != null) {
1130            // Ensure that this task starts running at the correct priority
1131            task.syncThreadPriority();
1132        }
1133
1134        // Load each of the widget/shortcut previews
1135        ArrayList<Object> items = data.items;
1136        ArrayList<Bitmap> images = data.generatedImages;
1137        int count = items.size();
1138        for (int i = 0; i < count; ++i) {
1139            if (task != null) {
1140                // Ensure we haven't been cancelled yet
1141                if (task.isCancelled()) break;
1142                // Before work on each item, ensure that this task is running at the correct
1143                // priority
1144                task.syncThreadPriority();
1145            }
1146
1147            Object rawInfo = items.get(i);
1148            if (rawInfo instanceof AppWidgetProviderInfo) {
1149                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1150                int[] cellSpans = mLauncher.getSpanForWidget(info, null);
1151                Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
1152                        cellSpans[0], cellSpans[1], data.maxImageWidth, data.maxImageHeight);
1153                images.add(b);
1154            } else if (rawInfo instanceof ResolveInfo) {
1155                // Fill in the shortcuts information
1156                ResolveInfo info = (ResolveInfo) rawInfo;
1157                images.add(getShortcutPreview(info));
1158            }
1159        }
1160    }
1161    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
1162        int page = data.page;
1163        PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
1164
1165        ArrayList<Object> items = data.items;
1166        int count = items.size();
1167        for (int i = 0; i < count; ++i) {
1168            PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1169            if (widget != null) {
1170                Bitmap preview = data.generatedImages.get(i);
1171                widget.applyPreview(new FastBitmapDrawable(preview), i);
1172            }
1173        }
1174        layout.createHardwareLayer();
1175
1176        invalidate();
1177        /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
1178        if (mFadeInAdjacentScreens) {
1179            prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages);
1180        }
1181        */
1182    }
1183    private void onHolographicPageItemsLoaded(AsyncTaskPageData data) {
1184        // Invalidate early to short-circuit children invalidates
1185        invalidate();
1186
1187        int page = data.page;
1188        ViewGroup layout = (ViewGroup) getPageAt(page);
1189        if (layout instanceof PagedViewCellLayout) {
1190            PagedViewCellLayout cl = (PagedViewCellLayout) layout;
1191            int count = cl.getPageChildCount();
1192            if (count != data.generatedImages.size()) return;
1193            for (int i = 0; i < count; ++i) {
1194                PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i);
1195                icon.setHolographicOutline(data.generatedImages.get(i));
1196            }
1197        } else {
1198            int count = layout.getChildCount();
1199            if (count != data.generatedImages.size()) return;
1200            for (int i = 0; i < count; ++i) {
1201                View v = layout.getChildAt(i);
1202                ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i));
1203            }
1204        }
1205    }
1206
1207    @Override
1208    public void syncPages() {
1209        removeAllViews();
1210        cancelAllTasks();
1211
1212        Context context = getContext();
1213        for (int j = 0; j < mNumWidgetPages; ++j) {
1214            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1215                    mWidgetCountY);
1216            setupPage(layout);
1217            addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT,
1218                    LayoutParams.MATCH_PARENT));
1219        }
1220
1221        for (int i = 0; i < mNumAppsPages; ++i) {
1222            PagedViewCellLayout layout = new PagedViewCellLayout(context);
1223            setupPage(layout);
1224            addView(layout);
1225        }
1226    }
1227
1228    @Override
1229    public void syncPageItems(int page, boolean immediate) {
1230        if (page < mNumAppsPages) {
1231            syncAppsPageItems(page, immediate);
1232        } else {
1233            syncWidgetPageItems(page - mNumAppsPages, immediate);
1234        }
1235    }
1236
1237    // We want our pages to be z-ordered such that the further a page is to the left, the higher
1238    // it is in the z-order. This is important to insure touch events are handled correctly.
1239    View getPageAt(int index) {
1240        return getChildAt(getChildCount() - index - 1);
1241    }
1242
1243    @Override
1244    protected int indexToPage(int index) {
1245        return getChildCount() - index - 1;
1246    }
1247
1248    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1249    @Override
1250    protected void screenScrolled(int screenCenter) {
1251        super.screenScrolled(screenCenter);
1252
1253        for (int i = 0; i < getChildCount(); i++) {
1254            View v = getPageAt(i);
1255            if (v != null) {
1256                float scrollProgress = getScrollProgress(screenCenter, v, i);
1257
1258                float interpolatedProgress =
1259                        mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
1260                float scale = (1 - interpolatedProgress) +
1261                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
1262                float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
1263
1264                float alpha;
1265
1266                if (!LauncherApplication.isScreenLarge() || scrollProgress < 0) {
1267                    alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1268                        1 - Math.abs(scrollProgress)) : 1.0f;
1269                } else {
1270                    // On large screens we need to fade the page as it nears its leftmost position
1271                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
1272                }
1273
1274                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
1275                int pageWidth = v.getMeasuredWidth();
1276                int pageHeight = v.getMeasuredHeight();
1277
1278                if (PERFORM_OVERSCROLL_ROTATION) {
1279                    if (i == 0 && scrollProgress < 0) {
1280                        // Overscroll to the left
1281                        v.setPivotX(TRANSITION_PIVOT * pageWidth);
1282                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1283                        scale = 1.0f;
1284                        alpha = 1.0f;
1285                        // On the first page, we don't want the page to have any lateral motion
1286                        translationX = 0;
1287                    } else if (i == getChildCount() - 1 && scrollProgress > 0) {
1288                        // Overscroll to the right
1289                        v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
1290                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1291                        scale = 1.0f;
1292                        alpha = 1.0f;
1293                        // On the last page, we don't want the page to have any lateral motion.
1294                        translationX = 0;
1295                    } else {
1296                        v.setPivotY(pageHeight / 2.0f);
1297                        v.setPivotX(pageWidth / 2.0f);
1298                        v.setRotationY(0f);
1299                    }
1300                }
1301
1302                v.setTranslationX(translationX);
1303                v.setScaleX(scale);
1304                v.setScaleY(scale);
1305                v.setAlpha(alpha);
1306
1307                // If the view has 0 alpha, we set it to be invisible so as to prevent
1308                // it from accepting touches
1309                if (alpha < ViewConfiguration.ALPHA_THRESHOLD) {
1310                    v.setVisibility(INVISIBLE);
1311                } else if (v.getVisibility() != VISIBLE) {
1312                    v.setVisibility(VISIBLE);
1313                }
1314            }
1315        }
1316    }
1317
1318    protected void overScroll(float amount) {
1319        acceleratedOverScroll(amount);
1320    }
1321
1322    /**
1323     * Used by the parent to get the content width to set the tab bar to
1324     * @return
1325     */
1326    public int getPageContentWidth() {
1327        return mContentWidth;
1328    }
1329
1330    @Override
1331    protected void onPageEndMoving() {
1332        super.onPageEndMoving();
1333
1334        // We reset the save index when we change pages so that it will be recalculated on next
1335        // rotation
1336        mSaveInstanceStateItemIndex = -1;
1337    }
1338
1339    /*
1340     * AllAppsView implementation
1341     */
1342    @Override
1343    public void setup(Launcher launcher, DragController dragController) {
1344        mLauncher = launcher;
1345        mDragController = dragController;
1346    }
1347    @Override
1348    public void zoom(float zoom, boolean animate) {
1349        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
1350    }
1351    @Override
1352    public boolean isVisible() {
1353        return (getVisibility() == VISIBLE);
1354    }
1355    @Override
1356    public boolean isAnimating() {
1357        return false;
1358    }
1359    @Override
1360    public void setApps(ArrayList<ApplicationInfo> list) {
1361        mApps = list;
1362        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
1363        updatePageCounts();
1364
1365        // The next layout pass will trigger data-ready if both widgets and apps are set, so
1366        // request a layout to do this test and invalidate the page data when ready.
1367        if (testDataReady()) requestLayout();
1368    }
1369    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1370        // We add it in place, in alphabetical order
1371        int count = list.size();
1372        for (int i = 0; i < count; ++i) {
1373            ApplicationInfo info = list.get(i);
1374            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
1375            if (index < 0) {
1376                mApps.add(-(index + 1), info);
1377            }
1378        }
1379    }
1380    @Override
1381    public void addApps(ArrayList<ApplicationInfo> list) {
1382        addAppsWithoutInvalidate(list);
1383        updatePageCounts();
1384        invalidatePageData();
1385    }
1386    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1387        ComponentName removeComponent = item.intent.getComponent();
1388        int length = list.size();
1389        for (int i = 0; i < length; ++i) {
1390            ApplicationInfo info = list.get(i);
1391            if (info.intent.getComponent().equals(removeComponent)) {
1392                return i;
1393            }
1394        }
1395        return -1;
1396    }
1397    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1398        // loop through all the apps and remove apps that have the same component
1399        int length = list.size();
1400        for (int i = 0; i < length; ++i) {
1401            ApplicationInfo info = list.get(i);
1402            int removeIndex = findAppByComponent(mApps, info);
1403            if (removeIndex > -1) {
1404                mApps.remove(removeIndex);
1405            }
1406        }
1407    }
1408    @Override
1409    public void removeApps(ArrayList<ApplicationInfo> list) {
1410        removeAppsWithoutInvalidate(list);
1411        updatePageCounts();
1412        invalidatePageData();
1413    }
1414    @Override
1415    public void updateApps(ArrayList<ApplicationInfo> list) {
1416        // We remove and re-add the updated applications list because it's properties may have
1417        // changed (ie. the title), and this will ensure that the items will be in their proper
1418        // place in the list.
1419        removeAppsWithoutInvalidate(list);
1420        addAppsWithoutInvalidate(list);
1421        updatePageCounts();
1422
1423        invalidatePageData();
1424    }
1425
1426    @Override
1427    public void reset() {
1428        AppsCustomizeTabHost tabHost = getTabHost();
1429        String tag = tabHost.getCurrentTabTag();
1430        if (tag != null) {
1431            if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
1432                tabHost.setCurrentTabFromContent(ContentType.Applications);
1433            }
1434        }
1435        if (mCurrentPage != 0) {
1436            invalidatePageData(0);
1437        }
1438    }
1439
1440    private AppsCustomizeTabHost getTabHost() {
1441        return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
1442    }
1443
1444    @Override
1445    public void dumpState() {
1446        // TODO: Dump information related to current list of Applications, Widgets, etc.
1447        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
1448        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
1449    }
1450
1451    private void dumpAppWidgetProviderInfoList(String tag, String label,
1452            ArrayList<Object> list) {
1453        Log.d(tag, label + " size=" + list.size());
1454        for (Object i: list) {
1455            if (i instanceof AppWidgetProviderInfo) {
1456                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1457                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1458                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1459                        + " initialLayout=" + info.initialLayout
1460                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1461            } else if (i instanceof ResolveInfo) {
1462                ResolveInfo info = (ResolveInfo) i;
1463                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1464                        + info.icon);
1465            }
1466        }
1467    }
1468
1469    @Override
1470    public void surrender() {
1471        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1472        // should stop this now.
1473
1474        // Stop all background tasks
1475        cancelAllTasks();
1476    }
1477
1478    /*
1479     * We load an extra page on each side to prevent flashes from scrolling and loading of the
1480     * widget previews in the background with the AsyncTasks.
1481     */
1482    protected int getAssociatedLowerPageBound(int page) {
1483        return Math.max(0, page - 2);
1484    }
1485    protected int getAssociatedUpperPageBound(int page) {
1486        final int count = getChildCount();
1487        return Math.min(page + 2, count - 1);
1488    }
1489
1490    @Override
1491    protected String getCurrentPageDescription() {
1492        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
1493        int stringId = R.string.default_scroll_format;
1494        int count = 0;
1495
1496        if (page < mNumAppsPages) {
1497            stringId = R.string.apps_customize_apps_scroll_format;
1498            count = mNumAppsPages;
1499        } else {
1500            page -= mNumAppsPages;
1501            stringId = R.string.apps_customize_widgets_scroll_format;
1502            count = mNumWidgetPages;
1503        }
1504
1505        return String.format(mContext.getString(stringId), page + 1, count);
1506    }
1507}
1508