• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
run(AppsCustomizeAsyncTask task, AsyncTaskPageData data)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 
AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR, AsyncTaskCallback postR)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     }
AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, int ccx, AsyncTaskCallback bgR, AsyncTaskCallback postR)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     }
cleanup(boolean cancelled)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> {
AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty)136     AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
137         page = p;
138         threadPriority = Process.THREAD_PRIORITY_DEFAULT;
139         dataType = ty;
140     }
141     @Override
doInBackground(AsyncTaskPageData... params)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
onPostExecute(AsyncTaskPageData result)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 
setThreadPriority(int p)154     void setThreadPriority(int p) {
155         threadPriority = p;
156     }
syncThreadPriority()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 
AppsCustomizePagedView(Context context, AttributeSet attrs)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
init()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
onUnhandledTap(MotionEvent ev)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. */
getMiddleComponentIndexOnCurrentPage()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. */
getSaveInstanceStateIndex()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. */
getPageForComponent(int 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      */
testDataReady()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 */
restorePageForIndex(int index)350     void restorePageForIndex(int index) {
351         if (index < 0) return;
352         mSaveInstanceStateItemIndex = index;
353     }
354 
updatePageCounts()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 
onDataReady(int width, int height)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 */
removeResolveInfoWithComponentName(List<ResolveInfo> list, ComponentName cn)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 
onPackagesUpdated()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 
updatePackages()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
onClick(View v)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
determineDraggingStart(android.view.MotionEvent ev)510     protected void determineDraggingStart(android.view.MotionEvent ev) {
511         // Disable dragging by pulling an app down for now.
512     }
513 
beginDraggingApplication(View v)514     private void beginDraggingApplication(View v) {
515         mLauncher.getWorkspace().onDragStartedWithItem(v);
516         mLauncher.getWorkspace().beginDragShared(v, this);
517     }
518 
beginDraggingWidget(View v)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
beginDragging(View v)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     }
endDragging(View target, boolean success)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
onDropCompleted(View target, DragObject d, boolean success)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
onDetachedFromWindow()628     protected void onDetachedFromWindow() {
629         super.onDetachedFromWindow();
630         cancelAllTasks();
631     }
632 
cancelAllTasks()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 
setContentType(ContentType type)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 
snapToPage(int whichPage, int delta, int duration)651     protected void snapToPage(int whichPage, int delta, int duration) {
652         super.snapToPage(whichPage, delta, duration);
653         updateCurrentTab(whichPage);
654     }
655 
updateCurrentTab(int currentPage)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      */
setVisibilityOnChildren(ViewGroup layout, int visibility)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     }
setupPage(PagedViewCellLayout layout)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 
syncAppsPageItems(int page, boolean immediate)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      */
getThreadPriorityForPage(int page)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     }
getSleepForPage(int page)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      */
prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, int cellWidth, int cellHeight, int cellCountX)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      */
prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items, ArrayList<Bitmap> images)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      */
setupPage(PagedViewGridLayout layout)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 
renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h)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     }
renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, float scale)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     }
renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, float scale, int multiplyColor)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     }
getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight)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     }
getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, int cellVSpan, int cellWidth, int cellHeight)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 
syncWidgetPageItems(int page, boolean immediate)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     }
loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, AsyncTaskPageData data)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     }
onSyncWidgetPageItems(AsyncTaskPageData data)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     }
onHolographicPageItemsLoaded(AsyncTaskPageData data)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
syncPages()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
syncPageItems(int page, boolean immediate)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.
getPageAt(int index)1212     View getPageAt(int index) {
1213         return getChildAt(getChildCount() - index - 1);
1214     }
1215 
1216     @Override
indexToPage(int index)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
screenScrolled(int screenCenter)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