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