Home | History | Annotate | Download | only in launcher2
      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 
     17 package com.android.launcher2;
     18 
     19 import android.animation.AnimatorSet;
     20 import android.animation.ObjectAnimator;
     21 import android.animation.ValueAnimator;
     22 import android.appwidget.AppWidgetManager;
     23 import android.appwidget.AppWidgetProviderInfo;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.content.res.Configuration;
     30 import android.content.res.Resources;
     31 import android.content.res.TypedArray;
     32 import android.graphics.Bitmap;
     33 import android.graphics.Bitmap.Config;
     34 import android.graphics.Canvas;
     35 import android.graphics.MaskFilter;
     36 import android.graphics.Paint;
     37 import android.graphics.PorterDuff;
     38 import android.graphics.Rect;
     39 import android.graphics.TableMaskFilter;
     40 import android.graphics.drawable.Drawable;
     41 import android.os.AsyncTask;
     42 import android.os.Process;
     43 import android.util.AttributeSet;
     44 import android.util.Log;
     45 import android.view.Gravity;
     46 import android.view.KeyEvent;
     47 import android.view.LayoutInflater;
     48 import android.view.MotionEvent;
     49 import android.view.View;
     50 import android.view.ViewConfiguration;
     51 import android.view.ViewGroup;
     52 import android.view.animation.AccelerateInterpolator;
     53 import android.view.animation.DecelerateInterpolator;
     54 import android.widget.GridLayout;
     55 import android.widget.ImageView;
     56 import android.widget.Toast;
     57 
     58 import com.android.launcher.R;
     59 import com.android.launcher2.DropTarget.DragObject;
     60 
     61 import java.util.ArrayList;
     62 import java.util.Collections;
     63 import java.util.Iterator;
     64 import java.util.List;
     65 
     66 /**
     67  * A simple callback interface which also provides the results of the task.
     68  */
     69 interface AsyncTaskCallback {
     70     void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
     71 }
     72 
     73 /**
     74  * The data needed to perform either of the custom AsyncTasks.
     75  */
     76 class AsyncTaskPageData {
     77     enum Type {
     78         LoadWidgetPreviewData,
     79         LoadHolographicIconsData
     80     }
     81 
     82     AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
     83             AsyncTaskCallback postR) {
     84         page = p;
     85         items = l;
     86         sourceImages = si;
     87         generatedImages = new ArrayList<Bitmap>();
     88         maxImageWidth = maxImageHeight = -1;
     89         doInBackgroundCallback = bgR;
     90         postExecuteCallback = postR;
     91     }
     92     AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
     93             AsyncTaskCallback postR) {
     94         page = p;
     95         items = l;
     96         generatedImages = new ArrayList<Bitmap>();
     97         maxImageWidth = cw;
     98         maxImageHeight = ch;
     99         doInBackgroundCallback = bgR;
    100         postExecuteCallback = postR;
    101     }
    102     void cleanup(boolean cancelled) {
    103         // Clean up any references to source/generated bitmaps
    104         if (sourceImages != null) {
    105             if (cancelled) {
    106                 for (Bitmap b : sourceImages) {
    107                     b.recycle();
    108                 }
    109             }
    110             sourceImages.clear();
    111         }
    112         if (generatedImages != null) {
    113             if (cancelled) {
    114                 for (Bitmap b : generatedImages) {
    115                     b.recycle();
    116                 }
    117             }
    118             generatedImages.clear();
    119         }
    120     }
    121     int page;
    122     ArrayList<Object> items;
    123     ArrayList<Bitmap> sourceImages;
    124     ArrayList<Bitmap> generatedImages;
    125     int maxImageWidth;
    126     int maxImageHeight;
    127     AsyncTaskCallback doInBackgroundCallback;
    128     AsyncTaskCallback postExecuteCallback;
    129 }
    130 
    131 /**
    132  * A generic template for an async task used in AppsCustomize.
    133  */
    134 class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
    135     AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
    136         page = p;
    137         threadPriority = Process.THREAD_PRIORITY_DEFAULT;
    138         dataType = ty;
    139     }
    140     @Override
    141     protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
    142         if (params.length != 1) return null;
    143         // Load each of the widget previews in the background
    144         params[0].doInBackgroundCallback.run(this, params[0]);
    145         return params[0];
    146     }
    147     @Override
    148     protected void onPostExecute(AsyncTaskPageData result) {
    149         // All the widget previews are loaded, so we can just callback to inflate the page
    150         result.postExecuteCallback.run(this, result);
    151     }
    152 
    153     void setThreadPriority(int p) {
    154         threadPriority = p;
    155     }
    156     void syncThreadPriority() {
    157         Process.setThreadPriority(threadPriority);
    158     }
    159 
    160     // The page that this async task is associated with
    161     AsyncTaskPageData.Type dataType;
    162     int page;
    163     int threadPriority;
    164 }
    165 
    166 /**
    167  * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
    168  */
    169 public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
    170         AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource {
    171     static final String LOG_TAG = "AppsCustomizePagedView";
    172 
    173     /**
    174      * The different content types that this paged view can show.
    175      */
    176     public enum ContentType {
    177         Applications,
    178         Widgets
    179     }
    180 
    181     // Refs
    182     private Launcher mLauncher;
    183     private DragController mDragController;
    184     private final LayoutInflater mLayoutInflater;
    185     private final PackageManager mPackageManager;
    186 
    187     // Save and Restore
    188     private int mSaveInstanceStateItemIndex = -1;
    189 
    190     // Content
    191     private ArrayList<ApplicationInfo> mApps;
    192     private ArrayList<Object> mWidgets;
    193 
    194     // Cling
    195     private boolean mHasShownAllAppsCling;
    196     private int mClingFocusedX;
    197     private int mClingFocusedY;
    198 
    199     // Caching
    200     private Canvas mCanvas;
    201     private Drawable mDefaultWidgetBackground;
    202     private IconCache mIconCache;
    203     private int mDragViewMultiplyColor;
    204 
    205     // Dimens
    206     private int mContentWidth;
    207     private int mAppIconSize;
    208     private int mMaxAppCellCountX, mMaxAppCellCountY;
    209     private int mWidgetCountX, mWidgetCountY;
    210     private int mWidgetWidthGap, mWidgetHeightGap;
    211     private final int mWidgetPreviewIconPaddedDimension;
    212     private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
    213     private PagedViewCellLayout mWidgetSpacingLayout;
    214     private int mNumAppsPages;
    215     private int mNumWidgetPages;
    216 
    217     // Relating to the scroll and overscroll effects
    218     Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
    219     private static float CAMERA_DISTANCE = 6500;
    220     private static float TRANSITION_SCALE_FACTOR = 0.74f;
    221     private static float TRANSITION_PIVOT = 0.65f;
    222     private static float TRANSITION_MAX_ROTATION = 22;
    223     private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
    224     private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
    225     private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
    226 
    227     // Previews & outlines
    228     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
    229     private HolographicOutlineHelper mHolographicOutlineHelper;
    230     private static final int sPageSleepDelay = 200;
    231 
    232     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
    233         super(context, attrs);
    234         mLayoutInflater = LayoutInflater.from(context);
    235         mPackageManager = context.getPackageManager();
    236         mApps = new ArrayList<ApplicationInfo>();
    237         mWidgets = new ArrayList<Object>();
    238         mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
    239         mHolographicOutlineHelper = new HolographicOutlineHelper();
    240         mCanvas = new Canvas();
    241         mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
    242 
    243         // Save the default widget preview background
    244         Resources resources = context.getResources();
    245         mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
    246         mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
    247         mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color);
    248 
    249         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
    250         mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
    251         mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1);
    252         mWidgetWidthGap =
    253             a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
    254         mWidgetHeightGap =
    255             a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
    256         mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
    257         mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
    258         mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
    259         mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
    260         a.recycle();
    261         mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
    262 
    263         // The padding on the non-matched dimension for the default widget preview icons
    264         // (top + bottom)
    265         mWidgetPreviewIconPaddedDimension =
    266             (int) (mAppIconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage)));
    267         mFadeInAdjacentScreens = false;
    268     }
    269 
    270     @Override
    271     protected void init() {
    272         super.init();
    273         mCenterPagesVertically = false;
    274 
    275         Context context = getContext();
    276         Resources r = context.getResources();
    277         setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
    278     }
    279 
    280     @Override
    281     protected void onUnhandledTap(MotionEvent ev) {
    282         if (LauncherApplication.isScreenLarge()) {
    283             // Dismiss AppsCustomize if we tap
    284             mLauncher.showWorkspace(true);
    285         }
    286     }
    287 
    288     /** Returns the item index of the center item on this page so that we can restore to this
    289      *  item index when we rotate. */
    290     private int getMiddleComponentIndexOnCurrentPage() {
    291         int i = -1;
    292         if (getPageCount() > 0) {
    293             int currentPage = getCurrentPage();
    294             if (currentPage < mNumAppsPages) {
    295                 PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage);
    296                 PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout();
    297                 int numItemsPerPage = mCellCountX * mCellCountY;
    298                 int childCount = childrenLayout.getChildCount();
    299                 if (childCount > 0) {
    300                     i = (currentPage * numItemsPerPage) + (childCount / 2);
    301                 }
    302             } else {
    303                 int numApps = mApps.size();
    304                 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
    305                 int numItemsPerPage = mWidgetCountX * mWidgetCountY;
    306                 int childCount = layout.getChildCount();
    307                 if (childCount > 0) {
    308                     i = numApps +
    309                         ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2);
    310                 }
    311             }
    312         }
    313         return i;
    314     }
    315 
    316     /** Get the index of the item to restore to if we need to restore the current page. */
    317     int getSaveInstanceStateIndex() {
    318         if (mSaveInstanceStateItemIndex == -1) {
    319             mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
    320         }
    321         return mSaveInstanceStateItemIndex;
    322     }
    323 
    324     /** Returns the page in the current orientation which is expected to contain the specified
    325      *  item index. */
    326     int getPageForComponent(int index) {
    327         if (index < 0) return 0;
    328 
    329         if (index < mApps.size()) {
    330             int numItemsPerPage = mCellCountX * mCellCountY;
    331             return (index / numItemsPerPage);
    332         } else {
    333             int numItemsPerPage = mWidgetCountX * mWidgetCountY;
    334             return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage);
    335         }
    336     }
    337 
    338     /**
    339      * This differs from isDataReady as this is the test done if isDataReady is not set.
    340      */
    341     private boolean testDataReady() {
    342         // We only do this test once, and we default to the Applications page, so we only really
    343         // have to wait for there to be apps.
    344         // TODO: What if one of them is validly empty
    345         return !mApps.isEmpty() && !mWidgets.isEmpty();
    346     }
    347 
    348     /** Restores the page for an item at the specified index */
    349     void restorePageForIndex(int index) {
    350         if (index < 0) return;
    351         mSaveInstanceStateItemIndex = index;
    352     }
    353 
    354     private void updatePageCounts() {
    355         mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
    356                 (float) (mWidgetCountX * mWidgetCountY));
    357         mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
    358     }
    359 
    360     protected void onDataReady(int width, int height) {
    361         // Note that we transpose the counts in portrait so that we get a similar layout
    362         boolean isLandscape = getResources().getConfiguration().orientation ==
    363             Configuration.ORIENTATION_LANDSCAPE;
    364         int maxCellCountX = Integer.MAX_VALUE;
    365         int maxCellCountY = Integer.MAX_VALUE;
    366         if (LauncherApplication.isScreenLarge()) {
    367             maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
    368                 LauncherModel.getCellCountY());
    369             maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
    370                 LauncherModel.getCellCountX());
    371         }
    372         if (mMaxAppCellCountX > -1) {
    373             maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX);
    374         }
    375         if (mMaxAppCellCountY > -1) {
    376             maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY);
    377         }
    378 
    379         // Now that the data is ready, we can calculate the content width, the number of cells to
    380         // use for each page
    381         mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
    382         mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
    383                 mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
    384         mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
    385         mCellCountX = mWidgetSpacingLayout.getCellCountX();
    386         mCellCountY = mWidgetSpacingLayout.getCellCountY();
    387         updatePageCounts();
    388 
    389         // Force a measure to update recalculate the gaps
    390         int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
    391         int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
    392         mWidgetSpacingLayout.measure(widthSpec, heightSpec);
    393         mContentWidth = mWidgetSpacingLayout.getContentWidth();
    394 
    395         AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost();
    396         final boolean hostIsTransitioning = host.isTransitioning();
    397 
    398         // Restore the page
    399         int page = getPageForComponent(mSaveInstanceStateItemIndex);
    400         invalidatePageData(Math.max(0, page), hostIsTransitioning);
    401 
    402         // Show All Apps cling if we are finished transitioning, otherwise, we will try again when
    403         // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be
    404         // returned while animating)
    405         if (!hostIsTransitioning) {
    406             post(new Runnable() {
    407                 @Override
    408                 public void run() {
    409                     showAllAppsCling();
    410                 }
    411             });
    412         }
    413     }
    414 
    415     void showAllAppsCling() {
    416         if (!mHasShownAllAppsCling && isDataReady() && testDataReady()) {
    417             mHasShownAllAppsCling = true;
    418             // Calculate the position for the cling punch through
    419             int[] offset = new int[2];
    420             int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
    421             mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
    422             // PagedViews are centered horizontally but top aligned
    423             pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
    424                     offset[0];
    425             pos[1] += offset[1];
    426             mLauncher.showFirstRunAllAppsCling(pos);
    427         }
    428     }
    429 
    430     @Override
    431     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    432         int width = MeasureSpec.getSize(widthMeasureSpec);
    433         int height = MeasureSpec.getSize(heightMeasureSpec);
    434         if (!isDataReady()) {
    435             if (testDataReady()) {
    436                 setDataIsReady();
    437                 setMeasuredDimension(width, height);
    438                 onDataReady(width, height);
    439             }
    440         }
    441 
    442         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    443     }
    444 
    445     public void onPackagesUpdated() {
    446         // TODO: this isn't ideal, but we actually need to delay here. This call is triggered
    447         // by a broadcast receiver, and in order for it to work correctly, we need to know that
    448         // the AppWidgetService has already received and processed the same broadcast. Since there
    449         // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally,
    450         // we should have a more precise way of ensuring the AppWidgetService is up to date.
    451         postDelayed(new Runnable() {
    452            public void run() {
    453                updatePackages();
    454            }
    455         }, 500);
    456     }
    457 
    458     public void updatePackages() {
    459         // Get the list of widgets and shortcuts
    460         boolean wasEmpty = mWidgets.isEmpty();
    461         mWidgets.clear();
    462         List<AppWidgetProviderInfo> widgets =
    463             AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
    464         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
    465         List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
    466         for (AppWidgetProviderInfo widget : widgets) {
    467             if (widget.minWidth > 0 && widget.minHeight > 0) {
    468                 mWidgets.add(widget);
    469             } else {
    470                 Log.e(LOG_TAG, "Widget " + widget.provider + " has invalid dimensions (" +
    471                         widget.minWidth + ", " + widget.minHeight + ")");
    472             }
    473         }
    474         mWidgets.addAll(shortcuts);
    475         Collections.sort(mWidgets,
    476                 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
    477         updatePageCounts();
    478 
    479         if (wasEmpty) {
    480             // The next layout pass will trigger data-ready if both widgets and apps are set, so request
    481             // a layout to do this test and invalidate the page data when ready.
    482             if (testDataReady()) requestLayout();
    483         } else {
    484             cancelAllTasks();
    485             invalidatePageData();
    486         }
    487     }
    488 
    489     @Override
    490     public void onClick(View v) {
    491         // When we have exited all apps or are in transition, disregard clicks
    492         if (!mLauncher.isAllAppsCustomizeOpen() ||
    493                 mLauncher.getWorkspace().isSwitchingState()) return;
    494 
    495         if (v instanceof PagedViewIcon) {
    496             // Animate some feedback to the click
    497             final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
    498             animateClickFeedback(v, new Runnable() {
    499                 @Override
    500                 public void run() {
    501                     mLauncher.startActivitySafely(appInfo.intent, appInfo);
    502                 }
    503             });
    504         } else if (v instanceof PagedViewWidget) {
    505             // Let the user know that they have to long press to add a widget
    506             Toast.makeText(getContext(), R.string.long_press_widget_to_add,
    507                     Toast.LENGTH_SHORT).show();
    508 
    509             // Create a little animation to show that the widget can move
    510             float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
    511             final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
    512             AnimatorSet bounce = new AnimatorSet();
    513             ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
    514             tyuAnim.setDuration(125);
    515             ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
    516             tydAnim.setDuration(100);
    517             bounce.play(tyuAnim).before(tydAnim);
    518             bounce.setInterpolator(new AccelerateInterpolator());
    519             bounce.start();
    520         }
    521     }
    522 
    523     public boolean onKey(View v, int keyCode, KeyEvent event) {
    524         return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
    525     }
    526 
    527     /*
    528      * PagedViewWithDraggableItems implementation
    529      */
    530     @Override
    531     protected void determineDraggingStart(android.view.MotionEvent ev) {
    532         // Disable dragging by pulling an app down for now.
    533     }
    534 
    535     private void beginDraggingApplication(View v) {
    536         mLauncher.getWorkspace().onDragStartedWithItem(v);
    537         mLauncher.getWorkspace().beginDragShared(v, this);
    538     }
    539 
    540     private void beginDraggingWidget(View v) {
    541         // Get the widget preview as the drag representation
    542         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
    543         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
    544 
    545         // Compose the drag image
    546         Bitmap preview;
    547         Bitmap outline;
    548         if (createItemInfo instanceof PendingAddWidgetInfo) {
    549             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
    550             int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null);
    551             createItemInfo.spanX = spanXY[0];
    552             createItemInfo.spanY = spanXY[1];
    553 
    554             int[] maxSize = mLauncher.getWorkspace().estimateItemSize(spanXY[0], spanXY[1],
    555                     createWidgetInfo, true);
    556             preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
    557                     createWidgetInfo.icon, spanXY[0], spanXY[1], maxSize[0], maxSize[1]);
    558         } else {
    559             // Workaround for the fact that we don't keep the original ResolveInfo associated with
    560             // the shortcut around.  To get the icon, we just render the preview image (which has
    561             // the shortcut icon) to a new drag bitmap that clips the non-icon space.
    562             preview = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension,
    563                     mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888);
    564             Drawable d = image.getDrawable();
    565             mCanvas.setBitmap(preview);
    566             d.draw(mCanvas);
    567             mCanvas.setBitmap(null);
    568             createItemInfo.spanX = createItemInfo.spanY = 1;
    569         }
    570 
    571         // We use a custom alpha clip table for the default widget previews
    572         Paint alphaClipPaint = null;
    573         if (createItemInfo instanceof PendingAddWidgetInfo) {
    574             if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) {
    575                 MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255);
    576                 alphaClipPaint = new Paint();
    577                 alphaClipPaint.setMaskFilter(alphaClipTable);
    578             }
    579         }
    580 
    581         // Save the preview for the outline generation, then dim the preview
    582         outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
    583                 false);
    584         mCanvas.setBitmap(preview);
    585         mCanvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
    586         mCanvas.setBitmap(null);
    587 
    588         // Start the drag
    589         alphaClipPaint = null;
    590         mLauncher.lockScreenOrientationOnLargeUI();
    591         mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint);
    592         mDragController.startDrag(image, preview, this, createItemInfo,
    593                 DragController.DRAG_ACTION_COPY, null);
    594         outline.recycle();
    595         preview.recycle();
    596     }
    597     @Override
    598     protected boolean beginDragging(View v) {
    599         // Dismiss the cling
    600         mLauncher.dismissAllAppsCling(null);
    601 
    602         if (!super.beginDragging(v)) return false;
    603 
    604         // Go into spring loaded mode (must happen before we startDrag())
    605         mLauncher.enterSpringLoadedDragMode();
    606 
    607         if (v instanceof PagedViewIcon) {
    608             beginDraggingApplication(v);
    609         } else if (v instanceof PagedViewWidget) {
    610             beginDraggingWidget(v);
    611         }
    612         return true;
    613     }
    614     private void endDragging(View target, boolean success) {
    615         mLauncher.getWorkspace().onDragStopped(success);
    616         if (!success || (target != mLauncher.getWorkspace() &&
    617                 !(target instanceof DeleteDropTarget))) {
    618             // Exit spring loaded mode if we have not successfully dropped or have not handled the
    619             // drop in Workspace
    620             mLauncher.exitSpringLoadedDragMode();
    621         }
    622         mLauncher.unlockScreenOrientationOnLargeUI();
    623 
    624     }
    625 
    626     @Override
    627     public void onDropCompleted(View target, DragObject d, boolean success) {
    628         endDragging(target, success);
    629 
    630         // Display an error message if the drag failed due to there not being enough space on the
    631         // target layout we were dropping on.
    632         if (!success) {
    633             boolean showOutOfSpaceMessage = false;
    634             if (target instanceof Workspace) {
    635                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
    636                 Workspace workspace = (Workspace) target;
    637                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
    638                 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
    639                 if (layout != null) {
    640                     layout.calculateSpans(itemInfo);
    641                     showOutOfSpaceMessage =
    642                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
    643                 }
    644             }
    645             if (showOutOfSpaceMessage) {
    646                 mLauncher.showOutOfSpaceMessage();
    647             }
    648         }
    649     }
    650 
    651     @Override
    652     protected void onDetachedFromWindow() {
    653         super.onDetachedFromWindow();
    654         cancelAllTasks();
    655     }
    656 
    657     public void clearAllWidgetPages() {
    658         cancelAllTasks();
    659         int count = getChildCount();
    660         for (int i = 0; i < count; i++) {
    661             View v = getPageAt(i);
    662             if (v instanceof PagedViewGridLayout) {
    663                 ((PagedViewGridLayout) v).removeAllViewsOnPage();
    664                 mDirtyPageContent.set(i, true);
    665             }
    666         }
    667     }
    668 
    669     private void cancelAllTasks() {
    670         // Clean up all the async tasks
    671         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
    672         while (iter.hasNext()) {
    673             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
    674             task.cancel(false);
    675             iter.remove();
    676         }
    677     }
    678 
    679     public void setContentType(ContentType type) {
    680         if (type == ContentType.Widgets) {
    681             invalidatePageData(mNumAppsPages, true);
    682         } else if (type == ContentType.Applications) {
    683             invalidatePageData(0, true);
    684         }
    685     }
    686 
    687     protected void snapToPage(int whichPage, int delta, int duration) {
    688         super.snapToPage(whichPage, delta, duration);
    689         updateCurrentTab(whichPage);
    690 
    691         // Update the thread priorities given the direction lookahead
    692         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
    693         while (iter.hasNext()) {
    694             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
    695             int pageIndex = task.page + mNumAppsPages;
    696             if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
    697                 (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
    698                 task.setThreadPriority(getThreadPriorityForPage(pageIndex));
    699             } else {
    700                 task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
    701             }
    702         }
    703     }
    704 
    705     private void updateCurrentTab(int currentPage) {
    706         AppsCustomizeTabHost tabHost = getTabHost();
    707         if (tabHost != null) {
    708             String tag = tabHost.getCurrentTabTag();
    709             if (tag != null) {
    710                 if (currentPage >= mNumAppsPages &&
    711                         !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
    712                     tabHost.setCurrentTabFromContent(ContentType.Widgets);
    713                 } else if (currentPage < mNumAppsPages &&
    714                         !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
    715                     tabHost.setCurrentTabFromContent(ContentType.Applications);
    716                 }
    717             }
    718         }
    719     }
    720 
    721     /*
    722      * Apps PagedView implementation
    723      */
    724     private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
    725         int childCount = layout.getChildCount();
    726         for (int i = 0; i < childCount; ++i) {
    727             layout.getChildAt(i).setVisibility(visibility);
    728         }
    729     }
    730     private void setupPage(PagedViewCellLayout layout) {
    731         layout.setCellCount(mCellCountX, mCellCountY);
    732         layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
    733         layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
    734                 mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
    735 
    736         // Note: We force a measure here to get around the fact that when we do layout calculations
    737         // immediately after syncing, we don't have a proper width.  That said, we already know the
    738         // expected page width, so we can actually optimize by hiding all the TextView-based
    739         // children that are expensive to measure, and let that happen naturally later.
    740         setVisibilityOnChildren(layout, View.GONE);
    741         int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
    742         int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
    743         layout.setMinimumWidth(getPageContentWidth());
    744         layout.measure(widthSpec, heightSpec);
    745         setVisibilityOnChildren(layout, View.VISIBLE);
    746     }
    747 
    748     public void syncAppsPageItems(int page, boolean immediate) {
    749         // ensure that we have the right number of items on the pages
    750         int numCells = mCellCountX * mCellCountY;
    751         int startIndex = page * numCells;
    752         int endIndex = Math.min(startIndex + numCells, mApps.size());
    753         PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
    754 
    755         layout.removeAllViewsOnPage();
    756         ArrayList<Object> items = new ArrayList<Object>();
    757         ArrayList<Bitmap> images = new ArrayList<Bitmap>();
    758         for (int i = startIndex; i < endIndex; ++i) {
    759             ApplicationInfo info = mApps.get(i);
    760             PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
    761                     R.layout.apps_customize_application, layout, false);
    762             icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper);
    763             icon.setOnClickListener(this);
    764             icon.setOnLongClickListener(this);
    765             icon.setOnTouchListener(this);
    766             icon.setOnKeyListener(this);
    767 
    768             int index = i - startIndex;
    769             int x = index % mCellCountX;
    770             int y = index / mCellCountX;
    771             layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
    772 
    773             items.add(info);
    774             images.add(info.iconBitmap);
    775         }
    776 
    777         layout.createHardwareLayers();
    778 
    779         /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
    780         if (mFadeInAdjacentScreens) {
    781             prepareGenerateHoloOutlinesTask(page, items, images);
    782         }
    783         */
    784     }
    785 
    786     /**
    787      * A helper to return the priority for loading of the specified widget page.
    788      */
    789     private int getWidgetPageLoadPriority(int page) {
    790         // If we are snapping to another page, use that index as the target page index
    791         int toPage = mCurrentPage;
    792         if (mNextPage > -1) {
    793             toPage = mNextPage;
    794         }
    795 
    796         // We use the distance from the target page as an initial guess of priority, but if there
    797         // are no pages of higher priority than the page specified, then bump up the priority of
    798         // the specified page.
    799         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
    800         int minPageDiff = Integer.MAX_VALUE;
    801         while (iter.hasNext()) {
    802             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
    803             minPageDiff = Math.abs(task.page + mNumAppsPages - toPage);
    804         }
    805 
    806         int rawPageDiff = Math.abs(page - toPage);
    807         return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
    808     }
    809     /**
    810      * Return the appropriate thread priority for loading for a given page (we give the current
    811      * page much higher priority)
    812      */
    813     private int getThreadPriorityForPage(int page) {
    814         // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
    815         int pageDiff = getWidgetPageLoadPriority(page);
    816         if (pageDiff <= 0) {
    817             return Process.THREAD_PRIORITY_LESS_FAVORABLE;
    818         } else if (pageDiff <= 1) {
    819             return Process.THREAD_PRIORITY_LOWEST;
    820         } else {
    821             return Process.THREAD_PRIORITY_LOWEST;
    822         }
    823     }
    824     private int getSleepForPage(int page) {
    825         int pageDiff = getWidgetPageLoadPriority(page);
    826         return Math.max(0, pageDiff * sPageSleepDelay);
    827     }
    828     /**
    829      * Creates and executes a new AsyncTask to load a page of widget previews.
    830      */
    831     private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
    832             int cellWidth, int cellHeight, int cellCountX) {
    833 
    834         // Prune all tasks that are no longer needed
    835         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
    836         while (iter.hasNext()) {
    837             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
    838             int taskPage = task.page + mNumAppsPages;
    839             if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
    840                     taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
    841                 task.cancel(false);
    842                 iter.remove();
    843             } else {
    844                 task.setThreadPriority(getThreadPriorityForPage(taskPage));
    845             }
    846         }
    847 
    848         // We introduce a slight delay to order the loading of side pages so that we don't thrash
    849         final int sleepMs = getSleepForPage(page + mNumAppsPages);
    850         AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
    851             new AsyncTaskCallback() {
    852                 @Override
    853                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
    854                     try {
    855                         try {
    856                             Thread.sleep(sleepMs);
    857                         } catch (Exception e) {}
    858                         loadWidgetPreviewsInBackground(task, data);
    859                     } finally {
    860                         if (task.isCancelled()) {
    861                             data.cleanup(true);
    862                         }
    863                     }
    864                 }
    865             },
    866             new AsyncTaskCallback() {
    867                 @Override
    868                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
    869                     try {
    870                         mRunningTasks.remove(task);
    871                         if (task.isCancelled()) return;
    872                         onSyncWidgetPageItems(data);
    873                     } finally {
    874                         data.cleanup(task.isCancelled());
    875                     }
    876                 }
    877             });
    878 
    879         // Ensure that the task is appropriately prioritized and runs in parallel
    880         AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
    881                 AsyncTaskPageData.Type.LoadWidgetPreviewData);
    882         t.setThreadPriority(getThreadPriorityForPage(page + mNumAppsPages));
    883         t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
    884         mRunningTasks.add(t);
    885     }
    886     /**
    887      * Creates and executes a new AsyncTask to load the outlines for a page of content.
    888      */
    889     private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items,
    890             ArrayList<Bitmap> images) {
    891         // Prune old tasks for this page
    892         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
    893         while (iter.hasNext()) {
    894             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
    895             int taskPage = task.page;
    896             if ((taskPage == page) &&
    897                     (task.dataType == AsyncTaskPageData.Type.LoadHolographicIconsData)) {
    898                 task.cancel(false);
    899                 iter.remove();
    900             }
    901         }
    902 
    903         AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images,
    904             new AsyncTaskCallback() {
    905                 @Override
    906                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
    907                     try {
    908                         // Ensure that this task starts running at the correct priority
    909                         task.syncThreadPriority();
    910 
    911                         ArrayList<Bitmap> images = data.generatedImages;
    912                         ArrayList<Bitmap> srcImages = data.sourceImages;
    913                         int count = srcImages.size();
    914                         Canvas c = new Canvas();
    915                         for (int i = 0; i < count && !task.isCancelled(); ++i) {
    916                             // Before work on each item, ensure that this task is running at the correct
    917                             // priority
    918                             task.syncThreadPriority();
    919 
    920                             Bitmap b = srcImages.get(i);
    921                             Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(),
    922                                     Bitmap.Config.ARGB_8888);
    923 
    924                             c.setBitmap(outline);
    925                             c.save();
    926                             c.drawBitmap(b, 0, 0, null);
    927                             c.restore();
    928                             c.setBitmap(null);
    929 
    930                             images.add(outline);
    931                         }
    932                     } finally {
    933                         if (task.isCancelled()) {
    934                             data.cleanup(true);
    935                         }
    936                     }
    937                 }
    938             },
    939             new AsyncTaskCallback() {
    940                 @Override
    941                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
    942                     try {
    943                         mRunningTasks.remove(task);
    944                         if (task.isCancelled()) return;
    945                         onHolographicPageItemsLoaded(data);
    946                     } finally {
    947                         data.cleanup(task.isCancelled());
    948                     }
    949                 }
    950             });
    951 
    952         // Ensure that the outline task always runs in the background, serially
    953         AppsCustomizeAsyncTask t =
    954             new AppsCustomizeAsyncTask(page, AsyncTaskPageData.Type.LoadHolographicIconsData);
    955         t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    956         t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData);
    957         mRunningTasks.add(t);
    958     }
    959 
    960     /*
    961      * Widgets PagedView implementation
    962      */
    963     private void setupPage(PagedViewGridLayout layout) {
    964         layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
    965                 mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
    966 
    967         // Note: We force a measure here to get around the fact that when we do layout calculations
    968         // immediately after syncing, we don't have a proper width.
    969         int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
    970         int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
    971         layout.setMinimumWidth(getPageContentWidth());
    972         layout.measure(widthSpec, heightSpec);
    973     }
    974 
    975     private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
    976         renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f, 0xFFFFFFFF);
    977     }
    978 
    979     private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
    980             float scale, int multiplyColor) {
    981         if (bitmap != null) {
    982             Canvas c = new Canvas(bitmap);
    983             c.scale(scale, scale);
    984             Rect oldBounds = d.copyBounds();
    985             d.setBounds(x, y, x + w, y + h);
    986             d.draw(c);
    987             d.setBounds(oldBounds); // Restore the bounds
    988             if (multiplyColor != 0xFFFFFFFF) {
    989                 c.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
    990             }
    991             c.setBitmap(null);
    992         }
    993     }
    994     private Bitmap getShortcutPreview(ResolveInfo info) {
    995         // Render the background
    996         int offset = 0;
    997         int bitmapSize = mAppIconSize;
    998         Bitmap preview = Bitmap.createBitmap(bitmapSize, bitmapSize, Config.ARGB_8888);
    999 
   1000         // Render the icon
   1001         Drawable icon = mIconCache.getFullResIcon(info);
   1002         renderDrawableToBitmap(icon, preview, offset, offset, mAppIconSize, mAppIconSize);
   1003         return preview;
   1004     }
   1005 
   1006     private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId,
   1007             int cellHSpan, int cellVSpan, int maxWidth, int maxHeight) {
   1008         // Load the preview image if possible
   1009         String packageName = provider.getPackageName();
   1010         if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
   1011         if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
   1012 
   1013         Drawable drawable = null;
   1014         if (previewImage != 0) {
   1015             drawable = mPackageManager.getDrawable(packageName, previewImage, null);
   1016             if (drawable == null) {
   1017                 Log.w(LOG_TAG, "Can't load widget preview drawable 0x" +
   1018                         Integer.toHexString(previewImage) + " for provider: " + provider);
   1019             }
   1020         }
   1021 
   1022         int bitmapWidth;
   1023         int bitmapHeight;
   1024         boolean widgetPreviewExists = (drawable != null);
   1025         if (widgetPreviewExists) {
   1026             bitmapWidth = drawable.getIntrinsicWidth();
   1027             bitmapHeight = drawable.getIntrinsicHeight();
   1028 
   1029             // Cap the size so widget previews don't appear larger than the actual widget
   1030             maxWidth = Math.min(maxWidth, mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
   1031             maxHeight = Math.min(maxHeight, mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
   1032         } else {
   1033             // Determine the size of the bitmap for the preview image we will generate
   1034             // TODO: This actually uses the apps customize cell layout params, where as we make want
   1035             // the Workspace params for more accuracy.
   1036             bitmapWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan);
   1037             bitmapHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan);
   1038             if (cellHSpan == cellVSpan) {
   1039                 // For square widgets, we just have a fixed size for 1x1 and larger-than-1x1
   1040                 int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
   1041                 if (cellHSpan <= 1) {
   1042                     bitmapWidth = bitmapHeight = mAppIconSize + 2 * minOffset;
   1043                 } else {
   1044                     bitmapWidth = bitmapHeight = mAppIconSize + 4 * minOffset;
   1045                 }
   1046             }
   1047         }
   1048 
   1049         float scale = 1f;
   1050         if (bitmapWidth > maxWidth) {
   1051             scale = maxWidth / (float) bitmapWidth;
   1052         }
   1053         if (bitmapHeight * scale > maxHeight) {
   1054             scale = maxHeight / (float) bitmapHeight;
   1055         }
   1056         if (scale != 1f) {
   1057             bitmapWidth = (int) (scale * bitmapWidth);
   1058             bitmapHeight = (int) (scale * bitmapHeight);
   1059         }
   1060 
   1061         Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888);
   1062 
   1063         if (widgetPreviewExists) {
   1064             renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, bitmapHeight);
   1065         } else {
   1066             // Generate a preview image if we couldn't load one
   1067             int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
   1068             int smallestSide = Math.min(bitmapWidth, bitmapHeight);
   1069             float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), 1f);
   1070             if (cellHSpan != 1 || cellVSpan != 1) {
   1071                 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, bitmapWidth,
   1072                         bitmapHeight);
   1073             }
   1074 
   1075             // Draw the icon in the top left corner
   1076             try {
   1077                 Drawable icon = null;
   1078                 int hoffset = (int) (bitmapWidth / 2 - mAppIconSize * iconScale / 2);
   1079                 int yoffset = (int) (bitmapHeight / 2 - mAppIconSize * iconScale / 2);
   1080                 if (iconId > 0) icon = mIconCache.getFullResIcon(packageName, iconId);
   1081                 Resources resources = mLauncher.getResources();
   1082                 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application);
   1083 
   1084                 renderDrawableToBitmap(icon, preview, hoffset, yoffset,
   1085                         (int) (mAppIconSize * iconScale),
   1086                         (int) (mAppIconSize * iconScale));
   1087             } catch (Resources.NotFoundException e) {}
   1088         }
   1089         return preview;
   1090     }
   1091 
   1092     public void syncWidgetPageItems(final int page, final boolean immediate) {
   1093         int numItemsPerPage = mWidgetCountX * mWidgetCountY;
   1094 
   1095         // Calculate the dimensions of each cell we are giving to each widget
   1096         final ArrayList<Object> items = new ArrayList<Object>();
   1097         int contentWidth = mWidgetSpacingLayout.getContentWidth();
   1098         final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
   1099                 - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
   1100         int contentHeight = mWidgetSpacingLayout.getContentHeight();
   1101         final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
   1102                 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
   1103 
   1104         // Prepare the set of widgets to load previews for in the background
   1105         int offset = page * numItemsPerPage;
   1106         for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
   1107             items.add(mWidgets.get(i));
   1108         }
   1109 
   1110         // Prepopulate the pages with the other widget info, and fill in the previews later
   1111         final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
   1112         layout.setColumnCount(layout.getCellCountX());
   1113         for (int i = 0; i < items.size(); ++i) {
   1114             Object rawInfo = items.get(i);
   1115             PendingAddItemInfo createItemInfo = null;
   1116             PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
   1117                     R.layout.apps_customize_widget, layout, false);
   1118             if (rawInfo instanceof AppWidgetProviderInfo) {
   1119                 // Fill in the widget information
   1120                 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
   1121                 createItemInfo = new PendingAddWidgetInfo(info, null, null);
   1122                 int[] cellSpans = mLauncher.getSpanForWidget(info, null);
   1123                 widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans,
   1124                         mHolographicOutlineHelper);
   1125                 widget.setTag(createItemInfo);
   1126             } else if (rawInfo instanceof ResolveInfo) {
   1127                 // Fill in the shortcuts information
   1128                 ResolveInfo info = (ResolveInfo) rawInfo;
   1129                 createItemInfo = new PendingAddItemInfo();
   1130                 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
   1131                 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
   1132                         info.activityInfo.name);
   1133                 widget.applyFromResolveInfo(mPackageManager, info, mHolographicOutlineHelper);
   1134                 widget.setTag(createItemInfo);
   1135             }
   1136             widget.setOnClickListener(this);
   1137             widget.setOnLongClickListener(this);
   1138             widget.setOnTouchListener(this);
   1139             widget.setOnKeyListener(this);
   1140 
   1141             // Layout each widget
   1142             int ix = i % mWidgetCountX;
   1143             int iy = i / mWidgetCountX;
   1144             GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
   1145                     GridLayout.spec(iy, GridLayout.LEFT),
   1146                     GridLayout.spec(ix, GridLayout.TOP));
   1147             lp.width = cellWidth;
   1148             lp.height = cellHeight;
   1149             lp.setGravity(Gravity.TOP | Gravity.LEFT);
   1150             if (ix > 0) lp.leftMargin = mWidgetWidthGap;
   1151             if (iy > 0) lp.topMargin = mWidgetHeightGap;
   1152             layout.addView(widget, lp);
   1153         }
   1154 
   1155         // wait until a call on onLayout to start loading, because
   1156         // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
   1157         // TODO: can we do a measure/layout immediately?
   1158         layout.setOnLayoutListener(new Runnable() {
   1159             public void run() {
   1160                 // Load the widget previews
   1161                 int maxPreviewWidth = cellWidth;
   1162                 int maxPreviewHeight = cellHeight;
   1163                 if (layout.getChildCount() > 0) {
   1164                     PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
   1165                     int[] maxSize = w.getPreviewSize();
   1166                     maxPreviewWidth = maxSize[0];
   1167                     maxPreviewHeight = maxSize[1];
   1168                 }
   1169                 if (immediate) {
   1170                     AsyncTaskPageData data = new AsyncTaskPageData(page, items,
   1171                             maxPreviewWidth, maxPreviewHeight, null, null);
   1172                     loadWidgetPreviewsInBackground(null, data);
   1173                     onSyncWidgetPageItems(data);
   1174                 } else {
   1175                     prepareLoadWidgetPreviewsTask(page, items,
   1176                             maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
   1177                 }
   1178             }
   1179         });
   1180     }
   1181     private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
   1182             AsyncTaskPageData data) {
   1183         // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
   1184         // previews synchronously
   1185         if (task != null) {
   1186             // Ensure that this task starts running at the correct priority
   1187             task.syncThreadPriority();
   1188         }
   1189 
   1190         // Load each of the widget/shortcut previews
   1191         ArrayList<Object> items = data.items;
   1192         ArrayList<Bitmap> images = data.generatedImages;
   1193         int count = items.size();
   1194         for (int i = 0; i < count; ++i) {
   1195             if (task != null) {
   1196                 // Ensure we haven't been cancelled yet
   1197                 if (task.isCancelled()) break;
   1198                 // Before work on each item, ensure that this task is running at the correct
   1199                 // priority
   1200                 task.syncThreadPriority();
   1201             }
   1202 
   1203             Object rawInfo = items.get(i);
   1204             if (rawInfo instanceof AppWidgetProviderInfo) {
   1205                 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
   1206                 int[] cellSpans = mLauncher.getSpanForWidget(info, null);
   1207                 Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
   1208                         cellSpans[0], cellSpans[1], data.maxImageWidth, data.maxImageHeight);
   1209                 images.add(b);
   1210             } else if (rawInfo instanceof ResolveInfo) {
   1211                 // Fill in the shortcuts information
   1212                 ResolveInfo info = (ResolveInfo) rawInfo;
   1213                 images.add(getShortcutPreview(info));
   1214             }
   1215         }
   1216     }
   1217     private void onSyncWidgetPageItems(AsyncTaskPageData data) {
   1218         int page = data.page;
   1219         PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
   1220 
   1221         ArrayList<Object> items = data.items;
   1222         int count = items.size();
   1223         for (int i = 0; i < count; ++i) {
   1224             PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
   1225             if (widget != null) {
   1226                 Bitmap preview = data.generatedImages.get(i);
   1227                 widget.applyPreview(new FastBitmapDrawable(preview), i);
   1228             }
   1229         }
   1230 
   1231         layout.createHardwareLayer();
   1232         invalidate();
   1233 
   1234         /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
   1235         if (mFadeInAdjacentScreens) {
   1236             prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages);
   1237         }
   1238         */
   1239 
   1240         // Update all thread priorities
   1241         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
   1242         while (iter.hasNext()) {
   1243             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
   1244             int pageIndex = task.page + mNumAppsPages;
   1245             task.setThreadPriority(getThreadPriorityForPage(pageIndex));
   1246         }
   1247     }
   1248     private void onHolographicPageItemsLoaded(AsyncTaskPageData data) {
   1249         // Invalidate early to short-circuit children invalidates
   1250         invalidate();
   1251 
   1252         int page = data.page;
   1253         ViewGroup layout = (ViewGroup) getPageAt(page);
   1254         if (layout instanceof PagedViewCellLayout) {
   1255             PagedViewCellLayout cl = (PagedViewCellLayout) layout;
   1256             int count = cl.getPageChildCount();
   1257             if (count != data.generatedImages.size()) return;
   1258             for (int i = 0; i < count; ++i) {
   1259                 PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i);
   1260                 icon.setHolographicOutline(data.generatedImages.get(i));
   1261             }
   1262         } else {
   1263             int count = layout.getChildCount();
   1264             if (count != data.generatedImages.size()) return;
   1265             for (int i = 0; i < count; ++i) {
   1266                 View v = layout.getChildAt(i);
   1267                 ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i));
   1268             }
   1269         }
   1270     }
   1271 
   1272     @Override
   1273     public void syncPages() {
   1274         removeAllViews();
   1275         cancelAllTasks();
   1276 
   1277         Context context = getContext();
   1278         for (int j = 0; j < mNumWidgetPages; ++j) {
   1279             PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
   1280                     mWidgetCountY);
   1281             setupPage(layout);
   1282             addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT,
   1283                     LayoutParams.MATCH_PARENT));
   1284         }
   1285 
   1286         for (int i = 0; i < mNumAppsPages; ++i) {
   1287             PagedViewCellLayout layout = new PagedViewCellLayout(context);
   1288             setupPage(layout);
   1289             addView(layout);
   1290         }
   1291     }
   1292 
   1293     @Override
   1294     public void syncPageItems(int page, boolean immediate) {
   1295         if (page < mNumAppsPages) {
   1296             syncAppsPageItems(page, immediate);
   1297         } else {
   1298             syncWidgetPageItems(page - mNumAppsPages, immediate);
   1299         }
   1300     }
   1301 
   1302     // We want our pages to be z-ordered such that the further a page is to the left, the higher
   1303     // it is in the z-order. This is important to insure touch events are handled correctly.
   1304     View getPageAt(int index) {
   1305         return getChildAt(getChildCount() - index - 1);
   1306     }
   1307 
   1308     @Override
   1309     protected int indexToPage(int index) {
   1310         return getChildCount() - index - 1;
   1311     }
   1312 
   1313     // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
   1314     @Override
   1315     protected void screenScrolled(int screenCenter) {
   1316         super.screenScrolled(screenCenter);
   1317 
   1318         for (int i = 0; i < getChildCount(); i++) {
   1319             View v = getPageAt(i);
   1320             if (v != null) {
   1321                 float scrollProgress = getScrollProgress(screenCenter, v, i);
   1322 
   1323                 float interpolatedProgress =
   1324                         mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
   1325                 float scale = (1 - interpolatedProgress) +
   1326                         interpolatedProgress * TRANSITION_SCALE_FACTOR;
   1327                 float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
   1328 
   1329                 float alpha;
   1330 
   1331                 if (!LauncherApplication.isScreenLarge() || scrollProgress < 0) {
   1332                     alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
   1333                         1 - Math.abs(scrollProgress)) : 1.0f;
   1334                 } else {
   1335                     // On large screens we need to fade the page as it nears its leftmost position
   1336                     alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
   1337                 }
   1338 
   1339                 v.setCameraDistance(mDensity * CAMERA_DISTANCE);
   1340                 int pageWidth = v.getMeasuredWidth();
   1341                 int pageHeight = v.getMeasuredHeight();
   1342 
   1343                 if (PERFORM_OVERSCROLL_ROTATION) {
   1344                     if (i == 0 && scrollProgress < 0) {
   1345                         // Overscroll to the left
   1346                         v.setPivotX(TRANSITION_PIVOT * pageWidth);
   1347                         v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
   1348                         scale = 1.0f;
   1349                         alpha = 1.0f;
   1350                         // On the first page, we don't want the page to have any lateral motion
   1351                         translationX = 0;
   1352                     } else if (i == getChildCount() - 1 && scrollProgress > 0) {
   1353                         // Overscroll to the right
   1354                         v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
   1355                         v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
   1356                         scale = 1.0f;
   1357                         alpha = 1.0f;
   1358                         // On the last page, we don't want the page to have any lateral motion.
   1359                         translationX = 0;
   1360                     } else {
   1361                         v.setPivotY(pageHeight / 2.0f);
   1362                         v.setPivotX(pageWidth / 2.0f);
   1363                         v.setRotationY(0f);
   1364                     }
   1365                 }
   1366 
   1367                 v.setTranslationX(translationX);
   1368                 v.setScaleX(scale);
   1369                 v.setScaleY(scale);
   1370                 v.setAlpha(alpha);
   1371 
   1372                 // If the view has 0 alpha, we set it to be invisible so as to prevent
   1373                 // it from accepting touches
   1374                 if (alpha < ViewConfiguration.ALPHA_THRESHOLD) {
   1375                     v.setVisibility(INVISIBLE);
   1376                 } else if (v.getVisibility() != VISIBLE) {
   1377                     v.setVisibility(VISIBLE);
   1378                 }
   1379             }
   1380         }
   1381     }
   1382 
   1383     protected void overScroll(float amount) {
   1384         acceleratedOverScroll(amount);
   1385     }
   1386 
   1387     /**
   1388      * Used by the parent to get the content width to set the tab bar to
   1389      * @return
   1390      */
   1391     public int getPageContentWidth() {
   1392         return mContentWidth;
   1393     }
   1394 
   1395     @Override
   1396     protected void onPageEndMoving() {
   1397         super.onPageEndMoving();
   1398 
   1399         // We reset the save index when we change pages so that it will be recalculated on next
   1400         // rotation
   1401         mSaveInstanceStateItemIndex = -1;
   1402     }
   1403 
   1404     /*
   1405      * AllAppsView implementation
   1406      */
   1407     @Override
   1408     public void setup(Launcher launcher, DragController dragController) {
   1409         mLauncher = launcher;
   1410         mDragController = dragController;
   1411     }
   1412     @Override
   1413     public void zoom(float zoom, boolean animate) {
   1414         // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
   1415     }
   1416     @Override
   1417     public boolean isVisible() {
   1418         return (getVisibility() == VISIBLE);
   1419     }
   1420     @Override
   1421     public boolean isAnimating() {
   1422         return false;
   1423     }
   1424     @Override
   1425     public void setApps(ArrayList<ApplicationInfo> list) {
   1426         mApps = list;
   1427         Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
   1428         updatePageCounts();
   1429 
   1430         // The next layout pass will trigger data-ready if both widgets and apps are set, so
   1431         // request a layout to do this test and invalidate the page data when ready.
   1432         if (testDataReady()) requestLayout();
   1433     }
   1434     private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
   1435         // We add it in place, in alphabetical order
   1436         int count = list.size();
   1437         for (int i = 0; i < count; ++i) {
   1438             ApplicationInfo info = list.get(i);
   1439             int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
   1440             if (index < 0) {
   1441                 mApps.add(-(index + 1), info);
   1442             }
   1443         }
   1444     }
   1445     @Override
   1446     public void addApps(ArrayList<ApplicationInfo> list) {
   1447         addAppsWithoutInvalidate(list);
   1448         updatePageCounts();
   1449         invalidatePageData();
   1450     }
   1451     private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
   1452         ComponentName removeComponent = item.intent.getComponent();
   1453         int length = list.size();
   1454         for (int i = 0; i < length; ++i) {
   1455             ApplicationInfo info = list.get(i);
   1456             if (info.intent.getComponent().equals(removeComponent)) {
   1457                 return i;
   1458             }
   1459         }
   1460         return -1;
   1461     }
   1462     private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
   1463         // loop through all the apps and remove apps that have the same component
   1464         int length = list.size();
   1465         for (int i = 0; i < length; ++i) {
   1466             ApplicationInfo info = list.get(i);
   1467             int removeIndex = findAppByComponent(mApps, info);
   1468             if (removeIndex > -1) {
   1469                 mApps.remove(removeIndex);
   1470             }
   1471         }
   1472     }
   1473     @Override
   1474     public void removeApps(ArrayList<ApplicationInfo> list) {
   1475         removeAppsWithoutInvalidate(list);
   1476         updatePageCounts();
   1477         invalidatePageData();
   1478     }
   1479     @Override
   1480     public void updateApps(ArrayList<ApplicationInfo> list) {
   1481         // We remove and re-add the updated applications list because it's properties may have
   1482         // changed (ie. the title), and this will ensure that the items will be in their proper
   1483         // place in the list.
   1484         removeAppsWithoutInvalidate(list);
   1485         addAppsWithoutInvalidate(list);
   1486         updatePageCounts();
   1487 
   1488         invalidatePageData();
   1489     }
   1490 
   1491     @Override
   1492     public void reset() {
   1493         AppsCustomizeTabHost tabHost = getTabHost();
   1494         String tag = tabHost.getCurrentTabTag();
   1495         if (tag != null) {
   1496             if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
   1497                 tabHost.setCurrentTabFromContent(ContentType.Applications);
   1498             }
   1499         }
   1500         if (mCurrentPage != 0) {
   1501             invalidatePageData(0);
   1502         }
   1503     }
   1504 
   1505     private AppsCustomizeTabHost getTabHost() {
   1506         return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
   1507     }
   1508 
   1509     @Override
   1510     public void dumpState() {
   1511         // TODO: Dump information related to current list of Applications, Widgets, etc.
   1512         ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps);
   1513         dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets);
   1514     }
   1515 
   1516     private void dumpAppWidgetProviderInfoList(String tag, String label,
   1517             ArrayList<Object> list) {
   1518         Log.d(tag, label + " size=" + list.size());
   1519         for (Object i: list) {
   1520             if (i instanceof AppWidgetProviderInfo) {
   1521                 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
   1522                 Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
   1523                         + " resizeMode=" + info.resizeMode + " configure=" + info.configure
   1524                         + " initialLayout=" + info.initialLayout
   1525                         + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
   1526             } else if (i instanceof ResolveInfo) {
   1527                 ResolveInfo info = (ResolveInfo) i;
   1528                 Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
   1529                         + info.icon);
   1530             }
   1531         }
   1532     }
   1533 
   1534     @Override
   1535     public void surrender() {
   1536         // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
   1537         // should stop this now.
   1538 
   1539         // Stop all background tasks
   1540         cancelAllTasks();
   1541     }
   1542 
   1543 
   1544     /*
   1545      * We load an extra page on each side to prevent flashes from scrolling and loading of the
   1546      * widget previews in the background with the AsyncTasks.
   1547      */
   1548     final static int sLookBehindPageCount = 2;
   1549     final static int sLookAheadPageCount = 2;
   1550     protected int getAssociatedLowerPageBound(int page) {
   1551         final int count = getChildCount();
   1552         int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
   1553         int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
   1554         return windowMinIndex;
   1555     }
   1556     protected int getAssociatedUpperPageBound(int page) {
   1557         final int count = getChildCount();
   1558         int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
   1559         int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
   1560                 count - 1);
   1561         return windowMaxIndex;
   1562     }
   1563 
   1564     @Override
   1565     protected String getCurrentPageDescription() {
   1566         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
   1567         int stringId = R.string.default_scroll_format;
   1568         int count = 0;
   1569 
   1570         if (page < mNumAppsPages) {
   1571             stringId = R.string.apps_customize_apps_scroll_format;
   1572             count = mNumAppsPages;
   1573         } else {
   1574             page -= mNumAppsPages;
   1575             stringId = R.string.apps_customize_widgets_scroll_format;
   1576             count = mNumWidgetPages;
   1577         }
   1578 
   1579         return String.format(mContext.getString(stringId), page + 1, count);
   1580     }
   1581 }
   1582