AppsCustomizePagedView.java revision 46af2e89164b391b7a0049c6ce9048a2b7a7f644
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.isScreenXLarge()) {
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                    isHardwareAccelerated() && (numPages > 1));
566        }
567    }
568    /*
569     * Widgets PagedView implementation
570     */
571    private void setupPage(PagedViewGridLayout layout) {
572        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
573                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
574
575        // Note: We force a measure here to get around the fact that when we do layout calculations
576        // immediately after syncing, we don't have a proper width.  That said, we already know the
577        // expected page width, so we can actually optimize by hiding all the TextView-based
578        // children that are expensive to measure, and let that happen naturally later.
579        setVisibilityOnChildren(layout, View.GONE);
580        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
581        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
582        layout.setMinimumWidth(getPageContentWidth());
583        layout.measure(widthSpec, heightSpec);
584        setVisibilityOnChildren(layout, View.VISIBLE);
585    }
586    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
587            float scaleX, float scaleY) {
588        Canvas c = new Canvas();
589        if (bitmap != null) c.setBitmap(bitmap);
590        c.save();
591        c.scale(scaleX, scaleY);
592        Rect oldBounds = d.copyBounds();
593        d.setBounds(x, y, x + w, y + h);
594        d.draw(c);
595        d.setBounds(oldBounds); // Restore the bounds
596        c.restore();
597    }
598    private FastBitmapDrawable getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) {
599        // Return the cached version if necessary
600        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
601        if (cachedBitmap != null) {
602            return new FastBitmapDrawable(cachedBitmap);
603        }
604
605        Resources resources = mLauncher.getResources();
606        int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
607        // We only need to make it wide enough so as not allow the preview to be scaled
608        int expectedWidth = cellWidth;
609        int expectedHeight = mWidgetPreviewIconPaddedDimension;
610        int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
611
612        // Render the icon
613        Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
614        Drawable icon = mIconCache.getFullResIcon(info, mPackageManager);
615        renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0,
616                mWidgetPreviewIconPaddedDimension, mWidgetPreviewIconPaddedDimension, 1f, 1f);
617        renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
618        FastBitmapDrawable iconDrawable = new FastBitmapDrawable(preview);
619        iconDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
620        mWidgetPreviewCache.put(info, preview);
621        return iconDrawable;
622    }
623    private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan,
624            int cellVSpan, int cellWidth, int cellHeight) {
625        // Return the cached version if necessary
626        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
627        if (cachedBitmap != null) {
628            return new FastBitmapDrawable(cachedBitmap);
629        }
630
631        // Calculate the size of the drawable
632        cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan));
633        cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan));
634        int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
635        int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
636
637        // Scale down the bitmap to fit the space
638        float widgetPreviewScale = (float) cellWidth / expectedWidth;
639        expectedWidth = (int) (widgetPreviewScale * expectedWidth);
640        expectedHeight = (int) (widgetPreviewScale * expectedHeight);
641
642        // Load the preview image if possible
643        String packageName = info.provider.getPackageName();
644        Drawable drawable = null;
645        FastBitmapDrawable newDrawable = null;
646        if (info.previewImage != 0) {
647            drawable = mPackageManager.getDrawable(packageName, info.previewImage, null);
648            if (drawable == null) {
649                Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
650                        + " for provider: " + info.provider);
651            } else {
652                // Scale down the preview to the dimensions we want
653                int imageWidth = drawable.getIntrinsicWidth();
654                int imageHeight = drawable.getIntrinsicHeight();
655                float aspect = (float) imageWidth / imageHeight;
656                int newWidth = imageWidth;
657                int newHeight = imageHeight;
658                if (aspect > 1f) {
659                    newWidth = expectedWidth;
660                    newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth));
661                } else {
662                    newHeight = expectedHeight;
663                    newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight));
664                }
665
666                Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
667                renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f);
668                newDrawable = new FastBitmapDrawable(preview);
669                newDrawable.setBounds(0, 0, newWidth, newHeight);
670                mWidgetPreviewCache.put(info, preview);
671            }
672        }
673
674        // Generate a preview image if we couldn't load one
675        if (drawable == null) {
676            Resources resources = mLauncher.getResources();
677            int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
678
679            // Specify the dimensions of the bitmap
680            if (info.minWidth >= info.minHeight) {
681                expectedWidth = cellWidth;
682                expectedHeight = mWidgetPreviewIconPaddedDimension;
683            } else {
684                // Note that in vertical widgets, we might not have enough space due to the text
685                // label, so be conservative and use the width as a height bound
686                expectedWidth = mWidgetPreviewIconPaddedDimension;
687                expectedHeight = cellWidth;
688            }
689
690            Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
691            renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth,
692                    expectedHeight, 1f,1f);
693
694            // Draw the icon in the top left corner
695            try {
696                Drawable icon = null;
697                if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null);
698                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
699
700                int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
701                renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
702            } catch (Resources.NotFoundException e) {}
703
704            newDrawable = new FastBitmapDrawable(preview);
705            newDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
706            mWidgetPreviewCache.put(info, preview);
707        }
708        return newDrawable;
709    }
710    public void syncWidgetPages() {
711        // Ensure that we have the right number of pages
712        Context context = getContext();
713        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
714        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
715        for (int i = 0; i < numPages; ++i) {
716            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
717                    mWidgetCountY);
718            setupPage(layout);
719            addView(layout);
720        }
721    }
722    public void syncWidgetPageItems(int page) {
723        PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page);
724        layout.removeAllViews();
725
726        // Calculate the dimensions of each cell we are giving to each widget
727        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
728        int offset = page * numWidgetsPerPage;
729        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
730                - ((mWidgetCountX - 1) * mCellWidthGap)) / mWidgetCountX);
731        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
732                - ((mWidgetCountY - 1) * mCellHeightGap)) / mWidgetCountY);
733        for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) {
734            Object rawInfo = mWidgets.get(offset + i);
735            PendingAddItemInfo createItemInfo = null;
736            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
737                    R.layout.apps_customize_widget, layout, false);
738            if (rawInfo instanceof AppWidgetProviderInfo) {
739                // Fill in the widget information
740                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
741                createItemInfo = new PendingAddWidgetInfo(info, null, null);
742                final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth,
743                        info.minHeight, null);
744                FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1],
745                        cellWidth, cellHeight);
746                widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false);
747                widget.setTag(createItemInfo);
748            } else if (rawInfo instanceof ResolveInfo) {
749                // Fill in the shortcuts information
750                ResolveInfo info = (ResolveInfo) rawInfo;
751                createItemInfo = new PendingAddItemInfo();
752                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
753                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
754                        info.activityInfo.name);
755                FastBitmapDrawable preview = getShortcutPreview(info, cellWidth, cellHeight);
756                widget.applyFromResolveInfo(mPackageManager, info, preview, null, false);
757                widget.setTag(createItemInfo);
758            }
759            widget.setOnClickListener(this);
760            widget.setOnLongClickListener(this);
761            widget.setOnTouchListener(this);
762
763            // Layout each widget
764            int ix = i % mWidgetCountX;
765            int iy = i / mWidgetCountX;
766            PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth,
767                    cellHeight);
768            lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap);
769            lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap);
770            layout.addView(widget, lp);
771        }
772    }
773
774    /*
775     * This method fetches an xml file specified in the manifest identified by
776     * WallpaperManager.WALLPAPER_PREVIEW_META_DATA). The xml file specifies
777     * an image which will be used as the wallpaper preview for an activity
778     * which responds to ACTION_SET_WALLPAPER. This image is returned and used
779     * in the customize drawer.
780     */
781    private Drawable parseWallpaperPreviewXml(ResolveInfo ri) {
782        ActivityInfo activityInfo = ri.activityInfo;
783        XmlResourceParser parser = null;
784        ComponentName component = new ComponentName(ri.activityInfo.packageName,
785                ri.activityInfo.name);
786        try {
787            parser = activityInfo.loadXmlMetaData(mPackageManager,
788                    WallpaperManager.WALLPAPER_PREVIEW_META_DATA);
789            if (parser == null) {
790                Slog.w(LOG_TAG, "No " + WallpaperManager.WALLPAPER_PREVIEW_META_DATA
791                        + " meta-data for " + "wallpaper provider '" + component + '\'');
792                return null;
793            }
794
795            AttributeSet attrs = Xml.asAttributeSet(parser);
796
797            int type;
798            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
799                    && type != XmlPullParser.START_TAG) {
800                // drain whitespace, comments, etc.
801            }
802
803            String nodeName = parser.getName();
804            if (!"wallpaper-preview".equals(nodeName)) {
805                Slog.w(LOG_TAG, "Meta-data does not start with wallpaper-preview tag for "
806                        + "wallpaper provider '" + component + '\'');
807                return null;
808            }
809
810            // If metaData was null, we would have returned earlier when getting
811            // the parser No need to do the check here
812            Resources res = mPackageManager.getResourcesForApplication(
813                    activityInfo.applicationInfo);
814
815            TypedArray sa = res.obtainAttributes(attrs,
816                    com.android.internal.R.styleable.WallpaperPreviewInfo);
817
818            TypedValue value = sa.peekValue(
819                    com.android.internal.R.styleable.WallpaperPreviewInfo_staticWallpaperPreview);
820            if (value == null) return null;
821
822            return res.getDrawable(value.resourceId);
823        } catch (Exception e) {
824            Slog.w(LOG_TAG, "XML parsing failed for wallpaper provider '" + component + '\'', e);
825            return null;
826        } finally {
827            if (parser != null) parser.close();
828        }
829    }
830    private FastBitmapDrawable getWallpaperPreview(ResolveInfo info, int cellWidth, int cellHeight){
831        // Return the cached version if necessary
832        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
833        if (cachedBitmap != null) {
834            return new FastBitmapDrawable(cachedBitmap);
835        }
836
837        // Get the preview
838        Resources resources = getContext().getResources();
839        Drawable wallpaperPreview = parseWallpaperPreviewXml(info);
840        Drawable wallpaperIcon = null;
841        int expectedWidth;
842        int expectedHeight;
843        if (wallpaperPreview != null) {
844            expectedWidth = wallpaperPreview.getIntrinsicWidth();
845            expectedHeight = wallpaperPreview.getIntrinsicHeight();
846        } else {
847            wallpaperPreview = mDefaultWidgetBackground;
848            expectedWidth = expectedHeight = Math.min(cellWidth, cellHeight);
849
850            // Draw the icon in the top left corner
851            String packageName = info.activityInfo.packageName;
852            try {
853                if (info.icon > 0) {
854                    wallpaperIcon = mPackageManager.getDrawable(packageName, info.icon, null);
855                }
856                if (wallpaperIcon == null) {
857                    wallpaperIcon = resources.getDrawable(R.drawable.ic_launcher_application);
858                }
859            } catch (Resources.NotFoundException e) {}
860        }
861
862        // Create the bitmap
863        Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
864        renderDrawableToBitmap(wallpaperPreview, preview, 0, 0, expectedWidth, expectedHeight,
865                1f, 1f);
866        if (wallpaperIcon != null) {
867            int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
868            int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
869            renderDrawableToBitmap(wallpaperIcon, preview, offset, offset, iconSize, iconSize,
870                    1f, 1f);
871        }
872
873        FastBitmapDrawable previewDrawable = new FastBitmapDrawable(preview);
874        previewDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
875        mWidgetPreviewCache.put(info, preview);
876        return previewDrawable;
877    }
878    /*
879     * Wallpapers PagedView implementation
880     */
881    public void syncWallpaperPages() {
882        // Ensure that we have the right number of pages
883        Context context = getContext();
884        int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY;
885        int numPages = (int) Math.ceil(mWallpapers.size() / (float) numWidgetsPerPage);
886        for (int i = 0; i < numPages; ++i) {
887            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWallpaperCountX,
888                    mWallpaperCountY);
889            setupPage(layout);
890            addView(layout);
891        }
892    }
893    public void syncWallpaperPageItems(int page) {
894        PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page);
895        layout.removeAllViews();
896
897        // Calculate the dimensions of each cell we are giving to each widget
898        int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY;
899        int offset = page * numWidgetsPerPage;
900        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
901                - ((mWallpaperCountX - 1) * mCellWidthGap)) / mWallpaperCountX);
902        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
903                - ((mWallpaperCountY - 1) * mCellHeightGap)) / mWallpaperCountY);
904        for (int i = 0; i < Math.min(numWidgetsPerPage, mWallpapers.size() - offset); ++i) {
905            ResolveInfo info = mWallpapers.get(offset + i);
906            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
907                    R.layout.apps_customize_wallpaper, layout, false);
908
909            // Fill in the shortcuts information
910            FastBitmapDrawable preview = getWallpaperPreview(info, cellWidth, cellHeight);
911            widget.applyFromResolveInfo(mPackageManager, info, preview, null, false);
912            widget.setTag(info);
913            widget.setOnClickListener(this);
914            widget.setOnLongClickListener(this);
915            widget.setOnTouchListener(this);
916
917            // Layout each widget
918            int ix = i % mWallpaperCountX;
919            int iy = i / mWallpaperCountX;
920            PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth,
921                    cellHeight);
922            lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap);
923            lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap);
924            layout.addView(widget, lp);
925        }
926    }
927
928    @Override
929    public void syncPages() {
930        removeAllViews();
931        switch (mContentType) {
932        case Applications:
933            syncAppsPages();
934            break;
935        case Widgets:
936            syncWidgetPages();
937            break;
938        case Wallpapers:
939            syncWallpaperPages();
940            break;
941        }
942    }
943    @Override
944    public void syncPageItems(int page) {
945        switch (mContentType) {
946        case Applications:
947            syncAppsPageItems(page);
948            break;
949        case Widgets:
950            syncWidgetPageItems(page);
951            break;
952        case Wallpapers:
953            syncWallpaperPageItems(page);
954            break;
955        }
956    }
957
958    /**
959     * Used by the parent to get the content width to set the tab bar to
960     * @return
961     */
962    public int getPageContentWidth() {
963        return mContentWidth;
964    }
965
966    /*
967     * AllAppsView implementation
968     */
969    @Override
970    public void setup(Launcher launcher, DragController dragController) {
971        mLauncher = launcher;
972        mDragController = dragController;
973    }
974    @Override
975    public void zoom(float zoom, boolean animate) {
976        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
977    }
978    @Override
979    public boolean isVisible() {
980        return (getVisibility() == VISIBLE);
981    }
982    @Override
983    public boolean isAnimating() {
984        return false;
985    }
986    @Override
987    public void setApps(ArrayList<ApplicationInfo> list) {
988        mApps = list;
989        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
990        invalidatePageData();
991    }
992    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
993        // We add it in place, in alphabetical order
994        int count = list.size();
995        for (int i = 0; i < count; ++i) {
996            ApplicationInfo info = list.get(i);
997            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
998            if (index < 0) {
999                mApps.add(-(index + 1), info);
1000            }
1001        }
1002    }
1003    @Override
1004    public void addApps(ArrayList<ApplicationInfo> list) {
1005        addAppsWithoutInvalidate(list);
1006        invalidatePageData();
1007    }
1008    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
1009        ComponentName removeComponent = item.intent.getComponent();
1010        int length = list.size();
1011        for (int i = 0; i < length; ++i) {
1012            ApplicationInfo info = list.get(i);
1013            if (info.intent.getComponent().equals(removeComponent)) {
1014                return i;
1015            }
1016        }
1017        return -1;
1018    }
1019    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
1020        // loop through all the apps and remove apps that have the same component
1021        int length = list.size();
1022        for (int i = 0; i < length; ++i) {
1023            ApplicationInfo info = list.get(i);
1024            int removeIndex = findAppByComponent(mApps, info);
1025            if (removeIndex > -1) {
1026                mApps.remove(removeIndex);
1027                mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info));
1028            }
1029        }
1030    }
1031    @Override
1032    public void removeApps(ArrayList<ApplicationInfo> list) {
1033        removeAppsWithoutInvalidate(list);
1034        invalidatePageData();
1035    }
1036    @Override
1037    public void updateApps(ArrayList<ApplicationInfo> list) {
1038        // We remove and re-add the updated applications list because it's properties may have
1039        // changed (ie. the title), and this will ensure that the items will be in their proper
1040        // place in the list.
1041        removeAppsWithoutInvalidate(list);
1042        addAppsWithoutInvalidate(list);
1043        invalidatePageData();
1044    }
1045    @Override
1046    public void reset() {
1047        if (mContentType != ContentType.Applications) {
1048            // Reset to the first page of the Apps pane
1049            AppsCustomizeTabHost tabs = (AppsCustomizeTabHost)
1050                    mLauncher.findViewById(R.id.apps_customize_pane);
1051            tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications));
1052        } else {
1053            setCurrentPage(0);
1054            invalidatePageData();
1055        }
1056    }
1057    @Override
1058    public void dumpState() {
1059        // TODO: Dump information related to current list of Applications, Widgets, etc.
1060        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
1061        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
1062    }
1063    private void dumpAppWidgetProviderInfoList(String tag, String label,
1064            List<Object> list) {
1065        Log.d(tag, label + " size=" + list.size());
1066        for (Object i: list) {
1067            if (i instanceof AppWidgetProviderInfo) {
1068                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1069                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1070                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1071                        + " initialLayout=" + info.initialLayout
1072                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1073            } else if (i instanceof ResolveInfo) {
1074                ResolveInfo info = (ResolveInfo) i;
1075                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1076                        + info.icon);
1077            }
1078        }
1079    }
1080    @Override
1081    public void surrender() {
1082        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1083        // should stop this now.
1084    }
1085}
1086