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