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