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