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