AppsCustomizePagedView.java revision fc79c8067e21e55fce3802a63e15ee4c6f11d595
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        // Hide the pane so that the user can drop onto the workspace, we must do this first,
366        // due to how the drop target layout is computed when we start dragging to the workspace.
367        mLauncher.showWorkspace(true);
368
369        if (v instanceof PagedViewIcon) {
370            beginDraggingApplication(v);
371        } else if (v instanceof PagedViewWidget) {
372            beginDraggingWidget(v);
373        }
374
375        return true;
376    }
377    private void endDragging(boolean success) {
378        post(new Runnable() {
379            // Once the drag operation has fully completed, hence the post, we want to disable the
380            // deleteZone and the appInfoButton in all apps, and re-enable the instance which
381            // live in the workspace
382            public void run() {
383                // if onDestroy was called on Launcher, we might have already deleted the
384                // all apps delete zone / info button, so check if they are null
385                DeleteZone allAppsDeleteZone =
386                        (DeleteZone) mLauncher.findViewById(R.id.all_apps_delete_zone);
387                ApplicationInfoDropTarget allAppsInfoButton =
388                    (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
389
390                if (allAppsDeleteZone != null) allAppsDeleteZone.setDragAndDropEnabled(false);
391                if (allAppsInfoButton != null) allAppsInfoButton.setDragAndDropEnabled(false);
392            }
393        });
394        mLauncher.getWorkspace().onDragStopped(success);
395        mLauncher.unlockScreenOrientation();
396    }
397
398    /*
399     * DragSource implementation
400     */
401    @Override
402    public void onDragViewVisible() {}
403    @Override
404    public void onDropCompleted(View target, Object dragInfo, boolean success) {
405        endDragging(success);
406
407        // Display an error message if the drag failed due to there not being enough space on the
408        // target layout we were dropping on.
409        if (!success) {
410            boolean showOutOfSpaceMessage = false;
411            if (target instanceof Workspace) {
412                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
413                Workspace workspace = (Workspace) target;
414                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
415                ItemInfo itemInfo = (ItemInfo) dragInfo;
416                if (layout != null) {
417                    layout.calculateSpans(itemInfo);
418                    showOutOfSpaceMessage =
419                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
420                }
421            }
422            // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later.
423            if (showOutOfSpaceMessage) {
424                mLauncher.showOutOfSpaceMessage();
425            }
426        }
427    }
428
429    public void setContentType(ContentType type) {
430        mContentType = type;
431        setCurrentPage(0);
432        invalidatePageData();
433    }
434
435    /*
436     * Apps PagedView implementation
437     */
438    private void setupPage(PagedViewCellLayout layout) {
439        layout.setCellCount(mCellCountX, mCellCountY);
440        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
441        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
442                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
443
444        // We force a measure here to get around the fact that when we do layout calculations
445        // immediately after syncing, we don't have a proper width.
446        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
447        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
448        layout.measure(widthSpec, heightSpec);
449    }
450    public void syncAppsPages() {
451        // Ensure that we have the right number of pages
452        Context context = getContext();
453        int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
454        for (int i = 0; i < numPages; ++i) {
455            PagedViewCellLayout layout = new PagedViewCellLayout(context);
456            setupPage(layout);
457            addView(layout);
458        }
459    }
460    public void syncAppsPageItems(int page) {
461        // ensure that we have the right number of items on the pages
462        int numPages = getPageCount();
463        int numCells = mCellCountX * mCellCountY;
464        int startIndex = page * numCells;
465        int endIndex = Math.min(startIndex + numCells, mApps.size());
466        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
467        layout.removeAllViewsOnPage();
468        for (int i = startIndex; i < endIndex; ++i) {
469            ApplicationInfo info = mApps.get(i);
470            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
471                    R.layout.apps_customize_application, layout, false);
472            icon.applyFromApplicationInfo(info, mPageViewIconCache, true, (numPages > 1));
473            icon.setOnClickListener(this);
474            icon.setOnLongClickListener(this);
475            icon.setOnTouchListener(this);
476
477            int index = i - startIndex;
478            int x = index % mCellCountX;
479            int y = index / mCellCountX;
480            setupPage(layout);
481            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
482        }
483    }
484    /*
485     * Widgets PagedView implementation
486     */
487    private void setupPage(PagedViewExtendedLayout layout) {
488        layout.setGravity(Gravity.LEFT);
489        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
490                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
491        layout.setMinimumWidth(getPageContentWidth());
492    }
493    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
494            float scaleX, float scaleY) {
495        Canvas c = new Canvas();
496        if (bitmap != null) c.setBitmap(bitmap);
497        c.save();
498        c.scale(scaleX, scaleY);
499        Rect oldBounds = d.copyBounds();
500        d.setBounds(x, y, x + w, y + h);
501        d.draw(c);
502        d.setBounds(oldBounds); // Restore the bounds
503        c.restore();
504    }
505    private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan,
506            int cellVSpan, int cellWidth, int cellHeight) {
507        // Calculate the size of the drawable
508        cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan));
509        cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan));
510        int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
511        int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
512
513        // Scale down the bitmap to fit the space
514        float widgetPreviewScale = (float) cellWidth / expectedWidth;
515        expectedWidth = (int) (widgetPreviewScale * expectedWidth);
516        expectedHeight = (int) (widgetPreviewScale * expectedHeight);
517
518        // Load the preview image if possible
519        String packageName = info.provider.getPackageName();
520        Drawable drawable = null;
521        FastBitmapDrawable newDrawable = null;
522        if (info.previewImage != 0) {
523            drawable = mPackageManager.getDrawable(packageName, info.previewImage, null);
524            if (drawable == null) {
525                Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
526                        + " for provider: " + info.provider);
527            } else {
528                // Scale down the preview to the dimensions we want
529                int imageWidth = drawable.getIntrinsicWidth();
530                int imageHeight = drawable.getIntrinsicHeight();
531                float aspect = (float) imageWidth / imageHeight;
532                int newWidth = imageWidth;
533                int newHeight = imageHeight;
534                if (aspect > 1f) {
535                    newWidth = expectedWidth;
536                    newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth));
537                } else {
538                    newHeight = expectedHeight;
539                    newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight));
540                }
541
542                Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
543                renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f);
544                newDrawable = new FastBitmapDrawable(preview);
545                newDrawable.setBounds(0, 0, newWidth, newHeight);
546            }
547        }
548
549        // Generate a preview image if we couldn't load one
550        if (drawable == null) {
551            // The icon itself takes up space, so update expected width/height to have min of 2
552            cellHSpan = Math.max(2, cellHSpan);
553            cellVSpan = Math.max(2, cellVSpan);
554            expectedWidth = (int) (widgetPreviewScale
555                    * mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
556            expectedHeight = (int) (widgetPreviewScale
557                    * mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
558
559            Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
560            Resources resources = mLauncher.getResources();
561            Drawable background = resources.getDrawable(R.drawable.default_widget_preview);
562            renderDrawableToBitmap(background, preview, 0, 0, expectedWidth, expectedHeight, 1f,1f);
563
564            // Draw the icon in the top left corner
565            try {
566                Drawable icon = null;
567                if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null);
568                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
569
570                int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
571                int offset = iconSize / 4;
572                renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
573            } catch (Resources.NotFoundException e) {}
574
575            newDrawable = new FastBitmapDrawable(preview);
576            newDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
577        }
578        return newDrawable;
579    }
580    public void syncWidgetPages() {
581        // Ensure that we have the right number of pages
582        Context context = getContext();
583        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
584        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
585        for (int i = 0; i < numPages; ++i) {
586            PagedViewExtendedLayout layout = new PagedViewExtendedLayout(context);
587            setupPage(layout);
588            addView(layout);
589        }
590    }
591    public void syncWidgetPageItems(int page) {
592        Context context = getContext();
593        PagedViewExtendedLayout layout = (PagedViewExtendedLayout) getChildAt(page);
594        layout.removeAllViews();
595
596        // Calculate the dimensions of each cell we are giving to each widget
597        FrameLayout container = new FrameLayout(context);
598        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
599        int offset = page * numWidgetsPerPage;
600        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
601                - ((mWidgetCountX - 1) * mWidgetCellWidthGap)) / mWidgetCountX);
602        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
603                - ((mWidgetCountY - 1) * mWidgetCellHeightGap)) / mWidgetCountY);
604        for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) {
605            AppWidgetProviderInfo info = (AppWidgetProviderInfo) mWidgets.get(offset + i);
606            PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo(info, null, null);
607            final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth,
608                    info.minHeight, null);
609            FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1],
610                    cellWidth, cellHeight);
611            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
612                    R.layout.apps_customize_widget, layout, false);
613            widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false);
614            widget.setTag(createItemInfo);
615            widget.setOnClickListener(this);
616            widget.setOnLongClickListener(this);
617            widget.setOnTouchListener(this);
618
619            // Layout each widget
620            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(cellWidth, cellHeight);
621            int ix = i % mWidgetCountX;
622            int iy = i / mWidgetCountX;
623            lp.leftMargin = (ix * cellWidth) + (ix * mWidgetCellWidthGap);
624            lp.topMargin = (iy * cellHeight) + (iy * mWidgetCellHeightGap);
625            container.addView(widget, lp);
626        }
627        layout.addView(container, new LinearLayout.LayoutParams(
628            LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
629    }
630    @Override
631    public void syncPages() {
632        removeAllViews();
633        switch (mContentType) {
634        case Applications:
635            syncAppsPages();
636            break;
637        case Widgets:
638            syncWidgetPages();
639            break;
640        }
641    }
642    @Override
643    public void syncPageItems(int page) {
644        switch (mContentType) {
645        case Applications:
646            syncAppsPageItems(page);
647            break;
648        case Widgets:
649            syncWidgetPageItems(page);
650            break;
651        }
652    }
653
654    /**
655     * Used by the parent to get the content width to set the tab bar to
656     * @return
657     */
658    public int getPageContentWidth() {
659        return mContentWidth;
660    }
661
662    /*
663     * AllAppsView implementation
664     */
665    @Override
666    public void setup(Launcher launcher, DragController dragController) {
667        mLauncher = launcher;
668        mDragController = dragController;
669    }
670    @Override
671    public void zoom(float zoom, boolean animate) {
672        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
673    }
674    @Override
675    public boolean isVisible() {
676        return (getVisibility() == VISIBLE);
677    }
678    @Override
679    public boolean isAnimating() {
680        return false;
681    }
682    @Override
683    public void setApps(ArrayList<ApplicationInfo> list) {
684        mApps = list;
685        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
686        invalidatePageData();
687    }
688    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
689        // We add it in place, in alphabetical order
690        int count = list.size();
691        for (int i = 0; i < count; ++i) {
692            ApplicationInfo info = list.get(i);
693            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
694            if (index < 0) {
695                mApps.add(-(index + 1), info);
696            }
697        }
698    }
699    @Override
700    public void addApps(ArrayList<ApplicationInfo> list) {
701        addAppsWithoutInvalidate(list);
702        invalidatePageData();
703    }
704    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
705        ComponentName removeComponent = item.intent.getComponent();
706        int length = list.size();
707        for (int i = 0; i < length; ++i) {
708            ApplicationInfo info = list.get(i);
709            if (info.intent.getComponent().equals(removeComponent)) {
710                return i;
711            }
712        }
713        return -1;
714    }
715    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
716        // loop through all the apps and remove apps that have the same component
717        int length = list.size();
718        for (int i = 0; i < length; ++i) {
719            ApplicationInfo info = list.get(i);
720            int removeIndex = findAppByComponent(mApps, info);
721            if (removeIndex > -1) {
722                mApps.remove(removeIndex);
723                mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info));
724            }
725        }
726    }
727    @Override
728    public void removeApps(ArrayList<ApplicationInfo> list) {
729        removeAppsWithoutInvalidate(list);
730        invalidatePageData();
731    }
732    @Override
733    public void updateApps(ArrayList<ApplicationInfo> list) {
734        // We remove and re-add the updated applications list because it's properties may have
735        // changed (ie. the title), and this will ensure that the items will be in their proper
736        // place in the list.
737        removeAppsWithoutInvalidate(list);
738        addAppsWithoutInvalidate(list);
739        invalidatePageData();
740    }
741    @Override
742    public void reset() {
743        if (mContentType != ContentType.Applications) {
744            // Reset to the first page of the Apps pane
745            AppsCustomizeTabHost tabs = (AppsCustomizeTabHost)
746                    mLauncher.findViewById(R.id.apps_customize_pane);
747            tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications));
748        } else {
749            setCurrentPage(0);
750            invalidatePageData();
751        }
752    }
753    @Override
754    public void dumpState() {
755        // TODO: Dump information related to current list of Applications, Widgets, etc.
756        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
757        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
758    }
759    private void dumpAppWidgetProviderInfoList(String tag, String label,
760            List<AppWidgetProviderInfo> list) {
761        Log.d(tag, label + " size=" + list.size());
762        for (AppWidgetProviderInfo info: list) {
763            Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
764                    + " resizeMode=" + info.resizeMode + " configure=" + info.configure
765                    + " initialLayout=" + info.initialLayout
766                    + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
767        }
768    }
769    @Override
770    public void surrender() {
771        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
772        // should stop this now.
773    }
774}
775