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