AppsCustomizePagedView.java revision 038f9d8bfb53288e7cf5812f62ec3d5b25fec965
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.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ActivityInfo;
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.Paint;
38import android.graphics.PorterDuff;
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.LayoutInflater;
49import android.view.MotionEvent;
50import android.view.View;
51import android.view.ViewConfiguration;
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        LoadHolographicIconsData
81    }
82
83    AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
84            AsyncTaskCallback postR) {
85        page = p;
86        items = l;
87        sourceImages = si;
88        generatedImages = new ArrayList<Bitmap>();
89        maxImageWidth = maxImageHeight = -1;
90        doInBackgroundCallback = bgR;
91        postExecuteCallback = postR;
92    }
93    AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
94            AsyncTaskCallback postR) {
95        page = p;
96        items = l;
97        generatedImages = new ArrayList<Bitmap>();
98        maxImageWidth = cw;
99        maxImageHeight = ch;
100        doInBackgroundCallback = bgR;
101        postExecuteCallback = postR;
102    }
103    void cleanup(boolean cancelled) {
104        // Clean up any references to source/generated bitmaps
105        if (sourceImages != null) {
106            if (cancelled) {
107                for (Bitmap b : sourceImages) {
108                    b.recycle();
109                }
110            }
111            sourceImages.clear();
112        }
113        if (generatedImages != null) {
114            if (cancelled) {
115                for (Bitmap b : generatedImages) {
116                    b.recycle();
117                }
118            }
119            generatedImages.clear();
120        }
121    }
122    int page;
123    ArrayList<Object> items;
124    ArrayList<Bitmap> sourceImages;
125    ArrayList<Bitmap> generatedImages;
126    int maxImageWidth;
127    int maxImageHeight;
128    AsyncTaskCallback doInBackgroundCallback;
129    AsyncTaskCallback postExecuteCallback;
130}
131
132/**
133 * A generic template for an async task used in AppsCustomize.
134 */
135class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
136    AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
137        page = p;
138        threadPriority = Process.THREAD_PRIORITY_DEFAULT;
139        dataType = ty;
140    }
141    @Override
142    protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
143        if (params.length != 1) return null;
144        // Load each of the widget previews in the background
145        params[0].doInBackgroundCallback.run(this, params[0]);
146        return params[0];
147    }
148    @Override
149    protected void onPostExecute(AsyncTaskPageData result) {
150        // All the widget previews are loaded, so we can just callback to inflate the page
151        result.postExecuteCallback.run(this, result);
152    }
153
154    void setThreadPriority(int p) {
155        threadPriority = p;
156    }
157    void syncThreadPriority() {
158        Process.setThreadPriority(threadPriority);
159    }
160
161    // The page that this async task is associated with
162    AsyncTaskPageData.Type dataType;
163    int page;
164    int threadPriority;
165}
166
167/**
168 * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
169 */
170public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
171        AllAppsView, View.OnClickListener, DragSource {
172    static final String LOG_TAG = "AppsCustomizePagedView";
173
174    /**
175     * The different content types that this paged view can show.
176     */
177    public enum ContentType {
178        Applications,
179        Widgets
180    }
181
182    // Refs
183    private Launcher mLauncher;
184    private DragController mDragController;
185    private final LayoutInflater mLayoutInflater;
186    private final PackageManager mPackageManager;
187
188    // Save and Restore
189    private int mSaveInstanceStateItemIndex = -1;
190
191    // Content
192    private ArrayList<ApplicationInfo> mApps;
193    private ArrayList<Object> mWidgets;
194
195    // Cling
196    private int mClingFocusedX;
197    private int mClingFocusedY;
198
199    // Caching
200    private Canvas mCanvas;
201    private Drawable mDefaultWidgetBackground;
202    private IconCache mIconCache;
203    private int mDragViewMultiplyColor;
204
205    // Dimens
206    private int mContentWidth;
207    private int mAppIconSize;
208    private int mMaxAppCellCountX, mMaxAppCellCountY;
209    private int mWidgetCountX, mWidgetCountY;
210    private int mWidgetWidthGap, mWidgetHeightGap;
211    private final int mWidgetPreviewIconPaddedDimension;
212    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
213    private PagedViewCellLayout mWidgetSpacingLayout;
214    private int mNumAppsPages;
215    private int mNumWidgetPages;
216
217    // Relating to the scroll and overscroll effects
218    Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
219    private static float CAMERA_DISTANCE = 6500;
220    private static float TRANSITION_SCALE_FACTOR = 0.74f;
221    private static float TRANSITION_PIVOT = 0.65f;
222    private static float TRANSITION_MAX_ROTATION = 22;
223    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
224    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
225    private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
226
227    // Previews & outlines
228    ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
229    private HolographicOutlineHelper mHolographicOutlineHelper;
230    private static final int sPageSleepDelay = 150;
231
232    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
233        super(context, attrs);
234        mLayoutInflater = LayoutInflater.from(context);
235        mPackageManager = context.getPackageManager();
236        mApps = new ArrayList<ApplicationInfo>();
237        mWidgets = new ArrayList<Object>();
238        mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
239        mHolographicOutlineHelper = new HolographicOutlineHelper();
240        mCanvas = new Canvas();
241        mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
242
243        // Save the default widget preview background
244        Resources resources = context.getResources();
245        mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
246        mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
247        mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color);
248
249        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
250        mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
251        mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1);
252        mWidgetWidthGap =
253            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
254        mWidgetHeightGap =
255            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
256        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
257        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
258        mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
259        mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
260        a.recycle();
261        mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
262
263        // The padding on the non-matched dimension for the default widget preview icons
264        // (top + bottom)
265        mWidgetPreviewIconPaddedDimension =
266            (int) (mAppIconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage)));
267        mFadeInAdjacentScreens = false;
268    }
269
270    @Override
271    protected void init() {
272        super.init();
273        mCenterPagesVertically = false;
274
275        Context context = getContext();
276        Resources r = context.getResources();
277        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
278    }
279
280    @Override
281    protected void onUnhandledTap(MotionEvent ev) {
282        if (LauncherApplication.isScreenLarge()) {
283            // Dismiss AppsCustomize if we tap
284            mLauncher.showWorkspace(true);
285        }
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    /**
339     * This differs from isDataReady as this is the test done if isDataReady is not set.
340     */
341    private boolean testDataReady() {
342        // We only do this test once, and we default to the Applications page, so we only really
343        // have to wait for there to be apps.
344        // TODO: What if one of them is validly empty
345        return !mApps.isEmpty() && !mWidgets.isEmpty();
346    }
347
348    /** Restores the page for an item at the specified index */
349    void restorePageForIndex(int index) {
350        if (index < 0) return;
351        mSaveInstanceStateItemIndex = index;
352    }
353
354    private void updatePageCounts() {
355        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
356                (float) (mWidgetCountX * mWidgetCountY));
357        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
358    }
359
360    protected void onDataReady(int width, int height) {
361        // Note that we transpose the counts in portrait so that we get a similar layout
362        boolean isLandscape = getResources().getConfiguration().orientation ==
363            Configuration.ORIENTATION_LANDSCAPE;
364        int maxCellCountX = Integer.MAX_VALUE;
365        int maxCellCountY = Integer.MAX_VALUE;
366        if (LauncherApplication.isScreenLarge()) {
367            maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
368                LauncherModel.getCellCountY());
369            maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
370                LauncherModel.getCellCountX());
371        }
372        if (mMaxAppCellCountX > -1) {
373            maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX);
374        }
375        if (mMaxAppCellCountY > -1) {
376            maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY);
377        }
378
379        // Now that the data is ready, we can calculate the content width, the number of cells to
380        // use for each page
381        mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
382        mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
383                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
384        mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
385        mCellCountX = mWidgetSpacingLayout.getCellCountX();
386        mCellCountY = mWidgetSpacingLayout.getCellCountY();
387        updatePageCounts();
388
389        // Force a measure to update recalculate the gaps
390        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
391        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
392        mWidgetSpacingLayout.measure(widthSpec, heightSpec);
393        mContentWidth = mWidgetSpacingLayout.getContentWidth();
394
395        // Restore the page
396        int page = getPageForComponent(mSaveInstanceStateItemIndex);
397        invalidatePageData(Math.max(0, page));
398
399        // Calculate the position for the cling punch through
400        int[] offset = new int[2];
401        int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
402        mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
403        pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + offset[0];
404        pos[1] += (getMeasuredHeight() - mWidgetSpacingLayout.getMeasuredHeight()) / 2 + offset[1];
405        mLauncher.showFirstRunAllAppsCling(pos);
406
407    }
408
409    @Override
410    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
411        int width = MeasureSpec.getSize(widthMeasureSpec);
412        int height = MeasureSpec.getSize(heightMeasureSpec);
413        if (!isDataReady()) {
414            if (testDataReady()) {
415                setDataIsReady();
416                setMeasuredDimension(width, height);
417                onDataReady(width, height);
418            }
419        }
420
421        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
422    }
423
424    /** Removes and returns the ResolveInfo with the specified ComponentName */
425    private ResolveInfo removeResolveInfoWithComponentName(List<ResolveInfo> list,
426            ComponentName cn) {
427        Iterator<ResolveInfo> iter = list.iterator();
428        while (iter.hasNext()) {
429            ResolveInfo rinfo = iter.next();
430            ActivityInfo info = rinfo.activityInfo;
431            ComponentName c = new ComponentName(info.packageName, info.name);
432            if (c.equals(cn)) {
433                iter.remove();
434                return rinfo;
435            }
436        }
437        return null;
438    }
439
440    public void onPackagesUpdated() {
441        // TODO: this isn't ideal, but we actually need to delay here. This call is triggered
442        // by a broadcast receiver, and in order for it to work correctly, we need to know that
443        // the AppWidgetService has already received and processed the same broadcast. Since there
444        // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally,
445        // we should have a more precise way of ensuring the AppWidgetService is up to date.
446        postDelayed(new Runnable() {
447           public void run() {
448               updatePackages();
449           }
450        }, 500);
451    }
452
453    public void updatePackages() {
454        // Get the list of widgets and shortcuts
455        boolean wasEmpty = mWidgets.isEmpty();
456        mWidgets.clear();
457        List<AppWidgetProviderInfo> widgets =
458            AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
459        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
460        List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
461        for (AppWidgetProviderInfo widget : widgets) {
462            if (widget.minWidth > 0 && widget.minHeight > 0) {
463                mWidgets.add(widget);
464            } else {
465                Log.e(LOG_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
474        if (wasEmpty) {
475            // The next layout pass will trigger data-ready if both widgets and apps are set, so request
476            // a layout to do this test and invalidate the page data when ready.
477            if (testDataReady()) requestLayout();
478        } else {
479            cancelAllTasks();
480            invalidatePageData();
481        }
482    }
483
484    @Override
485    public void onClick(View v) {
486        // When we have exited all apps or are in transition, disregard clicks
487        if (!mLauncher.isAllAppsCustomizeOpen() ||
488                mLauncher.getWorkspace().isSwitchingState()) return;
489
490        if (v instanceof PagedViewIcon) {
491            // Animate some feedback to the click
492            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
493            animateClickFeedback(v, new Runnable() {
494                @Override
495                public void run() {
496                    mLauncher.startActivitySafely(appInfo.intent, appInfo);
497                }
498            });
499        } else if (v instanceof PagedViewWidget) {
500            // Let the user know that they have to long press to add a widget
501            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
502                    Toast.LENGTH_SHORT).show();
503
504            // Create a little animation to show that the widget can move
505            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
506            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
507            AnimatorSet bounce = new AnimatorSet();
508            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
509            tyuAnim.setDuration(125);
510            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
511            tydAnim.setDuration(100);
512            bounce.play(tyuAnim).before(tydAnim);
513            bounce.setInterpolator(new AccelerateInterpolator());
514            bounce.start();
515        }
516    }
517
518    /*
519     * PagedViewWithDraggableItems implementation
520     */
521    @Override
522    protected void determineDraggingStart(android.view.MotionEvent ev) {
523        // Disable dragging by pulling an app down for now.
524    }
525
526    private void beginDraggingApplication(View v) {
527        mLauncher.getWorkspace().onDragStartedWithItem(v);
528        mLauncher.getWorkspace().beginDragShared(v, this);
529    }
530
531    private void beginDraggingWidget(View v) {
532        // Get the widget preview as the drag representation
533        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
534        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
535
536        // Compose the drag image
537        Bitmap b;
538        if (createItemInfo instanceof PendingAddWidgetInfo) {
539            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
540            int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null);
541            createItemInfo.spanX = spanXY[0];
542            createItemInfo.spanY = spanXY[1];
543
544            int[] maxSize = mLauncher.getWorkspace().estimateItemSize(spanXY[0], spanXY[1],
545                    createWidgetInfo, true);
546            b = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
547                    createWidgetInfo.icon, spanXY[0], spanXY[1], maxSize[0], maxSize[1]);
548        } else {
549            // Workaround for the fact that we don't keep the original ResolveInfo associated with
550            // the shortcut around.  To get the icon, we just render the preview image (which has
551            // the shortcut icon) to a new drag bitmap that clips the non-icon space.
552            b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension,
553                    mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888);
554            Drawable preview = image.getDrawable();
555            mCanvas.setBitmap(b);
556            mCanvas.save();
557            preview.draw(mCanvas);
558            mCanvas.restore();
559            mCanvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
560            mCanvas.setBitmap(null);
561            createItemInfo.spanX = createItemInfo.spanY = 1;
562        }
563
564        // We use a custom alpha clip table for the default widget previews
565        Paint alphaClipPaint = null;
566        if (createItemInfo instanceof PendingAddWidgetInfo) {
567            if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) {
568                MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255);
569                alphaClipPaint = new Paint();
570                alphaClipPaint.setMaskFilter(alphaClipTable);
571            }
572        }
573
574        // Start the drag
575        mLauncher.lockScreenOrientationOnLargeUI();
576        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, b, alphaClipPaint);
577        mDragController.startDrag(image, b, this, createItemInfo,
578                DragController.DRAG_ACTION_COPY, null);
579        b.recycle();
580    }
581    @Override
582    protected boolean beginDragging(View v) {
583        // Dismiss the cling
584        mLauncher.dismissAllAppsCling(null);
585
586        if (!super.beginDragging(v)) return false;
587
588        // Go into spring loaded mode (must happen before we startDrag())
589        mLauncher.enterSpringLoadedDragMode();
590
591        if (v instanceof PagedViewIcon) {
592            beginDraggingApplication(v);
593        } else if (v instanceof PagedViewWidget) {
594            beginDraggingWidget(v);
595        }
596        return true;
597    }
598    private void endDragging(View target, boolean success) {
599        mLauncher.getWorkspace().onDragStopped(success);
600        if (!success || (target != mLauncher.getWorkspace() &&
601                !(target instanceof DeleteDropTarget))) {
602            // Exit spring loaded mode if we have not successfully dropped or have not handled the
603            // drop in Workspace
604            mLauncher.exitSpringLoadedDragMode();
605        }
606        mLauncher.unlockScreenOrientationOnLargeUI();
607
608    }
609
610    @Override
611    public void onDropCompleted(View target, DragObject d, boolean success) {
612        endDragging(target, success);
613
614        // Display an error message if the drag failed due to there not being enough space on the
615        // target layout we were dropping on.
616        if (!success) {
617            boolean showOutOfSpaceMessage = false;
618            if (target instanceof Workspace) {
619                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
620                Workspace workspace = (Workspace) target;
621                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
622                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
623                if (layout != null) {
624                    layout.calculateSpans(itemInfo);
625                    showOutOfSpaceMessage =
626                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
627                }
628            }
629            if (showOutOfSpaceMessage) {
630                mLauncher.showOutOfSpaceMessage();
631            }
632        }
633    }
634
635    @Override
636    protected void onDetachedFromWindow() {
637        super.onDetachedFromWindow();
638        cancelAllTasks();
639    }
640
641    private void cancelAllTasks() {
642        // Clean up all the async tasks
643        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
644        while (iter.hasNext()) {
645            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
646            task.cancel(false);
647            iter.remove();
648        }
649    }
650
651    public void setContentType(ContentType type) {
652        if (type == ContentType.Widgets) {
653            invalidatePageData(mNumAppsPages, true);
654        } else if (type == ContentType.Applications) {
655            invalidatePageData(0, true);
656        }
657    }
658
659    protected void snapToPage(int whichPage, int delta, int duration) {
660        super.snapToPage(whichPage, delta, duration);
661        updateCurrentTab(whichPage);
662    }
663
664    private void updateCurrentTab(int currentPage) {
665        AppsCustomizeTabHost tabHost = getTabHost();
666        String tag = tabHost.getCurrentTabTag();
667        if (tag != null) {
668            if (currentPage >= mNumAppsPages &&
669                    !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
670                tabHost.setCurrentTabFromContent(ContentType.Widgets);
671            } else if (currentPage < mNumAppsPages &&
672                    !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
673                tabHost.setCurrentTabFromContent(ContentType.Applications);
674            }
675        }
676    }
677
678    /*
679     * Apps PagedView implementation
680     */
681    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
682        int childCount = layout.getChildCount();
683        for (int i = 0; i < childCount; ++i) {
684            layout.getChildAt(i).setVisibility(visibility);
685        }
686    }
687    private void setupPage(PagedViewCellLayout layout) {
688        layout.setCellCount(mCellCountX, mCellCountY);
689        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
690        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
691                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
692
693        // Note: We force a measure here to get around the fact that when we do layout calculations
694        // immediately after syncing, we don't have a proper width.  That said, we already know the
695        // expected page width, so we can actually optimize by hiding all the TextView-based
696        // children that are expensive to measure, and let that happen naturally later.
697        setVisibilityOnChildren(layout, View.GONE);
698        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
699        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
700        layout.setMinimumWidth(getPageContentWidth());
701        layout.measure(widthSpec, heightSpec);
702        setVisibilityOnChildren(layout, View.VISIBLE);
703    }
704
705    public void syncAppsPageItems(int page, boolean immediate) {
706        // ensure that we have the right number of items on the pages
707        int numCells = mCellCountX * mCellCountY;
708        int startIndex = page * numCells;
709        int endIndex = Math.min(startIndex + numCells, mApps.size());
710        PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
711
712        layout.removeAllViewsOnPage();
713        ArrayList<Object> items = new ArrayList<Object>();
714        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
715        for (int i = startIndex; i < endIndex; ++i) {
716            ApplicationInfo info = mApps.get(i);
717            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
718                    R.layout.apps_customize_application, layout, false);
719            icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper);
720            icon.setOnClickListener(this);
721            icon.setOnLongClickListener(this);
722            icon.setOnTouchListener(this);
723
724            int index = i - startIndex;
725            int x = index % mCellCountX;
726            int y = index / mCellCountX;
727            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
728
729            items.add(info);
730            images.add(info.iconBitmap);
731        }
732
733        layout.createHardwareLayers();
734
735        /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
736        if (mFadeInAdjacentScreens) {
737            prepareGenerateHoloOutlinesTask(page, items, images);
738        }
739        */
740    }
741
742    /**
743     * Return the appropriate thread priority for loading for a given page (we give the current
744     * page much higher priority)
745     */
746    private int getThreadPriorityForPage(int page) {
747        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
748        int pageDiff = Math.abs(page - mCurrentPage);
749        if (pageDiff <= 0) {
750            // return Process.THREAD_PRIORITY_DEFAULT;
751            return Process.THREAD_PRIORITY_MORE_FAVORABLE;
752        } else if (pageDiff <= 1) {
753            // return Process.THREAD_PRIORITY_BACKGROUND;
754            return Process.THREAD_PRIORITY_DEFAULT;
755        } else {
756            // return Process.THREAD_PRIORITY_LOWEST;
757            return Process.THREAD_PRIORITY_DEFAULT;
758        }
759    }
760    private int getSleepForPage(int page) {
761        int pageDiff = Math.abs(page - mCurrentPage) - 1;
762        return Math.max(0, pageDiff * sPageSleepDelay);
763    }
764    /**
765     * Creates and executes a new AsyncTask to load a page of widget previews.
766     */
767    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
768            int cellWidth, int cellHeight, int cellCountX) {
769        // Prune all tasks that are no longer needed
770        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
771        while (iter.hasNext()) {
772            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
773            int taskPage = task.page;
774            if ((taskPage == page) ||
775                    taskPage < getAssociatedLowerPageBound(mCurrentPage - mNumAppsPages) ||
776                    taskPage > getAssociatedUpperPageBound(mCurrentPage - mNumAppsPages)) {
777                task.cancel(false);
778                iter.remove();
779            } else {
780                task.setThreadPriority(getThreadPriorityForPage(taskPage + mNumAppsPages));
781            }
782        }
783
784        // We introduce a slight delay to order the loading of side pages so that we don't thrash
785        final int sleepMs = getSleepForPage(page + mNumAppsPages);
786        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
787            new AsyncTaskCallback() {
788                @Override
789                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
790                    try {
791                        try {
792                            Thread.sleep(sleepMs);
793                        } catch (Exception e) {}
794                        loadWidgetPreviewsInBackground(task, data);
795                    } finally {
796                        if (task.isCancelled()) {
797                            data.cleanup(true);
798                        }
799                    }
800                }
801            },
802            new AsyncTaskCallback() {
803                @Override
804                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
805                    try {
806                        mRunningTasks.remove(task);
807                        if (task.isCancelled()) return;
808                        onSyncWidgetPageItems(data);
809                    } finally {
810                        data.cleanup(task.isCancelled());
811                    }
812                }
813            });
814
815        // Ensure that the task is appropriately prioritized and runs in parallel
816        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
817                AsyncTaskPageData.Type.LoadWidgetPreviewData);
818        t.setThreadPriority(getThreadPriorityForPage(page));
819        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
820        mRunningTasks.add(t);
821    }
822    /**
823     * Creates and executes a new AsyncTask to load the outlines for a page of content.
824     */
825    private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items,
826            ArrayList<Bitmap> images) {
827        // Prune old tasks for this page
828        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
829        while (iter.hasNext()) {
830            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
831            int taskPage = task.page;
832            if ((taskPage == page) &&
833                    (task.dataType == AsyncTaskPageData.Type.LoadHolographicIconsData)) {
834                task.cancel(false);
835                iter.remove();
836            }
837        }
838
839        AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images,
840            new AsyncTaskCallback() {
841                @Override
842                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
843                    try {
844                        // Ensure that this task starts running at the correct priority
845                        task.syncThreadPriority();
846
847                        ArrayList<Bitmap> images = data.generatedImages;
848                        ArrayList<Bitmap> srcImages = data.sourceImages;
849                        int count = srcImages.size();
850                        Canvas c = new Canvas();
851                        for (int i = 0; i < count && !task.isCancelled(); ++i) {
852                            // Before work on each item, ensure that this task is running at the correct
853                            // priority
854                            task.syncThreadPriority();
855
856                            Bitmap b = srcImages.get(i);
857                            Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(),
858                                    Bitmap.Config.ARGB_8888);
859
860                            c.setBitmap(outline);
861                            c.save();
862                            c.drawBitmap(b, 0, 0, null);
863                            c.restore();
864                            c.setBitmap(null);
865
866                            images.add(outline);
867                        }
868                    } finally {
869                        if (task.isCancelled()) {
870                            data.cleanup(true);
871                        }
872                    }
873                }
874            },
875            new AsyncTaskCallback() {
876                @Override
877                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
878                    try {
879                        mRunningTasks.remove(task);
880                        if (task.isCancelled()) return;
881                        onHolographicPageItemsLoaded(data);
882                    } finally {
883                        data.cleanup(task.isCancelled());
884                    }
885                }
886            });
887
888        // Ensure that the outline task always runs in the background, serially
889        AppsCustomizeAsyncTask t =
890            new AppsCustomizeAsyncTask(page, AsyncTaskPageData.Type.LoadHolographicIconsData);
891        t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
892        t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData);
893        mRunningTasks.add(t);
894    }
895
896    /*
897     * Widgets PagedView implementation
898     */
899    private void setupPage(PagedViewGridLayout layout) {
900        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
901                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
902
903        // Note: We force a measure here to get around the fact that when we do layout calculations
904        // immediately after syncing, we don't have a proper width.
905        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
906        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
907        layout.setMinimumWidth(getPageContentWidth());
908        layout.measure(widthSpec, heightSpec);
909    }
910
911    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
912        renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f, 0xFFFFFFFF);
913    }
914    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
915            float scale) {
916        renderDrawableToBitmap(d, bitmap, x, y, w, h, scale, 0xFFFFFFFF);
917    }
918    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
919            float scale, int multiplyColor) {
920        if (bitmap != null) {
921            Canvas c = new Canvas(bitmap);
922            c.scale(scale, scale);
923            Rect oldBounds = d.copyBounds();
924            d.setBounds(x, y, x + w, y + h);
925            d.draw(c);
926            d.setBounds(oldBounds); // Restore the bounds
927            if (multiplyColor != 0xFFFFFFFF) {
928                c.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
929            }
930            c.setBitmap(null);
931        }
932    }
933    private Bitmap getShortcutPreview(ResolveInfo info) {
934        // Render the background
935        int offset = 0;
936        int bitmapSize = mAppIconSize;
937        Bitmap preview = Bitmap.createBitmap(bitmapSize, bitmapSize, Config.ARGB_8888);
938
939        // Render the icon
940        Drawable icon = mIconCache.getFullResIcon(info);
941        renderDrawableToBitmap(icon, preview, offset, offset, mAppIconSize, mAppIconSize);
942        return preview;
943    }
944
945    private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId,
946            int cellHSpan, int cellVSpan, int maxWidth, int maxHeight) {
947        // Load the preview image if possible
948        String packageName = provider.getPackageName();
949        if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
950        if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
951
952        Drawable drawable = null;
953        if (previewImage != 0) {
954            drawable = mPackageManager.getDrawable(packageName, previewImage, null);
955            if (drawable == null) {
956                Log.w(LOG_TAG, "Can't load widget preview drawable 0x" +
957                        Integer.toHexString(previewImage) + " for provider: " + provider);
958            }
959        }
960
961        int bitmapWidth;
962        int bitmapHeight;
963        boolean widgetPreviewExists = (drawable != null);
964        if (widgetPreviewExists) {
965            bitmapWidth = drawable.getIntrinsicWidth();
966            bitmapHeight = drawable.getIntrinsicHeight();
967
968            // Cap the size so widget previews don't appear larger than the actual widget
969            maxWidth = Math.min(maxWidth, mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
970            maxHeight = Math.min(maxHeight, mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
971        } else {
972            // Determine the size of the bitmap for the preview image we will generate
973            // TODO: This actually uses the apps customize cell layout params, where as we make want
974            // the Workspace params for more accuracy.
975            bitmapWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
976            bitmapHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
977            if (cellHSpan == cellVSpan) {
978                // For square widgets, we just have a fixed size for 1x1 and larger-than-1x1
979                int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
980                if (cellHSpan <= 1) {
981                    bitmapWidth = bitmapHeight = mAppIconSize + 2 * minOffset;
982                } else {
983                    bitmapWidth = bitmapHeight = mAppIconSize + 4 * minOffset;
984                }
985            }
986        }
987
988        float scale = 1f;
989        if (bitmapWidth > maxWidth) {
990            scale = maxWidth / (float) bitmapWidth;
991        }
992        if (bitmapHeight * scale > maxHeight) {
993            scale = maxHeight / (float) bitmapHeight;
994        }
995        if (scale != 1f) {
996            bitmapWidth = (int) (scale * bitmapWidth);
997            bitmapHeight = (int) (scale * bitmapHeight);
998        }
999
1000        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888);
1001
1002        if (widgetPreviewExists) {
1003            renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, bitmapHeight);
1004        } else {
1005            // Generate a preview image if we couldn't load one
1006            int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
1007            int smallestSide = Math.min(bitmapWidth, bitmapHeight);
1008            float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), 1f);
1009            if (cellHSpan != 1 || cellVSpan != 1) {
1010                renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, bitmapWidth,
1011                        bitmapHeight);
1012            }
1013
1014            // Draw the icon in the top left corner
1015            try {
1016                Drawable icon = null;
1017                int hoffset = (int) (bitmapWidth / 2 - mAppIconSize * iconScale / 2);
1018                int yoffset = (int) (bitmapHeight / 2 - mAppIconSize * iconScale / 2);
1019                if (iconId > 0) icon = mIconCache.getFullResIcon(packageName, iconId);
1020                Resources resources = mLauncher.getResources();
1021                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
1022
1023                renderDrawableToBitmap(icon, preview, hoffset, yoffset,
1024                        (int) (mAppIconSize * iconScale),
1025                        (int) (mAppIconSize * iconScale));
1026            } catch (Resources.NotFoundException e) {}
1027        }
1028        return preview;
1029    }
1030
1031    public void syncWidgetPageItems(final int page, final boolean immediate) {
1032        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1033
1034        // Calculate the dimensions of each cell we are giving to each widget
1035        final ArrayList<Object> items = new ArrayList<Object>();
1036        int contentWidth = mWidgetSpacingLayout.getContentWidth();
1037        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
1038                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
1039        int contentHeight = mWidgetSpacingLayout.getContentHeight();
1040        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
1041                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
1042
1043        // Prepare the set of widgets to load previews for in the background
1044        int offset = page * numItemsPerPage;
1045        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1046            items.add(mWidgets.get(i));
1047        }
1048
1049        // Prepopulate the pages with the other widget info, and fill in the previews later
1050        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
1051        layout.setColumnCount(layout.getCellCountX());
1052        for (int i = 0; i < items.size(); ++i) {
1053            Object rawInfo = items.get(i);
1054            PendingAddItemInfo createItemInfo = null;
1055            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1056                    R.layout.apps_customize_widget, layout, false);
1057            if (rawInfo instanceof AppWidgetProviderInfo) {
1058                // Fill in the widget information
1059                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1060                createItemInfo = new PendingAddWidgetInfo(info, null, null);
1061                int[] cellSpans = mLauncher.getSpanForWidget(info, null);
1062                widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans,
1063                        mHolographicOutlineHelper);
1064                widget.setTag(createItemInfo);
1065            } else if (rawInfo instanceof ResolveInfo) {
1066                // Fill in the shortcuts information
1067                ResolveInfo info = (ResolveInfo) rawInfo;
1068                createItemInfo = new PendingAddItemInfo();
1069                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1070                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1071                        info.activityInfo.name);
1072                widget.applyFromResolveInfo(mPackageManager, info, mHolographicOutlineHelper);
1073                widget.setTag(createItemInfo);
1074            }
1075            widget.setOnClickListener(this);
1076            widget.setOnLongClickListener(this);
1077            widget.setOnTouchListener(this);
1078
1079            // Layout each widget
1080            int ix = i % mWidgetCountX;
1081            int iy = i / mWidgetCountX;
1082            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1083                    GridLayout.spec(iy, GridLayout.LEFT),
1084                    GridLayout.spec(ix, GridLayout.TOP));
1085            lp.width = cellWidth;
1086            lp.height = cellHeight;
1087            lp.setGravity(Gravity.TOP | Gravity.LEFT);
1088            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
1089            if (iy > 0) lp.topMargin = mWidgetHeightGap;
1090            layout.addView(widget, lp);
1091        }
1092
1093        // wait until a call on onLayout to start loading, because
1094        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1095        // TODO: can we do a measure/layout immediately?
1096        layout.setOnLayoutListener(new Runnable() {
1097            public void run() {
1098                // Load the widget previews
1099                int maxPreviewWidth = cellWidth;
1100                int maxPreviewHeight = cellHeight;
1101                if (layout.getChildCount() > 0) {
1102                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1103                    int[] maxSize = w.getPreviewSize();
1104                    maxPreviewWidth = maxSize[0];
1105                    maxPreviewHeight = maxSize[1];
1106                }
1107                if (immediate) {
1108                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1109                            maxPreviewWidth, maxPreviewHeight, null, null);
1110                    loadWidgetPreviewsInBackground(null, data);
1111                    onSyncWidgetPageItems(data);
1112                } else {
1113                    prepareLoadWidgetPreviewsTask(page, items,
1114                            maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1115                }
1116            }
1117        });
1118    }
1119    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1120            AsyncTaskPageData data) {
1121        if (task != null) {
1122            // Ensure that this task starts running at the correct priority
1123            task.syncThreadPriority();
1124        }
1125
1126        // Load each of the widget/shortcut previews
1127        ArrayList<Object> items = data.items;
1128        ArrayList<Bitmap> images = data.generatedImages;
1129        int count = items.size();
1130        for (int i = 0; i < count; ++i) {
1131            if (task != null) {
1132                // Ensure we haven't been cancelled yet
1133                if (task.isCancelled()) break;
1134                // Before work on each item, ensure that this task is running at the correct
1135                // priority
1136                task.syncThreadPriority();
1137            }
1138
1139            Object rawInfo = items.get(i);
1140            if (rawInfo instanceof AppWidgetProviderInfo) {
1141                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1142                int[] cellSpans = mLauncher.getSpanForWidget(info, null);
1143                Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
1144                        cellSpans[0], cellSpans[1], data.maxImageWidth, data.maxImageHeight);
1145                images.add(b);
1146            } else if (rawInfo instanceof ResolveInfo) {
1147                // Fill in the shortcuts information
1148                ResolveInfo info = (ResolveInfo) rawInfo;
1149                images.add(getShortcutPreview(info));
1150            }
1151        }
1152    }
1153    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
1154        int page = data.page;
1155        PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
1156
1157        ArrayList<Object> items = data.items;
1158        int count = items.size();
1159        for (int i = 0; i < count; ++i) {
1160            PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1161            if (widget != null) {
1162                Bitmap preview = data.generatedImages.get(i);
1163                widget.applyPreview(new FastBitmapDrawable(preview), i);
1164            }
1165        }
1166        layout.createHardwareLayer();
1167
1168        invalidate();
1169        /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
1170        if (mFadeInAdjacentScreens) {
1171            prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages);
1172        }
1173        */
1174    }
1175    private void onHolographicPageItemsLoaded(AsyncTaskPageData data) {
1176        // Invalidate early to short-circuit children invalidates
1177        invalidate();
1178
1179        int page = data.page;
1180        ViewGroup layout = (ViewGroup) getPageAt(page);
1181        if (layout instanceof PagedViewCellLayout) {
1182            PagedViewCellLayout cl = (PagedViewCellLayout) layout;
1183            int count = cl.getPageChildCount();
1184            if (count != data.generatedImages.size()) return;
1185            for (int i = 0; i < count; ++i) {
1186                PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i);
1187                icon.setHolographicOutline(data.generatedImages.get(i));
1188            }
1189        } else {
1190            int count = layout.getChildCount();
1191            if (count != data.generatedImages.size()) return;
1192            for (int i = 0; i < count; ++i) {
1193                View v = layout.getChildAt(i);
1194                ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i));
1195            }
1196        }
1197    }
1198
1199    @Override
1200    public void syncPages() {
1201        removeAllViews();
1202        cancelAllTasks();
1203
1204        Context context = getContext();
1205        for (int j = 0; j < mNumWidgetPages; ++j) {
1206            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1207                    mWidgetCountY);
1208            setupPage(layout);
1209            addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT,
1210                    LayoutParams.MATCH_PARENT));
1211        }
1212
1213        for (int i = 0; i < mNumAppsPages; ++i) {
1214            PagedViewCellLayout layout = new PagedViewCellLayout(context);
1215            setupPage(layout);
1216            addView(layout);
1217        }
1218    }
1219
1220    @Override
1221    public void syncPageItems(int page, boolean immediate) {
1222        if (page < mNumAppsPages) {
1223            syncAppsPageItems(page, immediate);
1224        } else {
1225            syncWidgetPageItems(page - mNumAppsPages, immediate);
1226        }
1227    }
1228
1229    // We want our pages to be z-ordered such that the further a page is to the left, the higher
1230    // it is in the z-order. This is important to insure touch events are handled correctly.
1231    View getPageAt(int index) {
1232        return getChildAt(getChildCount() - index - 1);
1233    }
1234
1235    @Override
1236    protected int indexToPage(int index) {
1237        return getChildCount() - index - 1;
1238    }
1239
1240    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1241    @Override
1242    protected void screenScrolled(int screenCenter) {
1243        super.screenScrolled(screenCenter);
1244
1245        for (int i = 0; i < getChildCount(); i++) {
1246            View v = getPageAt(i);
1247            if (v != null) {
1248                float scrollProgress = getScrollProgress(screenCenter, v, i);
1249
1250                float interpolatedProgress =
1251                        mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
1252                float scale = (1 - interpolatedProgress) +
1253                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
1254                float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
1255
1256                float alpha;
1257
1258                if (!LauncherApplication.isScreenLarge() || scrollProgress < 0) {
1259                    alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1260                        1 - Math.abs(scrollProgress)) : 1.0f;
1261                } else {
1262                    // On large screens we need to fade the page as it nears its leftmost position
1263                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
1264                }
1265
1266                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
1267                int pageWidth = v.getMeasuredWidth();
1268                int pageHeight = v.getMeasuredHeight();
1269
1270                if (PERFORM_OVERSCROLL_ROTATION) {
1271                    if (i == 0 && scrollProgress < 0) {
1272                        // Overscroll to the left
1273                        v.setPivotX(TRANSITION_PIVOT * pageWidth);
1274                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1275                        scale = 1.0f;
1276                        alpha = 1.0f;
1277                        // On the first page, we don't want the page to have any lateral motion
1278                        translationX = 0;
1279                    } else if (i == getChildCount() - 1 && scrollProgress > 0) {
1280                        // Overscroll to the right
1281                        v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
1282                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
1283                        scale = 1.0f;
1284                        alpha = 1.0f;
1285                        // On the last page, we don't want the page to have any lateral motion.
1286                        translationX = 0;
1287                    } else {
1288                        v.setPivotY(pageHeight / 2.0f);
1289                        v.setPivotX(pageWidth / 2.0f);
1290                        v.setRotationY(0f);
1291                    }
1292                }
1293
1294                v.setTranslationX(translationX);
1295                v.setScaleX(scale);
1296                v.setScaleY(scale);
1297                v.setAlpha(alpha);
1298
1299                // If the view has 0 alpha, we set it to be invisible so as to prevent
1300                // it from accepting touches
1301                if (alpha < ViewConfiguration.ALPHA_THRESHOLD) {
1302                    v.setVisibility(INVISIBLE);
1303                } else if (v.getVisibility() != VISIBLE) {
1304                    v.setVisibility(VISIBLE);
1305                }
1306            }
1307        }
1308    }
1309
1310    protected void overScroll(float amount) {
1311        acceleratedOverScroll(amount);
1312    }
1313
1314    /**
1315     * Used by the parent to get the content width to set the tab bar to
1316     * @return
1317     */
1318    public int getPageContentWidth() {
1319        return mContentWidth;
1320    }
1321
1322    @Override
1323    protected void onPageEndMoving() {
1324        super.onPageEndMoving();
1325
1326        // We reset the save index when we change pages so that it will be recalculated on next
1327        // rotation
1328        mSaveInstanceStateItemIndex = -1;
1329    }
1330
1331    /*
1332     * AllAppsView implementation
1333     */
1334    @Override
1335    public void setup(Launcher launcher, DragController dragController) {
1336        mLauncher = launcher;
1337        mDragController = dragController;
1338    }
1339    @Override
1340    public void zoom(float zoom, boolean animate) {
1341        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
1342    }
1343    @Override
1344    public boolean isVisible() {
1345        return (getVisibility() == VISIBLE);
1346    }
1347    @Override
1348    public boolean isAnimating() {
1349        return false;
1350    }
1351    @Override
1352    public void setApps(ArrayList<ApplicationInfo> list) {
1353        mApps = list;
1354        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
1355        updatePageCounts();
1356
1357        // The next layout pass will trigger data-ready if both widgets and apps are set, so
1358        // request a layout to do this test and invalidate the page data when ready.
1359        if (testDataReady()) requestLayout();
1360    }
1361    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1362        // We add it in place, in alphabetical order
1363        int count = list.size();
1364        for (int i = 0; i < count; ++i) {
1365            ApplicationInfo info = list.get(i);
1366            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
1367            if (index < 0) {
1368                mApps.add(-(index + 1), info);
1369            }
1370        }
1371    }
1372    @Override
1373    public void addApps(ArrayList<ApplicationInfo> list) {
1374        addAppsWithoutInvalidate(list);
1375        updatePageCounts();
1376        invalidatePageData();
1377    }
1378    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1379        ComponentName removeComponent = item.intent.getComponent();
1380        int length = list.size();
1381        for (int i = 0; i < length; ++i) {
1382            ApplicationInfo info = list.get(i);
1383            if (info.intent.getComponent().equals(removeComponent)) {
1384                return i;
1385            }
1386        }
1387        return -1;
1388    }
1389    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1390        // loop through all the apps and remove apps that have the same component
1391        int length = list.size();
1392        for (int i = 0; i < length; ++i) {
1393            ApplicationInfo info = list.get(i);
1394            int removeIndex = findAppByComponent(mApps, info);
1395            if (removeIndex > -1) {
1396                mApps.remove(removeIndex);
1397            }
1398        }
1399    }
1400    @Override
1401    public void removeApps(ArrayList<ApplicationInfo> list) {
1402        removeAppsWithoutInvalidate(list);
1403        updatePageCounts();
1404        invalidatePageData();
1405    }
1406    @Override
1407    public void updateApps(ArrayList<ApplicationInfo> list) {
1408        // We remove and re-add the updated applications list because it's properties may have
1409        // changed (ie. the title), and this will ensure that the items will be in their proper
1410        // place in the list.
1411        removeAppsWithoutInvalidate(list);
1412        addAppsWithoutInvalidate(list);
1413        updatePageCounts();
1414
1415        invalidatePageData();
1416    }
1417
1418    @Override
1419    public void reset() {
1420        AppsCustomizeTabHost tabHost = getTabHost();
1421        String tag = tabHost.getCurrentTabTag();
1422        if (tag != null) {
1423            if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
1424                tabHost.setCurrentTabFromContent(ContentType.Applications);
1425            }
1426        }
1427        if (mCurrentPage != 0) {
1428            invalidatePageData(0);
1429        }
1430    }
1431
1432    private AppsCustomizeTabHost getTabHost() {
1433        return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
1434    }
1435
1436    @Override
1437    public void dumpState() {
1438        // TODO: Dump information related to current list of Applications, Widgets, etc.
1439        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
1440        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
1441    }
1442
1443    private void dumpAppWidgetProviderInfoList(String tag, String label,
1444            ArrayList<Object> list) {
1445        Log.d(tag, label + " size=" + list.size());
1446        for (Object i: list) {
1447            if (i instanceof AppWidgetProviderInfo) {
1448                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1449                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1450                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1451                        + " initialLayout=" + info.initialLayout
1452                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1453            } else if (i instanceof ResolveInfo) {
1454                ResolveInfo info = (ResolveInfo) i;
1455                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1456                        + info.icon);
1457            }
1458        }
1459    }
1460
1461    @Override
1462    public void surrender() {
1463        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1464        // should stop this now.
1465
1466        // Stop all background tasks
1467        cancelAllTasks();
1468    }
1469
1470    /*
1471     * We load an extra page on each side to prevent flashes from scrolling and loading of the
1472     * widget previews in the background with the AsyncTasks.
1473     */
1474    protected int getAssociatedLowerPageBound(int page) {
1475        return Math.max(0, page - 2);
1476    }
1477    protected int getAssociatedUpperPageBound(int page) {
1478        final int count = getChildCount();
1479        return Math.min(page + 2, count - 1);
1480    }
1481
1482    @Override
1483    protected String getCurrentPageDescription() {
1484        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
1485        int stringId = R.string.default_scroll_format;
1486        int count = 0;
1487
1488        if (page < mNumAppsPages) {
1489            stringId = R.string.apps_customize_apps_scroll_format;
1490            count = mNumAppsPages;
1491        } else {
1492            page -= mNumAppsPages;
1493            stringId = R.string.apps_customize_widgets_scroll_format;
1494            count = mNumWidgetPages;
1495        }
1496
1497        return String.format(mContext.getString(stringId), page + 1, count);
1498    }
1499}
1500