AppsCustomizePagedView.java revision 4b576be59e58072cc03b5a8d36afb6a322513575
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 java.util.ArrayList;
20import java.util.Collections;
21import java.util.List;
22
23import android.animation.Animator;
24import android.animation.AnimatorListenerAdapter;
25import android.animation.ObjectAnimator;
26import android.animation.PropertyValuesHolder;
27import android.appwidget.AppWidgetManager;
28import android.appwidget.AppWidgetProviderInfo;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
34import android.content.res.Resources;
35import android.content.res.TypedArray;
36import android.graphics.Bitmap;
37import android.graphics.Bitmap.Config;
38import android.graphics.Canvas;
39import android.graphics.Rect;
40import android.graphics.RectF;
41import android.graphics.drawable.Drawable;
42import android.util.AttributeSet;
43import android.util.Log;
44import android.view.Gravity;
45import android.view.LayoutInflater;
46import android.view.View;
47import android.view.animation.DecelerateInterpolator;
48import android.view.animation.LinearInterpolator;
49import android.widget.FrameLayout;
50import android.widget.ImageView;
51import android.widget.LinearLayout;
52import android.widget.TextView;
53
54import com.android.launcher.R;
55
56public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
57        AllAppsView, View.OnClickListener, DragSource {
58    static final String LOG_TAG = "AppsCustomizePagedView";
59
60    /**
61     * The different content types that this paged view can show.
62     */
63    public enum ContentType {
64        Applications,
65        Widgets
66    }
67
68    // Refs
69    private Launcher mLauncher;
70    private DragController mDragController;
71    private final LayoutInflater mLayoutInflater;
72    private final PackageManager mPackageManager;
73
74    // Content
75    private ContentType mContentType;
76    private ArrayList<ApplicationInfo> mApps;
77    private List<AppWidgetProviderInfo> mWidgets;
78    private List<ResolveInfo> mShortcuts;
79
80    // Dimens
81    private int mContentWidth;
82    private int mMaxWidgetSpan, mMinWidgetSpan;
83    private int mWidgetCellWidthGap, mWidgetCellHeightGap;
84    private int mWidgetCountX, mWidgetCountY;
85    private PagedViewCellLayout mWidgetSpacingLayout;
86
87    // Animations
88    private final float ANIMATION_SCALE = 0.5f;
89    private final int TRANSLATE_ANIM_DURATION = 400;
90    private final int DROP_ANIM_DURATION = 200;
91
92    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
93        super(context, attrs);
94        mLayoutInflater = LayoutInflater.from(context);
95        mPackageManager = context.getPackageManager();
96        mContentType = ContentType.Applications;
97        mApps = new ArrayList<ApplicationInfo>();
98        mWidgets = new ArrayList<AppWidgetProviderInfo>();
99        mShortcuts = new ArrayList<ResolveInfo>();
100
101        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0);
102        mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6);
103        mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4);
104        a.recycle();
105        a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
106        mWidgetCellWidthGap =
107            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 10);
108        mWidgetCellHeightGap =
109            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 10);
110        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
111        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
112        a.recycle();
113
114        // Create a dummy page that we can use to approximate the cell dimensions of widgets and
115        // the content width (to be used by our parent)
116        mWidgetSpacingLayout = new PagedViewCellLayout(context);
117        setupPage(mWidgetSpacingLayout);
118        mContentWidth = mWidgetSpacingLayout.getContentWidth();
119
120        // The max widget span is the length N, such that NxN is the largest bounds that the widget
121        // preview can be before applying the widget scaling
122        mMinWidgetSpan = 1;
123        mMaxWidgetSpan = 3;
124    }
125
126    @Override
127    protected void init() {
128        super.init();
129        mCenterPagesVertically = false;
130
131        Context context = getContext();
132        Resources r = context.getResources();
133        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
134    }
135
136    public void onPackagesUpdated() {
137        // Get the list of widgets
138        mWidgets = AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
139        Collections.sort(mWidgets, LauncherModel.WIDGET_NAME_COMPARATOR);
140
141        // Get the list of shortcuts
142        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
143        mShortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
144        Collections.sort(mShortcuts, new LauncherModel.ShortcutNameComparator(mPackageManager));
145    }
146
147    /**
148     * Animates the given item onto the center of a home screen, and then scales the item to
149     * look as though it's disappearing onto that screen.
150     */
151    private void animateItemOntoScreen(View dragView,
152            final CellLayout layout, final ItemInfo info) {
153        // On the phone, we only want to fade the widget preview out
154        float[] position = new float[2];
155        position[0] = layout.getWidth() / 2;
156        position[1] = layout.getHeight() / 2;
157
158        mLauncher.getWorkspace().mapPointFromChildToSelf(layout, position);
159
160        int dragViewWidth = dragView.getMeasuredWidth();
161        int dragViewHeight = dragView.getMeasuredHeight();
162        float heightOffset = 0;
163        float widthOffset = 0;
164
165        if (dragView instanceof ImageView) {
166            Drawable d = ((ImageView) dragView).getDrawable();
167            int width = d.getIntrinsicWidth();
168            int height = d.getIntrinsicHeight();
169
170            if ((1.0 * width / height) >= (1.0f * dragViewWidth) / dragViewHeight) {
171                float f = (dragViewWidth / (width * 1.0f));
172                heightOffset = ANIMATION_SCALE * (dragViewHeight - f * height) / 2;
173            } else {
174                float f = (dragViewHeight / (height * 1.0f));
175                widthOffset = ANIMATION_SCALE * (dragViewWidth - f * width) / 2;
176            }
177        }
178        final float toX = position[0] - dragView.getMeasuredWidth() / 2 + widthOffset;
179        final float toY = position[1] - dragView.getMeasuredHeight() / 2 + heightOffset;
180
181        final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
182        final View dragCopy = dragLayer.createDragView(dragView);
183        dragCopy.setAlpha(1.0f);
184
185        // Translate the item to the center of the appropriate home screen
186        animateIntoPosition(dragCopy, toX, toY, null);
187
188        // The drop-onto-screen animation begins a bit later, but ends at the same time.
189        final int startDelay = TRANSLATE_ANIM_DURATION - DROP_ANIM_DURATION;
190
191        // Scale down the icon and fade out the alpha
192        animateDropOntoScreen(dragCopy, info, DROP_ANIM_DURATION, startDelay);
193    }
194
195    /**
196     * Animation which scales the view down and animates its alpha, making it appear to disappear
197     * onto a home screen.
198     */
199    private void animateDropOntoScreen(
200            final View view, final ItemInfo info, int duration, int delay) {
201        final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
202        final CellLayout layout = mLauncher.getWorkspace().getCurrentDropLayout();
203
204        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
205                PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f),
206                PropertyValuesHolder.ofFloat("scaleX", ANIMATION_SCALE),
207                PropertyValuesHolder.ofFloat("scaleY", ANIMATION_SCALE));
208        anim.setInterpolator(new LinearInterpolator());
209        if (delay > 0) {
210            anim.setStartDelay(delay);
211        }
212        anim.setDuration(duration);
213        anim.addListener(new AnimatorListenerAdapter() {
214            public void onAnimationEnd(Animator animation) {
215                dragLayer.removeView(view);
216                mLauncher.addExternalItemToScreen(info, layout);
217                info.dropPos = null;
218            }
219        });
220        anim.start();
221    }
222
223    /**
224     * Animates the x,y position of the view, and optionally execute a Runnable on animation end.
225     */
226    private void animateIntoPosition(
227            View view, float toX, float toY, final Runnable endRunnable) {
228        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
229                PropertyValuesHolder.ofFloat("x", toX),
230                PropertyValuesHolder.ofFloat("y", toY));
231        anim.setInterpolator(new DecelerateInterpolator(2.5f));
232        anim.setDuration(TRANSLATE_ANIM_DURATION);
233        if (endRunnable != null) {
234            anim.addListener(new AnimatorListenerAdapter() {
235                @Override
236                public void onAnimationEnd(Animator animation) {
237                    endRunnable.run();
238                }
239            });
240        }
241        anim.start();
242    }
243
244    @Override
245    public void onClick(View v) {
246        if (v instanceof PagedViewIcon) {
247            // Animate some feedback to the click
248            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
249            animateClickFeedback(v, new Runnable() {
250                @Override
251                public void run() {
252                    mLauncher.startActivitySafely(appInfo.intent, appInfo);
253                }
254            });
255        } else if (v instanceof PagedViewWidget) {
256            Workspace w = mLauncher.getWorkspace();
257            int currentWorkspaceScreen = mLauncher.getCurrentWorkspaceScreen();
258            final CellLayout cl = (CellLayout) w.getChildAt(currentWorkspaceScreen);
259            final View dragView = v.findViewById(R.id.widget_preview);
260            final ItemInfo itemInfo = (ItemInfo) v.getTag();
261            animateClickFeedback(v, new Runnable() {
262                @Override
263                public void run() {
264                    cl.calculateSpans(itemInfo);
265                    if (cl.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY)) {
266                        if (LauncherApplication.isScreenXLarge()) {
267                            animateItemOntoScreen(dragView, cl, itemInfo);
268                        } else {
269                            mLauncher.addExternalItemToScreen(itemInfo, cl);
270                            itemInfo.dropPos = null;
271                        }
272
273                        // Hide the pane so we can see the workspace we dropped on
274                        mLauncher.showWorkspace(true);
275                    } else {
276                        mLauncher.showOutOfSpaceMessage();
277                    }
278                }
279            });
280        }
281    }
282
283    /*
284     * PagedViewWithDraggableItems implementation
285     */
286    @Override
287    protected void determineDraggingStart(android.view.MotionEvent ev) {
288        // Disable dragging by pulling an app down for now.
289    }
290    private void beginDraggingApplication(View v) {
291        // Make a copy of the ApplicationInfo
292        ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag());
293
294        // Show the uninstall button if the app is uninstallable.
295        if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0) {
296            DeleteZone allAppsDeleteZone = (DeleteZone)
297                    mLauncher.findViewById(R.id.all_apps_delete_zone);
298            allAppsDeleteZone.setDragAndDropEnabled(true);
299
300            if ((appInfo.flags & ApplicationInfo.UPDATED_SYSTEM_APP_FLAG) != 0) {
301                allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps_system_app);
302            } else {
303                allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps);
304            }
305        }
306
307        // Show the info button
308        ApplicationInfoDropTarget allAppsInfoButton =
309            (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
310        allAppsInfoButton.setDragAndDropEnabled(true);
311
312        // Compose the drag image (top compound drawable, index is 1)
313        final TextView tv = (TextView) v;
314        final Drawable icon = tv.getCompoundDrawables()[1];
315        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
316                Bitmap.Config.ARGB_8888);
317        Canvas c = new Canvas(b);
318        c.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop());
319        icon.draw(c);
320
321        // Compose the visible rect of the drag image
322        Rect dragRect = null;
323        if (v instanceof TextView) {
324            int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size);
325            int top = v.getPaddingTop();
326            int left = (b.getWidth() - iconSize) / 2;
327            int right = left + iconSize;
328            int bottom = top + iconSize;
329            dragRect = new Rect(left, top, right, bottom);
330        }
331
332        // Start the drag
333        mLauncher.lockScreenOrientation();
334        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
335        mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect);
336        b.recycle();
337    }
338    private void beginDraggingWidget(View v) {
339        // Get the widget preview as the drag representation
340        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
341        PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag();
342
343        // Compose the drag image
344        Drawable preview = image.getDrawable();
345        int w = preview.getIntrinsicWidth();
346        int h = preview.getIntrinsicHeight();
347        Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
348        renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1);
349
350        // Start the drag
351        int[] spanXY = CellLayout.rectToCell(getResources(),
352                createWidgetInfo.minWidth, createWidgetInfo.minHeight, null);
353        createWidgetInfo.spanX = spanXY[0];
354        createWidgetInfo.spanY = spanXY[1];
355        mLauncher.lockScreenOrientation();
356        mLauncher.getWorkspace().onDragStartedWithItemSpans(spanXY[0], spanXY[1], b);
357        mDragController.startDrag(image, b, this, createWidgetInfo,
358                DragController.DRAG_ACTION_COPY, null);
359        b.recycle();
360    }
361    @Override
362    protected boolean beginDragging(View v) {
363        if (!super.beginDragging(v)) return false;
364
365        if (v instanceof PagedViewIcon) {
366            beginDraggingApplication(v);
367        } else if (v instanceof PagedViewWidget) {
368            beginDraggingWidget(v);
369        }
370
371        // Hide the pane so that the user can drop onto the workspace
372        mLauncher.showWorkspace(true);
373        return true;
374    }
375    private void endDragging(boolean success) {
376        post(new Runnable() {
377            // Once the drag operation has fully completed, hence the post, we want to disable the
378            // deleteZone and the appInfoButton in all apps, and re-enable the instance which
379            // live in the workspace
380            public void run() {
381                // if onDestroy was called on Launcher, we might have already deleted the
382                // all apps delete zone / info button, so check if they are null
383                DeleteZone allAppsDeleteZone =
384                        (DeleteZone) mLauncher.findViewById(R.id.all_apps_delete_zone);
385                ApplicationInfoDropTarget allAppsInfoButton =
386                    (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
387
388                if (allAppsDeleteZone != null) allAppsDeleteZone.setDragAndDropEnabled(false);
389                if (allAppsInfoButton != null) allAppsInfoButton.setDragAndDropEnabled(false);
390            }
391        });
392        mLauncher.getWorkspace().onDragStopped(success);
393        mLauncher.unlockScreenOrientation();
394    }
395
396    /*
397     * DragSource implementation
398     */
399    @Override
400    public void onDragViewVisible() {}
401    @Override
402    public void onDropCompleted(View target, Object dragInfo, boolean success) {
403        endDragging(success);
404    }
405
406    public void setContentType(ContentType type) {
407        mContentType = type;
408        setCurrentPage(0);
409        invalidatePageData();
410    }
411
412    /*
413     * Apps PagedView implementation
414     */
415    private void setupPage(PagedViewCellLayout layout) {
416        layout.setCellCount(mCellCountX, mCellCountY);
417        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
418        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
419                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
420
421        // We force a measure here to get around the fact that when we do layout calculations
422        // immediately after syncing, we don't have a proper width.
423        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
424        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
425        layout.measure(widthSpec, heightSpec);
426    }
427    public void syncAppsPages() {
428        // Ensure that we have the right number of pages
429        Context context = getContext();
430        int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
431        for (int i = 0; i < numPages; ++i) {
432            PagedViewCellLayout layout = new PagedViewCellLayout(context);
433            setupPage(layout);
434            addView(layout);
435        }
436    }
437    public void syncAppsPageItems(int page) {
438        // ensure that we have the right number of items on the pages
439        int numPages = getPageCount();
440        int numCells = mCellCountX * mCellCountY;
441        int startIndex = page * numCells;
442        int endIndex = Math.min(startIndex + numCells, mApps.size());
443        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
444        layout.removeAllViewsOnPage();
445        for (int i = startIndex; i < endIndex; ++i) {
446            ApplicationInfo info = mApps.get(i);
447            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
448                    R.layout.apps_customize_application, layout, false);
449            icon.applyFromApplicationInfo(info, mPageViewIconCache, true, (numPages > 1));
450            icon.setOnClickListener(this);
451            icon.setOnLongClickListener(this);
452            icon.setOnTouchListener(this);
453
454            int index = i - startIndex;
455            int x = index % mCellCountX;
456            int y = index / mCellCountX;
457            setupPage(layout);
458            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
459        }
460    }
461    /*
462     * Widgets PagedView implementation
463     */
464    private void setupPage(PagedViewExtendedLayout layout) {
465        layout.setGravity(Gravity.LEFT);
466        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
467                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
468        layout.setMinimumWidth(getPageContentWidth());
469    }
470    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
471            float scaleX, float scaleY) {
472        Canvas c = new Canvas();
473        if (bitmap != null) c.setBitmap(bitmap);
474        c.save();
475        c.scale(scaleX, scaleY);
476        Rect oldBounds = d.copyBounds();
477        d.setBounds(x, y, x + w, y + h);
478        d.draw(c);
479        d.setBounds(oldBounds); // Restore the bounds
480        c.restore();
481    }
482    private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan,
483            int cellVSpan, int cellWidth, int cellHeight) {
484        // Calculate the size of the drawable
485        cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan));
486        cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan));
487        int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
488        int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
489
490        // Scale down the bitmap to fit the space
491        float widgetPreviewScale = (float) cellWidth / expectedWidth;
492        expectedWidth = (int) (widgetPreviewScale * expectedWidth);
493        expectedHeight = (int) (widgetPreviewScale * expectedHeight);
494
495        // Load the preview image if possible
496        String packageName = info.provider.getPackageName();
497        Drawable drawable = null;
498        FastBitmapDrawable newDrawable = null;
499        if (info.previewImage != 0) {
500            drawable = mPackageManager.getDrawable(packageName, info.previewImage, null);
501            if (drawable == null) {
502                Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
503                        + " for provider: " + info.provider);
504            } else {
505                // Scale down the preview to the dimensions we want
506                int imageWidth = drawable.getIntrinsicWidth();
507                int imageHeight = drawable.getIntrinsicHeight();
508                float aspect = (float) imageWidth / imageHeight;
509                int newWidth = imageWidth;
510                int newHeight = imageHeight;
511                if (aspect > 1f) {
512                    newWidth = expectedWidth;
513                    newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth));
514                } else {
515                    newHeight = expectedHeight;
516                    newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight));
517                }
518
519                Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
520                renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f);
521                newDrawable = new FastBitmapDrawable(preview);
522                newDrawable.setBounds(0, 0, newWidth, newHeight);
523            }
524        }
525
526        // Generate a preview image if we couldn't load one
527        if (drawable == null) {
528            // The icon itself takes up space, so update expected width/height to have min of 2
529            cellHSpan = Math.max(2, cellHSpan);
530            cellVSpan = Math.max(2, cellVSpan);
531            expectedWidth = (int) (widgetPreviewScale
532                    * mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
533            expectedHeight = (int) (widgetPreviewScale
534                    * mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
535
536            Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
537            Resources resources = mLauncher.getResources();
538            Drawable background = resources.getDrawable(R.drawable.default_widget_preview);
539            renderDrawableToBitmap(background, preview, 0, 0, expectedWidth, expectedHeight, 1f,1f);
540
541            // Draw the icon in the top left corner
542            try {
543                Drawable icon = null;
544                if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null);
545                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
546
547                int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
548                int offset = iconSize / 4;
549                renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
550            } catch (Resources.NotFoundException e) {}
551
552            newDrawable = new FastBitmapDrawable(preview);
553            newDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
554        }
555        return newDrawable;
556    }
557    public void syncWidgetPages() {
558        // Ensure that we have the right number of pages
559        Context context = getContext();
560        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
561        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
562        for (int i = 0; i < numPages; ++i) {
563            PagedViewExtendedLayout layout = new PagedViewExtendedLayout(context);
564            setupPage(layout);
565            addView(layout);
566        }
567    }
568    public void syncWidgetPageItems(int page) {
569        Context context = getContext();
570        PagedViewExtendedLayout layout = (PagedViewExtendedLayout) getChildAt(page);
571        layout.removeAllViews();
572
573        // Calculate the dimensions of each cell we are giving to each widget
574        FrameLayout container = new FrameLayout(context);
575        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
576        int offset = page * numWidgetsPerPage;
577        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
578                - ((mWidgetCountX - 1) * mWidgetCellWidthGap)) / mWidgetCountX);
579        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
580                - ((mWidgetCountY - 1) * mWidgetCellHeightGap)) / mWidgetCountY);
581        for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) {
582            AppWidgetProviderInfo info = (AppWidgetProviderInfo) mWidgets.get(offset + i);
583            PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo(info, null, null);
584            final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth,
585                    info.minHeight, null);
586            FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1],
587                    cellWidth, cellHeight);
588            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
589                    R.layout.apps_customize_widget, layout, false);
590            widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false);
591            widget.setTag(createItemInfo);
592            widget.setOnClickListener(this);
593            widget.setOnLongClickListener(this);
594            widget.setOnTouchListener(this);
595
596            // Layout each widget
597            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(cellWidth, cellHeight);
598            int ix = i % mWidgetCountX;
599            int iy = i / mWidgetCountX;
600            lp.leftMargin = (ix * cellWidth) + (ix * mWidgetCellWidthGap);
601            lp.topMargin = (iy * cellHeight) + (iy * mWidgetCellHeightGap);
602            container.addView(widget, lp);
603        }
604        layout.addView(container, new LinearLayout.LayoutParams(
605            LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
606    }
607    @Override
608    public void syncPages() {
609        removeAllViews();
610        switch (mContentType) {
611        case Applications:
612            syncAppsPages();
613            break;
614        case Widgets:
615            syncWidgetPages();
616            break;
617        }
618    }
619    @Override
620    public void syncPageItems(int page) {
621        switch (mContentType) {
622        case Applications:
623            syncAppsPageItems(page);
624            break;
625        case Widgets:
626            syncWidgetPageItems(page);
627            break;
628        }
629    }
630
631    /**
632     * Used by the parent to get the content width to set the tab bar to
633     * @return
634     */
635    public int getPageContentWidth() {
636        return mContentWidth;
637    }
638
639    /*
640     * AllAppsView implementation
641     */
642    @Override
643    public void setup(Launcher launcher, DragController dragController) {
644        mLauncher = launcher;
645        mDragController = dragController;
646    }
647    @Override
648    public void zoom(float zoom, boolean animate) {
649        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
650    }
651    @Override
652    public boolean isVisible() {
653        return (getVisibility() == VISIBLE);
654    }
655    @Override
656    public boolean isAnimating() {
657        return false;
658    }
659    @Override
660    public void setApps(ArrayList<ApplicationInfo> list) {
661        mApps = list;
662        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
663        invalidatePageData();
664    }
665    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
666        // We add it in place, in alphabetical order
667        int count = list.size();
668        for (int i = 0; i < count; ++i) {
669            ApplicationInfo info = list.get(i);
670            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
671            if (index < 0) {
672                mApps.add(-(index + 1), info);
673            }
674        }
675    }
676    @Override
677    public void addApps(ArrayList<ApplicationInfo> list) {
678        addAppsWithoutInvalidate(list);
679        invalidatePageData();
680    }
681    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
682        ComponentName removeComponent = item.intent.getComponent();
683        int length = list.size();
684        for (int i = 0; i < length; ++i) {
685            ApplicationInfo info = list.get(i);
686            if (info.intent.getComponent().equals(removeComponent)) {
687                return i;
688            }
689        }
690        return -1;
691    }
692    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
693        // loop through all the apps and remove apps that have the same component
694        int length = list.size();
695        for (int i = 0; i < length; ++i) {
696            ApplicationInfo info = list.get(i);
697            int removeIndex = findAppByComponent(mApps, info);
698            if (removeIndex > -1) {
699                mApps.remove(removeIndex);
700                mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info));
701            }
702        }
703    }
704    @Override
705    public void removeApps(ArrayList<ApplicationInfo> list) {
706        removeAppsWithoutInvalidate(list);
707        invalidatePageData();
708    }
709    @Override
710    public void updateApps(ArrayList<ApplicationInfo> list) {
711        // We remove and re-add the updated applications list because it's properties may have
712        // changed (ie. the title), and this will ensure that the items will be in their proper
713        // place in the list.
714        removeAppsWithoutInvalidate(list);
715        addAppsWithoutInvalidate(list);
716        invalidatePageData();
717    }
718    @Override
719    public void reset() {
720        setCurrentPage(0);
721        invalidatePageData();
722    }
723    @Override
724    public void dumpState() {
725        // TODO: Dump information related to current list of Applications, Widgets, etc.
726        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
727        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
728    }
729    private void dumpAppWidgetProviderInfoList(String tag, String label,
730            List<AppWidgetProviderInfo> list) {
731        Log.d(tag, label + " size=" + list.size());
732        for (AppWidgetProviderInfo info: list) {
733            Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
734                    + " resizeMode=" + info.resizeMode + " configure=" + info.configure
735                    + " initialLayout=" + info.initialLayout
736                    + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
737        }
738    }
739    @Override
740    public void surrender() {
741        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
742        // should stop this now.
743    }
744}
745