AppsCustomizePagedView.java revision b8f5c401de0a081557d8c014c0ecf00264d5f266
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            if (widget.minWidth > 0 && widget.minHeight > 0) {
452                // Ensure that all widgets we show can be added on a workspace of this size
453                int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
454                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
455                int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
456                int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
457                if (minSpanX <= LauncherModel.getCellCountX() &&
458                        minSpanY <= LauncherModel.getCellCountY()) {
459                    mWidgets.add(widget);
460                } else {
461                    Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
462                            widget.minWidth + ", " + widget.minHeight + ")");
463                }
464            } else {
465                Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
466                        widget.minWidth + ", " + widget.minHeight + ")");
467            }
468        }
469        mWidgets.addAll(shortcuts);
470        Collections.sort(mWidgets,
471                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
472        updatePageCounts();
473        invalidateOnDataChange();
474    }
475
476    @Override
477    public void onClick(View v) {
478        // When we have exited all apps or are in transition, disregard clicks
479        if (!mLauncher.isAllAppsVisible() ||
480                mLauncher.getWorkspace().isSwitchingState()) return;
481
482        if (v instanceof PagedViewIcon) {
483            // Animate some feedback to the click
484            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
485
486            // Lock the drawable state to pressed until we return to Launcher
487            if (mPressedIcon != null) {
488                mPressedIcon.lockDrawableState();
489            }
490
491            // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
492            // to be consistent.  So re-enable the flag here, and we will re-disable it as necessary
493            // when Launcher resumes and we are still in AllApps.
494            mLauncher.updateWallpaperVisibility(true);
495            mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
496
497        } else if (v instanceof PagedViewWidget) {
498            // Let the user know that they have to long press to add a widget
499            if (mWidgetInstructionToast != null) {
500                mWidgetInstructionToast.cancel();
501            }
502            mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
503                Toast.LENGTH_SHORT);
504            mWidgetInstructionToast.show();
505
506            // Create a little animation to show that the widget can move
507            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
508            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
509            AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
510            ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
511            tyuAnim.setDuration(125);
512            ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
513            tydAnim.setDuration(100);
514            bounce.play(tyuAnim).before(tydAnim);
515            bounce.setInterpolator(new AccelerateInterpolator());
516            bounce.start();
517        }
518    }
519
520    public boolean onKey(View v, int keyCode, KeyEvent event) {
521        return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
522    }
523
524    /*
525     * PagedViewWithDraggableItems implementation
526     */
527    @Override
528    protected void determineDraggingStart(android.view.MotionEvent ev) {
529        // Disable dragging by pulling an app down for now.
530    }
531
532    private void beginDraggingApplication(View v) {
533        mLauncher.getWorkspace().onDragStartedWithItem(v);
534        mLauncher.getWorkspace().beginDragShared(v, this);
535    }
536
537    Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
538        Bundle options = null;
539        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
540            AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect);
541            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher,
542                    info.componentName, null);
543
544            float density = getResources().getDisplayMetrics().density;
545            int xPaddingDips = (int) ((padding.left + padding.right) / density);
546            int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
547
548            options = new Bundle();
549            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
550                    mTmpRect.left - xPaddingDips);
551            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
552                    mTmpRect.top - yPaddingDips);
553            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
554                    mTmpRect.right - xPaddingDips);
555            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
556                    mTmpRect.bottom - yPaddingDips);
557        }
558        return options;
559    }
560
561    private void preloadWidget(final PendingAddWidgetInfo info) {
562        final AppWidgetProviderInfo pInfo = info.info;
563        final Bundle options = getDefaultOptionsForWidget(mLauncher, info);
564
565        if (pInfo.configure != null) {
566            info.bindOptions = options;
567            return;
568        }
569
570        mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
571        mBindWidgetRunnable = new Runnable() {
572            @Override
573            public void run() {
574                mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
575                // Options will be null for platforms with JB or lower, so this serves as an
576                // SDK level check.
577                if (options == null) {
578                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
579                            mWidgetLoadingId, info.componentName)) {
580                        mWidgetCleanupState = WIDGET_BOUND;
581                    }
582                } else {
583                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
584                            mWidgetLoadingId, info.componentName, options)) {
585                        mWidgetCleanupState = WIDGET_BOUND;
586                    }
587                }
588            }
589        };
590        post(mBindWidgetRunnable);
591
592        mInflateWidgetRunnable = new Runnable() {
593            @Override
594            public void run() {
595                if (mWidgetCleanupState != WIDGET_BOUND) {
596                    return;
597                }
598                AppWidgetHostView hostView = mLauncher.
599                        getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
600                info.boundWidget = hostView;
601                mWidgetCleanupState = WIDGET_INFLATED;
602                hostView.setVisibility(INVISIBLE);
603                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
604                        info.spanY, info, false);
605
606                // We want the first widget layout to be the correct size. This will be important
607                // for width size reporting to the AppWidgetManager.
608                DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
609                        unScaledSize[1]);
610                lp.x = lp.y = 0;
611                lp.customPosition = true;
612                hostView.setLayoutParams(lp);
613                mLauncher.getDragLayer().addView(hostView);
614            }
615        };
616        post(mInflateWidgetRunnable);
617    }
618
619    @Override
620    public void onShortPress(View v) {
621        // We are anticipating a long press, and we use this time to load bind and instantiate
622        // the widget. This will need to be cleaned up if it turns out no long press occurs.
623        if (mCreateWidgetInfo != null) {
624            // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
625            cleanupWidgetPreloading(false);
626        }
627        mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
628        preloadWidget(mCreateWidgetInfo);
629    }
630
631    private void cleanupWidgetPreloading(boolean widgetWasAdded) {
632        if (!widgetWasAdded) {
633            // If the widget was not added, we may need to do further cleanup.
634            PendingAddWidgetInfo info = mCreateWidgetInfo;
635            mCreateWidgetInfo = null;
636
637            if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
638                // We never did any preloading, so just remove pending callbacks to do so
639                removeCallbacks(mBindWidgetRunnable);
640                removeCallbacks(mInflateWidgetRunnable);
641            } else if (mWidgetCleanupState == WIDGET_BOUND) {
642                 // Delete the widget id which was allocated
643                if (mWidgetLoadingId != -1) {
644                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
645                }
646
647                // We never got around to inflating the widget, so remove the callback to do so.
648                removeCallbacks(mInflateWidgetRunnable);
649            } else if (mWidgetCleanupState == WIDGET_INFLATED) {
650                // Delete the widget id which was allocated
651                if (mWidgetLoadingId != -1) {
652                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
653                }
654
655                // The widget was inflated and added to the DragLayer -- remove it.
656                AppWidgetHostView widget = info.boundWidget;
657                mLauncher.getDragLayer().removeView(widget);
658            }
659        }
660        mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
661        mWidgetLoadingId = -1;
662        mCreateWidgetInfo = null;
663        PagedViewWidget.resetShortPressTarget();
664    }
665
666    @Override
667    public void cleanUpShortPress(View v) {
668        if (!mDraggingWidget) {
669            cleanupWidgetPreloading(false);
670        }
671    }
672
673    private boolean beginDraggingWidget(View v) {
674        mDraggingWidget = true;
675        // Get the widget preview as the drag representation
676        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
677        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
678
679        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
680        // we abort the drag.
681        if (image.getDrawable() == null) {
682            mDraggingWidget = false;
683            return false;
684        }
685
686        // Compose the drag image
687        Bitmap preview;
688        Bitmap outline;
689        float scale = 1f;
690        Point previewPadding = null;
691
692        if (createItemInfo instanceof PendingAddWidgetInfo) {
693            // This can happen in some weird cases involving multi-touch. We can't start dragging
694            // the widget if this is null, so we break out.
695            if (mCreateWidgetInfo == null) {
696                return false;
697            }
698
699            PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
700            createItemInfo = createWidgetInfo;
701            int spanX = createItemInfo.spanX;
702            int spanY = createItemInfo.spanY;
703            int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
704                    createWidgetInfo, true);
705
706            FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
707            float minScale = 1.25f;
708            int maxWidth, maxHeight;
709            maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
710            maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
711
712            int[] previewSizeBeforeScale = new int[1];
713
714            preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName,
715                    createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY,
716                    maxWidth, maxHeight, null, previewSizeBeforeScale);
717
718            // Compare the size of the drag preview to the preview in the AppsCustomize tray
719            int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
720                    mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX));
721            scale = previewWidthInAppsCustomize / (float) preview.getWidth();
722
723            // The bitmap in the AppsCustomize tray is always the the same size, so there
724            // might be extra pixels around the preview itself - this accounts for that
725            if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
726                int padding =
727                        (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
728                previewPadding = new Point(padding, 0);
729            }
730        } else {
731            PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
732            Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
733            preview = Bitmap.createBitmap(icon.getIntrinsicWidth(),
734                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
735
736            mCanvas.setBitmap(preview);
737            mCanvas.save();
738            WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0,
739                    icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
740            mCanvas.restore();
741            mCanvas.setBitmap(null);
742            createItemInfo.spanX = createItemInfo.spanY = 1;
743        }
744
745        // Don't clip alpha values for the drag outline if we're using the default widget preview
746        boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
747                (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
748
749        // Save the preview for the outline generation, then dim the preview
750        outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
751                false);
752
753        // Start the drag
754        mLauncher.lockScreenOrientation();
755        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
756        mDragController.startDrag(image, preview, this, createItemInfo,
757                DragController.DRAG_ACTION_COPY, previewPadding, scale);
758        outline.recycle();
759        preview.recycle();
760        return true;
761    }
762
763    @Override
764    protected boolean beginDragging(final View v) {
765        if (!super.beginDragging(v)) return false;
766
767        if (v instanceof PagedViewIcon) {
768            beginDraggingApplication(v);
769        } else if (v instanceof PagedViewWidget) {
770            if (!beginDraggingWidget(v)) {
771                return false;
772            }
773        }
774
775        // We delay entering spring-loaded mode slightly to make sure the UI
776        // thready is free of any work.
777        postDelayed(new Runnable() {
778            @Override
779            public void run() {
780                // We don't enter spring-loaded mode if the drag has been cancelled
781                if (mLauncher.getDragController().isDragging()) {
782                    // Dismiss the cling
783                    mLauncher.dismissAllAppsCling(null);
784
785                    // Reset the alpha on the dragged icon before we drag
786                    resetDrawableState();
787
788                    // Go into spring loaded mode (must happen before we startDrag())
789                    mLauncher.enterSpringLoadedDragMode();
790                }
791            }
792        }, 150);
793
794        return true;
795    }
796
797    /**
798     * Clean up after dragging.
799     *
800     * @param target where the item was dragged to (can be null if the item was flung)
801     */
802    private void endDragging(View target, boolean isFlingToDelete, boolean success) {
803        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
804                !(target instanceof DeleteDropTarget))) {
805            // Exit spring loaded mode if we have not successfully dropped or have not handled the
806            // drop in Workspace
807            mLauncher.exitSpringLoadedDragMode();
808        }
809        mLauncher.unlockScreenOrientation(false);
810    }
811
812    @Override
813    public View getContent() {
814        return null;
815    }
816
817    @Override
818    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
819        mInTransition = true;
820        if (toWorkspace) {
821            cancelAllTasks();
822        }
823    }
824
825    @Override
826    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
827    }
828
829    @Override
830    public void onLauncherTransitionStep(Launcher l, float t) {
831    }
832
833    @Override
834    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
835        mInTransition = false;
836        for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
837            onSyncWidgetPageItems(d);
838        }
839        mDeferredSyncWidgetPageItems.clear();
840        for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
841            r.run();
842        }
843        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
844        mForceDrawAllChildrenNextFrame = !toWorkspace;
845    }
846
847    @Override
848    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
849            boolean success) {
850        // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
851        if (isFlingToDelete) return;
852
853        endDragging(target, false, success);
854
855        // Display an error message if the drag failed due to there not being enough space on the
856        // target layout we were dropping on.
857        if (!success) {
858            boolean showOutOfSpaceMessage = false;
859            if (target instanceof Workspace) {
860                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
861                Workspace workspace = (Workspace) target;
862                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
863                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
864                if (layout != null) {
865                    layout.calculateSpans(itemInfo);
866                    showOutOfSpaceMessage =
867                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
868                }
869            }
870            if (showOutOfSpaceMessage) {
871                mLauncher.showOutOfSpaceMessage(false);
872            }
873
874            d.deferDragViewCleanupPostAnimation = false;
875        }
876        cleanupWidgetPreloading(success);
877        mDraggingWidget = false;
878    }
879
880    @Override
881    public void onFlingToDeleteCompleted() {
882        // We just dismiss the drag when we fling, so cleanup here
883        endDragging(null, true, true);
884        cleanupWidgetPreloading(false);
885        mDraggingWidget = false;
886    }
887
888    @Override
889    public boolean supportsFlingToDelete() {
890        return true;
891    }
892
893    @Override
894    protected void onDetachedFromWindow() {
895        super.onDetachedFromWindow();
896        cancelAllTasks();
897    }
898
899    public void clearAllWidgetPages() {
900        cancelAllTasks();
901        int count = getChildCount();
902        for (int i = 0; i < count; i++) {
903            View v = getPageAt(i);
904            if (v instanceof PagedViewGridLayout) {
905                ((PagedViewGridLayout) v).removeAllViewsOnPage();
906                mDirtyPageContent.set(i, true);
907            }
908        }
909    }
910
911    private void cancelAllTasks() {
912        // Clean up all the async tasks
913        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
914        while (iter.hasNext()) {
915            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
916            task.cancel(false);
917            iter.remove();
918            mDirtyPageContent.set(task.page, true);
919
920            // We've already preallocated the views for the data to load into, so clear them as well
921            View v = getPageAt(task.page);
922            if (v instanceof PagedViewGridLayout) {
923                ((PagedViewGridLayout) v).removeAllViewsOnPage();
924            }
925        }
926        mDeferredSyncWidgetPageItems.clear();
927        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
928    }
929
930    public void setContentType(ContentType type) {
931        if (type == ContentType.Widgets) {
932            invalidatePageData(mNumAppsPages, true);
933        } else if (type == ContentType.Applications) {
934            invalidatePageData(0, true);
935        }
936    }
937
938    protected void snapToPage(int whichPage, int delta, int duration) {
939        super.snapToPage(whichPage, delta, duration);
940        updateCurrentTab(whichPage);
941
942        // Update the thread priorities given the direction lookahead
943        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
944        while (iter.hasNext()) {
945            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
946            int pageIndex = task.page;
947            if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
948                (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
949                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
950            } else {
951                task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
952            }
953        }
954    }
955
956    private void updateCurrentTab(int currentPage) {
957        AppsCustomizeTabHost tabHost = getTabHost();
958        if (tabHost != null) {
959            String tag = tabHost.getCurrentTabTag();
960            if (tag != null) {
961                if (currentPage >= mNumAppsPages &&
962                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
963                    tabHost.setCurrentTabFromContent(ContentType.Widgets);
964                } else if (currentPage < mNumAppsPages &&
965                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
966                    tabHost.setCurrentTabFromContent(ContentType.Applications);
967                }
968            }
969        }
970    }
971
972    /*
973     * Apps PagedView implementation
974     */
975    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
976        int childCount = layout.getChildCount();
977        for (int i = 0; i < childCount; ++i) {
978            layout.getChildAt(i).setVisibility(visibility);
979        }
980    }
981    private void setupPage(PagedViewCellLayout layout) {
982        layout.setCellCount(mCellCountX, mCellCountY);
983        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
984        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
985                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
986
987        // Note: We force a measure here to get around the fact that when we do layout calculations
988        // immediately after syncing, we don't have a proper width.  That said, we already know the
989        // expected page width, so we can actually optimize by hiding all the TextView-based
990        // children that are expensive to measure, and let that happen naturally later.
991        setVisibilityOnChildren(layout, View.GONE);
992        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
993        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
994        layout.setMinimumWidth(getPageContentWidth());
995        layout.measure(widthSpec, heightSpec);
996        setVisibilityOnChildren(layout, View.VISIBLE);
997    }
998
999    public void syncAppsPageItems(int page, boolean immediate) {
1000        // ensure that we have the right number of items on the pages
1001        int numCells = mCellCountX * mCellCountY;
1002        int startIndex = page * numCells;
1003        int endIndex = Math.min(startIndex + numCells, mApps.size());
1004        PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
1005
1006        layout.removeAllViewsOnPage();
1007        ArrayList<Object> items = new ArrayList<Object>();
1008        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
1009        for (int i = startIndex; i < endIndex; ++i) {
1010            ApplicationInfo info = mApps.get(i);
1011            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
1012                    R.layout.apps_customize_application, layout, false);
1013            icon.applyFromApplicationInfo(info, true, this);
1014            icon.setOnClickListener(this);
1015            icon.setOnLongClickListener(this);
1016            icon.setOnTouchListener(this);
1017            icon.setOnKeyListener(this);
1018
1019            int index = i - startIndex;
1020            int x = index % mCellCountX;
1021            int y = index / mCellCountX;
1022            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
1023
1024            items.add(info);
1025            images.add(info.iconBitmap);
1026        }
1027
1028        enableHwLayersOnVisiblePages();
1029    }
1030
1031    /**
1032     * A helper to return the priority for loading of the specified widget page.
1033     */
1034    private int getWidgetPageLoadPriority(int page) {
1035        // If we are snapping to another page, use that index as the target page index
1036        int toPage = mCurrentPage;
1037        if (mNextPage > -1) {
1038            toPage = mNextPage;
1039        }
1040
1041        // We use the distance from the target page as an initial guess of priority, but if there
1042        // are no pages of higher priority than the page specified, then bump up the priority of
1043        // the specified page.
1044        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1045        int minPageDiff = Integer.MAX_VALUE;
1046        while (iter.hasNext()) {
1047            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1048            minPageDiff = Math.abs(task.page - toPage);
1049        }
1050
1051        int rawPageDiff = Math.abs(page - toPage);
1052        return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
1053    }
1054    /**
1055     * Return the appropriate thread priority for loading for a given page (we give the current
1056     * page much higher priority)
1057     */
1058    private int getThreadPriorityForPage(int page) {
1059        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
1060        int pageDiff = getWidgetPageLoadPriority(page);
1061        if (pageDiff <= 0) {
1062            return Process.THREAD_PRIORITY_LESS_FAVORABLE;
1063        } else if (pageDiff <= 1) {
1064            return Process.THREAD_PRIORITY_LOWEST;
1065        } else {
1066            return Process.THREAD_PRIORITY_LOWEST;
1067        }
1068    }
1069    private int getSleepForPage(int page) {
1070        int pageDiff = getWidgetPageLoadPriority(page);
1071        return Math.max(0, pageDiff * sPageSleepDelay);
1072    }
1073    /**
1074     * Creates and executes a new AsyncTask to load a page of widget previews.
1075     */
1076    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
1077            int cellWidth, int cellHeight, int cellCountX) {
1078
1079        // Prune all tasks that are no longer needed
1080        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1081        while (iter.hasNext()) {
1082            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1083            int taskPage = task.page;
1084            if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
1085                    taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
1086                task.cancel(false);
1087                iter.remove();
1088            } else {
1089                task.setThreadPriority(getThreadPriorityForPage(taskPage));
1090            }
1091        }
1092
1093        // We introduce a slight delay to order the loading of side pages so that we don't thrash
1094        final int sleepMs = getSleepForPage(page);
1095        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
1096            new AsyncTaskCallback() {
1097                @Override
1098                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1099                    try {
1100                        try {
1101                            Thread.sleep(sleepMs);
1102                        } catch (Exception e) {}
1103                        loadWidgetPreviewsInBackground(task, data);
1104                    } finally {
1105                        if (task.isCancelled()) {
1106                            data.cleanup(true);
1107                        }
1108                    }
1109                }
1110            },
1111            new AsyncTaskCallback() {
1112                @Override
1113                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1114                    mRunningTasks.remove(task);
1115                    if (task.isCancelled()) return;
1116                    // do cleanup inside onSyncWidgetPageItems
1117                    onSyncWidgetPageItems(data);
1118                }
1119            }, mWidgetPreviewLoader);
1120
1121        // Ensure that the task is appropriately prioritized and runs in parallel
1122        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
1123                AsyncTaskPageData.Type.LoadWidgetPreviewData);
1124        t.setThreadPriority(getThreadPriorityForPage(page));
1125        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
1126        mRunningTasks.add(t);
1127    }
1128
1129    /*
1130     * Widgets PagedView implementation
1131     */
1132    private void setupPage(PagedViewGridLayout layout) {
1133        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
1134                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
1135
1136        // Note: We force a measure here to get around the fact that when we do layout calculations
1137        // immediately after syncing, we don't have a proper width.
1138        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
1139        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
1140        layout.setMinimumWidth(getPageContentWidth());
1141        layout.measure(widthSpec, heightSpec);
1142    }
1143
1144    public void syncWidgetPageItems(final int page, final boolean immediate) {
1145        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1146
1147        // Calculate the dimensions of each cell we are giving to each widget
1148        final ArrayList<Object> items = new ArrayList<Object>();
1149        int contentWidth = mWidgetSpacingLayout.getContentWidth();
1150        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
1151                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
1152        int contentHeight = mWidgetSpacingLayout.getContentHeight();
1153        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
1154                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
1155
1156        // Prepare the set of widgets to load previews for in the background
1157        int offset = (page - mNumAppsPages) * numItemsPerPage;
1158        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1159            items.add(mWidgets.get(i));
1160        }
1161
1162        // Prepopulate the pages with the other widget info, and fill in the previews later
1163        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1164        layout.setColumnCount(layout.getCellCountX());
1165        for (int i = 0; i < items.size(); ++i) {
1166            Object rawInfo = items.get(i);
1167            PendingAddItemInfo createItemInfo = null;
1168            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1169                    R.layout.apps_customize_widget, layout, false);
1170            if (rawInfo instanceof AppWidgetProviderInfo) {
1171                // Fill in the widget information
1172                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1173                createItemInfo = new PendingAddWidgetInfo(info, null, null);
1174
1175                // Determine the widget spans and min resize spans.
1176                int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
1177                createItemInfo.spanX = spanXY[0];
1178                createItemInfo.spanY = spanXY[1];
1179                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
1180                createItemInfo.minSpanX = minSpanXY[0];
1181                createItemInfo.minSpanY = minSpanXY[1];
1182
1183                widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader);
1184                widget.setTag(createItemInfo);
1185                widget.setShortPressListener(this);
1186            } else if (rawInfo instanceof ResolveInfo) {
1187                // Fill in the shortcuts information
1188                ResolveInfo info = (ResolveInfo) rawInfo;
1189                createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
1190                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1191                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1192                        info.activityInfo.name);
1193                widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader);
1194                widget.setTag(createItemInfo);
1195            }
1196            widget.setOnClickListener(this);
1197            widget.setOnLongClickListener(this);
1198            widget.setOnTouchListener(this);
1199            widget.setOnKeyListener(this);
1200
1201            // Layout each widget
1202            int ix = i % mWidgetCountX;
1203            int iy = i / mWidgetCountX;
1204            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1205                    GridLayout.spec(iy, GridLayout.START),
1206                    GridLayout.spec(ix, GridLayout.TOP));
1207            lp.width = cellWidth;
1208            lp.height = cellHeight;
1209            lp.setGravity(Gravity.TOP | Gravity.START);
1210            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
1211            if (iy > 0) lp.topMargin = mWidgetHeightGap;
1212            layout.addView(widget, lp);
1213        }
1214
1215        // wait until a call on onLayout to start loading, because
1216        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1217        // TODO: can we do a measure/layout immediately?
1218        layout.setOnLayoutListener(new Runnable() {
1219            public void run() {
1220                // Load the widget previews
1221                int maxPreviewWidth = cellWidth;
1222                int maxPreviewHeight = cellHeight;
1223                if (layout.getChildCount() > 0) {
1224                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1225                    int[] maxSize = w.getPreviewSize();
1226                    maxPreviewWidth = maxSize[0];
1227                    maxPreviewHeight = maxSize[1];
1228                }
1229
1230                mWidgetPreviewLoader.setPreviewSize(
1231                        maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
1232                if (immediate) {
1233                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1234                            maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
1235                    loadWidgetPreviewsInBackground(null, data);
1236                    onSyncWidgetPageItems(data);
1237                } else {
1238                    if (mInTransition) {
1239                        mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
1240                    } else {
1241                        prepareLoadWidgetPreviewsTask(page, items,
1242                                maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1243                    }
1244                }
1245                layout.setOnLayoutListener(null);
1246            }
1247        });
1248    }
1249    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1250            AsyncTaskPageData data) {
1251        // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
1252        // previews synchronously
1253        if (task != null) {
1254            // Ensure that this task starts running at the correct priority
1255            task.syncThreadPriority();
1256        }
1257
1258        // Load each of the widget/shortcut previews
1259        ArrayList<Object> items = data.items;
1260        ArrayList<Bitmap> images = data.generatedImages;
1261        int count = items.size();
1262        for (int i = 0; i < count; ++i) {
1263            if (task != null) {
1264                // Ensure we haven't been cancelled yet
1265                if (task.isCancelled()) break;
1266                // Before work on each item, ensure that this task is running at the correct
1267                // priority
1268                task.syncThreadPriority();
1269            }
1270
1271            images.add(mWidgetPreviewLoader.getPreview(items.get(i)));
1272        }
1273    }
1274
1275    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
1276        if (mInTransition) {
1277            mDeferredSyncWidgetPageItems.add(data);
1278            return;
1279        }
1280        try {
1281            int page = data.page;
1282            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1283
1284            ArrayList<Object> items = data.items;
1285            int count = items.size();
1286            for (int i = 0; i < count; ++i) {
1287                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1288                if (widget != null) {
1289                    Bitmap preview = data.generatedImages.get(i);
1290                    widget.applyPreview(new FastBitmapDrawable(preview), i);
1291                }
1292            }
1293
1294            enableHwLayersOnVisiblePages();
1295
1296            // Update all thread priorities
1297            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1298            while (iter.hasNext()) {
1299                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1300                int pageIndex = task.page;
1301                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
1302            }
1303        } finally {
1304            data.cleanup(false);
1305        }
1306    }
1307
1308    @Override
1309    public void syncPages() {
1310        removeAllViews();
1311        cancelAllTasks();
1312
1313        Context context = getContext();
1314        for (int j = 0; j < mNumWidgetPages; ++j) {
1315            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1316                    mWidgetCountY);
1317            setupPage(layout);
1318            addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
1319                    LayoutParams.MATCH_PARENT));
1320        }
1321
1322        for (int i = 0; i < mNumAppsPages; ++i) {
1323            PagedViewCellLayout layout = new PagedViewCellLayout(context);
1324            setupPage(layout);
1325            addView(layout);
1326        }
1327    }
1328
1329    @Override
1330    public void syncPageItems(int page, boolean immediate) {
1331        if (page < mNumAppsPages) {
1332            syncAppsPageItems(page, immediate);
1333        } else {
1334            syncWidgetPageItems(page, immediate);
1335        }
1336    }
1337
1338    // We want our pages to be z-ordered such that the further a page is to the left, the higher
1339    // it is in the z-order. This is important to insure touch events are handled correctly.
1340    View getPageAt(int index) {
1341        return getChildAt(indexToPage(index));
1342    }
1343
1344    @Override
1345    protected int indexToPage(int index) {
1346        return getChildCount() - index - 1;
1347    }
1348
1349    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1350    @Override
1351    protected void screenScrolled(int screenCenter) {
1352        final boolean isRtl = isLayoutRtl();
1353        super.screenScrolled(screenCenter);
1354
1355        for (int i = 0; i < getChildCount(); i++) {
1356            View v = getPageAt(i);
1357            if (v != null) {
1358                float scrollProgress = getScrollProgress(screenCenter, v, i);
1359
1360                float interpolatedProgress;
1361                float translationX;
1362                float maxScrollProgress = Math.max(0, scrollProgress);
1363                float minScrollProgress = Math.min(0, scrollProgress);
1364
1365                if (isRtl) {
1366                    translationX = maxScrollProgress * v.getMeasuredWidth();
1367                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
1368                } else {
1369                    translationX = minScrollProgress * v.getMeasuredWidth();
1370                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
1371                }
1372                float scale = (1 - interpolatedProgress) +
1373                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
1374
1375                float alpha;
1376                if (isRtl && (scrollProgress > 0)) {
1377                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
1378                } else if (!isRtl && (scrollProgress < 0)) {
1379                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
1380                } else {
1381                    //  On large screens we need to fade the page as it nears its leftmost position
1382                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
1383                }
1384
1385                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
1386                int pageWidth = v.getMeasuredWidth();
1387                int pageHeight = v.getMeasuredHeight();
1388
1389                if (PERFORM_OVERSCROLL_ROTATION) {
1390                    float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
1391                    boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
1392                    boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
1393
1394                    if (i == 0 && isOverscrollingFirstPage) {
1395                        // Overscroll to the left
1396                        v.setPivotX(xPivot * pageWidth);
1397                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1398                        scale = 1.0f;
1399                        alpha = 1.0f;
1400                        // On the first page, we don't want the page to have any lateral motion
1401                        translationX = 0;
1402                    } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
1403                        // Overscroll to the right
1404                        v.setPivotX((1 - xPivot) * pageWidth);
1405                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1406                        scale = 1.0f;
1407                        alpha = 1.0f;
1408                        // On the last page, we don't want the page to have any lateral motion.
1409                        translationX = 0;
1410                    } else {
1411                        v.setPivotY(pageHeight / 2.0f);
1412                        v.setPivotX(pageWidth / 2.0f);
1413                        v.setRotationY(0f);
1414                    }
1415                }
1416
1417                v.setTranslationX(translationX);
1418                v.setScaleX(scale);
1419                v.setScaleY(scale);
1420                v.setAlpha(alpha);
1421
1422                // If the view has 0 alpha, we set it to be invisible so as to prevent
1423                // it from accepting touches
1424                if (alpha == 0) {
1425                    v.setVisibility(INVISIBLE);
1426                } else if (v.getVisibility() != VISIBLE) {
1427                    v.setVisibility(VISIBLE);
1428                }
1429            }
1430        }
1431
1432        enableHwLayersOnVisiblePages();
1433    }
1434
1435    private void enableHwLayersOnVisiblePages() {
1436        final int screenCount = getChildCount();
1437
1438        getVisiblePages(mTempVisiblePagesRange);
1439        int leftScreen = mTempVisiblePagesRange[0];
1440        int rightScreen = mTempVisiblePagesRange[1];
1441        int forceDrawScreen = -1;
1442        if (leftScreen == rightScreen) {
1443            // make sure we're caching at least two pages always
1444            if (rightScreen < screenCount - 1) {
1445                rightScreen++;
1446                forceDrawScreen = rightScreen;
1447            } else if (leftScreen > 0) {
1448                leftScreen--;
1449                forceDrawScreen = leftScreen;
1450            }
1451        } else {
1452            forceDrawScreen = leftScreen + 1;
1453        }
1454
1455        for (int i = 0; i < screenCount; i++) {
1456            final View layout = (View) getPageAt(i);
1457            if (!(leftScreen <= i && i <= rightScreen &&
1458                    (i == forceDrawScreen || shouldDrawChild(layout)))) {
1459                layout.setLayerType(LAYER_TYPE_NONE, null);
1460            }
1461        }
1462
1463        for (int i = 0; i < screenCount; i++) {
1464            final View layout = (View) getPageAt(i);
1465
1466            if (leftScreen <= i && i <= rightScreen &&
1467                    (i == forceDrawScreen || shouldDrawChild(layout))) {
1468                if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
1469                    layout.setLayerType(LAYER_TYPE_HARDWARE, null);
1470                }
1471            }
1472        }
1473    }
1474
1475    protected void overScroll(float amount) {
1476        acceleratedOverScroll(amount);
1477    }
1478
1479    /**
1480     * Used by the parent to get the content width to set the tab bar to
1481     * @return
1482     */
1483    public int getPageContentWidth() {
1484        return mContentWidth;
1485    }
1486
1487    @Override
1488    protected void onPageEndMoving() {
1489        super.onPageEndMoving();
1490        mForceDrawAllChildrenNextFrame = true;
1491        // We reset the save index when we change pages so that it will be recalculated on next
1492        // rotation
1493        mSaveInstanceStateItemIndex = -1;
1494    }
1495
1496    /*
1497     * AllAppsView implementation
1498     */
1499    public void setup(Launcher launcher, DragController dragController) {
1500        mLauncher = launcher;
1501        mDragController = dragController;
1502    }
1503
1504    /**
1505     * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
1506     * appropriately determine when to invalidate the PagedView page data.  In cases where the data
1507     * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
1508     * next onMeasure() pass, which will trigger an invalidatePageData() itself.
1509     */
1510    private void invalidateOnDataChange() {
1511        if (!isDataReady()) {
1512            // The next layout pass will trigger data-ready if both widgets and apps are set, so
1513            // request a layout to trigger the page data when ready.
1514            requestLayout();
1515        } else {
1516            cancelAllTasks();
1517            invalidatePageData();
1518        }
1519    }
1520
1521    public void setApps(ArrayList<ApplicationInfo> list) {
1522        mApps = list;
1523        Collections.sort(mApps, LauncherModel.getAppNameComparator());
1524        updatePageCounts();
1525        invalidateOnDataChange();
1526    }
1527    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1528        // We add it in place, in alphabetical order
1529        int count = list.size();
1530        for (int i = 0; i < count; ++i) {
1531            ApplicationInfo info = list.get(i);
1532            int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
1533            if (index < 0) {
1534                mApps.add(-(index + 1), info);
1535            }
1536        }
1537    }
1538    public void addApps(ArrayList<ApplicationInfo> list) {
1539        addAppsWithoutInvalidate(list);
1540        updatePageCounts();
1541        invalidateOnDataChange();
1542    }
1543    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1544        ComponentName removeComponent = item.intent.getComponent();
1545        int length = list.size();
1546        for (int i = 0; i < length; ++i) {
1547            ApplicationInfo info = list.get(i);
1548            if (info.intent.getComponent().equals(removeComponent)) {
1549                return i;
1550            }
1551        }
1552        return -1;
1553    }
1554    private int findAppByPackage(List<ApplicationInfo> list, String packageName) {
1555        int length = list.size();
1556        for (int i = 0; i < length; ++i) {
1557            ApplicationInfo info = list.get(i);
1558            if (ItemInfo.getPackageName(info.intent).equals(packageName)) {
1559                return i;
1560            }
1561        }
1562        return -1;
1563    }
1564    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1565        // loop through all the apps and remove apps that have the same component
1566        int length = list.size();
1567        for (int i = 0; i < length; ++i) {
1568            ApplicationInfo info = list.get(i);
1569            int removeIndex = findAppByComponent(mApps, info);
1570            if (removeIndex > -1) {
1571                mApps.remove(removeIndex);
1572            }
1573        }
1574    }
1575    private void removeAppsWithPackageNameWithoutInvalidate(ArrayList<String> packageNames) {
1576        // loop through all the package names and remove apps that have the same package name
1577        for (String pn : packageNames) {
1578            int removeIndex = findAppByPackage(mApps, pn);
1579            while (removeIndex > -1) {
1580                mApps.remove(removeIndex);
1581                removeIndex = findAppByPackage(mApps, pn);
1582            }
1583        }
1584    }
1585    public void removeApps(ArrayList<String> packageNames) {
1586        removeAppsWithPackageNameWithoutInvalidate(packageNames);
1587        updatePageCounts();
1588        invalidateOnDataChange();
1589    }
1590    public void updateApps(ArrayList<ApplicationInfo> list) {
1591        // We remove and re-add the updated applications list because it's properties may have
1592        // changed (ie. the title), and this will ensure that the items will be in their proper
1593        // place in the list.
1594        removeAppsWithoutInvalidate(list);
1595        addAppsWithoutInvalidate(list);
1596        updatePageCounts();
1597        invalidateOnDataChange();
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