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