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