• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.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                         boolean attached = true;
370                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
371                             attached = isAttachedToWindow();
372                         }
373                         if (attached) {
374                             setDataIsReady();
375                             onDataReady(getMeasuredWidth(), getMeasuredHeight());
376                         }
377                     }
378                 });
379             }
380         }
381     }
382 
onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts)383     public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
384         LauncherAppState app = LauncherAppState.getInstance();
385         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
386 
387         // Get the list of widgets and shortcuts
388         mWidgets.clear();
389         for (Object o : widgetsAndShortcuts) {
390             if (o instanceof AppWidgetProviderInfo) {
391                 AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o;
392                 if (!app.shouldShowAppOrWidgetProvider(widget.provider)) {
393                     continue;
394                 }
395                 if (widget.minWidth > 0 && widget.minHeight > 0) {
396                     // Ensure that all widgets we show can be added on a workspace of this size
397                     int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
398                     int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
399                     int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
400                     int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
401                     if (minSpanX <= (int) grid.numColumns &&
402                         minSpanY <= (int) grid.numRows) {
403                         mWidgets.add(widget);
404                     } else {
405                         Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
406                               widget.minWidth + ", " + widget.minHeight + ")");
407                     }
408                 } else {
409                     Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
410                           widget.minWidth + ", " + widget.minHeight + ")");
411                 }
412             } else {
413                 // just add shortcuts
414                 mWidgets.add(o);
415             }
416         }
417         updatePageCountsAndInvalidateData();
418     }
419 
setBulkBind(boolean bulkBind)420     public void setBulkBind(boolean bulkBind) {
421         if (bulkBind) {
422             mInBulkBind = true;
423         } else {
424             mInBulkBind = false;
425             if (mNeedToUpdatePageCountsAndInvalidateData) {
426                 updatePageCountsAndInvalidateData();
427             }
428         }
429     }
430 
updatePageCountsAndInvalidateData()431     private void updatePageCountsAndInvalidateData() {
432         if (mInBulkBind) {
433             mNeedToUpdatePageCountsAndInvalidateData = true;
434         } else {
435             updatePageCounts();
436             invalidateOnDataChange();
437             mNeedToUpdatePageCountsAndInvalidateData = false;
438         }
439     }
440 
441     @Override
onClick(View v)442     public void onClick(View v) {
443         // When we have exited all apps or are in transition, disregard clicks
444         if (!mLauncher.isAllAppsVisible()
445                 || mLauncher.getWorkspace().isSwitchingState()
446                 || !(v instanceof PagedViewWidget)) return;
447 
448         // Let the user know that they have to long press to add a widget
449         if (mWidgetInstructionToast != null) {
450             mWidgetInstructionToast.cancel();
451         }
452         mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
453             Toast.LENGTH_SHORT);
454         mWidgetInstructionToast.show();
455 
456         // Create a little animation to show that the widget can move
457         float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
458         final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
459         AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
460         ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
461         tyuAnim.setDuration(125);
462         ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
463         tydAnim.setDuration(100);
464         bounce.play(tyuAnim).before(tydAnim);
465         bounce.setInterpolator(new AccelerateInterpolator());
466         bounce.start();
467     }
468 
onKey(View v, int keyCode, KeyEvent event)469     public boolean onKey(View v, int keyCode, KeyEvent event) {
470         return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
471     }
472 
473     /*
474      * PagedViewWithDraggableItems implementation
475      */
476     @Override
determineDraggingStart(android.view.MotionEvent ev)477     protected void determineDraggingStart(android.view.MotionEvent ev) {
478         // Disable dragging by pulling an app down for now.
479     }
480 
beginDraggingApplication(View v)481     private void beginDraggingApplication(View v) {
482         mLauncher.getWorkspace().beginDragShared(v, this);
483     }
484 
getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info)485     static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
486         Bundle options = null;
487         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
488             AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, sTmpRect);
489             Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
490                     info.componentName, null);
491 
492             float density = launcher.getResources().getDisplayMetrics().density;
493             int xPaddingDips = (int) ((padding.left + padding.right) / density);
494             int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
495 
496             options = new Bundle();
497             options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
498                     sTmpRect.left - xPaddingDips);
499             options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
500                     sTmpRect.top - yPaddingDips);
501             options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
502                     sTmpRect.right - xPaddingDips);
503             options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
504                     sTmpRect.bottom - yPaddingDips);
505         }
506         return options;
507     }
508 
preloadWidget(final PendingAddWidgetInfo info)509     private void preloadWidget(final PendingAddWidgetInfo info) {
510         final AppWidgetProviderInfo pInfo = info.info;
511         final Bundle options = getDefaultOptionsForWidget(mLauncher, info);
512 
513         if (pInfo.configure != null) {
514             info.bindOptions = options;
515             return;
516         }
517 
518         mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
519         mBindWidgetRunnable = new Runnable() {
520             @Override
521             public void run() {
522                 mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
523                 if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
524                         mWidgetLoadingId, pInfo, options)) {
525                     mWidgetCleanupState = WIDGET_BOUND;
526                 }
527             }
528         };
529         post(mBindWidgetRunnable);
530 
531         mInflateWidgetRunnable = new Runnable() {
532             @Override
533             public void run() {
534                 if (mWidgetCleanupState != WIDGET_BOUND) {
535                     return;
536                 }
537                 AppWidgetHostView hostView = mLauncher.
538                         getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
539                 info.boundWidget = hostView;
540                 mWidgetCleanupState = WIDGET_INFLATED;
541                 hostView.setVisibility(INVISIBLE);
542                 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
543                         info.spanY, info, false);
544 
545                 // We want the first widget layout to be the correct size. This will be important
546                 // for width size reporting to the AppWidgetManager.
547                 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
548                         unScaledSize[1]);
549                 lp.x = lp.y = 0;
550                 lp.customPosition = true;
551                 hostView.setLayoutParams(lp);
552                 mLauncher.getDragLayer().addView(hostView);
553             }
554         };
555         post(mInflateWidgetRunnable);
556     }
557 
558     @Override
onShortPress(View v)559     public void onShortPress(View v) {
560         // We are anticipating a long press, and we use this time to load bind and instantiate
561         // the widget. This will need to be cleaned up if it turns out no long press occurs.
562         if (mCreateWidgetInfo != null) {
563             // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
564             cleanupWidgetPreloading(false);
565         }
566         mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
567         preloadWidget(mCreateWidgetInfo);
568     }
569 
cleanupWidgetPreloading(boolean widgetWasAdded)570     private void cleanupWidgetPreloading(boolean widgetWasAdded) {
571         if (!widgetWasAdded) {
572             // If the widget was not added, we may need to do further cleanup.
573             PendingAddWidgetInfo info = mCreateWidgetInfo;
574             mCreateWidgetInfo = null;
575 
576             if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
577                 // We never did any preloading, so just remove pending callbacks to do so
578                 removeCallbacks(mBindWidgetRunnable);
579                 removeCallbacks(mInflateWidgetRunnable);
580             } else if (mWidgetCleanupState == WIDGET_BOUND) {
581                  // Delete the widget id which was allocated
582                 if (mWidgetLoadingId != -1) {
583                     mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
584                 }
585 
586                 // We never got around to inflating the widget, so remove the callback to do so.
587                 removeCallbacks(mInflateWidgetRunnable);
588             } else if (mWidgetCleanupState == WIDGET_INFLATED) {
589                 // Delete the widget id which was allocated
590                 if (mWidgetLoadingId != -1) {
591                     mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
592                 }
593 
594                 // The widget was inflated and added to the DragLayer -- remove it.
595                 AppWidgetHostView widget = info.boundWidget;
596                 mLauncher.getDragLayer().removeView(widget);
597             }
598         }
599         mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
600         mWidgetLoadingId = -1;
601         mCreateWidgetInfo = null;
602         PagedViewWidget.resetShortPressTarget();
603     }
604 
605     @Override
cleanUpShortPress(View v)606     public void cleanUpShortPress(View v) {
607         if (!mDraggingWidget) {
608             cleanupWidgetPreloading(false);
609         }
610     }
611 
beginDraggingWidget(View v)612     private boolean beginDraggingWidget(View v) {
613         mDraggingWidget = true;
614         // Get the widget preview as the drag representation
615         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
616         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
617 
618         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
619         // we abort the drag.
620         if (image.getDrawable() == null) {
621             mDraggingWidget = false;
622             return false;
623         }
624 
625         // Compose the drag image
626         Bitmap preview;
627         Bitmap outline;
628         float scale = 1f;
629         Point previewPadding = null;
630 
631         if (createItemInfo instanceof PendingAddWidgetInfo) {
632             // This can happen in some weird cases involving multi-touch. We can't start dragging
633             // the widget if this is null, so we break out.
634             if (mCreateWidgetInfo == null) {
635                 return false;
636             }
637 
638             PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
639             createItemInfo = createWidgetInfo;
640             int spanX = createItemInfo.spanX;
641             int spanY = createItemInfo.spanY;
642             int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
643                     createWidgetInfo, true);
644 
645             FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
646             float minScale = 1.25f;
647             int maxWidth, maxHeight;
648             maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
649             maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
650 
651             int[] previewSizeBeforeScale = new int[1];
652 
653             preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
654                     spanX, spanY, maxWidth, maxHeight, null, previewSizeBeforeScale);
655 
656             // Compare the size of the drag preview to the preview in the AppsCustomize tray
657             int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
658                     getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
659             scale = previewWidthInAppsCustomize / (float) preview.getWidth();
660 
661             // The bitmap in the AppsCustomize tray is always the the same size, so there
662             // might be extra pixels around the preview itself - this accounts for that
663             if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
664                 int padding =
665                         (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
666                 previewPadding = new Point(padding, 0);
667             }
668         } else {
669             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
670             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
671             preview = Utilities.createIconBitmap(icon, mLauncher);
672             createItemInfo.spanX = createItemInfo.spanY = 1;
673         }
674 
675         // Don't clip alpha values for the drag outline if we're using the default widget preview
676         boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
677                 (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
678 
679         // Save the preview for the outline generation, then dim the preview
680         outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
681                 false);
682 
683         // Start the drag
684         mLauncher.lockScreenOrientation();
685         mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
686         mDragController.startDrag(image, preview, this, createItemInfo,
687                 DragController.DRAG_ACTION_COPY, previewPadding, scale);
688         outline.recycle();
689         preview.recycle();
690         return true;
691     }
692 
693     @Override
beginDragging(final View v)694     protected boolean beginDragging(final View v) {
695         if (!super.beginDragging(v)) return false;
696 
697         if (v instanceof BubbleTextView) {
698             beginDraggingApplication(v);
699         } else if (v instanceof PagedViewWidget) {
700             if (!beginDraggingWidget(v)) {
701                 return false;
702             }
703         }
704 
705         // We delay entering spring-loaded mode slightly to make sure the UI
706         // thready is free of any work.
707         postDelayed(new Runnable() {
708             @Override
709             public void run() {
710                 // We don't enter spring-loaded mode if the drag has been cancelled
711                 if (mLauncher.getDragController().isDragging()) {
712                     // Go into spring loaded mode (must happen before we startDrag())
713                     mLauncher.enterSpringLoadedDragMode();
714                 }
715             }
716         }, 150);
717 
718         return true;
719     }
720 
721     /**
722      * Clean up after dragging.
723      *
724      * @param target where the item was dragged to (can be null if the item was flung)
725      */
endDragging(View target, boolean isFlingToDelete, boolean success)726     private void endDragging(View target, boolean isFlingToDelete, boolean success) {
727         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
728                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
729             // Exit spring loaded mode if we have not successfully dropped or have not handled the
730             // drop in Workspace
731             mLauncher.exitSpringLoadedDragMode();
732             mLauncher.unlockScreenOrientation(false);
733         } else {
734             mLauncher.unlockScreenOrientation(false);
735         }
736     }
737 
738     @Override
getContent()739     public View getContent() {
740         if (getChildCount() > 0) {
741             return getChildAt(0);
742         }
743         return null;
744     }
745 
746     @Override
onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace)747     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
748         mInTransition = true;
749         if (toWorkspace) {
750             cancelAllTasks();
751         }
752     }
753 
754     @Override
onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace)755     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
756     }
757 
758     @Override
onLauncherTransitionStep(Launcher l, float t)759     public void onLauncherTransitionStep(Launcher l, float t) {
760     }
761 
762     @Override
onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace)763     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
764         mInTransition = false;
765         for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
766             onSyncWidgetPageItems(d, false);
767         }
768         mDeferredSyncWidgetPageItems.clear();
769         for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
770             r.run();
771         }
772         mDeferredPrepareLoadWidgetPreviewsTasks.clear();
773         mForceDrawAllChildrenNextFrame = !toWorkspace;
774     }
775 
776     @Override
onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success)777     public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
778             boolean success) {
779         // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
780         if (isFlingToDelete) return;
781 
782         endDragging(target, false, success);
783 
784         // Display an error message if the drag failed due to there not being enough space on the
785         // target layout we were dropping on.
786         if (!success) {
787             boolean showOutOfSpaceMessage = false;
788             if (target instanceof Workspace) {
789                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
790                 Workspace workspace = (Workspace) target;
791                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
792                 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
793                 if (layout != null) {
794                     layout.calculateSpans(itemInfo);
795                     showOutOfSpaceMessage =
796                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
797                 }
798             }
799             if (showOutOfSpaceMessage) {
800                 mLauncher.showOutOfSpaceMessage(false);
801             }
802 
803             d.deferDragViewCleanupPostAnimation = false;
804         }
805         cleanupWidgetPreloading(success);
806         mDraggingWidget = false;
807     }
808 
809     @Override
onFlingToDeleteCompleted()810     public void onFlingToDeleteCompleted() {
811         // We just dismiss the drag when we fling, so cleanup here
812         endDragging(null, true, true);
813         cleanupWidgetPreloading(false);
814         mDraggingWidget = false;
815     }
816 
817     @Override
supportsFlingToDelete()818     public boolean supportsFlingToDelete() {
819         return true;
820     }
821 
822     @Override
supportsAppInfoDropTarget()823     public boolean supportsAppInfoDropTarget() {
824         return true;
825     }
826 
827     @Override
supportsDeleteDropTarget()828     public boolean supportsDeleteDropTarget() {
829         return false;
830     }
831 
832     @Override
getIntrinsicIconScaleFactor()833     public float getIntrinsicIconScaleFactor() {
834         LauncherAppState app = LauncherAppState.getInstance();
835         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
836         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
837     }
838 
839     @Override
onDetachedFromWindow()840     protected void onDetachedFromWindow() {
841         super.onDetachedFromWindow();
842         cancelAllTasks();
843     }
844 
clearAllWidgetPages()845     public void clearAllWidgetPages() {
846         cancelAllTasks();
847         int count = getChildCount();
848         for (int i = 0; i < count; i++) {
849             View v = getPageAt(i);
850             if (v instanceof PagedViewGridLayout) {
851                 ((PagedViewGridLayout) v).removeAllViewsOnPage();
852                 mDirtyPageContent.set(i, true);
853             }
854         }
855     }
856 
cancelAllTasks()857     private void cancelAllTasks() {
858         // Clean up all the async tasks
859         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
860         while (iter.hasNext()) {
861             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
862             task.cancel(false);
863             iter.remove();
864             mDirtyPageContent.set(task.page, true);
865 
866             // We've already preallocated the views for the data to load into, so clear them as well
867             View v = getPageAt(task.page);
868             if (v instanceof PagedViewGridLayout) {
869                 ((PagedViewGridLayout) v).removeAllViewsOnPage();
870             }
871         }
872         mDeferredSyncWidgetPageItems.clear();
873         mDeferredPrepareLoadWidgetPreviewsTasks.clear();
874     }
875 
setContentType(ContentType type)876     public void setContentType(ContentType type) {
877         // Widgets appear to be cleared every time you leave, always force invalidate for them
878         if (mContentType != type || type == ContentType.Widgets) {
879             int page = (mContentType != type) ? 0 : getCurrentPage();
880             mContentType = type;
881             invalidatePageData(page, true);
882         }
883     }
884 
getContentType()885     public ContentType getContentType() {
886         return mContentType;
887     }
888 
snapToPage(int whichPage, int delta, int duration)889     protected void snapToPage(int whichPage, int delta, int duration) {
890         super.snapToPage(whichPage, delta, duration);
891 
892         // Update the thread priorities given the direction lookahead
893         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
894         while (iter.hasNext()) {
895             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
896             int pageIndex = task.page;
897             if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
898                 (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
899                 task.setThreadPriority(getThreadPriorityForPage(pageIndex));
900             } else {
901                 task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
902             }
903         }
904     }
905 
906     /*
907      * Apps PagedView implementation
908      */
setVisibilityOnChildren(ViewGroup layout, int visibility)909     private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
910         int childCount = layout.getChildCount();
911         for (int i = 0; i < childCount; ++i) {
912             layout.getChildAt(i).setVisibility(visibility);
913         }
914     }
setupPage(AppsCustomizeCellLayout layout)915     private void setupPage(AppsCustomizeCellLayout layout) {
916         layout.setGridSize(mCellCountX, mCellCountY);
917 
918         // Note: We force a measure here to get around the fact that when we do layout calculations
919         // immediately after syncing, we don't have a proper width.  That said, we already know the
920         // expected page width, so we can actually optimize by hiding all the TextView-based
921         // children that are expensive to measure, and let that happen naturally later.
922         setVisibilityOnChildren(layout, View.GONE);
923         int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
924         int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
925         layout.measure(widthSpec, heightSpec);
926 
927         Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
928         if (bg != null) {
929             bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
930             layout.setBackground(bg);
931         }
932 
933         setVisibilityOnChildren(layout, View.VISIBLE);
934     }
935 
setPageBackgroundsVisible(boolean visible)936     public void setPageBackgroundsVisible(boolean visible) {
937         mPageBackgroundsVisible = visible;
938         int childCount = getChildCount();
939         for (int i = 0; i < childCount; ++i) {
940             Drawable bg = getChildAt(i).getBackground();
941             if (bg != null) {
942                 bg.setAlpha(visible ? 255 : 0);
943             }
944         }
945     }
946 
syncAppsPageItems(int page, boolean immediate)947     public void syncAppsPageItems(int page, boolean immediate) {
948         // ensure that we have the right number of items on the pages
949         final boolean isRtl = isLayoutRtl();
950         int numCells = mCellCountX * mCellCountY;
951         int startIndex = page * numCells;
952         int endIndex = Math.min(startIndex + numCells, mApps.size());
953         AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
954 
955         layout.removeAllViewsOnPage();
956         ArrayList<Object> items = new ArrayList<Object>();
957         ArrayList<Bitmap> images = new ArrayList<Bitmap>();
958         for (int i = startIndex; i < endIndex; ++i) {
959             AppInfo info = mApps.get(i);
960             BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
961                     R.layout.apps_customize_application, layout, false);
962             icon.applyFromApplicationInfo(info);
963             icon.setOnClickListener(mLauncher);
964             icon.setOnLongClickListener(this);
965             icon.setOnTouchListener(this);
966             icon.setOnKeyListener(this);
967             icon.setOnFocusChangeListener(layout.mFocusHandlerView);
968 
969             int index = i - startIndex;
970             int x = index % mCellCountX;
971             int y = index / mCellCountX;
972             if (isRtl) {
973                 x = mCellCountX - x - 1;
974             }
975             layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
976 
977             items.add(info);
978             images.add(info.iconBitmap);
979         }
980 
981         enableHwLayersOnVisiblePages();
982     }
983 
984     /**
985      * A helper to return the priority for loading of the specified widget page.
986      */
getWidgetPageLoadPriority(int page)987     private int getWidgetPageLoadPriority(int page) {
988         // If we are snapping to another page, use that index as the target page index
989         int toPage = mCurrentPage;
990         if (mNextPage > -1) {
991             toPage = mNextPage;
992         }
993 
994         // We use the distance from the target page as an initial guess of priority, but if there
995         // are no pages of higher priority than the page specified, then bump up the priority of
996         // the specified page.
997         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
998         int minPageDiff = Integer.MAX_VALUE;
999         while (iter.hasNext()) {
1000             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1001             minPageDiff = Math.abs(task.page - toPage);
1002         }
1003 
1004         int rawPageDiff = Math.abs(page - toPage);
1005         return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
1006     }
1007     /**
1008      * Return the appropriate thread priority for loading for a given page (we give the current
1009      * page much higher priority)
1010      */
getThreadPriorityForPage(int page)1011     private int getThreadPriorityForPage(int page) {
1012         // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
1013         int pageDiff = getWidgetPageLoadPriority(page);
1014         if (pageDiff <= 0) {
1015             return Process.THREAD_PRIORITY_LESS_FAVORABLE;
1016         } else if (pageDiff <= 1) {
1017             return Process.THREAD_PRIORITY_LOWEST;
1018         } else {
1019             return Process.THREAD_PRIORITY_LOWEST;
1020         }
1021     }
getSleepForPage(int page)1022     private int getSleepForPage(int page) {
1023         int pageDiff = getWidgetPageLoadPriority(page);
1024         return Math.max(0, pageDiff * sPageSleepDelay);
1025     }
1026     /**
1027      * Creates and executes a new AsyncTask to load a page of widget previews.
1028      */
prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, int cellWidth, int cellHeight, int cellCountX)1029     private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
1030             int cellWidth, int cellHeight, int cellCountX) {
1031 
1032         // Prune all tasks that are no longer needed
1033         Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1034         while (iter.hasNext()) {
1035             AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1036             int taskPage = task.page;
1037             if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
1038                     taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
1039                 task.cancel(false);
1040                 iter.remove();
1041             } else {
1042                 task.setThreadPriority(getThreadPriorityForPage(taskPage));
1043             }
1044         }
1045 
1046         // We introduce a slight delay to order the loading of side pages so that we don't thrash
1047         final int sleepMs = getSleepForPage(page);
1048         AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
1049             new AsyncTaskCallback() {
1050                 @Override
1051                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1052                     try {
1053                         try {
1054                             Thread.sleep(sleepMs);
1055                         } catch (Exception e) {}
1056                         loadWidgetPreviewsInBackground(task, data);
1057                     } finally {
1058                         if (task.isCancelled()) {
1059                             data.cleanup(true);
1060                         }
1061                     }
1062                 }
1063             },
1064             new AsyncTaskCallback() {
1065                 @Override
1066                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
1067                     mRunningTasks.remove(task);
1068                     if (task.isCancelled()) return;
1069                     // do cleanup inside onSyncWidgetPageItems
1070                     onSyncWidgetPageItems(data, false);
1071                 }
1072             }, getWidgetPreviewLoader());
1073 
1074         // Ensure that the task is appropriately prioritized and runs in parallel
1075         AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
1076                 AsyncTaskPageData.Type.LoadWidgetPreviewData);
1077         t.setThreadPriority(getThreadPriorityForPage(page));
1078         t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
1079         mRunningTasks.add(t);
1080     }
1081 
1082     /*
1083      * Widgets PagedView implementation
1084      */
setupPage(PagedViewGridLayout layout)1085     private void setupPage(PagedViewGridLayout layout) {
1086         // Note: We force a measure here to get around the fact that when we do layout calculations
1087         // immediately after syncing, we don't have a proper width.
1088         int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
1089         int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
1090 
1091         Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel_dark);
1092         if (bg != null) {
1093             bg.setAlpha(mPageBackgroundsVisible ? 255 : 0);
1094             layout.setBackground(bg);
1095         }
1096         layout.measure(widthSpec, heightSpec);
1097     }
1098 
syncWidgetPageItems(final int page, final boolean immediate)1099     public void syncWidgetPageItems(final int page, final boolean immediate) {
1100         int numItemsPerPage = mWidgetCountX * mWidgetCountY;
1101 
1102         final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1103 
1104         // Calculate the dimensions of each cell we are giving to each widget
1105         final ArrayList<Object> items = new ArrayList<Object>();
1106         int contentWidth = mContentWidth - layout.getPaddingLeft() - layout.getPaddingRight();
1107         final int cellWidth = contentWidth / mWidgetCountX;
1108         int contentHeight = mContentHeight - layout.getPaddingTop() - layout.getPaddingBottom();
1109 
1110         final int cellHeight = contentHeight / mWidgetCountY;
1111 
1112         // Prepare the set of widgets to load previews for in the background
1113         int offset = page * numItemsPerPage;
1114         for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
1115             items.add(mWidgets.get(i));
1116         }
1117 
1118         // Prepopulate the pages with the other widget info, and fill in the previews later
1119         layout.setColumnCount(layout.getCellCountX());
1120         for (int i = 0; i < items.size(); ++i) {
1121             Object rawInfo = items.get(i);
1122             PendingAddItemInfo createItemInfo = null;
1123             PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
1124                     R.layout.apps_customize_widget, layout, false);
1125             if (rawInfo instanceof AppWidgetProviderInfo) {
1126                 // Fill in the widget information
1127                 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
1128                 createItemInfo = new PendingAddWidgetInfo(info, null, null);
1129 
1130                 // Determine the widget spans and min resize spans.
1131                 int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
1132                 createItemInfo.spanX = spanXY[0];
1133                 createItemInfo.spanY = spanXY[1];
1134                 int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
1135                 createItemInfo.minSpanX = minSpanXY[0];
1136                 createItemInfo.minSpanY = minSpanXY[1];
1137 
1138                 widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, getWidgetPreviewLoader());
1139                 widget.setTag(createItemInfo);
1140                 widget.setShortPressListener(this);
1141             } else if (rawInfo instanceof ResolveInfo) {
1142                 // Fill in the shortcuts information
1143                 ResolveInfo info = (ResolveInfo) rawInfo;
1144                 createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
1145                 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1146                 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
1147                         info.activityInfo.name);
1148                 widget.applyFromResolveInfo(mPackageManager, info, getWidgetPreviewLoader());
1149                 widget.setTag(createItemInfo);
1150             }
1151             widget.setOnClickListener(this);
1152             widget.setOnLongClickListener(this);
1153             widget.setOnTouchListener(this);
1154             widget.setOnKeyListener(this);
1155 
1156             // Layout each widget
1157             int ix = i % mWidgetCountX;
1158             int iy = i / mWidgetCountX;
1159 
1160             if (ix > 0) {
1161                 View border = widget.findViewById(R.id.left_border);
1162                 border.setVisibility(View.VISIBLE);
1163             }
1164             if (ix < mWidgetCountX - 1) {
1165                 View border = widget.findViewById(R.id.right_border);
1166                 border.setVisibility(View.VISIBLE);
1167             }
1168 
1169             GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
1170                     GridLayout.spec(iy, GridLayout.START),
1171                     GridLayout.spec(ix, GridLayout.TOP));
1172             lp.width = cellWidth;
1173             lp.height = cellHeight;
1174             lp.setGravity(Gravity.TOP | Gravity.START);
1175             layout.addView(widget, lp);
1176         }
1177 
1178         // wait until a call on onLayout to start loading, because
1179         // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
1180         // TODO: can we do a measure/layout immediately?
1181         layout.setOnLayoutListener(new Runnable() {
1182             public void run() {
1183                 // Load the widget previews
1184                 int maxPreviewWidth = cellWidth;
1185                 int maxPreviewHeight = cellHeight;
1186                 if (layout.getChildCount() > 0) {
1187                     PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
1188                     int[] maxSize = w.getPreviewSize();
1189                     maxPreviewWidth = maxSize[0];
1190                     maxPreviewHeight = maxSize[1];
1191                 }
1192 
1193                 getWidgetPreviewLoader().setPreviewSize(
1194                         maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
1195                 if (immediate) {
1196                     AsyncTaskPageData data = new AsyncTaskPageData(page, items,
1197                             maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
1198                     loadWidgetPreviewsInBackground(null, data);
1199                     onSyncWidgetPageItems(data, immediate);
1200                 } else {
1201                     if (mInTransition) {
1202                         mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
1203                     } else {
1204                         prepareLoadWidgetPreviewsTask(page, items,
1205                                 maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
1206                     }
1207                 }
1208                 layout.setOnLayoutListener(null);
1209             }
1210         });
1211     }
loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, AsyncTaskPageData data)1212     private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
1213             AsyncTaskPageData data) {
1214         // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
1215         // previews synchronously
1216         if (task != null) {
1217             // Ensure that this task starts running at the correct priority
1218             task.syncThreadPriority();
1219         }
1220 
1221         // Load each of the widget/shortcut previews
1222         ArrayList<Object> items = data.items;
1223         ArrayList<Bitmap> images = data.generatedImages;
1224         int count = items.size();
1225         for (int i = 0; i < count; ++i) {
1226             if (task != null) {
1227                 // Ensure we haven't been cancelled yet
1228                 if (task.isCancelled()) break;
1229                 // Before work on each item, ensure that this task is running at the correct
1230                 // priority
1231                 task.syncThreadPriority();
1232             }
1233 
1234             images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
1235         }
1236     }
1237 
onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems)1238     private void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
1239         if (!immediatelySyncItems && mInTransition) {
1240             mDeferredSyncWidgetPageItems.add(data);
1241             return;
1242         }
1243         try {
1244             int page = data.page;
1245             PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
1246 
1247             ArrayList<Object> items = data.items;
1248             int count = items.size();
1249             for (int i = 0; i < count; ++i) {
1250                 PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
1251                 if (widget != null) {
1252                     Bitmap preview = data.generatedImages.get(i);
1253                     widget.applyPreview(new FastBitmapDrawable(preview), i);
1254                 }
1255             }
1256 
1257             enableHwLayersOnVisiblePages();
1258 
1259             // Update all thread priorities
1260             Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
1261             while (iter.hasNext()) {
1262                 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
1263                 int pageIndex = task.page;
1264                 task.setThreadPriority(getThreadPriorityForPage(pageIndex));
1265             }
1266         } finally {
1267             data.cleanup(false);
1268         }
1269     }
1270 
1271     @Override
syncPages()1272     public void syncPages() {
1273         disablePagedViewAnimations();
1274 
1275         removeAllViews();
1276         cancelAllTasks();
1277 
1278         Context context = getContext();
1279         if (mContentType == ContentType.Applications) {
1280             for (int i = 0; i < mNumAppsPages; ++i) {
1281                 AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
1282                 setupPage(layout);
1283                 addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
1284                         LayoutParams.MATCH_PARENT));
1285             }
1286         } else if (mContentType == ContentType.Widgets) {
1287             for (int j = 0; j < mNumWidgetPages; ++j) {
1288                 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
1289                         mWidgetCountY);
1290                 setupPage(layout);
1291                 addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
1292                         LayoutParams.MATCH_PARENT));
1293             }
1294         } else {
1295             throw new RuntimeException("Invalid ContentType");
1296         }
1297 
1298         enablePagedViewAnimations();
1299     }
1300 
1301     @Override
syncPageItems(int page, boolean immediate)1302     public void syncPageItems(int page, boolean immediate) {
1303         if (mContentType == ContentType.Widgets) {
1304             syncWidgetPageItems(page, immediate);
1305         } else {
1306             syncAppsPageItems(page, immediate);
1307         }
1308     }
1309 
1310     // We want our pages to be z-ordered such that the further a page is to the left, the higher
1311     // it is in the z-order. This is important to insure touch events are handled correctly.
getPageAt(int index)1312     View getPageAt(int index) {
1313         return getChildAt(indexToPage(index));
1314     }
1315 
1316     @Override
indexToPage(int index)1317     protected int indexToPage(int index) {
1318         return getChildCount() - index - 1;
1319     }
1320 
1321     // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
1322     @Override
screenScrolled(int screenCenter)1323     protected void screenScrolled(int screenCenter) {
1324         super.screenScrolled(screenCenter);
1325         enableHwLayersOnVisiblePages();
1326     }
1327 
enableHwLayersOnVisiblePages()1328     private void enableHwLayersOnVisiblePages() {
1329         final int screenCount = getChildCount();
1330 
1331         getVisiblePages(mTempVisiblePagesRange);
1332         int leftScreen = mTempVisiblePagesRange[0];
1333         int rightScreen = mTempVisiblePagesRange[1];
1334         int forceDrawScreen = -1;
1335         if (leftScreen == rightScreen) {
1336             // make sure we're caching at least two pages always
1337             if (rightScreen < screenCount - 1) {
1338                 rightScreen++;
1339                 forceDrawScreen = rightScreen;
1340             } else if (leftScreen > 0) {
1341                 leftScreen--;
1342                 forceDrawScreen = leftScreen;
1343             }
1344         } else {
1345             forceDrawScreen = leftScreen + 1;
1346         }
1347 
1348         for (int i = 0; i < screenCount; i++) {
1349             final View layout = (View) getPageAt(i);
1350             if (!(leftScreen <= i && i <= rightScreen &&
1351                     (i == forceDrawScreen || shouldDrawChild(layout)))) {
1352                 layout.setLayerType(LAYER_TYPE_NONE, null);
1353             }
1354         }
1355 
1356         for (int i = 0; i < screenCount; i++) {
1357             final View layout = (View) getPageAt(i);
1358 
1359             if (leftScreen <= i && i <= rightScreen &&
1360                     (i == forceDrawScreen || shouldDrawChild(layout))) {
1361                 if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
1362                     layout.setLayerType(LAYER_TYPE_HARDWARE, null);
1363                 }
1364             }
1365         }
1366     }
1367 
overScroll(float amount)1368     protected void overScroll(float amount) {
1369         dampedOverScroll(amount);
1370     }
1371 
1372     /**
1373      * Used by the parent to get the content width to set the tab bar to
1374      * @return
1375      */
getPageContentWidth()1376     public int getPageContentWidth() {
1377         return mContentWidth;
1378     }
1379 
1380     @Override
onPageEndMoving()1381     protected void onPageEndMoving() {
1382         super.onPageEndMoving();
1383         mForceDrawAllChildrenNextFrame = true;
1384         // We reset the save index when we change pages so that it will be recalculated on next
1385         // rotation
1386         mSaveInstanceStateItemIndex = -1;
1387     }
1388 
1389     /*
1390      * AllAppsView implementation
1391      */
setup(Launcher launcher, DragController dragController)1392     public void setup(Launcher launcher, DragController dragController) {
1393         mLauncher = launcher;
1394         mDragController = dragController;
1395     }
1396 
1397     /**
1398      * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
1399      * appropriately determine when to invalidate the PagedView page data.  In cases where the data
1400      * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
1401      * next onMeasure() pass, which will trigger an invalidatePageData() itself.
1402      */
invalidateOnDataChange()1403     private void invalidateOnDataChange() {
1404         if (!isDataReady()) {
1405             // The next layout pass will trigger data-ready if both widgets and apps are set, so
1406             // request a layout to trigger the page data when ready.
1407             requestLayout();
1408         } else {
1409             cancelAllTasks();
1410             invalidatePageData();
1411         }
1412     }
1413 
setApps(ArrayList<AppInfo> list)1414     public void setApps(ArrayList<AppInfo> list) {
1415         if (!LauncherAppState.isDisableAllApps()) {
1416             mApps = list;
1417             Collections.sort(mApps, LauncherModel.getAppNameComparator());
1418             updatePageCountsAndInvalidateData();
1419         }
1420     }
addAppsWithoutInvalidate(ArrayList<AppInfo> list)1421     private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
1422         // We add it in place, in alphabetical order
1423         int count = list.size();
1424         for (int i = 0; i < count; ++i) {
1425             AppInfo info = list.get(i);
1426             int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
1427             if (index < 0) {
1428                 mApps.add(-(index + 1), info);
1429             }
1430         }
1431     }
addApps(ArrayList<AppInfo> list)1432     public void addApps(ArrayList<AppInfo> list) {
1433         if (!LauncherAppState.isDisableAllApps()) {
1434             addAppsWithoutInvalidate(list);
1435             updatePageCountsAndInvalidateData();
1436         }
1437     }
findAppByComponent(List<AppInfo> list, AppInfo item)1438     private int findAppByComponent(List<AppInfo> list, AppInfo item) {
1439         ComponentName removeComponent = item.intent.getComponent();
1440         int length = list.size();
1441         for (int i = 0; i < length; ++i) {
1442             AppInfo info = list.get(i);
1443             if (info.user.equals(item.user)
1444                     && info.intent.getComponent().equals(removeComponent)) {
1445                 return i;
1446             }
1447         }
1448         return -1;
1449     }
removeAppsWithoutInvalidate(ArrayList<AppInfo> list)1450     private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) {
1451         // loop through all the apps and remove apps that have the same component
1452         int length = list.size();
1453         for (int i = 0; i < length; ++i) {
1454             AppInfo info = list.get(i);
1455             int removeIndex = findAppByComponent(mApps, info);
1456             if (removeIndex > -1) {
1457                 mApps.remove(removeIndex);
1458             }
1459         }
1460     }
removeApps(ArrayList<AppInfo> appInfos)1461     public void removeApps(ArrayList<AppInfo> appInfos) {
1462         if (!LauncherAppState.isDisableAllApps()) {
1463             removeAppsWithoutInvalidate(appInfos);
1464             updatePageCountsAndInvalidateData();
1465         }
1466     }
updateApps(ArrayList<AppInfo> list)1467     public void updateApps(ArrayList<AppInfo> list) {
1468         // We remove and re-add the updated applications list because it's properties may have
1469         // changed (ie. the title), and this will ensure that the items will be in their proper
1470         // place in the list.
1471         if (!LauncherAppState.isDisableAllApps()) {
1472             removeAppsWithoutInvalidate(list);
1473             addAppsWithoutInvalidate(list);
1474             updatePageCountsAndInvalidateData();
1475         }
1476     }
1477 
reset()1478     public void reset() {
1479         // If we have reset, then we should not continue to restore the previous state
1480         mSaveInstanceStateItemIndex = -1;
1481 
1482         if (mContentType != ContentType.Applications) {
1483             setContentType(ContentType.Applications);
1484         }
1485 
1486         if (mCurrentPage != 0) {
1487             invalidatePageData(0);
1488         }
1489     }
1490 
getTabHost()1491     private AppsCustomizeTabHost getTabHost() {
1492         return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
1493     }
1494 
dumpState()1495     public void dumpState() {
1496         // TODO: Dump information related to current list of Applications, Widgets, etc.
1497         AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
1498         dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
1499     }
1500 
dumpAppWidgetProviderInfoList(String tag, String label, ArrayList<Object> list)1501     private void dumpAppWidgetProviderInfoList(String tag, String label,
1502             ArrayList<Object> list) {
1503         Log.d(tag, label + " size=" + list.size());
1504         for (Object i: list) {
1505             if (i instanceof AppWidgetProviderInfo) {
1506                 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
1507                 Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
1508                         + " resizeMode=" + info.resizeMode + " configure=" + info.configure
1509                         + " initialLayout=" + info.initialLayout
1510                         + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
1511             } else if (i instanceof ResolveInfo) {
1512                 ResolveInfo info = (ResolveInfo) i;
1513                 Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
1514                         + info.icon);
1515             }
1516         }
1517     }
1518 
surrender()1519     public void surrender() {
1520         // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
1521         // should stop this now.
1522 
1523         // Stop all background tasks
1524         cancelAllTasks();
1525     }
1526 
1527     /*
1528      * We load an extra page on each side to prevent flashes from scrolling and loading of the
1529      * widget previews in the background with the AsyncTasks.
1530      */
1531     final static int sLookBehindPageCount = 2;
1532     final static int sLookAheadPageCount = 2;
getAssociatedLowerPageBound(int page)1533     protected int getAssociatedLowerPageBound(int page) {
1534         final int count = getChildCount();
1535         int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1536         int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
1537         return windowMinIndex;
1538     }
getAssociatedUpperPageBound(int page)1539     protected int getAssociatedUpperPageBound(int page) {
1540         final int count = getChildCount();
1541         int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
1542         int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
1543                 count - 1);
1544         return windowMaxIndex;
1545     }
1546 
getCurrentPageDescription()1547     protected String getCurrentPageDescription() {
1548         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
1549         int stringId = R.string.default_scroll_format;
1550         int count = 0;
1551 
1552         if (mContentType == ContentType.Applications) {
1553             stringId = R.string.apps_customize_apps_scroll_format;
1554             count = mNumAppsPages;
1555         } else if (mContentType == ContentType.Widgets) {
1556             stringId = R.string.apps_customize_widgets_scroll_format;
1557             count = mNumWidgetPages;
1558         } else {
1559             throw new RuntimeException("Invalid ContentType");
1560         }
1561 
1562         return String.format(getContext().getString(stringId), page + 1, count);
1563     }
1564 }
1565