AppsCustomizePagedView.java revision 007c69867d821ea2b271398577a8b3440b3a7046
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import android.animation.AnimatorSet;
20import android.animation.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.content.res.TypedArray;
33import android.graphics.Bitmap;
34import android.graphics.Bitmap.Config;
35import android.graphics.Canvas;
36import android.graphics.Rect;
37import android.graphics.drawable.Drawable;
38import android.util.AttributeSet;
39import android.util.Log;
40import android.util.LruCache;
41import android.view.LayoutInflater;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.animation.AccelerateInterpolator;
45import android.widget.ImageView;
46import android.widget.TextView;
47import android.widget.Toast;
48
49import com.android.launcher.R;
50import com.android.launcher2.DropTarget.DragObject;
51
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.Iterator;
55import java.util.List;
56
57public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
58        AllAppsView, View.OnClickListener, DragSource {
59    static final String LOG_TAG = "AppsCustomizePagedView";
60
61    /**
62     * The different content types that this paged view can show.
63     */
64    public enum ContentType {
65        Applications,
66        Widgets
67    }
68
69    // Refs
70    private Launcher mLauncher;
71    private DragController mDragController;
72    private final LayoutInflater mLayoutInflater;
73    private final PackageManager mPackageManager;
74
75    // Content
76    private ContentType mContentType;
77    private ArrayList<ApplicationInfo> mApps;
78    private List<Object> mWidgets;
79
80    // Caching
81    private Drawable mDefaultWidgetBackground;
82    private final int sWidgetPreviewCacheSize = 1 * 1024 * 1024; // 1 MiB
83    private LruCache<Object, Bitmap> mWidgetPreviewCache;
84    private IconCache mIconCache;
85
86    // Dimens
87    private int mContentWidth;
88    private int mMaxWidgetSpan, mMinWidgetSpan;
89    private int mWidgetWidthGap, mWidgetHeightGap;
90    private int mWidgetCountX, mWidgetCountY;
91    private final int mWidgetPreviewIconPaddedDimension;
92    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
93    private PagedViewCellLayout mWidgetSpacingLayout;
94
95    // Animations
96    private final float ANIMATION_SCALE = 0.5f;
97    private final int TRANSLATE_ANIM_DURATION = 400;
98    private final int DROP_ANIM_DURATION = 200;
99
100    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
101        super(context, attrs);
102        mLayoutInflater = LayoutInflater.from(context);
103        mPackageManager = context.getPackageManager();
104        mContentType = ContentType.Applications;
105        mApps = new ArrayList<ApplicationInfo>();
106        mWidgets = new ArrayList<Object>();
107        mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
108        mWidgetPreviewCache = new LruCache<Object, Bitmap>(sWidgetPreviewCacheSize) {
109            protected int sizeOf(Object key, Bitmap value) {
110                return value.getByteCount();
111            }
112        };
113
114        // Save the default widget preview background
115        Resources resources = context.getResources();
116        mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview);
117
118        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0);
119        // TODO-APPS_CUSTOMIZE: remove these unnecessary attrs after
120        mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6);
121        mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4);
122        a.recycle();
123        a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
124        mWidgetWidthGap =
125            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
126        mWidgetHeightGap =
127            a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
128        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
129        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
130        a.recycle();
131        mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
132
133        // The max widget span is the length N, such that NxN is the largest bounds that the widget
134        // preview can be before applying the widget scaling
135        mMinWidgetSpan = 1;
136        mMaxWidgetSpan = 3;
137
138        // The padding on the non-matched dimension for the default widget preview icons
139        // (top + bottom)
140        int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
141        mWidgetPreviewIconPaddedDimension =
142            (int) (iconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage)));
143    }
144
145    @Override
146    protected void init() {
147        super.init();
148        mCenterPagesVertically = false;
149
150        Context context = getContext();
151        Resources r = context.getResources();
152        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
153    }
154
155    @Override
156    protected void onWallpaperTap(android.view.MotionEvent ev) {
157        mLauncher.showWorkspace(true);
158    }
159
160    /**
161     * This differs from isDataReady as this is the test done if isDataReady is not set.
162     */
163    private boolean testDataReady() {
164        return !mApps.isEmpty() && !mWidgets.isEmpty();
165    }
166
167    protected void onDataReady(int width, int height) {
168        // Note that we transpose the counts in portrait so that we get a similar layout
169        boolean isLandscape = getResources().getConfiguration().orientation ==
170            Configuration.ORIENTATION_LANDSCAPE;
171        int maxCellCountX = Integer.MAX_VALUE;
172        int maxCellCountY = Integer.MAX_VALUE;
173        if (LauncherApplication.isScreenLarge()) {
174            maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
175                LauncherModel.getCellCountY());
176            maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
177                LauncherModel.getCellCountX());
178        }
179
180        // Now that the data is ready, we can calculate the content width, the number of cells to
181        // use for each page
182        mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
183        mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
184                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
185        mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
186        mCellCountX = mWidgetSpacingLayout.getCellCountX();
187        mCellCountY = mWidgetSpacingLayout.getCellCountY();
188        mWidgetCountX = Math.max(1, (int) Math.round(mCellCountX / 2f));
189        mWidgetCountY = Math.max(1, (int) Math.round(mCellCountY / 3f));
190        mContentWidth = mWidgetSpacingLayout.getContentWidth();
191
192        invalidatePageData();
193    }
194
195    @Override
196    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
197        int width = MeasureSpec.getSize(widthMeasureSpec);
198        int height = MeasureSpec.getSize(heightMeasureSpec);
199        if (!isDataReady()) {
200            if (testDataReady()) {
201                setDataIsReady();
202                setMeasuredDimension(width, height);
203                onDataReady(width, height);
204            }
205        }
206
207        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
208    }
209
210    /** Removes and returns the ResolveInfo with the specified ComponentName */
211    private ResolveInfo removeResolveInfoWithComponentName(List<ResolveInfo> list,
212            ComponentName cn) {
213        Iterator<ResolveInfo> iter = list.iterator();
214        while (iter.hasNext()) {
215            ResolveInfo rinfo = iter.next();
216            ActivityInfo info = rinfo.activityInfo;
217            ComponentName c = new ComponentName(info.packageName, info.name);
218            if (c.equals(cn)) {
219                iter.remove();
220                return rinfo;
221            }
222        }
223        return null;
224    }
225
226    public void onPackagesUpdated() {
227        // Get the list of widgets and shortcuts
228        mWidgets.clear();
229        List<AppWidgetProviderInfo> widgets =
230            AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
231        Collections.sort(widgets,
232                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
233        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
234        List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
235        Collections.sort(shortcuts,
236                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
237        mWidgets.addAll(widgets);
238        mWidgets.addAll(shortcuts);
239
240        // The next layout pass will trigger data-ready if both widgets and apps are set, so request
241        // a layout to do this test and invalidate the page data when ready.
242        if (testDataReady()) requestLayout();
243    }
244
245    @Override
246    public void onClick(View v) {
247        if (v instanceof PagedViewIcon) {
248            // Animate some feedback to the click
249            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
250            animateClickFeedback(v, new Runnable() {
251                @Override
252                public void run() {
253                    mLauncher.startActivitySafely(appInfo.intent, appInfo);
254                }
255            });
256        } else if (v instanceof PagedViewWidget) {
257            // Let the user know that they have to long press to add a widget
258            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
259                    Toast.LENGTH_SHORT).show();
260
261            // Create a little animation to show that the widget can move
262            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
263            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
264            AnimatorSet bounce = new AnimatorSet();
265            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
266            tyuAnim.setDuration(125);
267            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
268            tydAnim.setDuration(100);
269            bounce.play(tyuAnim).before(tydAnim);
270            bounce.setInterpolator(new AccelerateInterpolator());
271            bounce.start();
272        }
273    }
274
275    /*
276     * PagedViewWithDraggableItems implementation
277     */
278    @Override
279    protected void determineDraggingStart(android.view.MotionEvent ev) {
280        // Disable dragging by pulling an app down for now.
281    }
282    private void beginDraggingApplication(View v) {
283        // Make a copy of the ApplicationInfo
284        ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag());
285
286        // Compose the drag image (top compound drawable, index is 1)
287        final TextView tv = (TextView) v;
288        final Drawable icon = tv.getCompoundDrawables()[1];
289        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
290                Bitmap.Config.ARGB_8888);
291        Canvas c = new Canvas(b);
292        c.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop());
293        icon.draw(c);
294
295        // Compose the visible rect of the drag image
296        Rect dragRect = null;
297        if (v instanceof TextView) {
298            int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size);
299            int top = v.getPaddingTop();
300            int left = (b.getWidth() - iconSize) / 2;
301            int right = left + iconSize;
302            int bottom = top + iconSize;
303            dragRect = new Rect(left, top, right, bottom);
304        }
305
306        // Start the drag
307        mLauncher.lockScreenOrientation();
308        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
309        mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect);
310        b.recycle();
311    }
312    private void beginDraggingWidget(View v) {
313        // Get the widget preview as the drag representation
314        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
315        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
316
317        // Compose the drag image
318        Bitmap b;
319        Drawable preview = image.getDrawable();
320        int w = preview.getIntrinsicWidth();
321        int h = preview.getIntrinsicHeight();
322        if (createItemInfo instanceof PendingAddWidgetInfo) {
323            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
324            int[] spanXY = CellLayout.rectToCell(getResources(),
325                    createWidgetInfo.minWidth, createWidgetInfo.minHeight, null);
326            createItemInfo.spanX = spanXY[0];
327            createItemInfo.spanY = spanXY[1];
328
329            b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
330            renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1);
331        } else {
332            // Workaround for the fact that we don't keep the original ResolveInfo associated with
333            // the shortcut around.  To get the icon, we just render the preview image (which has
334            // the shortcut icon) to a new drag bitmap that clips the non-icon space.
335            b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension,
336                    mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888);
337            Canvas c = new Canvas(b);
338            preview.draw(c);
339            createItemInfo.spanX = createItemInfo.spanY = 1;
340        }
341
342        // Start the drag
343        mLauncher.lockScreenOrientation();
344        mLauncher.getWorkspace().onDragStartedWithItemSpans(createItemInfo.spanX,
345                createItemInfo.spanY, b);
346        mDragController.startDrag(image, b, this, createItemInfo,
347                DragController.DRAG_ACTION_COPY, null);
348        b.recycle();
349    }
350    @Override
351    protected boolean beginDragging(View v) {
352        if (!super.beginDragging(v)) return false;
353
354
355        if (v instanceof PagedViewIcon) {
356            beginDraggingApplication(v);
357        } else if (v instanceof PagedViewWidget) {
358            beginDraggingWidget(v);
359        }
360
361        // Go into spring loaded mode
362        int currentPageIndex = mLauncher.getWorkspace().getCurrentPage();
363        CellLayout currentPage = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPageIndex);
364        mLauncher.enterSpringLoadedDragMode(currentPage);
365        return true;
366    }
367    private void endDragging(boolean success) {
368        post(new Runnable() {
369            // Once the drag operation has fully completed, hence the post, we want to disable the
370            // deleteZone and the appInfoButton in all apps, and re-enable the instance which
371            // live in the workspace
372            public void run() {
373                // if onDestroy was called on Launcher, we might have already deleted the
374                // all apps delete zone / info button, so check if they are null
375                DeleteZone allAppsDeleteZone =
376                        (DeleteZone) mLauncher.findViewById(R.id.all_apps_delete_zone);
377                ApplicationInfoDropTarget allAppsInfoButton =
378                    (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target);
379
380                if (allAppsDeleteZone != null) allAppsDeleteZone.setDragAndDropEnabled(false);
381                if (allAppsInfoButton != null) allAppsInfoButton.setDragAndDropEnabled(false);
382            }
383        });
384        mLauncher.exitSpringLoadedDragMode();
385        mLauncher.getWorkspace().onDragStopped(success);
386        mLauncher.unlockScreenOrientation();
387
388    }
389
390    /*
391     * DragSource implementation
392     */
393    @Override
394    public void onDragViewVisible() {}
395    @Override
396    public void onDropCompleted(View target, DragObject d, boolean success) {
397        endDragging(success);
398
399        // Display an error message if the drag failed due to there not being enough space on the
400        // target layout we were dropping on.
401        if (!success) {
402            boolean showOutOfSpaceMessage = false;
403            if (target instanceof Workspace) {
404                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
405                Workspace workspace = (Workspace) target;
406                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
407                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
408                if (layout != null) {
409                    layout.calculateSpans(itemInfo);
410                    showOutOfSpaceMessage =
411                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
412                }
413            }
414            // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later.
415            if (showOutOfSpaceMessage) {
416                mLauncher.showOutOfSpaceMessage();
417            }
418        }
419    }
420
421    public void setContentType(ContentType type) {
422        mContentType = type;
423        setCurrentPage(0);
424        invalidatePageData();
425    }
426
427    /*
428     * Apps PagedView implementation
429     */
430    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
431        int childCount = layout.getChildCount();
432        for (int i = 0; i < childCount; ++i) {
433            layout.getChildAt(i).setVisibility(visibility);
434        }
435    }
436    private void setupPage(PagedViewCellLayout layout) {
437        layout.setCellCount(mCellCountX, mCellCountY);
438        layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
439        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
440                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
441
442        // Note: We force a measure here to get around the fact that when we do layout calculations
443        // immediately after syncing, we don't have a proper width.  That said, we already know the
444        // expected page width, so we can actually optimize by hiding all the TextView-based
445        // children that are expensive to measure, and let that happen naturally later.
446        setVisibilityOnChildren(layout, View.GONE);
447        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
448        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
449        layout.setMinimumWidth(getPageContentWidth());
450        layout.measure(widthSpec, heightSpec);
451        setVisibilityOnChildren(layout, View.VISIBLE);
452    }
453    public void syncAppsPages() {
454        // Ensure that we have the right number of pages
455        Context context = getContext();
456        int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
457        for (int i = 0; i < numPages; ++i) {
458            PagedViewCellLayout layout = new PagedViewCellLayout(context);
459            setupPage(layout);
460            addView(layout);
461        }
462    }
463    public void syncAppsPageItems(int page) {
464        // ensure that we have the right number of items on the pages
465        int numPages = getPageCount();
466        int numCells = mCellCountX * mCellCountY;
467        int startIndex = page * numCells;
468        int endIndex = Math.min(startIndex + numCells, mApps.size());
469        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
470        layout.removeAllViewsOnPage();
471        for (int i = startIndex; i < endIndex; ++i) {
472            ApplicationInfo info = mApps.get(i);
473            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
474                    R.layout.apps_customize_application, layout, false);
475            icon.applyFromApplicationInfo(
476                    info, mPageViewIconCache, true, (numPages > 1));
477            icon.setOnClickListener(this);
478            icon.setOnLongClickListener(this);
479            icon.setOnTouchListener(this);
480
481            int index = i - startIndex;
482            int x = index % mCellCountX;
483            int y = index / mCellCountX;
484            layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
485        }
486
487        // Create the hardware layers
488        layout.allowHardwareLayerCreation();
489        layout.createHardwareLayers();
490    }
491    /*
492     * Widgets PagedView implementation
493     */
494    private void setupPage(PagedViewGridLayout layout) {
495        layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
496                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
497
498        // Note: We force a measure here to get around the fact that when we do layout calculations
499        // immediately after syncing, we don't have a proper width.  That said, we already know the
500        // expected page width, so we can actually optimize by hiding all the TextView-based
501        // children that are expensive to measure, and let that happen naturally later.
502        setVisibilityOnChildren(layout, View.GONE);
503        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
504        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
505        layout.setMinimumWidth(getPageContentWidth());
506        layout.measure(widthSpec, heightSpec);
507        setVisibilityOnChildren(layout, View.VISIBLE);
508    }
509    private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
510            float scaleX, float scaleY) {
511        Canvas c = new Canvas();
512        if (bitmap != null) c.setBitmap(bitmap);
513        c.save();
514        c.scale(scaleX, scaleY);
515        Rect oldBounds = d.copyBounds();
516        d.setBounds(x, y, x + w, y + h);
517        d.draw(c);
518        d.setBounds(oldBounds); // Restore the bounds
519        c.restore();
520    }
521    private FastBitmapDrawable getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) {
522        // Return the cached version if necessary
523        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
524        if (cachedBitmap != null) {
525            return new FastBitmapDrawable(cachedBitmap);
526        }
527
528        Resources resources = mLauncher.getResources();
529        int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
530        // We only need to make it wide enough so as not allow the preview to be scaled
531        int expectedWidth = cellWidth;
532        int expectedHeight = mWidgetPreviewIconPaddedDimension;
533
534        // Render the icon
535        Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
536        Drawable icon = mIconCache.getFullResIcon(info, mPackageManager);
537        renderDrawableToBitmap(icon, preview, 0, 0, iconSize, iconSize, 1f, 1f);
538        FastBitmapDrawable iconDrawable = new FastBitmapDrawable(preview);
539        iconDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
540        mWidgetPreviewCache.put(info, preview);
541        return iconDrawable;
542    }
543    private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan,
544            int cellVSpan, int cellWidth, int cellHeight) {
545        // Return the cached version if necessary
546        Bitmap cachedBitmap = mWidgetPreviewCache.get(info);
547        if (cachedBitmap != null) {
548            return new FastBitmapDrawable(cachedBitmap);
549        }
550
551        // Calculate the size of the drawable
552        cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan));
553        cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan));
554        int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
555        int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
556
557        // Scale down the bitmap to fit the space
558        float widgetPreviewScale = (float) cellWidth / expectedWidth;
559        expectedWidth = (int) (widgetPreviewScale * expectedWidth);
560        expectedHeight = (int) (widgetPreviewScale * expectedHeight);
561
562        // Load the preview image if possible
563        String packageName = info.provider.getPackageName();
564        Drawable drawable = null;
565        FastBitmapDrawable newDrawable = null;
566        if (info.previewImage != 0) {
567            drawable = mPackageManager.getDrawable(packageName, info.previewImage, null);
568            if (drawable == null) {
569                Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
570                        + " for provider: " + info.provider);
571            } else {
572                // Scale down the preview to the dimensions we want
573                int imageWidth = drawable.getIntrinsicWidth();
574                int imageHeight = drawable.getIntrinsicHeight();
575                float aspect = (float) imageWidth / imageHeight;
576                int newWidth = imageWidth;
577                int newHeight = imageHeight;
578                if (aspect > 1f) {
579                    newWidth = expectedWidth;
580                    newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth));
581                } else {
582                    newHeight = expectedHeight;
583                    newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight));
584                }
585
586                Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
587                renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f);
588                newDrawable = new FastBitmapDrawable(preview);
589                newDrawable.setBounds(0, 0, newWidth, newHeight);
590                mWidgetPreviewCache.put(info, preview);
591            }
592        }
593
594        // Generate a preview image if we couldn't load one
595        if (drawable == null) {
596            Resources resources = mLauncher.getResources();
597            int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
598
599            // Specify the dimensions of the bitmap
600            if (info.minWidth >= info.minHeight) {
601                expectedWidth = cellWidth;
602                expectedHeight = mWidgetPreviewIconPaddedDimension;
603            } else {
604                // Note that in vertical widgets, we might not have enough space due to the text
605                // label, so be conservative and use the width as a height bound
606                expectedWidth = mWidgetPreviewIconPaddedDimension;
607                expectedHeight = cellWidth;
608            }
609
610            Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888);
611            renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth,
612                    expectedHeight, 1f,1f);
613
614            // Draw the icon in the top left corner
615            try {
616                Drawable icon = null;
617                if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null);
618                if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
619
620                int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage);
621                renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f);
622            } catch (Resources.NotFoundException e) {}
623
624            newDrawable = new FastBitmapDrawable(preview);
625            newDrawable.setBounds(0, 0, expectedWidth, expectedHeight);
626            mWidgetPreviewCache.put(info, preview);
627        }
628        return newDrawable;
629    }
630    public void syncWidgetPages() {
631        // Ensure that we have the right number of pages
632        Context context = getContext();
633        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
634        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
635        for (int i = 0; i < numPages; ++i) {
636            PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
637                    mWidgetCountY);
638            setupPage(layout);
639            addView(layout);
640        }
641    }
642    public void syncWidgetPageItems(int page) {
643        PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page);
644        layout.removeAllViews();
645
646        // Calculate the dimensions of each cell we are giving to each widget
647        int numWidgetsPerPage = mWidgetCountX * mWidgetCountY;
648        int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage);
649        int offset = page * numWidgetsPerPage;
650        int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap
651                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
652        int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap
653                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
654        for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) {
655            Object rawInfo = mWidgets.get(offset + i);
656            PendingAddItemInfo createItemInfo = null;
657            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
658                    R.layout.apps_customize_widget, layout, false);
659            if (rawInfo instanceof AppWidgetProviderInfo) {
660                // Fill in the widget information
661                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
662                createItemInfo = new PendingAddWidgetInfo(info, null, null);
663                final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth,
664                        info.minHeight, null);
665                FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1],
666                        cellWidth, cellHeight);
667                widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans,
668                        mPageViewIconCache, (numPages > 1));
669                widget.setTag(createItemInfo);
670            } else if (rawInfo instanceof ResolveInfo) {
671                // Fill in the shortcuts information
672                ResolveInfo info = (ResolveInfo) rawInfo;
673                createItemInfo = new PendingAddItemInfo();
674                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
675                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
676                        info.activityInfo.name);
677                FastBitmapDrawable preview = getShortcutPreview(info, cellWidth, cellHeight);
678                widget.applyFromResolveInfo(mPackageManager, info, preview, mPageViewIconCache,
679                        (numPages > 1));
680                widget.setTag(createItemInfo);
681            }
682            widget.setOnClickListener(this);
683            widget.setOnLongClickListener(this);
684            widget.setOnTouchListener(this);
685
686            // Layout each widget
687            int ix = i % mWidgetCountX;
688            int iy = i / mWidgetCountX;
689            PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth,
690                    cellHeight);
691            lp.leftMargin = (ix * cellWidth) + (ix * mWidgetWidthGap);
692            lp.topMargin = (iy * cellHeight) + (iy * mWidgetHeightGap);
693            layout.addView(widget, lp);
694        }
695    }
696
697    @Override
698    public void syncPages() {
699        removeAllViews();
700        switch (mContentType) {
701        case Applications:
702            syncAppsPages();
703            break;
704        case Widgets:
705            syncWidgetPages();
706            break;
707        }
708    }
709    @Override
710    public void syncPageItems(int page) {
711        switch (mContentType) {
712        case Applications:
713            syncAppsPageItems(page);
714            break;
715        case Widgets:
716            syncWidgetPageItems(page);
717            break;
718        }
719    }
720
721    /**
722     * Used by the parent to get the content width to set the tab bar to
723     * @return
724     */
725    public int getPageContentWidth() {
726        return mContentWidth;
727    }
728
729    @Override
730    protected void onPageBeginMoving() {
731        /* TO BE ENABLED LATER
732        setChildrenDrawnWithCacheEnabled(true);
733        for (int i = 0; i < getChildCount(); ++i) {
734            View v = getChildAt(i);
735            if (v instanceof PagedViewCellLayout) {
736                ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(true);
737            }
738        }
739        */
740        super.onPageBeginMoving();
741    }
742
743    @Override
744    protected void onPageEndMoving() {
745        /* TO BE ENABLED LATER
746        for (int i = 0; i < getChildCount(); ++i) {
747            View v = getChildAt(i);
748            if (v instanceof PagedViewCellLayout) {
749                ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(false);
750            }
751        }
752        setChildrenDrawnWithCacheEnabled(false);
753        */
754        super.onPageEndMoving();
755    }
756
757    /*
758     * AllAppsView implementation
759     */
760    @Override
761    public void setup(Launcher launcher, DragController dragController) {
762        mLauncher = launcher;
763        mDragController = dragController;
764    }
765    @Override
766    public void zoom(float zoom, boolean animate) {
767        // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
768    }
769    @Override
770    public boolean isVisible() {
771        return (getVisibility() == VISIBLE);
772    }
773    @Override
774    public boolean isAnimating() {
775        return false;
776    }
777    @Override
778    public void setApps(ArrayList<ApplicationInfo> list) {
779        mApps = list;
780        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
781
782        // The next layout pass will trigger data-ready if both widgets and apps are set, so request
783        // a layout to do this test and invalidate the page data when ready.
784        if (testDataReady()) requestLayout();
785    }
786    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
787        // We add it in place, in alphabetical order
788        int count = list.size();
789        for (int i = 0; i < count; ++i) {
790            ApplicationInfo info = list.get(i);
791            int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
792            if (index < 0) {
793                mApps.add(-(index + 1), info);
794            }
795        }
796    }
797    @Override
798    public void addApps(ArrayList<ApplicationInfo> list) {
799        addAppsWithoutInvalidate(list);
800        invalidatePageData();
801    }
802    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
803        ComponentName removeComponent = item.intent.getComponent();
804        int length = list.size();
805        for (int i = 0; i < length; ++i) {
806            ApplicationInfo info = list.get(i);
807            if (info.intent.getComponent().equals(removeComponent)) {
808                return i;
809            }
810        }
811        return -1;
812    }
813    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
814        // loop through all the apps and remove apps that have the same component
815        int length = list.size();
816        for (int i = 0; i < length; ++i) {
817            ApplicationInfo info = list.get(i);
818            int removeIndex = findAppByComponent(mApps, info);
819            if (removeIndex > -1) {
820                mApps.remove(removeIndex);
821                mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info));
822            }
823        }
824    }
825    @Override
826    public void removeApps(ArrayList<ApplicationInfo> list) {
827        removeAppsWithoutInvalidate(list);
828        invalidatePageData();
829    }
830    @Override
831    public void updateApps(ArrayList<ApplicationInfo> list) {
832        // We remove and re-add the updated applications list because it's properties may have
833        // changed (ie. the title), and this will ensure that the items will be in their proper
834        // place in the list.
835        removeAppsWithoutInvalidate(list);
836        addAppsWithoutInvalidate(list);
837        invalidatePageData();
838    }
839    @Override
840    public void reset() {
841        if (mContentType != ContentType.Applications) {
842            // Reset to the first page of the Apps pane
843            AppsCustomizeTabHost tabs = (AppsCustomizeTabHost)
844                    mLauncher.findViewById(R.id.apps_customize_pane);
845            tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications));
846        } else {
847            setCurrentPage(0);
848            invalidatePageData();
849        }
850    }
851    @Override
852    public void dumpState() {
853        // TODO: Dump information related to current list of Applications, Widgets, etc.
854        ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
855        dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
856    }
857    private void dumpAppWidgetProviderInfoList(String tag, String label,
858            List<Object> list) {
859        Log.d(tag, label + " size=" + list.size());
860        for (Object i: list) {
861            if (i instanceof AppWidgetProviderInfo) {
862                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
863                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
864                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
865                        + " initialLayout=" + info.initialLayout
866                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
867            } else if (i instanceof ResolveInfo) {
868                ResolveInfo info = (ResolveInfo) i;
869                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
870                        + info.icon);
871            }
872        }
873    }
874    @Override
875    public void surrender() {
876        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
877        // should stop this now.
878    }
879
880    @Override
881    protected int getPageWidthForScrollingIndicator() {
882        return getPageContentWidth();
883    }
884}
885