AppsCustomizePagedView.java revision 6a70e9fc3c62cc83d6abe59323d622dc6cd224a7
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 org.xmlpull.v1.XmlPullParser;
24
25import android.animation.Animator;
26import android.animation.AnimatorListenerAdapter;
27import android.animation.ObjectAnimator;
28import android.animation.PropertyValuesHolder;
29import android.app.WallpaperManager;
30import android.appwidget.AppWidgetManager;
31import android.appwidget.AppWidgetProviderInfo;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.pm.ActivityInfo;
36import android.content.pm.PackageManager;
37import android.content.pm.ResolveInfo;
38import android.content.res.Resources;
39import android.content.res.TypedArray;
40import android.content.res.XmlResourceParser;
41import android.graphics.Bitmap;
42import android.graphics.Bitmap.Config;
43import android.graphics.Canvas;
44import android.graphics.Rect;
45import android.graphics.drawable.Drawable;
46import android.util.AttributeSet;
47import android.util.Log;
48import android.util.LruCache;
49import android.util.Slog;
50import android.util.TypedValue;
51import android.util.Xml;
52import android.view.LayoutInflater;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.animation.DecelerateInterpolator;
56import android.view.animation.LinearInterpolator;
57import android.widget.ImageView;
58import android.widget.TextView;
59
60import com.android.launcher.R;
61
62public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
63        AllAppsView, View.OnClickListener, DragSource {
64    static final String LOG_TAG = "AppsCustomizePagedView";
65
66    /**
67     * The different content types that this paged view can show.
68     */
69    public enum ContentType {
70        Applications,
71        Widgets,
72        Wallpapers
73    }
74
75    // Refs
76    private Launcher mLauncher;
77    private DragController mDragController;
78    private final LayoutInflater mLayoutInflater;
79    private final PackageManager mPackageManager;
80
81    // Content
82    private ContentType mContentType;
83    private ArrayList<ApplicationInfo> mApps;
84    private List<Object> mWidgets;
85    private List<ResolveInfo> mWallpapers;
86
87    // Caching
88    private Drawable mDefaultWidgetBackground;
89    private final int sWidgetPreviewCacheSize = 1 * 1024 * 1024; // 1 MiB
90    private LruCache<Object, Bitmap> mWidgetPreviewCache;
91    private IconCache mIconCache;
92
93    // Dimens
94    private int mContentWidth;
95    private int mMaxWidgetSpan, mMinWidgetSpan;
96    private int mCellWidthGap, mCellHeightGap;
97    private int mWidgetCountX, mWidgetCountY;
98    private int mWallpaperCountX, mWallpaperCountY;
99    private final int mWidgetPreviewIconPaddedDimension;
100    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
101    private PagedViewCellLayout mWidgetSpacingLayout;
102
103    // Animations
104    private final float ANIMATION_SCALE = 0.5f;
105    private final int TRANSLATE_ANIM_DURATION = 400;
106    private final int DROP_ANIM_DURATION = 200;
107
108    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
109        super(context, attrs);
110        mLayoutInflater = LayoutInflater.from(context);
111        mPackageManager = context.getPackageManager();
112        mContentType = ContentType.Applications;
113        mApps = new ArrayList<ApplicationInfo>();
114        mWidgets = new ArrayList<Object>();
115        mWallpapers = new ArrayList<ResolveInfo>();
116        mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
117        mWidgetPreviewCache = new LruCache<Object, Bitmap>(sWidgetPreviewCacheSize) {
118            protected int sizeOf(Object key, Bitmap value) {
119                return value.getByteCount();
120            }
121        };
122
123        // Save the default widget preview background
124        Resources resources = context.getResources();
125        mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview);
126
127        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0);
128        mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6);
129        mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4);
130        a.recycle();
131        a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
132        mCellWidthGap =
133            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 10);
134        mCellHeightGap =
135            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 10);
136        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
137        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
138        mWallpaperCountX = a.getInt(R.styleable.AppsCustomizePagedView_wallpaperCountX, 2);
139        mWallpaperCountY = a.getInt(R.styleable.AppsCustomizePagedView_wallpaperCountY, 2);
140        a.recycle();
141
142        // Create a dummy page that we can use to approximate the cell dimensions of widgets and
143        // the content width (to be used by our parent)
144        mWidgetSpacingLayout = new PagedViewCellLayout(context);
145        setupPage(mWidgetSpacingLayout);
146        mContentWidth = mWidgetSpacingLayout.getContentWidth();
147
148        // The max widget span is the length N, such that NxN is the largest bounds that the widget
149        // preview can be before applying the widget scaling
150        mMinWidgetSpan = 1;
151        mMaxWidgetSpan = 3;
152
153        // The padding on the non-matched dimension for the default widget preview icons
154        // (top + bottom)
155        int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
156        mWidgetPreviewIconPaddedDimension =
157            (int) (iconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage)));
158    }
159
160    @Override
161    protected void init() {
162        super.init();
163        mCenterPagesVertically = false;
164
165        Context context = getContext();
166        Resources r = context.getResources();
167        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
168    }
169
170    public void onPackagesUpdated() {
171        // Get the list of widgets and shortcuts
172        mWidgets.clear();
173        mWidgets.addAll(AppWidgetManager.getInstance(mLauncher).getInstalledProviders());
174        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
175        mWidgets.addAll(mPackageManager.queryIntentActivities(shortcutsIntent, 0));
176        Collections.sort(mWidgets,
177                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
178
179        // Get the list of wallpapers
180        Intent wallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
181        mWallpapers = mPackageManager.queryIntentActivities(wallpapersIntent,
182                PackageManager.GET_META_DATA);
183        Collections.sort(mWallpapers,
184                new LauncherModel.ShortcutNameComparator(mPackageManager));
185    }
186
187    /**
188     * Animates the given item onto the center of a home screen, and then scales the item to
189     * look as though it's disappearing onto that screen.
190     */
191    private void animateItemOntoScreen(View dragView,
192            final CellLayout layout, final ItemInfo info) {
193        // On the phone, we only want to fade the widget preview out
194        float[] position = new float[2];
195        position[0] = layout.getWidth() / 2;
196        position[1] = layout.getHeight() / 2;
197
198        mLauncher.getWorkspace().mapPointFromChildToSelf(layout, position);
199
200        int dragViewWidth = dragView.getMeasuredWidth();
201        int dragViewHeight = dragView.getMeasuredHeight();
202        float heightOffset = 0;
203        float widthOffset = 0;
204
205        if (dragView instanceof ImageView) {
206            Drawable d = ((ImageView) dragView).getDrawable();
207            int width = d.getIntrinsicWidth();
208            int height = d.getIntrinsicHeight();
209
210            if ((1.0 * width / height) >= (1.0f * dragViewWidth) / dragViewHeight) {
211                float f = (dragViewWidth / (width * 1.0f));
212                heightOffset = ANIMATION_SCALE * (dragViewHeight - f * height) / 2;
213            } else {
214                float f = (dragViewHeight / (height * 1.0f));
215                widthOffset = ANIMATION_SCALE * (dragViewWidth - f * width) / 2;
216            }
217        }
218        final float toX = position[0] - dragView.getMeasuredWidth() / 2 + widthOffset;
219        final float toY = position[1] - dragView.getMeasuredHeight() / 2 + heightOffset;
220
221        final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
222        final View dragCopy = dragLayer.createDragView(dragView);
223        dragCopy.setAlpha(1.0f);
224
225        // Translate the item to the center of the appropriate home screen
226        animateIntoPosition(dragCopy, toX, toY, null);
227
228        // The drop-onto-screen animation begins a bit later, but ends at the same time.
229        final int startDelay = TRANSLATE_ANIM_DURATION - DROP_ANIM_DURATION;
230
231        // Scale down the icon and fade out the alpha
232        animateDropOntoScreen(dragCopy, info, DROP_ANIM_DURATION, startDelay);
233    }
234
235    /**
236     * Animation which scales the view down and animates its alpha, making it appear to disappear
237     * onto a home screen.
238     */
239    private void animateDropOntoScreen(
240            final View view, final ItemInfo info, int duration, int delay) {
241        final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
242        final CellLayout layout = mLauncher.getWorkspace().getCurrentDropLayout();
243
244        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
245                PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f),
246                PropertyValuesHolder.ofFloat("scaleX", ANIMATION_SCALE),
247                PropertyValuesHolder.ofFloat("scaleY", ANIMATION_SCALE));
248        anim.setInterpolator(new LinearInterpolator());
249        if (delay > 0) {
250            anim.setStartDelay(delay);
251        }
252        anim.setDuration(duration);
253        anim.addListener(new AnimatorListenerAdapter() {
254            public void onAnimationEnd(Animator animation) {
255                dragLayer.removeView(view);
256                mLauncher.addExternalItemToScreen(info, layout);
257                info.dropPos = null;
258            }
259        });
260        anim.start();
261    }
262
263    /**
264     * Animates the x,y position of the view, and optionally execute a Runnable on animation end.
265     */
266    private void animateIntoPosition(
267            View view, float toX, float toY, final Runnable endRunnable) {
268        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
269                PropertyValuesHolder.ofFloat("x", toX),
270                PropertyValuesHolder.ofFloat("y", toY));
271        anim.setInterpolator(new DecelerateInterpolator(2.5f));
272        anim.setDuration(TRANSLATE_ANIM_DURATION);
273        if (endRunnable != null) {
274            anim.addListener(new AnimatorListenerAdapter() {
275                @Override
276                public void onAnimationEnd(Animator animation) {
277                    endRunnable.run();
278                }
279            });
280        }
281        anim.start();
282    }
283
284    @Override
285    public void onClick(View v) {
286        if (v instanceof PagedViewIcon) {
287            // Animate some feedback to the click
288            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
289            animateClickFeedback(v, new Runnable() {
290                @Override
291                public void run() {
292                    mLauncher.startActivitySafely(appInfo.intent, appInfo);
293                }
294            });
295        } else if (v instanceof PagedViewWidget) {
296            final ResolveInfo info = (ResolveInfo) v.getTag();
297            if (mWallpapers.contains(info)) {
298                // Start the wallpaper picker
299                animateClickFeedback(v, new Runnable() {
300                    @Override
301                    public void run() {
302                        // add the shortcut
303                        Intent createWallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
304                        ComponentName name = new ComponentName(info.activityInfo.packageName,
305                                info.activityInfo.name);
306                        createWallpapersIntent.setComponent(name);
307                        mLauncher.processWallpaper(createWallpapersIntent);
308                    }
309                });
310            } else {
311                // Add the widget to the current workspace screen
312                Workspace w = mLauncher.getWorkspace();
313                int currentWorkspaceScreen = mLauncher.getCurrentWorkspaceScreen();
314                final CellLayout cl = (CellLayout) w.getChildAt(currentWorkspaceScreen);
315                final View dragView = v.findViewById(R.id.widget_preview);
316                final ItemInfo itemInfo = (ItemInfo) v.getTag();
317                animateClickFeedback(v, new Runnable() {
318                    @Override
319                    public void run() {
320                        cl.calculateSpans(itemInfo);
321                        if (cl.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY)) {
322                            if (LauncherApplication.isScreenLarge()) {
323                                animateItemOntoScreen(dragView, cl, itemInfo);
324                            } else {
325                                mLauncher.addExternalItemToScreen(itemInfo, cl);
326                                itemInfo.dropPos = null;
327                            }
328
329                            // Hide the pane so we can see the workspace we dropped on
330                            mLauncher.showWorkspace(true);
331                        } else {
332                            mLauncher.showOutOfSpaceMessage();
333                        }
334                    }
335                });
336            }
337        }
338    }
339
340    /*
341     * PagedViewWithDraggableItems implementation
342     */
343    @Override
344    protected void determineDraggingStart(android.view.MotionEvent ev) {
345        // Disable dragging by pulling an app down for now.
346    }
347    private void beginDraggingApplication(View v) {
348        // Make a copy of the ApplicationInfo
349        ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag());
350
351        // Show the uninstall button if the app is uninstallable.
352        if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0) {
353            DeleteZone allAppsDeleteZone = (DeleteZone)
354                    mLauncher.findViewById(R.id.all_apps_delete_zone);
355            allAppsDeleteZone.setDragAndDropEnabled(true);
356
357            if ((appInfo.flags & ApplicationInfo.UPDATED_SYSTEM_APP_FLAG) != 0) {
358                allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps_system_app);
359            } else {
360                allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps);
361            }
362        }
363
364        // Show the info button
365        ApplicationInfoDropTarget allAppsInfoButton =
366            (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
367        allAppsInfoButton.setDragAndDropEnabled(true);
368
369        // Compose the drag image (top compound drawable, index is 1)
370        final TextView tv = (TextView) v;
371        final Drawable icon = tv.getCompoundDrawables()[1];
372        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
373                Bitmap.Config.ARGB_8888);
374        Canvas c = new Canvas(b);
375        c.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop());
376        icon.draw(c);
377
378        // Compose the visible rect of the drag image
379        Rect dragRect = null;
380        if (v instanceof TextView) {
381            int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size);
382            int top = v.getPaddingTop();
383            int left = (b.getWidth() - iconSize) / 2;
384            int right = left + iconSize;
385            int bottom = top + iconSize;
386            dragRect = new Rect(left, top, right, bottom);
387        }
388
389        // Start the drag
390        mLauncher.lockScreenOrientation();
391        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
392        mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect);
393        b.recycle();
394    }
395    private void beginDraggingWidget(View v) {
396        // Get the widget preview as the drag representation
397        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
398        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
399
400        // Compose the drag image
401        Bitmap b;
402        Drawable preview = image.getDrawable();
403        int w = preview.getIntrinsicWidth();
404        int h = preview.getIntrinsicHeight();
405        if (createItemInfo instanceof PendingAddWidgetInfo) {
406            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
407            int[] spanXY = CellLayout.rectToCell(getResources(),
408                    createWidgetInfo.minWidth, createWidgetInfo.minHeight, null);
409            createItemInfo.spanX = spanXY[0];
410            createItemInfo.spanY = spanXY[1];
411
412            b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
413            renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1);
414        } else {
415            // Workaround for the fact that we don't keep the original ResolveInfo associated with
416            // the shortcut around.  To get the icon, we just render the preview image (which has
417            // the shortcut icon) to a new drag bitmap that clips the non-icon space.
418            b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension,
419                    mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888);
420            Canvas c = new Canvas(b);
421            preview.draw(c);
422            createItemInfo.spanX = createItemInfo.spanY = 1;
423        }
424
425        // Start the drag
426        mLauncher.lockScreenOrientation();
427        mLauncher.getWorkspace().onDragStartedWithItemSpans(createItemInfo.spanX,
428                createItemInfo.spanY, b);
429        mDragController.startDrag(image, b, this, createItemInfo,
430                DragController.DRAG_ACTION_COPY, null);
431        b.recycle();
432    }
433    @Override
434    protected boolean beginDragging(View v) {
435        if (!super.beginDragging(v)) return false;
436
437        // Hide the pane so that the user can drop onto the workspace, we must do this first,
438        // due to how the drop target layout is computed when we start dragging to the workspace.
439        mLauncher.showWorkspace(true);
440
441        if (v instanceof PagedViewIcon) {
442            beginDraggingApplication(v);
443        } else if (v instanceof PagedViewWidget) {
444            beginDraggingWidget(v);
445        }
446
447        return true;
448    }
449    private void endDragging(boolean success) {
450        post(new Runnable() {
451            // Once the drag operation has fully completed, hence the post, we want to disable the
452            // deleteZone and the appInfoButton in all apps, and re-enable the instance which
453            // live in the workspace
454            public void run() {
455                // if onDestroy was called on Launcher, we might have already deleted the
456                // all apps delete zone / info button, so check if they are null
457                DeleteZone allAppsDeleteZone =
458                        (DeleteZone) mLauncher.findViewById(R.id.all_apps_delete_zone);
459                ApplicationInfoDropTarget allAppsInfoButton =
460                    (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
461
462                if (allAppsDeleteZone != null) allAppsDeleteZone.setDragAndDropEnabled(false);
463                if (allAppsInfoButton != null) allAppsInfoButton.setDragAndDropEnabled(false);
464            }
465        });
466        mLauncher.getWorkspace().onDragStopped(success);
467        mLauncher.unlockScreenOrientation();
468    }
469
470    /*
471     * DragSource implementation
472     */
473    @Override
474    public void onDragViewVisible() {}
475    @Override
476    public void onDropCompleted(View target, Object dragInfo, boolean success) {
477        endDragging(success);
478
479        // Display an error message if the drag failed due to there not being enough space on the
480        // target layout we were dropping on.
481        if (!success) {
482            boolean showOutOfSpaceMessage = false;
483            if (target instanceof Workspace) {
484                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
485                Workspace workspace = (Workspace) target;
486                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
487                ItemInfo itemInfo = (ItemInfo) dragInfo;
488                if (layout != null) {
489                    layout.calculateSpans(itemInfo);
490                    showOutOfSpaceMessage =
491                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
492                }
493            }
494            // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later.
495            if (showOutOfSpaceMessage) {
496                mLauncher.showOutOfSpaceMessage();
497            }
498        }
499    }
500
501    public void setContentType(ContentType type) {
502        mContentType = type;
503        setCurrentPage(0);
504        invalidatePageData();
505    }
506
507    /*
508     * Apps PagedView implementation
509     */
510    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
511        int childCount = layout.getChildCount();
512        for (int i = 0; i < childCount; ++i) {
513            layout.getChildAt(i).setVisibility(visibility);
514        }
515    }
516    private void setupPage(PagedViewCellLayout layout) {
517        layout.setCellCount(mCellCountX, mCellCountY);
518        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
519        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
520                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
521
522        // Note: We force a measure here to get around the fact that when we do layout calculations
523        // immediately after syncing, we don't have a proper width.  That said, we already know the
524        // expected page width, so we can actually optimize by hiding all the TextView-based
525        // children that are expensive to measure, and let that happen naturally later.
526        setVisibilityOnChildren(layout, View.GONE);
527        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
528        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
529        layout.setMinimumWidth(getPageContentWidth());
530        layout.measure(widthSpec, heightSpec);
531        setVisibilityOnChildren(layout, View.VISIBLE);
532    }
533    public void syncAppsPages() {
534        // Ensure that we have the right number of pages
535        Context context = getContext();
536        int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
537        for (int i = 0; i < numPages; ++i) {
538            PagedViewCellLayout layout = new PagedViewCellLayout(context);
539            setupPage(layout);
540            addView(layout);
541        }
542    }
543    public void syncAppsPageItems(int page) {
544        // ensure that we have the right number of items on the pages
545        int numPages = getPageCount();
546        int numCells = mCellCountX * mCellCountY;
547        int startIndex = page * numCells;
548        int endIndex = Math.min(startIndex + numCells, mApps.size());
549        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
550        layout.removeAllViewsOnPage();
551        for (int i = startIndex; i < endIndex; ++i) {
552            ApplicationInfo info = mApps.get(i);
553            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
554                    R.layout.apps_customize_application, layout, false);
555            icon.applyFromApplicationInfo(
556                    info, mPageViewIconCache, true, isHardwareAccelerated() && (numPages > 1));
557            icon.setOnClickListener(this);
558            icon.setOnLongClickListener(this);
559            icon.setOnTouchListener(this);
560
561            int index = i - startIndex;
562            int x = index % mCellCountX;
563            int y = index / mCellCountX;
564            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
565        }
566    }
567    /*
568     * Widgets PagedView implementation
569     */
570    private void setupPage(PagedViewGridLayout layout) {
571        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
572                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
573
574        // Note: We force a measure here to get around the fact that when we do layout calculations
575        // immediately after syncing, we don't have a proper width.  That said, we already know the
576        // expected page width, so we can actually optimize by hiding all the TextView-based
577        // children that are expensive to measure, and let that happen naturally later.
578        setVisibilityOnChildren(layout, View.GONE);
579        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
580        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
581        layout.setMinimumWidth(getPageContentWidth());
582        layout.measure(widthSpec, heightSpec);
583        setVisibilityOnChildren(layout, View.VISIBLE);
584    }
585    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
586            float scaleX, float scaleY) {
587        Canvas c = new Canvas();
588        if (bitmap != null) c.setBitmap(bitmap);
589        c.save();
590        c.scale(scaleX, scaleY);
591        Rect oldBounds = d.copyBounds();
592        d.setBounds(x, y, x + w, y + h);
593        d.draw(c);
594        d.setBounds(oldBounds); // Restore the bounds
595        c.restore();
596    }
597    private FastBitmapDrawable getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) {
598        // Return the cached version if necessary
599        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
600        if (cachedBitmap != null) {
601            return new FastBitmapDrawable(cachedBitmap);
602        }
603
604        Resources resources = mLauncher.getResources();
605        int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
606        // We only need to make it wide enough so as not allow the preview to be scaled
607        int expectedWidth = cellWidth;
608        int expectedHeight = mWidgetPreviewIconPaddedDimension;
609        int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
610
611        // Render the icon
612        Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
613        Drawable icon = mIconCache.getFullResIcon(info, mPackageManager);
614        renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0,
615                mWidgetPreviewIconPaddedDimension, mWidgetPreviewIconPaddedDimension, 1f, 1f);
616        renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
617        FastBitmapDrawable iconDrawable = new FastBitmapDrawable(preview);
618        iconDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
619        mWidgetPreviewCache.put(info, preview);
620        return iconDrawable;
621    }
622    private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan,
623            int cellVSpan, int cellWidth, int cellHeight) {
624        // Return the cached version if necessary
625        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
626        if (cachedBitmap != null) {
627            return new FastBitmapDrawable(cachedBitmap);
628        }
629
630        // Calculate the size of the drawable
631        cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan));
632        cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan));
633        int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
634        int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
635
636        // Scale down the bitmap to fit the space
637        float widgetPreviewScale = (float) cellWidth / expectedWidth;
638        expectedWidth = (int) (widgetPreviewScale * expectedWidth);
639        expectedHeight = (int) (widgetPreviewScale * expectedHeight);
640
641        // Load the preview image if possible
642        String packageName = info.provider.getPackageName();
643        Drawable drawable = null;
644        FastBitmapDrawable newDrawable = null;
645        if (info.previewImage != 0) {
646            drawable = mPackageManager.getDrawable(packageName, info.previewImage, null);
647            if (drawable == null) {
648                Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
649                        + " for provider: " + info.provider);
650            } else {
651                // Scale down the preview to the dimensions we want
652                int imageWidth = drawable.getIntrinsicWidth();
653                int imageHeight = drawable.getIntrinsicHeight();
654                float aspect = (float) imageWidth / imageHeight;
655                int newWidth = imageWidth;
656                int newHeight = imageHeight;
657                if (aspect > 1f) {
658                    newWidth = expectedWidth;
659                    newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth));
660                } else {
661                    newHeight = expectedHeight;
662                    newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight));
663                }
664
665                Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
666                renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f);
667                newDrawable = new FastBitmapDrawable(preview);
668                newDrawable.setBounds(0, 0, newWidth, newHeight);
669                mWidgetPreviewCache.put(info, preview);
670            }
671        }
672
673        // Generate a preview image if we couldn't load one
674        if (drawable == null) {
675            Resources resources = mLauncher.getResources();
676            int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
677
678            // Specify the dimensions of the bitmap
679            if (info.minWidth >= info.minHeight) {
680                expectedWidth = cellWidth;
681                expectedHeight = mWidgetPreviewIconPaddedDimension;
682            } else {
683                // Note that in vertical widgets, we might not have enough space due to the text
684                // label, so be conservative and use the width as a height bound
685                expectedWidth = mWidgetPreviewIconPaddedDimension;
686                expectedHeight = cellWidth;
687            }
688
689            Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
690            renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth,
691                    expectedHeight, 1f,1f);
692
693            // Draw the icon in the top left corner
694            try {
695                Drawable icon = null;
696                if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null);
697                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
698
699                int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
700                renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
701            } catch (Resources.NotFoundException e) {}
702
703            newDrawable = new FastBitmapDrawable(preview);
704            newDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
705            mWidgetPreviewCache.put(info, preview);
706        }
707        return newDrawable;
708    }
709    public void syncWidgetPages() {
710        // Ensure that we have the right number of pages
711        Context context = getContext();
712        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
713        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
714        for (int i = 0; i < numPages; ++i) {
715            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
716                    mWidgetCountY);
717            setupPage(layout);
718            addView(layout);
719        }
720    }
721    public void syncWidgetPageItems(int page) {
722        PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page);
723        layout.removeAllViews();
724
725        // Calculate the dimensions of each cell we are giving to each widget
726        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
727        int offset = page * numWidgetsPerPage;
728        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
729                - ((mWidgetCountX - 1) * mCellWidthGap)) / mWidgetCountX);
730        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
731                - ((mWidgetCountY - 1) * mCellHeightGap)) / mWidgetCountY);
732        for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) {
733            Object rawInfo = mWidgets.get(offset + i);
734            PendingAddItemInfo createItemInfo = null;
735            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
736                    R.layout.apps_customize_widget, layout, false);
737            if (rawInfo instanceof AppWidgetProviderInfo) {
738                // Fill in the widget information
739                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
740                createItemInfo = new PendingAddWidgetInfo(info, null, null);
741                final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth,
742                        info.minHeight, null);
743                FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1],
744                        cellWidth, cellHeight);
745                widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false);
746                widget.setTag(createItemInfo);
747            } else if (rawInfo instanceof ResolveInfo) {
748                // Fill in the shortcuts information
749                ResolveInfo info = (ResolveInfo) rawInfo;
750                createItemInfo = new PendingAddItemInfo();
751                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
752                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
753                        info.activityInfo.name);
754                FastBitmapDrawable preview = getShortcutPreview(info, cellWidth, cellHeight);
755                widget.applyFromResolveInfo(mPackageManager, info, preview, null, false);
756                widget.setTag(createItemInfo);
757            }
758            widget.setOnClickListener(this);
759            widget.setOnLongClickListener(this);
760            widget.setOnTouchListener(this);
761
762            // Layout each widget
763            int ix = i % mWidgetCountX;
764            int iy = i / mWidgetCountX;
765            PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth,
766                    cellHeight);
767            lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap);
768            lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap);
769            layout.addView(widget, lp);
770        }
771    }
772
773    /*
774     * This method fetches an xml file specified in the manifest identified by
775     * WallpaperManager.WALLPAPER_PREVIEW_META_DATA). The xml file specifies
776     * an image which will be used as the wallpaper preview for an activity
777     * which responds to ACTION_SET_WALLPAPER. This image is returned and used
778     * in the customize drawer.
779     */
780    private Drawable parseWallpaperPreviewXml(ResolveInfo ri) {
781        ActivityInfo activityInfo = ri.activityInfo;
782        XmlResourceParser parser = null;
783        ComponentName component = new ComponentName(ri.activityInfo.packageName,
784                ri.activityInfo.name);
785        try {
786            parser = activityInfo.loadXmlMetaData(mPackageManager,
787                    WallpaperManager.WALLPAPER_PREVIEW_META_DATA);
788            if (parser == null) {
789                Slog.w(LOG_TAG, "No " + WallpaperManager.WALLPAPER_PREVIEW_META_DATA
790                        + " meta-data for " + "wallpaper provider '" + component + '\'');
791                return null;
792            }
793
794            AttributeSet attrs = Xml.asAttributeSet(parser);
795
796            int type;
797            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
798                    && type != XmlPullParser.START_TAG) {
799                // drain whitespace, comments, etc.
800            }
801
802            String nodeName = parser.getName();
803            if (!"wallpaper-preview".equals(nodeName)) {
804                Slog.w(LOG_TAG, "Meta-data does not start with wallpaper-preview tag for "
805                        + "wallpaper provider '" + component + '\'');
806                return null;
807            }
808
809            // If metaData was null, we would have returned earlier when getting
810            // the parser No need to do the check here
811            Resources res = mPackageManager.getResourcesForApplication(
812                    activityInfo.applicationInfo);
813
814            TypedArray sa = res.obtainAttributes(attrs,
815                    com.android.internal.R.styleable.WallpaperPreviewInfo);
816
817            TypedValue value = sa.peekValue(
818                    com.android.internal.R.styleable.WallpaperPreviewInfo_staticWallpaperPreview);
819            if (value == null) return null;
820
821            return res.getDrawable(value.resourceId);
822        } catch (Exception e) {
823            Slog.w(LOG_TAG, "XML parsing failed for wallpaper provider '" + component + '\'', e);
824            return null;
825        } finally {
826            if (parser != null) parser.close();
827        }
828    }
829    private FastBitmapDrawable getWallpaperPreview(ResolveInfo info, int cellWidth, int cellHeight){
830        // Return the cached version if necessary
831        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
832        if (cachedBitmap != null) {
833            return new FastBitmapDrawable(cachedBitmap);
834        }
835
836        // Get the preview
837        Resources resources = getContext().getResources();
838        Drawable wallpaperPreview = parseWallpaperPreviewXml(info);
839        Drawable wallpaperIcon = null;
840        int expectedWidth;
841        int expectedHeight;
842        if (wallpaperPreview != null) {
843            expectedWidth = wallpaperPreview.getIntrinsicWidth();
844            expectedHeight = wallpaperPreview.getIntrinsicHeight();
845        } else {
846            wallpaperPreview = mDefaultWidgetBackground;
847            expectedWidth = expectedHeight = Math.min(cellWidth, cellHeight);
848
849            // Draw the icon in the top left corner
850            String packageName = info.activityInfo.packageName;
851            try {
852                if (info.icon > 0) {
853                    wallpaperIcon = mPackageManager.getDrawable(packageName, info.icon, null);
854                }
855                if (wallpaperIcon == null) {
856                    wallpaperIcon = resources.getDrawable(R.drawable.ic_launcher_application);
857                }
858            } catch (Resources.NotFoundException e) {}
859        }
860
861        // Create the bitmap
862        Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
863        renderDrawableToBitmap(wallpaperPreview, preview, 0, 0, expectedWidth, expectedHeight,
864                1f, 1f);
865        if (wallpaperIcon != null) {
866            int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
867            int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
868            renderDrawableToBitmap(wallpaperIcon, preview, offset, offset, iconSize, iconSize,
869                    1f, 1f);
870        }
871
872        FastBitmapDrawable previewDrawable = new FastBitmapDrawable(preview);
873        previewDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
874        mWidgetPreviewCache.put(info, preview);
875        return previewDrawable;
876    }
877    /*
878     * Wallpapers PagedView implementation
879     */
880    public void syncWallpaperPages() {
881        // Ensure that we have the right number of pages
882        Context context = getContext();
883        int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY;
884        int numPages = (int) Math.ceil(mWallpapers.size() / (float) numWidgetsPerPage);
885        for (int i = 0; i < numPages; ++i) {
886            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWallpaperCountX,
887                    mWallpaperCountY);
888            setupPage(layout);
889            addView(layout);
890        }
891    }
892    public void syncWallpaperPageItems(int page) {
893        PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page);
894        layout.removeAllViews();
895
896        // Calculate the dimensions of each cell we are giving to each widget
897        int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY;
898        int offset = page * numWidgetsPerPage;
899        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
900                - ((mWallpaperCountX - 1) * mCellWidthGap)) / mWallpaperCountX);
901        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
902                - ((mWallpaperCountY - 1) * mCellHeightGap)) / mWallpaperCountY);
903        for (int i = 0; i < Math.min(numWidgetsPerPage, mWallpapers.size() - offset); ++i) {
904            ResolveInfo info = mWallpapers.get(offset + i);
905            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
906                    R.layout.apps_customize_wallpaper, layout, false);
907
908            // Fill in the shortcuts information
909            FastBitmapDrawable preview = getWallpaperPreview(info, cellWidth, cellHeight);
910            widget.applyFromResolveInfo(mPackageManager, info, preview, null, false);
911            widget.setTag(info);
912            widget.setOnClickListener(this);
913            widget.setOnLongClickListener(this);
914            widget.setOnTouchListener(this);
915
916            // Layout each widget
917            int ix = i % mWallpaperCountX;
918            int iy = i / mWallpaperCountX;
919            PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth,
920                    cellHeight);
921            lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap);
922            lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap);
923            layout.addView(widget, lp);
924        }
925    }
926
927    @Override
928    public void syncPages() {
929        removeAllViews();
930        switch (mContentType) {
931        case Applications:
932            syncAppsPages();
933            break;
934        case Widgets:
935            syncWidgetPages();
936            break;
937        case Wallpapers:
938            syncWallpaperPages();
939            break;
940        }
941    }
942    @Override
943    public void syncPageItems(int page) {
944        switch (mContentType) {
945        case Applications:
946            syncAppsPageItems(page);
947            break;
948        case Widgets:
949            syncWidgetPageItems(page);
950            break;
951        case Wallpapers:
952            syncWallpaperPageItems(page);
953            break;
954        }
955    }
956
957    /**
958     * Used by the parent to get the content width to set the tab bar to
959     * @return
960     */
961    public int getPageContentWidth() {
962        return mContentWidth;
963    }
964
965    /*
966     * AllAppsView implementation
967     */
968    @Override
969    public void setup(Launcher launcher, DragController dragController) {
970        mLauncher = launcher;
971        mDragController = dragController;
972    }
973    @Override
974    public void zoom(float zoom, boolean animate) {
975        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
976    }
977    @Override
978    public boolean isVisible() {
979        return (getVisibility() == VISIBLE);
980    }
981    @Override
982    public boolean isAnimating() {
983        return false;
984    }
985    @Override
986    public void setApps(ArrayList<ApplicationInfo> list) {
987        mApps = list;
988        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
989        invalidatePageData();
990    }
991    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
992        // We add it in place, in alphabetical order
993        int count = list.size();
994        for (int i = 0; i < count; ++i) {
995            ApplicationInfo info = list.get(i);
996            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
997            if (index < 0) {
998                mApps.add(-(index + 1), info);
999            }
1000        }
1001    }
1002    @Override
1003    public void addApps(ArrayList<ApplicationInfo> list) {
1004        addAppsWithoutInvalidate(list);
1005        invalidatePageData();
1006    }
1007    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1008        ComponentName removeComponent = item.intent.getComponent();
1009        int length = list.size();
1010        for (int i = 0; i < length; ++i) {
1011            ApplicationInfo info = list.get(i);
1012            if (info.intent.getComponent().equals(removeComponent)) {
1013                return i;
1014            }
1015        }
1016        return -1;
1017    }
1018    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1019        // loop through all the apps and remove apps that have the same component
1020        int length = list.size();
1021        for (int i = 0; i < length; ++i) {
1022            ApplicationInfo info = list.get(i);
1023            int removeIndex = findAppByComponent(mApps, info);
1024            if (removeIndex > -1) {
1025                mApps.remove(removeIndex);
1026                mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info));
1027            }
1028        }
1029    }
1030    @Override
1031    public void removeApps(ArrayList<ApplicationInfo> list) {
1032        removeAppsWithoutInvalidate(list);
1033        invalidatePageData();
1034    }
1035    @Override
1036    public void updateApps(ArrayList<ApplicationInfo> list) {
1037        // We remove and re-add the updated applications list because it's properties may have
1038        // changed (ie. the title), and this will ensure that the items will be in their proper
1039        // place in the list.
1040        removeAppsWithoutInvalidate(list);
1041        addAppsWithoutInvalidate(list);
1042        invalidatePageData();
1043    }
1044    @Override
1045    public void reset() {
1046        if (mContentType != ContentType.Applications) {
1047            // Reset to the first page of the Apps pane
1048            AppsCustomizeTabHost tabs = (AppsCustomizeTabHost)
1049                    mLauncher.findViewById(R.id.apps_customize_pane);
1050            tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications));
1051        } else {
1052            setCurrentPage(0);
1053            invalidatePageData();
1054        }
1055    }
1056    @Override
1057    public void dumpState() {
1058        // TODO: Dump information related to current list of Applications, Widgets, etc.
1059        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
1060        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
1061    }
1062    private void dumpAppWidgetProviderInfoList(String tag, String label,
1063            List<Object> list) {
1064        Log.d(tag, label + " size=" + list.size());
1065        for (Object i: list) {
1066            if (i instanceof AppWidgetProviderInfo) {
1067                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1068                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1069                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1070                        + " initialLayout=" + info.initialLayout
1071                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1072            } else if (i instanceof ResolveInfo) {
1073                ResolveInfo info = (ResolveInfo) i;
1074                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1075                        + info.icon);
1076            }
1077        }
1078    }
1079    @Override
1080    public void surrender() {
1081        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1082        // should stop this now.
1083    }
1084}
1085