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