AppsCustomizePagedView.java revision ee8e99fe3bde78885904b4d9ea789b4d2a6f2b16
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        if (mWidgetPreviewLoader != null) {
898            mWidgetPreviewLoader.closeDb();
899        }
900    }
901
902    public void clearAllWidgetPages() {
903        cancelAllTasks();
904        int count = getChildCount();
905        for (int i = 0; i < count; i++) {
906            View v = getPageAt(i);
907            if (v instanceof PagedViewGridLayout) {
908                ((PagedViewGridLayout) v).removeAllViewsOnPage();
909                mDirtyPageContent.set(i, true);
910            }
911        }
912    }
913
914    private void cancelAllTasks() {
915        // Clean up all the async tasks
916        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
917        while (iter.hasNext()) {
918            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
919            task.cancel(false);
920            iter.remove();
921            mDirtyPageContent.set(task.page, true);
922
923            // We've already preallocated the views for the data to load into, so clear them as well
924            View v = getPageAt(task.page);
925            if (v instanceof PagedViewGridLayout) {
926                ((PagedViewGridLayout) v).removeAllViewsOnPage();
927            }
928        }
929        mDeferredSyncWidgetPageItems.clear();
930        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
931    }
932
933    public void setContentType(ContentType type) {
934        if (type == ContentType.Widgets) {
935            invalidatePageData(mNumAppsPages, true);
936        } else if (type == ContentType.Applications) {
937            invalidatePageData(0, true);
938        }
939    }
940
941    protected void snapToPage(int whichPage, int delta, int duration) {
942        super.snapToPage(whichPage, delta, duration);
943        updateCurrentTab(whichPage);
944
945        // Update the thread priorities given the direction lookahead
946        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
947        while (iter.hasNext()) {
948            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
949            int pageIndex = task.page;
950            if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
951                (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
952                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
953            } else {
954                task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
955            }
956        }
957    }
958
959    private void updateCurrentTab(int currentPage) {
960        AppsCustomizeTabHost tabHost = getTabHost();
961        if (tabHost != null) {
962            String tag = tabHost.getCurrentTabTag();
963            if (tag != null) {
964                if (currentPage >= mNumAppsPages &&
965                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
966                    tabHost.setCurrentTabFromContent(ContentType.Widgets);
967                } else if (currentPage < mNumAppsPages &&
968                        !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
969                    tabHost.setCurrentTabFromContent(ContentType.Applications);
970                }
971            }
972        }
973    }
974
975    /*
976     * Apps PagedView implementation
977     */
978    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
979        int childCount = layout.getChildCount();
980        for (int i = 0; i < childCount; ++i) {
981            layout.getChildAt(i).setVisibility(visibility);
982        }
983    }
984    private void setupPage(PagedViewCellLayout layout) {
985        layout.setCellCount(mCellCountX, mCellCountY);
986        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
987        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
988                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
989
990        // Note: We force a measure here to get around the fact that when we do layout calculations
991        // immediately after syncing, we don't have a proper width.  That said, we already know the
992        // expected page width, so we can actually optimize by hiding all the TextView-based
993        // children that are expensive to measure, and let that happen naturally later.
994        setVisibilityOnChildren(layout, View.GONE);
995        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
996        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
997        layout.setMinimumWidth(getPageContentWidth());
998        layout.measure(widthSpec, heightSpec);
999        setVisibilityOnChildren(layout, View.VISIBLE);
1000    }
1001
1002    public void syncAppsPageItems(int page, boolean immediate) {
1003        // ensure that we have the right number of items on the pages
1004        int numCells = mCellCountX * mCellCountY;
1005        int startIndex = page * numCells;
1006        int endIndex = Math.min(startIndex + numCells, mApps.size());
1007        PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
1008
1009        layout.removeAllViewsOnPage();
1010        ArrayList<Object> items = new ArrayList<Object>();
1011        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
1012        for (int i = startIndex; i < endIndex; ++i) {
1013            ApplicationInfo info = mApps.get(i);
1014            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
1015                    R.layout.apps_customize_application, layout, false);
1016            icon.applyFromApplicationInfo(info, true, this);
1017            icon.setOnClickListener(this);
1018            icon.setOnLongClickListener(this);
1019            icon.setOnTouchListener(this);
1020            icon.setOnKeyListener(this);
1021
1022            int index = i - startIndex;
1023            int x = index % mCellCountX;
1024            int y = index / mCellCountX;
1025            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
1026
1027            items.add(info);
1028            images.add(info.iconBitmap);
1029        }
1030
1031        enableHwLayersOnVisiblePages();
1032    }
1033
1034    /**
1035     * A helper to return the priority for loading of the specified widget page.
1036     */
1037    private int getWidgetPageLoadPriority(int page) {
1038        // If we are snapping to another page, use that index as the target page index
1039        int toPage = mCurrentPage;
1040        if (mNextPage > -1) {
1041            toPage = mNextPage;
1042        }
1043
1044        // We use the distance from the target page as an initial guess of priority, but if there
1045        // are no pages of higher priority than the page specified, then bump up the priority of
1046        // the specified page.
1047        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1048        int minPageDiff = Integer.MAX_VALUE;
1049        while (iter.hasNext()) {
1050            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1051            minPageDiff = Math.abs(task.page - toPage);
1052        }
1053
1054        int rawPageDiff = Math.abs(page - toPage);
1055        return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
1056    }
1057    /**
1058     * Return the appropriate thread priority for loading for a given page (we give the current
1059     * page much higher priority)
1060     */
1061    private int getThreadPriorityForPage(int page) {
1062        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
1063        int pageDiff = getWidgetPageLoadPriority(page);
1064        if (pageDiff <= 0) {
1065            return Process.THREAD_PRIORITY_LESS_FAVORABLE;
1066        } else if (pageDiff <= 1) {
1067            return Process.THREAD_PRIORITY_LOWEST;
1068        } else {
1069            return Process.THREAD_PRIORITY_LOWEST;
1070        }
1071    }
1072    private int getSleepForPage(int page) {
1073        int pageDiff = getWidgetPageLoadPriority(page);
1074        return Math.max(0, pageDiff * sPageSleepDelay);
1075    }
1076    /**
1077     * Creates and executes a new AsyncTask to load a page of widget previews.
1078     */
1079    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
1080            int cellWidth, int cellHeight, int cellCountX) {
1081
1082        // Prune all tasks that are no longer needed
1083        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1084        while (iter.hasNext()) {
1085            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1086            int taskPage = task.page;
1087            if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
1088                    taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
1089                task.cancel(false);
1090                iter.remove();
1091            } else {
1092                task.setThreadPriority(getThreadPriorityForPage(taskPage));
1093            }
1094        }
1095
1096        // We introduce a slight delay to order the loading of side pages so that we don't thrash
1097        final int sleepMs = getSleepForPage(page);
1098        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
1099            new AsyncTaskCallback() {
1100                @Override
1101                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1102                    try {
1103                        try {
1104                            Thread.sleep(sleepMs);
1105                        } catch (Exception e) {}
1106                        loadWidgetPreviewsInBackground(task, data);
1107                    } finally {
1108                        if (task.isCancelled()) {
1109                            data.cleanup(true);
1110                        }
1111                    }
1112                }
1113            },
1114            new AsyncTaskCallback() {
1115                @Override
1116                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1117                    mRunningTasks.remove(task);
1118                    if (task.isCancelled()) return;
1119                    // do cleanup inside onSyncWidgetPageItems
1120                    onSyncWidgetPageItems(data);
1121                }
1122            }, mWidgetPreviewLoader);
1123
1124        // Ensure that the task is appropriately prioritized and runs in parallel
1125        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
1126                AsyncTaskPageData.Type.LoadWidgetPreviewData);
1127        t.setThreadPriority(getThreadPriorityForPage(page));
1128        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
1129        mRunningTasks.add(t);
1130    }
1131
1132    /*
1133     * Widgets PagedView implementation
1134     */
1135    private void setupPage(PagedViewGridLayout layout) {
1136        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
1137                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
1138
1139        // Note: We force a measure here to get around the fact that when we do layout calculations
1140        // immediately after syncing, we don't have a proper width.
1141        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
1142        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
1143        layout.setMinimumWidth(getPageContentWidth());
1144        layout.measure(widthSpec, heightSpec);
1145    }
1146
1147    public void syncWidgetPageItems(final int page, final boolean immediate) {
1148        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1149
1150        // Calculate the dimensions of each cell we are giving to each widget
1151        final ArrayList<Object> items = new ArrayList<Object>();
1152        int contentWidth = mWidgetSpacingLayout.getContentWidth();
1153        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
1154                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
1155        int contentHeight = mWidgetSpacingLayout.getContentHeight();
1156        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
1157                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
1158
1159        // Prepare the set of widgets to load previews for in the background
1160        int offset = (page - mNumAppsPages) * numItemsPerPage;
1161        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1162            items.add(mWidgets.get(i));
1163        }
1164
1165        // Prepopulate the pages with the other widget info, and fill in the previews later
1166        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1167        layout.setColumnCount(layout.getCellCountX());
1168        for (int i = 0; i < items.size(); ++i) {
1169            Object rawInfo = items.get(i);
1170            PendingAddItemInfo createItemInfo = null;
1171            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1172                    R.layout.apps_customize_widget, layout, false);
1173            if (rawInfo instanceof AppWidgetProviderInfo) {
1174                // Fill in the widget information
1175                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1176                createItemInfo = new PendingAddWidgetInfo(info, null, null);
1177
1178                // Determine the widget spans and min resize spans.
1179                int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
1180                createItemInfo.spanX = spanXY[0];
1181                createItemInfo.spanY = spanXY[1];
1182                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
1183                createItemInfo.minSpanX = minSpanXY[0];
1184                createItemInfo.minSpanY = minSpanXY[1];
1185
1186                widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader);
1187                widget.setTag(createItemInfo);
1188                widget.setShortPressListener(this);
1189            } else if (rawInfo instanceof ResolveInfo) {
1190                // Fill in the shortcuts information
1191                ResolveInfo info = (ResolveInfo) rawInfo;
1192                createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
1193                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1194                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1195                        info.activityInfo.name);
1196                widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader);
1197                widget.setTag(createItemInfo);
1198            }
1199            widget.setOnClickListener(this);
1200            widget.setOnLongClickListener(this);
1201            widget.setOnTouchListener(this);
1202            widget.setOnKeyListener(this);
1203
1204            // Layout each widget
1205            int ix = i % mWidgetCountX;
1206            int iy = i / mWidgetCountX;
1207            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1208                    GridLayout.spec(iy, GridLayout.START),
1209                    GridLayout.spec(ix, GridLayout.TOP));
1210            lp.width = cellWidth;
1211            lp.height = cellHeight;
1212            lp.setGravity(Gravity.TOP | Gravity.START);
1213            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
1214            if (iy > 0) lp.topMargin = mWidgetHeightGap;
1215            layout.addView(widget, lp);
1216        }
1217
1218        // wait until a call on onLayout to start loading, because
1219        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1220        // TODO: can we do a measure/layout immediately?
1221        layout.setOnLayoutListener(new Runnable() {
1222            public void run() {
1223                // Load the widget previews
1224                int maxPreviewWidth = cellWidth;
1225                int maxPreviewHeight = cellHeight;
1226                if (layout.getChildCount() > 0) {
1227                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1228                    int[] maxSize = w.getPreviewSize();
1229                    maxPreviewWidth = maxSize[0];
1230                    maxPreviewHeight = maxSize[1];
1231                }
1232
1233                mWidgetPreviewLoader.setPreviewSize(
1234                        maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
1235                if (immediate) {
1236                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1237                            maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
1238                    loadWidgetPreviewsInBackground(null, data);
1239                    onSyncWidgetPageItems(data);
1240                } else {
1241                    if (mInTransition) {
1242                        mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
1243                    } else {
1244                        prepareLoadWidgetPreviewsTask(page, items,
1245                                maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1246                    }
1247                }
1248                layout.setOnLayoutListener(null);
1249            }
1250        });
1251    }
1252    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1253            AsyncTaskPageData data) {
1254        // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
1255        // previews synchronously
1256        if (task != null) {
1257            // Ensure that this task starts running at the correct priority
1258            task.syncThreadPriority();
1259        }
1260
1261        // Load each of the widget/shortcut previews
1262        ArrayList<Object> items = data.items;
1263        ArrayList<Bitmap> images = data.generatedImages;
1264        int count = items.size();
1265        for (int i = 0; i < count; ++i) {
1266            if (task != null) {
1267                // Ensure we haven't been cancelled yet
1268                if (task.isCancelled()) break;
1269                // Before work on each item, ensure that this task is running at the correct
1270                // priority
1271                task.syncThreadPriority();
1272            }
1273
1274            images.add(mWidgetPreviewLoader.getPreview(items.get(i)));
1275        }
1276    }
1277
1278    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
1279        if (mInTransition) {
1280            mDeferredSyncWidgetPageItems.add(data);
1281            return;
1282        }
1283        try {
1284            int page = data.page;
1285            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1286
1287            ArrayList<Object> items = data.items;
1288            int count = items.size();
1289            for (int i = 0; i < count; ++i) {
1290                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1291                if (widget != null) {
1292                    Bitmap preview = data.generatedImages.get(i);
1293                    widget.applyPreview(new FastBitmapDrawable(preview), i);
1294                }
1295            }
1296
1297            enableHwLayersOnVisiblePages();
1298
1299            // Update all thread priorities
1300            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1301            while (iter.hasNext()) {
1302                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1303                int pageIndex = task.page;
1304                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
1305            }
1306        } finally {
1307            data.cleanup(false);
1308        }
1309    }
1310
1311    @Override
1312    public void syncPages() {
1313        removeAllViews();
1314        cancelAllTasks();
1315
1316        Context context = getContext();
1317        for (int j = 0; j < mNumWidgetPages; ++j) {
1318            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1319                    mWidgetCountY);
1320            setupPage(layout);
1321            addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
1322                    LayoutParams.MATCH_PARENT));
1323        }
1324
1325        for (int i = 0; i < mNumAppsPages; ++i) {
1326            PagedViewCellLayout layout = new PagedViewCellLayout(context);
1327            setupPage(layout);
1328            addView(layout);
1329        }
1330    }
1331
1332    @Override
1333    public void syncPageItems(int page, boolean immediate) {
1334        if (page < mNumAppsPages) {
1335            syncAppsPageItems(page, immediate);
1336        } else {
1337            syncWidgetPageItems(page, immediate);
1338        }
1339    }
1340
1341    // We want our pages to be z-ordered such that the further a page is to the left, the higher
1342    // it is in the z-order. This is important to insure touch events are handled correctly.
1343    View getPageAt(int index) {
1344        return getChildAt(indexToPage(index));
1345    }
1346
1347    @Override
1348    protected int indexToPage(int index) {
1349        return getChildCount() - index - 1;
1350    }
1351
1352    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1353    @Override
1354    protected void screenScrolled(int screenCenter) {
1355        final boolean isRtl = isLayoutRtl();
1356        super.screenScrolled(screenCenter);
1357
1358        for (int i = 0; i < getChildCount(); i++) {
1359            View v = getPageAt(i);
1360            if (v != null) {
1361                float scrollProgress = getScrollProgress(screenCenter, v, i);
1362
1363                float interpolatedProgress;
1364                float translationX;
1365                float maxScrollProgress = Math.max(0, scrollProgress);
1366                float minScrollProgress = Math.min(0, scrollProgress);
1367
1368                if (isRtl) {
1369                    translationX = maxScrollProgress * v.getMeasuredWidth();
1370                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
1371                } else {
1372                    translationX = minScrollProgress * v.getMeasuredWidth();
1373                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
1374                }
1375                float scale = (1 - interpolatedProgress) +
1376                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
1377
1378                float alpha;
1379                if (isRtl && (scrollProgress > 0)) {
1380                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
1381                } else if (!isRtl && (scrollProgress < 0)) {
1382                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
1383                } else {
1384                    //  On large screens we need to fade the page as it nears its leftmost position
1385                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
1386                }
1387
1388                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
1389                int pageWidth = v.getMeasuredWidth();
1390                int pageHeight = v.getMeasuredHeight();
1391
1392                if (PERFORM_OVERSCROLL_ROTATION) {
1393                    float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
1394                    boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
1395                    boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
1396
1397                    if (i == 0 && isOverscrollingFirstPage) {
1398                        // Overscroll to the left
1399                        v.setPivotX(xPivot * pageWidth);
1400                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1401                        scale = 1.0f;
1402                        alpha = 1.0f;
1403                        // On the first page, we don't want the page to have any lateral motion
1404                        translationX = 0;
1405                    } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
1406                        // Overscroll to the right
1407                        v.setPivotX((1 - xPivot) * pageWidth);
1408                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1409                        scale = 1.0f;
1410                        alpha = 1.0f;
1411                        // On the last page, we don't want the page to have any lateral motion.
1412                        translationX = 0;
1413                    } else {
1414                        v.setPivotY(pageHeight / 2.0f);
1415                        v.setPivotX(pageWidth / 2.0f);
1416                        v.setRotationY(0f);
1417                    }
1418                }
1419
1420                v.setTranslationX(translationX);
1421                v.setScaleX(scale);
1422                v.setScaleY(scale);
1423                v.setAlpha(alpha);
1424
1425                // If the view has 0 alpha, we set it to be invisible so as to prevent
1426                // it from accepting touches
1427                if (alpha == 0) {
1428                    v.setVisibility(INVISIBLE);
1429                } else if (v.getVisibility() != VISIBLE) {
1430                    v.setVisibility(VISIBLE);
1431                }
1432            }
1433        }
1434
1435        enableHwLayersOnVisiblePages();
1436    }
1437
1438    private void enableHwLayersOnVisiblePages() {
1439        final int screenCount = getChildCount();
1440
1441        getVisiblePages(mTempVisiblePagesRange);
1442        int leftScreen = mTempVisiblePagesRange[0];
1443        int rightScreen = mTempVisiblePagesRange[1];
1444        int forceDrawScreen = -1;
1445        if (leftScreen == rightScreen) {
1446            // make sure we're caching at least two pages always
1447            if (rightScreen < screenCount - 1) {
1448                rightScreen++;
1449                forceDrawScreen = rightScreen;
1450            } else if (leftScreen > 0) {
1451                leftScreen--;
1452                forceDrawScreen = leftScreen;
1453            }
1454        } else {
1455            forceDrawScreen = leftScreen + 1;
1456        }
1457
1458        for (int i = 0; i < screenCount; i++) {
1459            final View layout = (View) getPageAt(i);
1460            if (!(leftScreen <= i && i <= rightScreen &&
1461                    (i == forceDrawScreen || shouldDrawChild(layout)))) {
1462                layout.setLayerType(LAYER_TYPE_NONE, null);
1463            }
1464        }
1465
1466        for (int i = 0; i < screenCount; i++) {
1467            final View layout = (View) getPageAt(i);
1468
1469            if (leftScreen <= i && i <= rightScreen &&
1470                    (i == forceDrawScreen || shouldDrawChild(layout))) {
1471                if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
1472                    layout.setLayerType(LAYER_TYPE_HARDWARE, null);
1473                }
1474            }
1475        }
1476    }
1477
1478    protected void overScroll(float amount) {
1479        acceleratedOverScroll(amount);
1480    }
1481
1482    /**
1483     * Used by the parent to get the content width to set the tab bar to
1484     * @return
1485     */
1486    public int getPageContentWidth() {
1487        return mContentWidth;
1488    }
1489
1490    @Override
1491    protected void onPageEndMoving() {
1492        super.onPageEndMoving();
1493        mForceDrawAllChildrenNextFrame = true;
1494        // We reset the save index when we change pages so that it will be recalculated on next
1495        // rotation
1496        mSaveInstanceStateItemIndex = -1;
1497    }
1498
1499    /*
1500     * AllAppsView implementation
1501     */
1502    public void setup(Launcher launcher, DragController dragController) {
1503        mLauncher = launcher;
1504        mDragController = dragController;
1505    }
1506
1507    /**
1508     * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
1509     * appropriately determine when to invalidate the PagedView page data.  In cases where the data
1510     * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
1511     * next onMeasure() pass, which will trigger an invalidatePageData() itself.
1512     */
1513    private void invalidateOnDataChange() {
1514        if (!isDataReady()) {
1515            // The next layout pass will trigger data-ready if both widgets and apps are set, so
1516            // request a layout to trigger the page data when ready.
1517            requestLayout();
1518        } else {
1519            cancelAllTasks();
1520            invalidatePageData();
1521        }
1522    }
1523
1524    public void setApps(ArrayList<ApplicationInfo> list) {
1525        mApps = list;
1526        Collections.sort(mApps, LauncherModel.getAppNameComparator());
1527        updatePageCounts();
1528        invalidateOnDataChange();
1529    }
1530    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1531        // We add it in place, in alphabetical order
1532        int count = list.size();
1533        for (int i = 0; i < count; ++i) {
1534            ApplicationInfo info = list.get(i);
1535            int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
1536            if (index < 0) {
1537                mApps.add(-(index + 1), info);
1538            }
1539        }
1540    }
1541    public void addApps(ArrayList<ApplicationInfo> list) {
1542        addAppsWithoutInvalidate(list);
1543        updatePageCounts();
1544        invalidateOnDataChange();
1545    }
1546    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1547        ComponentName removeComponent = item.intent.getComponent();
1548        int length = list.size();
1549        for (int i = 0; i < length; ++i) {
1550            ApplicationInfo info = list.get(i);
1551            if (info.intent.getComponent().equals(removeComponent)) {
1552                return i;
1553            }
1554        }
1555        return -1;
1556    }
1557    private int findAppByPackage(List<ApplicationInfo> list, String packageName) {
1558        int length = list.size();
1559        for (int i = 0; i < length; ++i) {
1560            ApplicationInfo info = list.get(i);
1561            if (ItemInfo.getPackageName(info.intent).equals(packageName)) {
1562                return i;
1563            }
1564        }
1565        return -1;
1566    }
1567    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1568        // loop through all the apps and remove apps that have the same component
1569        int length = list.size();
1570        for (int i = 0; i < length; ++i) {
1571            ApplicationInfo info = list.get(i);
1572            int removeIndex = findAppByComponent(mApps, info);
1573            if (removeIndex > -1) {
1574                mApps.remove(removeIndex);
1575            }
1576        }
1577    }
1578    private void removeAppsWithPackageNameWithoutInvalidate(ArrayList<String> packageNames) {
1579        // loop through all the package names and remove apps that have the same package name
1580        for (String pn : packageNames) {
1581            int removeIndex = findAppByPackage(mApps, pn);
1582            while (removeIndex > -1) {
1583                mApps.remove(removeIndex);
1584                removeIndex = findAppByPackage(mApps, pn);
1585            }
1586        }
1587    }
1588    public void removeApps(ArrayList<String> packageNames) {
1589        removeAppsWithPackageNameWithoutInvalidate(packageNames);
1590        updatePageCounts();
1591        invalidateOnDataChange();
1592    }
1593    public void updateApps(ArrayList<ApplicationInfo> list) {
1594        // We remove and re-add the updated applications list because it's properties may have
1595        // changed (ie. the title), and this will ensure that the items will be in their proper
1596        // place in the list.
1597        removeAppsWithoutInvalidate(list);
1598        addAppsWithoutInvalidate(list);
1599        updatePageCounts();
1600        invalidateOnDataChange();
1601    }
1602
1603    public void reset() {
1604        // If we have reset, then we should not continue to restore the previous state
1605        mSaveInstanceStateItemIndex = -1;
1606
1607        AppsCustomizeTabHost tabHost = getTabHost();
1608        String tag = tabHost.getCurrentTabTag();
1609        if (tag != null) {
1610            if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
1611                tabHost.setCurrentTabFromContent(ContentType.Applications);
1612            }
1613        }
1614
1615        if (mCurrentPage != 0) {
1616            invalidatePageData(0);
1617        }
1618    }
1619
1620    private AppsCustomizeTabHost getTabHost() {
1621        return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
1622    }
1623
1624    public void dumpState() {
1625        // TODO: Dump information related to current list of Applications, Widgets, etc.
1626        ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
1627        dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
1628    }
1629
1630    private void dumpAppWidgetProviderInfoList(String tag, String label,
1631            ArrayList<Object> list) {
1632        Log.d(tag, label + " size=" + list.size());
1633        for (Object i: list) {
1634            if (i instanceof AppWidgetProviderInfo) {
1635                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1636                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1637                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1638                        + " initialLayout=" + info.initialLayout
1639                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1640            } else if (i instanceof ResolveInfo) {
1641                ResolveInfo info = (ResolveInfo) i;
1642                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1643                        + info.icon);
1644            }
1645        }
1646    }
1647
1648    public void surrender() {
1649        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1650        // should stop this now.
1651
1652        // Stop all background tasks
1653        cancelAllTasks();
1654    }
1655
1656    @Override
1657    public void iconPressed(PagedViewIcon icon) {
1658        // Reset the previously pressed icon and store a reference to the pressed icon so that
1659        // we can reset it on return to Launcher (in Launcher.onResume())
1660        if (mPressedIcon != null) {
1661            mPressedIcon.resetDrawableState();
1662        }
1663        mPressedIcon = icon;
1664    }
1665
1666    public void resetDrawableState() {
1667        if (mPressedIcon != null) {
1668            mPressedIcon.resetDrawableState();
1669            mPressedIcon = null;
1670        }
1671    }
1672
1673    /*
1674     * We load an extra page on each side to prevent flashes from scrolling and loading of the
1675     * widget previews in the background with the AsyncTasks.
1676     */
1677    final static int sLookBehindPageCount = 2;
1678    final static int sLookAheadPageCount = 2;
1679    protected int getAssociatedLowerPageBound(int page) {
1680        final int count = getChildCount();
1681        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1682        int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
1683        return windowMinIndex;
1684    }
1685    protected int getAssociatedUpperPageBound(int page) {
1686        final int count = getChildCount();
1687        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1688        int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
1689                count - 1);
1690        return windowMaxIndex;
1691    }
1692
1693    @Override
1694    protected String getCurrentPageDescription() {
1695        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
1696        int stringId = R.string.default_scroll_format;
1697        int count = 0;
1698
1699        if (page < mNumAppsPages) {
1700            stringId = R.string.apps_customize_apps_scroll_format;
1701            count = mNumAppsPages;
1702        } else {
1703            page -= mNumAppsPages;
1704            stringId = R.string.apps_customize_widgets_scroll_format;
1705            count = mNumWidgetPages;
1706        }
1707
1708        return String.format(getContext().getString(stringId), page + 1, count);
1709    }
1710}
1711