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