content: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.lb.launcher;
  
  import android.animation.AnimatorSet;
  import android.animation.ValueAnimator;
  import android.appwidget.AppWidgetHostView;
  import android.appwidget.AppWidgetManager;
  import android.appwidget.AppWidgetProviderInfo;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.content.pm.ResolveInfo;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.Point;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.os.AsyncTask;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Process;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.animation.AccelerateInterpolator;
  import android.widget.GridLayout;
  import android.widget.ImageView;
  import android.widget.Toast;
  import com.lb.launcher.R;
  import com.lb.launcher.DropTarget.DragObject;
  import com.lb.launcher.compat.AppWidgetManagerCompat;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  
  /**
   * A simple callback interface which also provides the results of the task.
   */
  interface AsyncTaskCallback {
      void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
  }
  
  /**
   * The data needed to perform either of the custom AsyncTasks.
   */
  class AsyncTaskPageData {
      enum Type {
          LoadWidgetPreviewData
      }
  
      AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
              AsyncTaskCallback postR, WidgetPreviewLoader w) {
          page = p;
          items = l;
          generatedImages = new ArrayList<Bitmap>();
          maxImageWidth = cw;
          maxImageHeight = ch;
          doInBackgroundCallback = bgR;
          postExecuteCallback = postR;
          widgetPreviewLoader = w;
      }
      void cleanup(boolean cancelled) {
          // Clean up any references to source/generated bitmaps
          if (generatedImages != null) {
              if (cancelled) {
                  for (int i = 0; i < generatedImages.size(); i++) {
                      widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
                  }
              }
              generatedImages.clear();
          }
      }
      int page;
      ArrayList<Object> items;
      ArrayList<Bitmap> sourceImages;
      ArrayList<Bitmap> generatedImages;
      int maxImageWidth;
      int maxImageHeight;
      AsyncTaskCallback doInBackgroundCallback;
      AsyncTaskCallback postExecuteCallback;
      WidgetPreviewLoader widgetPreviewLoader;
  }
  
  /**
   * A generic template for an async task used in AppsCustomize.
   */
  class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
      AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
          page = p;
          threadPriority = Process.THREAD_PRIORITY_DEFAULT;
          dataType = ty;
      }
      @Override
      protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
          if (params.length != 1) return null;
          // Load each of the widget previews in the background
          params[0].doInBackgroundCallback.run(this, params[0]);
          return params[0];
      }
      @Override
      protected void onPostExecute(AsyncTaskPageData result) {
          // All the widget previews are loaded, so we can just callback to inflate the page
          result.postExecuteCallback.run(this, result);
      }
  
      void setThreadPriority(int p) {
          threadPriority = p;
      }
      void syncThreadPriority() {
          Process.setThreadPriority(threadPriority);
      }
  
      // The page that this async task is associated with
      AsyncTaskPageData.Type dataType;
      int page;
      int threadPriority;
  }
  
  /**
   * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
   */
  public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
          View.OnClickListener, View.OnKeyListener, DragSource,
          PagedViewWidget.ShortPressListener, LauncherTransitionable {
      static final String TAG = "AppsCustomizePagedView";
  
      private static Rect sTmpRect = new Rect();
  
      /**
       * The different content types that this paged view can show.
       */
      public enum ContentType {
          Applications,
          Widgets
      }
      private ContentType mContentType = ContentType.Applications;
  
      // Refs
      private Launcher mLauncher;
      private DragController mDragController;
      private final LayoutInflater mLayoutInflater;
      private final PackageManager mPackageManager;
  
      // Save and Restore
      private int mSaveInstanceStateItemIndex = -1;
  
      // Content
      private ArrayList<AppInfo> mApps;
      private ArrayList<Object> mWidgets;
  
      // Caching
      private IconCache mIconCache;
  
      // Dimens
      private int mContentWidth, mContentHeight;
      private int mWidgetCountX, mWidgetCountY;
      private PagedViewCellLayout mWidgetSpacingLayout;
      private int mNumAppsPages;
      private int mNumWidgetPages;
      private Rect mAllAppsPadding = new Rect();
  
      // Previews & outlines
      ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
      private static final int sPageSleepDelay = 200;
  
      private Runnable mInflateWidgetRunnable = null;
      private Runnable mBindWidgetRunnable = null;
      static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
      static final int WIDGET_PRELOAD_PENDING = 0;
      static final int WIDGET_BOUND = 1;
      static final int WIDGET_INFLATED = 2;
      int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
      int mWidgetLoadingId = -1;
      PendingAddWidgetInfo mCreateWidgetInfo = null;
      private boolean mDraggingWidget = false;
      boolean mPageBackgroundsVisible = true;
  
      private Toast mWidgetInstructionToast;
  
      // Deferral of loading widget previews during launcher transitions
      private boolean mInTransition;
      private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
          new ArrayList<AsyncTaskPageData>();
      private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
          new ArrayList<Runnable>();
  
      WidgetPreviewLoader mWidgetPreviewLoader;
  
      private boolean mInBulkBind;
      private boolean mNeedToUpdatePageCountsAndInvalidateData;
  
      public AppsCustomizePagedView(Context context, AttributeSet attrs) {
          super(context, attrs);
          mLayoutInflater = LayoutInflater.from(context);
          mPackageManager = context.getPackageManager();
          mApps = new ArrayList<AppInfo>();
          mWidgets = new ArrayList<Object>();
          mIconCache = (LauncherAppState.getInstance()).getIconCache();
          mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
  
          // Save the default widget preview background
          TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
          mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
          mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
          a.recycle();
          mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
  
          // The padding on the non-matched dimension for the default widget preview icons
          // (top + bottom)
          mFadeInAdjacentScreens = false;
  
          // Unless otherwise specified this view is important for accessibility.
          if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
              setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
          }
          setSinglePageInViewport();
      }
  
      @Override
      protected void init() {
          super.init();
          mCenterPagesVertically = false;
  
          Context context = getContext();
          Resources r = context.getResources();
          setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
      }
  
      public void onFinishInflate() {
          super.onFinishInflate();
  
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          setPadding(grid.edgeMarginPx, 2 * grid.edgeMarginPx,
                  grid.edgeMarginPx, 2 * grid.edgeMarginPx);
      }
  
      void setAllAppsPadding(Rect r) {
          mAllAppsPadding.set(r);
      }
  
      void setWidgetsPageIndicatorPadding(int pageIndicatorHeight) {
          setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), pageIndicatorHeight);
      }
  
      WidgetPreviewLoader getWidgetPreviewLoader() {
          if (mWidgetPreviewLoader == null) {
              mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
          }
          return mWidgetPreviewLoader;
      }
  
      /** Returns the item index of the center item on this page so that we can restore to this
       *  item index when we rotate. */
      private int getMiddleComponentIndexOnCurrentPage() {
          int i = -1;
          if (getPageCount() > 0) {
              int currentPage = getCurrentPage();
              if (mContentType == ContentType.Applications) {
                  AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(currentPage);
                  ShortcutAndWidgetContainer childrenLayout = layout.getShortcutsAndWidgets();
                  int numItemsPerPage = mCellCountX * mCellCountY;
                  int childCount = childrenLayout.getChildCount();
                  if (childCount > 0) {
                      i = (currentPage * numItemsPerPage) + (childCount / 2);
                  }
              } else if (mContentType == ContentType.Widgets) {
                  int numApps = mApps.size();
                  PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
                  int numItemsPerPage = mWidgetCountX * mWidgetCountY;
                  int childCount = layout.getChildCount();
                  if (childCount > 0) {
                      i = numApps +
                          (currentPage * numItemsPerPage) + (childCount / 2);
                  }
              } else {
                  throw new RuntimeException("Invalid ContentType");
              }
          }
          return i;
      }
  
      /** Get the index of the item to restore to if we need to restore the current page. */
      int getSaveInstanceStateIndex() {
          if (mSaveInstanceStateItemIndex == -1) {
              mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
          }
          return mSaveInstanceStateItemIndex;
      }
  
      /** Returns the page in the current orientation which is expected to contain the specified
       *  item index. */
      int getPageForComponent(int index) {
          if (index < 0) return 0;
  
          if (index < mApps.size()) {
              int numItemsPerPage = mCellCountX * mCellCountY;
              return (index / numItemsPerPage);
          } else {
              int numItemsPerPage = mWidgetCountX * mWidgetCountY;
              return (index - mApps.size()) / numItemsPerPage;
          }
      }
  
      /** Restores the page for an item at the specified index */
      void restorePageForIndex(int index) {
          if (index < 0) return;
          mSaveInstanceStateItemIndex = index;
      }
  
      private void updatePageCounts() {
          mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
                  (float) (mWidgetCountX * mWidgetCountY));
          mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
      }
  
      protected void onDataReady(int width, int height) {
          // Now that the data is ready, we can calculate the content width, the number of cells to
          // use for each page
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          mCellCountX = (int) grid.allAppsNumCols;
          mCellCountY = (int) grid.allAppsNumRows;
          updatePageCounts();
  
          // Force a measure to update recalculate the gaps
          mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
          mContentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
          int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
          int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
          mWidgetSpacingLayout.measure(widthSpec, heightSpec);
  
          final boolean hostIsTransitioning = getTabHost().isInTransition();
          int page = getPageForComponent(mSaveInstanceStateItemIndex);
          invalidatePageData(Math.max(0, page), hostIsTransitioning);
      }
  
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          super.onLayout(changed, l, t, r, b);
  
          if (!isDataReady()) {
              if ((LauncherAppState.isDisableAllApps() || !mApps.isEmpty()) && !mWidgets.isEmpty()) {
                  post(new Runnable() {
                      // This code triggers requestLayout so must be posted outside of the
                      // layout pass.
                      public void run() {
                          if (Utilities.isViewAttachedToWindow(AppsCustomizePagedView.this)) {
                              setDataIsReady();
                              onDataReady(getMeasuredWidth(), getMeasuredHeight());
                          }
                      }
                  });
              }
          }
      }
  
      public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
  
          // Get the list of widgets and shortcuts
          mWidgets.clear();
          for (Object o : widgetsAndShortcuts) {
              if (o instanceof AppWidgetProviderInfo) {
                  AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o;
                  if (!app.shouldShowAppOrWidgetProvider(widget.provider)) {
                      continue;
                  }
                  if (widget.minWidth > 0 && widget.minHeight > 0) {
                      // Ensure that all widgets we show can be added on a workspace of this size
                      int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
                      int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
                      int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
                      int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
                      if (minSpanX <= (int) grid.numColumns &&
                          minSpanY <= (int) grid.numRows) {
                          mWidgets.add(widget);
                      } else {
                          Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
                                widget.minWidth + ", " + widget.minHeight + ")");
                      }
                  } else {
                      Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
                            widget.minWidth + ", " + widget.minHeight + ")");
                  }
              } else {
                  // just add shortcuts
                  mWidgets.add(o);
              }
          }
          updatePageCountsAndInvalidateData();
      }
  
      public void setBulkBind(boolean bulkBind) {
          if (bulkBind) {
              mInBulkBind = true;
          } else {
              mInBulkBind = false;
              if (mNeedToUpdatePageCountsAndInvalidateData) {
                  updatePageCountsAndInvalidateData();
              }
          }
      }
  
      private void updatePageCountsAndInvalidateData() {
          if (mInBulkBind) {
              mNeedToUpdatePageCountsAndInvalidateData = true;
          } else {
              updatePageCounts();
              invalidateOnDataChange();
              mNeedToUpdatePageCountsAndInvalidateData = false;
          }
      }
  
      @Override
      public void onClick(View v) {
          // When we have exited all apps or are in transition, disregard clicks
          if (!mLauncher.isAllAppsVisible()
                  || mLauncher.getWorkspace().isSwitchingState()
                  || !(v instanceof PagedViewWidget)) return;
  
          // Let the user know that they have to long press to add a widget
          if (mWidgetInstructionToast != null) {
              mWidgetInstructionToast.cancel();
          }
          mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
              Toast.LENGTH_SHORT);
          mWidgetInstructionToast.show();
  
          // Create a little animation to show that the widget can move
          float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
          final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
          AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
          ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
          tyuAnim.setDuration(125);
          ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
          tydAnim.setDuration(100);
          bounce.play(tyuAnim).before(tydAnim);
          bounce.setInterpolator(new AccelerateInterpolator());
          bounce.start();
      }
  
      public boolean onKey(View v, int keyCode, KeyEvent event) {
          return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
      }
  
      /*
       * PagedViewWithDraggableItems implementation
       */
      @Override
      protected void determineDraggingStart(android.view.MotionEvent ev) {
          // Disable dragging by pulling an app down for now.
      }
  
      private void beginDraggingApplication(View v) {
          mLauncher.getWorkspace().beginDragShared(v, this);
      }
  
      static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
          Bundle options = null;
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
              AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, sTmpRect);
              Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
                      info.componentName, null);
  
              float density = launcher.getResources().getDisplayMetrics().density;
              int xPaddingDips = (int) ((padding.left + padding.right) / density);
              int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
  
              options = new Bundle();
              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
                      sTmpRect.left - xPaddingDips);
              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
                      sTmpRect.top - yPaddingDips);
              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
                      sTmpRect.right - xPaddingDips);
              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
                      sTmpRect.bottom - yPaddingDips);
          }
          return options;
      }
  
      private void preloadWidget(final PendingAddWidgetInfo info) {
          final AppWidgetProviderInfo pInfo = info.info;
          final Bundle options = getDefaultOptionsForWidget(mLauncher, info);
  
          if (pInfo.configure != null) {
              info.bindOptions = options;
              return;
          }
  
          mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
          mBindWidgetRunnable = new Runnable() {
              @Override
              public void run() {
                  mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
                  if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
                          mWidgetLoadingId, pInfo, options)) {
                      mWidgetCleanupState = WIDGET_BOUND;
                  }
              }
          };
          post(mBindWidgetRunnable);
  
          mInflateWidgetRunnable = new Runnable() {
              @Override
              public void run() {
                  if (mWidgetCleanupState != WIDGET_BOUND) {
                      return;
                  }
                  AppWidgetHostView hostView = mLauncher.
                          getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
                  info.boundWidget = hostView;
                  mWidgetCleanupState = WIDGET_INFLATED;
                  hostView.setVisibility(INVISIBLE);
                  int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
                          info.spanY, info, false);
  
                  // We want the first widget layout to be the correct size. This will be important
                  // for width size reporting to the AppWidgetManager.
                  DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
                          unScaledSize[1]);
                  lp.x = lp.y = 0;
                  lp.customPosition = true;
                  hostView.setLayoutParams(lp);
                  mLauncher.getDragLayer().addView(hostView);
              }
          };
          post(mInflateWidgetRunnable);
      }
  
      @Override
      public void onShortPress(View v) {
          // We are anticipating a long press, and we use this time to load bind and instantiate
          // the widget. This will need to be cleaned up if it turns out no long press occurs.
          if (mCreateWidgetInfo != null) {
              // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
              cleanupWidgetPreloading(false);
          }
          mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
          preloadWidget(mCreateWidgetInfo);
      }
  
      private void cleanupWidgetPreloading(boolean widgetWasAdded) {
          if (!widgetWasAdded) {
              // If the widget was not added, we may need to do further cleanup.
              PendingAddWidgetInfo info = mCreateWidgetInfo;
              mCreateWidgetInfo = null;
  
              if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
                  // We never did any preloading, so just remove pending callbacks to do so
                  removeCallbacks(mBindWidgetRunnable);
                  removeCallbacks(mInflateWidgetRunnable);
              } else if (mWidgetCleanupState == WIDGET_BOUND) {
                   // Delete the widget id which was allocated
                  if (mWidgetLoadingId != -1) {
                      mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
                  }
  
                  // We never got around to inflating the widget, so remove the callback to do so.
                  removeCallbacks(mInflateWidgetRunnable);
              } else if (mWidgetCleanupState == WIDGET_INFLATED) {
                  // Delete the widget id which was allocated
                  if (mWidgetLoadingId != -1) {
                      mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
                  }
  
                  // The widget was inflated and added to the DragLayer -- remove it.
                  AppWidgetHostView widget = info.boundWidget;
                  mLauncher.getDragLayer().removeView(widget);
              }
          }
          mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
          mWidgetLoadingId = -1;
          mCreateWidgetInfo = null;
          PagedViewWidget.resetShortPressTarget();
      }
  
      @Override
      public void cleanUpShortPress(View v) {
          if (!mDraggingWidget) {
              cleanupWidgetPreloading(false);
          }
      }
  
      private boolean beginDraggingWidget(View v) {
          mDraggingWidget = true;
          // Get the widget preview as the drag representation
          ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
          PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
  
          // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
          // we abort the drag.
          if (image.getDrawable() == null) {
              mDraggingWidget = false;
              return false;
          }
  
          // Compose the drag image
          Bitmap preview;
          Bitmap outline;
          float scale = 1f;
          Point previewPadding = null;
  
          if (createItemInfo instanceof PendingAddWidgetInfo) {
              // This can happen in some weird cases involving multi-touch. We can't start dragging
              // the widget if this is null, so we break out.
              if (mCreateWidgetInfo == null) {
                  return false;
              }
  
              PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
              createItemInfo = createWidgetInfo;
              int spanX = createItemInfo.spanX;
              int spanY = createItemInfo.spanY;
              int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
                      createWidgetInfo, true);
  
              FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
              float minScale = 1.25f;
              int maxWidth, maxHeight;
              maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
              maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
  
              int[] previewSizeBeforeScale = new int[1];
  
              preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
                      spanX, spanY, maxWidth, maxHeight, null, previewSizeBeforeScale);
  
              // Compare the size of the drag preview to the preview in the AppsCustomize tray
              int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
                      getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
              scale = previewWidthInAppsCustomize / (float) preview.getWidth();
  
              // The bitmap in the AppsCustomize tray is always the the same size, so there
              // might be extra pixels around the preview itself - this accounts for that
              if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
                  int padding =
                          (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
                  previewPadding = new Point(padding, 0);
              }
          } else {
              PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
              Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
              preview = Utilities.createIconBitmap(icon, mLauncher);
              createItemInfo.spanX = createItemInfo.spanY = 1;
          }
  
          // Don't clip alpha values for the drag outline if we're using the default widget preview
          boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
                  (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
  
          // Save the preview for the outline generation, then dim the preview
          outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
                  false);
  
          // Start the drag
          mLauncher.lockScreenOrientation();
          mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
          mDragController.startDrag(image, preview, this, createItemInfo,
                  DragController.DRAG_ACTION_COPY, previewPadding, scale);
          outline.recycle();
          preview.recycle();
          return true;
      }
  
      @Override
      protected boolean beginDragging(final View v) {
          if (!super.beginDragging(v)) return false;
  
          if (v instanceof BubbleTextView) {
              beginDraggingApplication(v);
          } else if (v instanceof PagedViewWidget) {
              if (!beginDraggingWidget(v)) {
                  return false;
              }
          }
  
          // We delay entering spring-loaded mode slightly to make sure the UI
          // thready is free of any work.
          postDelayed(new Runnable() {
              @Override
              public void run() {
                  // We don't enter spring-loaded mode if the drag has been cancelled
                  if (mLauncher.getDragController().isDragging()) {
                      // Go into spring loaded mode (must happen before we startDrag())
                      mLauncher.enterSpringLoadedDragMode();
                  }
              }
          }, 150);
  
          return true;
      }
  
      /**
       * Clean up after dragging.
       *
       * @param target where the item was dragged to (can be null if the item was flung)
       */
      private void endDragging(View target, boolean isFlingToDelete, boolean success) {
          if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
                  !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
              // Exit spring loaded mode if we have not successfully dropped or have not handled the
              // drop in Workspace
              mLauncher.exitSpringLoadedDragModeDelayed(true,
                      Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
              mLauncher.unlockScreenOrientation(false);
          } else {
              mLauncher.unlockScreenOrientation(false);
          }
      }
  
      @Override
      public View getContent() {
          if (getChildCount() > 0) {
              return getChildAt(0);
          }
          return null;
      }
  
      @Override
      public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
          mInTransition = true;
          if (toWorkspace) {
              cancelAllTasks();
          }
      }
  
      @Override
      public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
      }
  
      @Override
      public void onLauncherTransitionStep(Launcher l, float t) {
      }
  
      @Override
      public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
          mInTransition = false;
          for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
              onSyncWidgetPageItems(d, false);
          }
          mDeferredSyncWidgetPageItems.clear();
          for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
              r.run();
          }
          mDeferredPrepareLoadWidgetPreviewsTasks.clear();
          mForceDrawAllChildrenNextFrame = !toWorkspace;
      }
  
      @Override
      public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
              boolean success) {
          // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
          if (isFlingToDelete) return;
  
          endDragging(target, false, success);
  
          // Display an error message if the drag failed due to there not being enough space on the
          // target layout we were dropping on.
          if (!success) {
              boolean showOutOfSpaceMessage = false;
              if (target instanceof Workspace) {
                  int currentScreen = mLauncher.getCurrentWorkspaceScreen();
                  Workspace workspace = (Workspace) target;
                  CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
                  ItemInfo itemInfo = (ItemInfo) d.dragInfo;
                  if (layout != null) {
                      layout.calculateSpans(itemInfo);
                      showOutOfSpaceMessage =
                              !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
                  }
              }
              if (showOutOfSpaceMessage) {
                  mLauncher.showOutOfSpaceMessage(false);
              }
  
              d.deferDragViewCleanupPostAnimation = false;
          }
          cleanupWidgetPreloading(success);
          mDraggingWidget = false;
      }
  
      @Override
      public void onFlingToDeleteCompleted() {
          // We just dismiss the drag when we fling, so cleanup here
          endDragging(null, true, true);
          cleanupWidgetPreloading(false);
          mDraggingWidget = false;
      }
  
      @Override
      public boolean supportsFlingToDelete() {
          return true;
      }
  
      @Override
      public boolean supportsAppInfoDropTarget() {
          return true;
      }
  
      @Override
      public boolean supportsDeleteDropTarget() {
          return false;
      }
  
      @Override
      public float getIntrinsicIconScaleFactor() {
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          return (float) grid.allAppsIconSizePx / grid.iconSizePx;
      }
  
      @Override
      protected void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          cancelAllTasks();
      }
  
      @Override
      public void trimMemory() {
          super.trimMemory();
          clearAllWidgetPages();
      }
  
      public void clearAllWidgetPages() {
          cancelAllTasks();
          int count = getChildCount();
          for (int i = 0; i < count; i++) {
              View v = getPageAt(i);
              if (v instanceof PagedViewGridLayout) {
                  ((PagedViewGridLayout) v).removeAllViewsOnPage();
                  mDirtyPageContent.set(i, true);
              }
          }
      }
  
      private void cancelAllTasks() {
          // Clean up all the async tasks
          Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
          while (iter.hasNext()) {
              AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
              task.cancel(false);
              iter.remove();
              mDirtyPageContent.set(task.page, true);
  
              // We've already preallocated the views for the data to load into, so clear them as well
              View v = getPageAt(task.page);
              if (v instanceof PagedViewGridLayout) {
                  ((PagedViewGridLayout) v).removeAllViewsOnPage();
              }
          }
          mDeferredSyncWidgetPageItems.clear();
          mDeferredPrepareLoadWidgetPreviewsTasks.clear();
      }
  
      public void setContentType(ContentType type) {
          // Widgets appear to be cleared every time you leave, always force invalidate for them
          if (mContentType != type || type == ContentType.Widgets) {
              int page = (mContentType != type) ? 0 : getCurrentPage();
              mContentType = type;
              invalidatePageData(page, true);
          }
      }
  
      public ContentType getContentType() {
          return mContentType;
      }
  
      protected void snapToPage(int whichPage, int delta, int duration) {
          super.snapToPage(whichPage, delta, duration);
  
          // Update the thread priorities given the direction lookahead
          Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
          while (iter.hasNext()) {
              AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
              int pageIndex = task.page;
              if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
                  (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
                  task.setThreadPriority(getThreadPriorityForPage(pageIndex));
              } else {
                  task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
              }
          }
      }
  
      /*
       * Apps PagedView implementation
       */
      private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
          int childCount = layout.getChildCount();
          for (int i = 0; i < childCount; ++i) {
              layout.getChildAt(i).setVisibility(visibility);
          }
      }
      private void setupPage(AppsCustomizeCellLayout layout) {
          layout.setGridSize(mCellCountX, mCellCountY);
  
          // Note: We force a measure here to get around the fact that when we do layout calculations
          // immediately after syncing, we don't have a proper width.  That said, we already know the
          // expected page width, so we can actually optimize by hiding all the TextView-based
          // children that are expensive to measure, and let that happen naturally later.
          setVisibilityOnChildren(layout, View.GONE);
          int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
          int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
          layout.measure(widthSpec, heightSpec);
  
          Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
          if (bg != null) {
              bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
              layout.setBackground(bg);
          }
  
          setVisibilityOnChildren(layout, View.VISIBLE);
      }
  
      public void setPageBackgroundsVisible(boolean visible) {
          mPageBackgroundsVisible = visible;
          int childCount = getChildCount();
          for (int i = 0; i < childCount; ++i) {
              Drawable bg = getChildAt(i).getBackground();
              if (bg != null) {
                  bg.setAlpha(visible ? 255 : 0);
              }
          }
      }
  
      public void syncAppsPageItems(int page, boolean immediate) {
          // ensure that we have the right number of items on the pages
          final boolean isRtl = isLayoutRtl();
          int numCells = mCellCountX * mCellCountY;
          int startIndex = page * numCells;
          int endIndex = Math.min(startIndex + numCells, mApps.size());
          AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
  
          layout.removeAllViewsOnPage();
          ArrayList<Object> items = new ArrayList<Object>();
          ArrayList<Bitmap> images = new ArrayList<Bitmap>();
          for (int i = startIndex; i < endIndex; ++i) {
              AppInfo info = mApps.get(i);
              BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                      R.layout.apps_customize_application, layout, false);
              icon.applyFromApplicationInfo(info);
              icon.setOnClickListener(mLauncher);
              icon.setOnLongClickListener(this);
              icon.setOnTouchListener(this);
              icon.setOnKeyListener(this);
              icon.setOnFocusChangeListener(layout.mFocusHandlerView);
  
              int index = i - startIndex;
              int x = index % mCellCountX;
              int y = index / mCellCountX;
              if (isRtl) {
                  x = mCellCountX - x - 1;
              }
              layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
  
              items.add(info);
              images.add(info.iconBitmap);
          }
  
          enableHwLayersOnVisiblePages();
      }
  
      /**
       * A helper to return the priority for loading of the specified widget page.
       */
      private int getWidgetPageLoadPriority(int page) {
          // If we are snapping to another page, use that index as the target page index
          int toPage = mCurrentPage;
          if (mNextPage > -1) {
              toPage = mNextPage;
          }
  
          // We use the distance from the target page as an initial guess of priority, but if there
          // are no pages of higher priority than the page specified, then bump up the priority of
          // the specified page.
          Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
          int minPageDiff = Integer.MAX_VALUE;
          while (iter.hasNext()) {
              AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
              minPageDiff = Math.abs(task.page - toPage);
          }
  
          int rawPageDiff = Math.abs(page - toPage);
          return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
      }
      /**
       * Return the appropriate thread priority for loading for a given page (we give the current
       * page much higher priority)
       */
      private int getThreadPriorityForPage(int page) {
          // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
          int pageDiff = getWidgetPageLoadPriority(page);
          if (pageDiff <= 0) {
              return Process.THREAD_PRIORITY_LESS_FAVORABLE;
          } else if (pageDiff <= 1) {
              return Process.THREAD_PRIORITY_LOWEST;
          } else {
              return Process.THREAD_PRIORITY_LOWEST;
          }
      }
      private int getSleepForPage(int page) {
          int pageDiff = getWidgetPageLoadPriority(page);
          return Math.max(0, pageDiff * sPageSleepDelay);
      }
      /**
       * Creates and executes a new AsyncTask to load a page of widget previews.
       */
      private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
              int cellWidth, int cellHeight, int cellCountX) {
  
          // Prune all tasks that are no longer needed
          Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
          while (iter.hasNext()) {
              AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
              int taskPage = task.page;
              if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
                      taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
                  task.cancel(false);
                  iter.remove();
              } else {
                  task.setThreadPriority(getThreadPriorityForPage(taskPage));
              }
          }
  
          // We introduce a slight delay to order the loading of side pages so that we don't thrash
          final int sleepMs = getSleepForPage(page);
          AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
              new AsyncTaskCallback() {
                  @Override
                  public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
                      try {
                          try {
                              Thread.sleep(sleepMs);
                          } catch (Exception e) {}
                          loadWidgetPreviewsInBackground(task, data);
                      } finally {
                          if (task.isCancelled()) {
                              data.cleanup(true);
                          }
                      }
                  }
              },
              new AsyncTaskCallback() {
                  @Override
                  public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
                      mRunningTasks.remove(task);
                      if (task.isCancelled()) return;
                      // do cleanup inside onSyncWidgetPageItems
                      onSyncWidgetPageItems(data, false);
                  }
              }, getWidgetPreviewLoader());
  
          // Ensure that the task is appropriately prioritized and runs in parallel
          AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
                  AsyncTaskPageData.Type.LoadWidgetPreviewData);
          t.setThreadPriority(getThreadPriorityForPage(page));
          t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
          mRunningTasks.add(t);
      }
  
      /*
       * Widgets PagedView implementation
       */
      private void setupPage(PagedViewGridLayout layout) {
          // Note: We force a measure here to get around the fact that when we do layout calculations
          // immediately after syncing, we don't have a proper width.
          int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
          int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
  
          Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel_dark);
          if (bg != null) {
              bg.setAlpha(mPageBackgroundsVisible ? 255 : 0);
              layout.setBackground(bg);
          }
          layout.measure(widthSpec, heightSpec);
      }
  
      public void syncWidgetPageItems(final int page, final boolean immediate) {
          int numItemsPerPage = mWidgetCountX * mWidgetCountY;
  
          final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
  
          // Calculate the dimensions of each cell we are giving to each widget
          final ArrayList<Object> items = new ArrayList<Object>();
          int contentWidth = mContentWidth - layout.getPaddingLeft() - layout.getPaddingRight();
          final int cellWidth = contentWidth / mWidgetCountX;
          int contentHeight = mContentHeight - layout.getPaddingTop() - layout.getPaddingBottom();
  
          final int cellHeight = contentHeight / mWidgetCountY;
  
          // Prepare the set of widgets to load previews for in the background
          int offset = page * numItemsPerPage;
          for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
              items.add(mWidgets.get(i));
          }
  
          // Prepopulate the pages with the other widget info, and fill in the previews later
          layout.setColumnCount(layout.getCellCountX());
          for (int i = 0; i < items.size(); ++i) {
              Object rawInfo = items.get(i);
              PendingAddItemInfo createItemInfo = null;
              PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
                      R.layout.apps_customize_widget, layout, false);
              if (rawInfo instanceof AppWidgetProviderInfo) {
                  // Fill in the widget information
                  AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
                  createItemInfo = new PendingAddWidgetInfo(info, null, null);
  
                  // Determine the widget spans and min resize spans.
                  int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
                  createItemInfo.spanX = spanXY[0];
                  createItemInfo.spanY = spanXY[1];
                  int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
                  createItemInfo.minSpanX = minSpanXY[0];
                  createItemInfo.minSpanY = minSpanXY[1];
  
                  widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, getWidgetPreviewLoader());
                  widget.setTag(createItemInfo);
                  widget.setShortPressListener(this);
              } else if (rawInfo instanceof ResolveInfo) {
                  // Fill in the shortcuts information
                  ResolveInfo info = (ResolveInfo) rawInfo;
                  createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
                  createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
                  createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
                          info.activityInfo.name);
                  widget.applyFromResolveInfo(mPackageManager, info, getWidgetPreviewLoader());
                  widget.setTag(createItemInfo);
              }
              widget.setOnClickListener(this);
              widget.setOnLongClickListener(this);
              widget.setOnTouchListener(this);
              widget.setOnKeyListener(this);
  
              // Layout each widget
              int ix = i % mWidgetCountX;
              int iy = i / mWidgetCountX;
  
              if (ix > 0) {
                  View border = widget.findViewById(R.id.left_border);
                  border.setVisibility(View.VISIBLE);
              }
              if (ix < mWidgetCountX - 1) {
                  View border = widget.findViewById(R.id.right_border);
                  border.setVisibility(View.VISIBLE);
              }
  
              GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
                      GridLayout.spec(iy, GridLayout.START),
                      GridLayout.spec(ix, GridLayout.TOP));
              lp.width = cellWidth;
              lp.height = cellHeight;
              lp.setGravity(Gravity.TOP | Gravity.START);
              layout.addView(widget, lp);
          }
  
          // wait until a call on onLayout to start loading, because
          // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
          // TODO: can we do a measure/layout immediately?
          layout.setOnLayoutListener(new Runnable() {
              public void run() {
                  // Load the widget previews
                  int maxPreviewWidth = cellWidth;
                  int maxPreviewHeight = cellHeight;
                  if (layout.getChildCount() > 0) {
                      PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
                      int[] maxSize = w.getPreviewSize();
                      maxPreviewWidth = maxSize[0];
                      maxPreviewHeight = maxSize[1];
                  }
  
                  getWidgetPreviewLoader().setPreviewSize(
                          maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
                  if (immediate) {
                      AsyncTaskPageData data = new AsyncTaskPageData(page, items,
                              maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
                      loadWidgetPreviewsInBackground(null, data);
                      onSyncWidgetPageItems(data, immediate);
                  } else {
                      if (mInTransition) {
                          mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
                      } else {
                          prepareLoadWidgetPreviewsTask(page, items,
                                  maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
                      }
                  }
                  layout.setOnLayoutListener(null);
              }
          });
      }
      private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
              AsyncTaskPageData data) {
          // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
          // previews synchronously
          if (task != null) {
              // Ensure that this task starts running at the correct priority
              task.syncThreadPriority();
          }
  
          // Load each of the widget/shortcut previews
          ArrayList<Object> items = data.items;
          ArrayList<Bitmap> images = data.generatedImages;
          int count = items.size();
          for (int i = 0; i < count; ++i) {
              if (task != null) {
                  // Ensure we haven't been cancelled yet
                  if (task.isCancelled()) break;
                  // Before work on each item, ensure that this task is running at the correct
                  // priority
                  task.syncThreadPriority();
              }
  
              images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
          }
      }
  
      private void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
          if (!immediatelySyncItems && mInTransition) {
              mDeferredSyncWidgetPageItems.add(data);
              return;
          }
          try {
              int page = data.page;
              PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
  
              ArrayList<Object> items = data.items;
              int count = items.size();
              for (int i = 0; i < count; ++i) {
                  PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
                  if (widget != null) {
                      Bitmap preview = data.generatedImages.get(i);
                      widget.applyPreview(new FastBitmapDrawable(preview), i);
                  }
              }
  
              enableHwLayersOnVisiblePages();
  
              // Update all thread priorities
              Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
              while (iter.hasNext()) {
                  AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
                  int pageIndex = task.page;
                  task.setThreadPriority(getThreadPriorityForPage(pageIndex));
              }
          } finally {
              data.cleanup(false);
          }
      }
  
      @Override
      public void syncPages() {
          disablePagedViewAnimations();
  
          removeAllViews();
          cancelAllTasks();
  
          Context context = getContext();
          if (mContentType == ContentType.Applications) {
              for (int i = 0; i < mNumAppsPages; ++i) {
                  AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
                  setupPage(layout);
                  addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
                          LayoutParams.MATCH_PARENT));
              }
          } else if (mContentType == ContentType.Widgets) {
              for (int j = 0; j < mNumWidgetPages; ++j) {
                  PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
                          mWidgetCountY);
                  setupPage(layout);
                  addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
                          LayoutParams.MATCH_PARENT));
              }
          } else {
              throw new RuntimeException("Invalid ContentType");
          }
  
          enablePagedViewAnimations();
      }
  
      @Override
      public void syncPageItems(int page, boolean immediate) {
          if (mContentType == ContentType.Widgets) {
              syncWidgetPageItems(page, immediate);
          } else {
              syncAppsPageItems(page, immediate);
          }
      }
  
      // We want our pages to be z-ordered such that the further a page is to the left, the higher
      // it is in the z-order. This is important to insure touch events are handled correctly.
      View getPageAt(int index) {
          return getChildAt(indexToPage(index));
      }
  
      @Override
      protected int indexToPage(int index) {
          return getChildCount() - index - 1;
      }
  
      // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
      @Override
      protected void screenScrolled(int screenCenter) {
          super.screenScrolled(screenCenter);
          enableHwLayersOnVisiblePages();
      }
  
      private void enableHwLayersOnVisiblePages() {
          final int screenCount = getChildCount();
  
          getVisiblePages(mTempVisiblePagesRange);
          int leftScreen = mTempVisiblePagesRange[0];
          int rightScreen = mTempVisiblePagesRange[1];
          int forceDrawScreen = -1;
          if (leftScreen == rightScreen) {
              // make sure we're caching at least two pages always
              if (rightScreen < screenCount - 1) {
                  rightScreen++;
                  forceDrawScreen = rightScreen;
              } else if (leftScreen > 0) {
                  leftScreen--;
                  forceDrawScreen = leftScreen;
              }
          } else {
              forceDrawScreen = leftScreen + 1;
          }
  
          for (int i = 0; i < screenCount; i++) {
              final View layout = (View) getPageAt(i);
              if (!(leftScreen <= i && i <= rightScreen &&
                      (i == forceDrawScreen || shouldDrawChild(layout)))) {
                  layout.setLayerType(LAYER_TYPE_NONE, null);
              }
          }
  
          for (int i = 0; i < screenCount; i++) {
              final View layout = (View) getPageAt(i);
  
              if (leftScreen <= i && i <= rightScreen &&
                      (i == forceDrawScreen || shouldDrawChild(layout))) {
                  if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
                      layout.setLayerType(LAYER_TYPE_HARDWARE, null);
                  }
              }
          }
      }
  
      protected void overScroll(float amount) {
          dampedOverScroll(amount);
      }
  
      /**
       * Used by the parent to get the content width to set the tab bar to
       * @return
       */
      public int getPageContentWidth() {
          return mContentWidth;
      }
  
      @Override
      protected void onPageEndMoving() {
          super.onPageEndMoving();
          mForceDrawAllChildrenNextFrame = true;
          // We reset the save index when we change pages so that it will be recalculated on next
          // rotation
          mSaveInstanceStateItemIndex = -1;
      }
  
      /*
       * AllAppsView implementation
       */
      public void setup(Launcher launcher, DragController dragController) {
          mLauncher = launcher;
          mDragController = dragController;
      }
  
      /**
       * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
       * appropriately determine when to invalidate the PagedView page data.  In cases where the data
       * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
       * next onMeasure() pass, which will trigger an invalidatePageData() itself.
       */
      private void invalidateOnDataChange() {
          if (!isDataReady()) {
              // The next layout pass will trigger data-ready if both widgets and apps are set, so
              // request a layout to trigger the page data when ready.
              requestLayout();
          } else {
              cancelAllTasks();
              invalidatePageData();
          }
      }
  
      public void setApps(ArrayList<AppInfo> list) {
          if (!LauncherAppState.isDisableAllApps()) {
              mApps = list;
              Collections.sort(mApps, LauncherModel.getAppNameComparator());
              updatePageCountsAndInvalidateData();
          }
      }
      private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
          // We add it in place, in alphabetical order
          int count = list.size();
          for (int i = 0; i < count; ++i) {
              AppInfo info = list.get(i);
              int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
              if (index < 0) {
                  mApps.add(-(index + 1), info);
              }
          }
      }
      public void addApps(ArrayList<AppInfo> list) {
          if (!LauncherAppState.isDisableAllApps()) {
              addAppsWithoutInvalidate(list);
              updatePageCountsAndInvalidateData();
          }
      }
      private int findAppByComponent(List<AppInfo> list, AppInfo item) {
          ComponentName removeComponent = item.intent.getComponent();
          int length = list.size();
          for (int i = 0; i < length; ++i) {
              AppInfo info = list.get(i);
              if (info.user.equals(item.user)
                      && info.intent.getComponent().equals(removeComponent)) {
                  return i;
              }
          }
          return -1;
      }
      private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) {
          // loop through all the apps and remove apps that have the same component
          int length = list.size();
          for (int i = 0; i < length; ++i) {
              AppInfo info = list.get(i);
              int removeIndex = findAppByComponent(mApps, info);
              if (removeIndex > -1) {
                  mApps.remove(removeIndex);
              }
          }
      }
      public void removeApps(ArrayList<AppInfo> appInfos) {
          if (!LauncherAppState.isDisableAllApps()) {
              removeAppsWithoutInvalidate(appInfos);
              updatePageCountsAndInvalidateData();
          }
      }
      public void updateApps(ArrayList<AppInfo> list) {
          // We remove and re-add the updated applications list because it's properties may have
          // changed (ie. the title), and this will ensure that the items will be in their proper
          // place in the list.
          if (!LauncherAppState.isDisableAllApps()) {
              removeAppsWithoutInvalidate(list);
              addAppsWithoutInvalidate(list);
              updatePageCountsAndInvalidateData();
          }
      }
  
      public void reset() {
          // If we have reset, then we should not continue to restore the previous state
          mSaveInstanceStateItemIndex = -1;
  
          if (mContentType != ContentType.Applications) {
              setContentType(ContentType.Applications);
          }
  
          if (mCurrentPage != 0) {
              invalidatePageData(0);
          }
      }
  
      private AppsCustomizeTabHost getTabHost() {
          return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
      }
  
      public void dumpState() {
          // TODO: Dump information related to current list of Applications, Widgets, etc.
          AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
          dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
      }
  
      private void dumpAppWidgetProviderInfoList(String tag, String label,
              ArrayList<Object> list) {
          Log.d(tag, label + " size=" + list.size());
          for (Object i: list) {
              if (i instanceof AppWidgetProviderInfo) {
                  AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
                  Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
                          + " resizeMode=" + info.resizeMode + " configure=" + info.configure
                          + " initialLayout=" + info.initialLayout
                          + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
              } else if (i instanceof ResolveInfo) {
                  ResolveInfo info = (ResolveInfo) i;
                  Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
                          + info.icon);
              }
          }
      }
  
      public void surrender() {
          // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
          // should stop this now.
  
          // Stop all background tasks
          cancelAllTasks();
      }
  
      /*
       * We load an extra page on each side to prevent flashes from scrolling and loading of the
       * widget previews in the background with the AsyncTasks.
       */
      final static int sLookBehindPageCount = 2;
      final static int sLookAheadPageCount = 2;
      protected int getAssociatedLowerPageBound(int page) {
          final int count = getChildCount();
          int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
          int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
          return windowMinIndex;
      }
      protected int getAssociatedUpperPageBound(int page) {
          final int count = getChildCount();
          int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
          int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
                  count - 1);
          return windowMaxIndex;
      }
  
      protected String getCurrentPageDescription() {
          int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
          int stringId = R.string.default_scroll_format;
          int count = 0;
  
          if (mContentType == ContentType.Applications) {
              stringId = R.string.apps_customize_apps_scroll_format;
              count = mNumAppsPages;
          } else if (mContentType == ContentType.Widgets) {
              stringId = R.string.apps_customize_widgets_scroll_format;
              count = mNumWidgetPages;
          } else {
              throw new RuntimeException("Invalid ContentType");
          }
  
          return String.format(getContext().getString(stringId), page + 1, count);
      }
  }
  