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