summaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java')
-rw-r--r--src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java1899
1 files changed, 1899 insertions, 0 deletions
diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java
new file mode 100644
index 000000000..5a58a9a95
--- /dev/null
+++ b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java
@@ -0,0 +1,1899 @@
+/*
+ * 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.cyanogenmod.trebuchet;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+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.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Insets;
+import android.graphics.MaskFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.TableMaskFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+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.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.cyanogenmod.trebuchet.R;
+import com.cyanogenmod.trebuchet.DropTarget.DragObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.lang.ref.WeakReference;
+
+/**
+ * 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, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
+ AsyncTaskCallback postR) {
+ page = p;
+ items = l;
+ sourceImages = si;
+ generatedImages = new ArrayList<Bitmap>();
+ maxImageWidth = maxImageHeight = -1;
+ doInBackgroundCallback = bgR;
+ postExecuteCallback = postR;
+ }
+ AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
+ AsyncTaskCallback postR) {
+ page = p;
+ items = l;
+ generatedImages = new ArrayList<Bitmap>();
+ maxImageWidth = cw;
+ maxImageHeight = ch;
+ doInBackgroundCallback = bgR;
+ postExecuteCallback = postR;
+ }
+ void cleanup(boolean cancelled) {
+ // Clean up any references to source/generated bitmaps
+ if (sourceImages != null) {
+ if (cancelled) {
+ for (Bitmap b : sourceImages) {
+ b.recycle();
+ }
+ }
+ sourceImages.clear();
+ }
+ if (generatedImages != null) {
+ if (cancelled) {
+ for (Bitmap b : generatedImages) {
+ b.recycle();
+ }
+ }
+ generatedImages.clear();
+ }
+ }
+ int page;
+ ArrayList<Object> items;
+ ArrayList<Bitmap> sourceImages;
+ ArrayList<Bitmap> generatedImages;
+ int maxImageWidth;
+ int maxImageHeight;
+ AsyncTaskCallback doInBackgroundCallback;
+ AsyncTaskCallback postExecuteCallback;
+}
+
+/**
+ * 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;
+}
+
+abstract class WeakReferenceThreadLocal<T> {
+ private ThreadLocal<WeakReference<T>> mThreadLocal;
+ public WeakReferenceThreadLocal() {
+ mThreadLocal = new ThreadLocal<WeakReference<T>>();
+ }
+
+ abstract T initialValue();
+
+ public void set(T t) {
+ mThreadLocal.set(new WeakReference<T>(t));
+ }
+
+ public T get() {
+ WeakReference<T> reference = mThreadLocal.get();
+ T obj;
+ if (reference == null) {
+ obj = initialValue();
+ mThreadLocal.set(new WeakReference<T>(obj));
+ return obj;
+ } else {
+ obj = reference.get();
+ if (obj == null) {
+ obj = initialValue();
+ mThreadLocal.set(new WeakReference<T>(obj));
+ }
+ return obj;
+ }
+ }
+}
+
+class CanvasCache extends WeakReferenceThreadLocal<Canvas> {
+ @Override
+ protected Canvas initialValue() {
+ return new Canvas();
+ }
+}
+
+class PaintCache extends WeakReferenceThreadLocal<Paint> {
+ @Override
+ protected Paint initialValue() {
+ return null;
+ }
+}
+
+class BitmapCache extends WeakReferenceThreadLocal<Bitmap> {
+ @Override
+ protected Bitmap initialValue() {
+ return null;
+ }
+}
+
+class RectCache extends WeakReferenceThreadLocal<Rect> {
+ @Override
+ protected Rect initialValue() {
+ return new Rect();
+ }
+}
+
+/**
+ * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
+ */
+public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
+ AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource,
+ PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
+ LauncherTransitionable {
+ static final String TAG = "AppsCustomizePagedView";
+
+ /**
+ * The different content types that this paged view can show.
+ */
+ public enum ContentType {
+ Applications,
+ Widgets
+ }
+
+ // Refs
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private final LayoutInflater mLayoutInflater;
+ private final PackageManager mPackageManager;
+
+ // Save and Restore
+ private int mSaveInstanceStateItemIndex = -1;
+ private PagedViewIcon mPressedIcon;
+
+ // Content
+ private ArrayList<ApplicationInfo> mApps;
+ private ArrayList<Object> mWidgets;
+
+ // Cling
+ private boolean mHasShownAllAppsCling;
+ private int mClingFocusedX;
+ private int mClingFocusedY;
+
+ // Caching
+ private Canvas mCanvas;
+ private Drawable mDefaultWidgetBackground;
+ private IconCache mIconCache;
+
+ // Dimens
+ private int mContentWidth;
+ private int mAppIconSize;
+ private int mMaxAppCellCountX, mMaxAppCellCountY;
+ private int mWidgetCountX, mWidgetCountY;
+ private int mWidgetWidthGap, mWidgetHeightGap;
+ private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
+ private PagedViewCellLayout mWidgetSpacingLayout;
+ private int mNumAppsPages;
+ private int mNumWidgetPages;
+
+ // Relating to the scroll and overscroll effects
+ Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
+ private static float CAMERA_DISTANCE = 6500;
+ private static float TRANSITION_SCALE_FACTOR = 0.74f;
+ private static float TRANSITION_PIVOT = 0.65f;
+ private static float TRANSITION_MAX_ROTATION = 22;
+ private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
+ private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
+ private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+
+ // 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;
+
+ // 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>();
+
+ // Used for drawing shortcut previews
+ BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
+ PaintCache mCachedShortcutPreviewPaint = new PaintCache();
+ CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
+
+ // Used for drawing widget previews
+ CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
+ RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
+ RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
+ PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
+
+ public AppsCustomizePagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mPackageManager = context.getPackageManager();
+ mApps = new ArrayList<ApplicationInfo>();
+ mWidgets = new ArrayList<Object>();
+ mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
+ mCanvas = new Canvas();
+ mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
+
+ // Save the default widget preview background
+ Resources resources = context.getResources();
+ mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
+ mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
+ mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
+ mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1);
+ mWidgetWidthGap =
+ a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0);
+ mWidgetHeightGap =
+ a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0);
+ mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
+ mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
+ mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
+ mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
+ 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);
+ }
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ mCenterPagesVertically = false;
+
+ Context context = getContext();
+ Resources r = context.getResources();
+ setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
+ }
+
+ @Override
+ protected void onUnhandledTap(MotionEvent ev) {
+ if (LauncherApplication.isScreenLarge()) {
+ // Dismiss AppsCustomize if we tap
+ mLauncher.showWorkspace(true);
+ }
+ }
+
+ /** 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 (currentPage < mNumAppsPages) {
+ PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage);
+ PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout();
+ int numItemsPerPage = mCellCountX * mCellCountY;
+ int childCount = childrenLayout.getChildCount();
+ if (childCount > 0) {
+ i = (currentPage * numItemsPerPage) + (childCount / 2);
+ }
+ } else {
+ int numApps = mApps.size();
+ PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
+ int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+ int childCount = layout.getChildCount();
+ if (childCount > 0) {
+ i = numApps +
+ ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2);
+ }
+ }
+ }
+ 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 mNumAppsPages + ((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) {
+ // Note that we transpose the counts in portrait so that we get a similar layout
+ boolean isLandscape = getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
+ int maxCellCountX = Integer.MAX_VALUE;
+ int maxCellCountY = Integer.MAX_VALUE;
+ if (LauncherApplication.isScreenLarge()) {
+ maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() :
+ LauncherModel.getCellCountY());
+ maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() :
+ LauncherModel.getCellCountX());
+ }
+ if (mMaxAppCellCountX > -1) {
+ maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX);
+ }
+ if (mMaxAppCellCountY > -1) {
+ maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY);
+ }
+
+ // Now that the data is ready, we can calculate the content width, the number of cells to
+ // use for each page
+ mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
+ mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+ mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY);
+ mCellCountX = mWidgetSpacingLayout.getCellCountX();
+ mCellCountY = mWidgetSpacingLayout.getCellCountY();
+ updatePageCounts();
+
+ // Force a measure to update recalculate the gaps
+ int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
+ mWidgetSpacingLayout.measure(widthSpec, heightSpec);
+ mContentWidth = mWidgetSpacingLayout.getContentWidth();
+
+ AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost();
+ final boolean hostIsTransitioning = host.isTransitioning();
+
+ // Restore the page
+ int page = getPageForComponent(mSaveInstanceStateItemIndex);
+ invalidatePageData(Math.max(0, page), hostIsTransitioning);
+
+ // Show All Apps cling if we are finished transitioning, otherwise, we will try again when
+ // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be
+ // returned while animating)
+ if (!hostIsTransitioning) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ showAllAppsCling();
+ }
+ });
+ }
+ }
+
+ void showAllAppsCling() {
+ if (!mHasShownAllAppsCling && isDataReady()) {
+ mHasShownAllAppsCling = true;
+ // Calculate the position for the cling punch through
+ int[] offset = new int[2];
+ int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
+ mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
+ // PagedViews are centered horizontally but top aligned
+ pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
+ offset[0];
+ pos[1] += offset[1];
+ mLauncher.showFirstRunAllAppsCling(pos);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (!isDataReady()) {
+ if (!mApps.isEmpty() && !mWidgets.isEmpty()) {
+ setDataIsReady();
+ setMeasuredDimension(width, height);
+ onDataReady(width, height);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public void onPackagesUpdated() {
+ // TODO: this isn't ideal, but we actually need to delay here. This call is triggered
+ // by a broadcast receiver, and in order for it to work correctly, we need to know that
+ // the AppWidgetService has already received and processed the same broadcast. Since there
+ // is no guarantee about ordering of broadcast receipt, we just delay here. This is a
+ // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget
+ // packages are added, updated or removed.
+ postDelayed(new Runnable() {
+ public void run() {
+ updatePackages();
+ }
+ }, 1500);
+ }
+
+ public void updatePackages() {
+ // Get the list of widgets and shortcuts
+ mWidgets.clear();
+ List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
+ Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
+ for (AppWidgetProviderInfo widget : widgets) {
+ 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 <= LauncherModel.getCellCountX() &&
+ minSpanY <= LauncherModel.getCellCountY()) {
+ 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 + ")");
+ }
+ }
+ mWidgets.addAll(shortcuts);
+ Collections.sort(mWidgets,
+ new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
+ updatePageCounts();
+ invalidateOnDataChange();
+ }
+
+ @Override
+ public void onClick(View v) {
+ // When we have exited all apps or are in transition, disregard clicks
+ if (!mLauncher.isAllAppsCustomizeOpen() ||
+ mLauncher.getWorkspace().isSwitchingState()) return;
+
+ if (v instanceof PagedViewIcon) {
+ // Animate some feedback to the click
+ final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
+
+ // Lock the drawable state to pressed until we return to Launcher
+ if (mPressedIcon != null) {
+ mPressedIcon.lockDrawableState();
+ }
+
+ // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
+ // to be consistent. So re-enable the flag here, and we will re-disable it as necessary
+ // when Launcher resumes and we are still in AllApps.
+ mLauncher.updateWallpaperVisibility(true);
+ mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
+
+ } else if (v instanceof PagedViewWidget) {
+ // Let the user know that they have to long press to add a widget
+ Toast.makeText(getContext(), R.string.long_press_widget_to_add,
+ Toast.LENGTH_SHORT).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 = new AnimatorSet();
+ ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
+ tyuAnim.setDuration(125);
+ ValueAnimator tydAnim = ObjectAnimator.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().onDragStartedWithItem(v);
+ mLauncher.getWorkspace().beginDragShared(v, this);
+ }
+
+ private void preloadWidget(final PendingAddWidgetInfo info) {
+ final AppWidgetProviderInfo pInfo = info.info;
+ if (pInfo.configure != null) {
+ return;
+ }
+
+ mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
+ mBindWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+ if (AppWidgetManager.getInstance(mLauncher)
+ .bindAppWidgetIdIfAllowed(mWidgetLoadingId, info.componentName)) {
+ mWidgetCleanupState = WIDGET_BOUND;
+ }
+ }
+ };
+ post(mBindWidgetRunnable);
+
+ mInflateWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ 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;
+ 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]);
+ preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
+ createWidgetInfo.icon, spanX, spanY, maxWidth, maxHeight);
+
+ // Determine the image view drawable scale relative to the preview
+ float[] mv = new float[9];
+ Matrix m = new Matrix();
+ m.setRectToRect(
+ new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()),
+ new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(),
+ (float) previewDrawable.getIntrinsicHeight()),
+ Matrix.ScaleToFit.START);
+ m.getValues(mv);
+ scale = (float) mv[0];
+ } else {
+ PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
+ Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
+ preview = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(preview);
+ mCanvas.save();
+ renderDrawableToBitmap(icon, preview, 0, 0,
+ icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ mCanvas.restore();
+ mCanvas.setBitmap(null);
+ createItemInfo.spanX = createItemInfo.spanY = 1;
+ }
+
+ // We use a custom alpha clip table for the default widget previews
+ Paint alphaClipPaint = null;
+ if (createItemInfo instanceof PendingAddWidgetInfo) {
+ if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) {
+ MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255);
+ alphaClipPaint = new Paint();
+ alphaClipPaint.setMaskFilter(alphaClipTable);
+ }
+ }
+
+ // Save the preview for the outline generation, then dim the preview
+ outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
+ false);
+
+ // Start the drag
+ alphaClipPaint = null;
+ mLauncher.lockScreenOrientation();
+ mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint);
+ mDragController.startDrag(image, preview, this, createItemInfo,
+ DragController.DRAG_ACTION_COPY, null, scale);
+ outline.recycle();
+ preview.recycle();
+ return true;
+ }
+
+ @Override
+ protected boolean beginDragging(final View v) {
+ if (!super.beginDragging(v)) return false;
+
+ if (v instanceof PagedViewIcon) {
+ 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()) {
+ // Dismiss the cling
+ mLauncher.dismissAllAppsCling(null);
+
+ // Reset the alpha on the dragged icon before we drag
+ resetDrawableState();
+
+ // 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))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.exitSpringLoadedDragMode();
+ }
+ mLauncher.unlockScreenOrientation(false);
+ }
+
+ @Override
+ public View getContent() {
+ 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);
+ }
+ 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
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ cancelAllTasks();
+ }
+
+ 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) {
+ if (type == ContentType.Widgets) {
+ invalidatePageData(mNumAppsPages, true);
+ } else if (type == ContentType.Applications) {
+ invalidatePageData(0, true);
+ }
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ super.snapToPage(whichPage, delta, duration);
+ updateCurrentTab(whichPage);
+
+ // 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);
+ }
+ }
+ }
+
+ private void updateCurrentTab(int currentPage) {
+ AppsCustomizeTabHost tabHost = getTabHost();
+ if (tabHost != null) {
+ String tag = tabHost.getCurrentTabTag();
+ if (tag != null) {
+ if (currentPage >= mNumAppsPages &&
+ !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) {
+ tabHost.setCurrentTabFromContent(ContentType.Widgets);
+ } else if (currentPage < mNumAppsPages &&
+ !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
+ tabHost.setCurrentTabFromContent(ContentType.Applications);
+ }
+ }
+ }
+ }
+
+ /*
+ * 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(PagedViewCellLayout layout) {
+ layout.setCellCount(mCellCountX, mCellCountY);
+ layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
+ layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+
+ // 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(getMeasuredWidth(), MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
+ layout.setMinimumWidth(getPageContentWidth());
+ layout.measure(widthSpec, heightSpec);
+ setVisibilityOnChildren(layout, View.VISIBLE);
+ }
+
+ public void syncAppsPageItems(int page, boolean immediate) {
+ // ensure that we have the right number of items on the pages
+ int numCells = mCellCountX * mCellCountY;
+ int startIndex = page * numCells;
+ int endIndex = Math.min(startIndex + numCells, mApps.size());
+ PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page);
+
+ layout.removeAllViewsOnPage();
+ ArrayList<Object> items = new ArrayList<Object>();
+ ArrayList<Bitmap> images = new ArrayList<Bitmap>();
+ for (int i = startIndex; i < endIndex; ++i) {
+ ApplicationInfo info = mApps.get(i);
+ PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
+ R.layout.apps_customize_application, layout, false);
+ icon.applyFromApplicationInfo(info, true, this);
+ icon.setOnClickListener(this);
+ icon.setOnLongClickListener(this);
+ icon.setOnTouchListener(this);
+ icon.setOnKeyListener(this);
+
+ int index = i - startIndex;
+ int x = index % mCellCountX;
+ int y = index / mCellCountX;
+ layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
+
+ items.add(info);
+ images.add(info.iconBitmap);
+ }
+
+ layout.createHardwareLayers();
+ }
+
+ /**
+ * 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);
+ }
+ });
+
+ // 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) {
+ layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+
+ // 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(getMeasuredWidth(), MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
+ layout.setMinimumWidth(getPageContentWidth());
+ layout.measure(widthSpec, heightSpec);
+ }
+
+ private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
+ renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
+ }
+
+ private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
+ float scale) {
+ if (bitmap != null) {
+ Canvas c = new Canvas(bitmap);
+ c.scale(scale, scale);
+ Rect oldBounds = d.copyBounds();
+ d.setBounds(x, y, x + w, y + h);
+ d.draw(c);
+ d.setBounds(oldBounds); // Restore the bounds
+ c.setBitmap(null);
+ }
+ }
+
+ private Bitmap getShortcutPreview(ResolveInfo info, int maxWidth, int maxHeight) {
+ Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
+ final Canvas c = mCachedShortcutPreviewCanvas.get();
+ if (tempBitmap == null ||
+ tempBitmap.getWidth() != maxWidth ||
+ tempBitmap.getHeight() != maxHeight) {
+ tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ mCachedShortcutPreviewBitmap.set(tempBitmap);
+ } else {
+ c.setBitmap(tempBitmap);
+ c.drawColor(0, PorterDuff.Mode.CLEAR);
+ c.setBitmap(null);
+ }
+ // Render the icon
+ Drawable icon = mIconCache.getFullResIcon(info);
+
+ int paddingTop =
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
+ int paddingLeft =
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
+ int paddingRight =
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
+
+ int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
+ float scaleSize = scaledIconWidth / (float) mAppIconSize;
+
+ renderDrawableToBitmap(
+ icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
+
+ Bitmap preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ c.setBitmap(preview);
+ Paint p = mCachedShortcutPreviewPaint.get();
+ if (p == null) {
+ p = new Paint();
+ ColorMatrix colorMatrix = new ColorMatrix();
+ colorMatrix.setSaturation(0);
+ p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ p.setAlpha((int) (255 * 0.06f));
+ //float density = 1f;
+ //p.setMaskFilter(new BlurMaskFilter(15*density, BlurMaskFilter.Blur.NORMAL));
+ mCachedShortcutPreviewPaint.set(p);
+ }
+ c.drawBitmap(tempBitmap, 0, 0, p);
+ c.setBitmap(null);
+
+ renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
+
+ return preview;
+ }
+
+ private Bitmap getWidgetPreview(ComponentName provider, int previewImage,
+ int iconId, int cellHSpan, int cellVSpan, int maxWidth,
+ int maxHeight) {
+ // Load the preview image if possible
+ String packageName = provider.getPackageName();
+ if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
+ if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
+
+ Drawable drawable = null;
+ if (previewImage != 0) {
+ drawable = mPackageManager.getDrawable(packageName, previewImage, null);
+ if (drawable == null) {
+ Log.w(TAG, "Can't load widget preview drawable 0x" +
+ Integer.toHexString(previewImage) + " for provider: " + provider);
+ }
+ }
+
+ int bitmapWidth;
+ int bitmapHeight;
+ Bitmap defaultPreview = null;
+ boolean widgetPreviewExists = (drawable != null);
+ if (widgetPreviewExists) {
+ bitmapWidth = drawable.getIntrinsicWidth();
+ bitmapHeight = drawable.getIntrinsicHeight();
+ } else {
+ // Generate a preview image if we couldn't load one
+ if (cellHSpan < 1) cellHSpan = 1;
+ if (cellVSpan < 1) cellVSpan = 1;
+
+ BitmapDrawable previewDrawable = (BitmapDrawable) getResources()
+ .getDrawable(R.drawable.widget_preview_tile);
+ final int previewDrawableWidth = previewDrawable
+ .getIntrinsicWidth();
+ final int previewDrawableHeight = previewDrawable
+ .getIntrinsicHeight();
+ bitmapWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips
+ bitmapHeight = previewDrawableHeight * cellVSpan;
+
+ defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
+ Config.ARGB_8888);
+ final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+ c.setBitmap(defaultPreview);
+ previewDrawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
+ previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
+ Shader.TileMode.REPEAT);
+ previewDrawable.draw(c);
+ c.setBitmap(null);
+
+ // Draw the icon in the top left corner
+ int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
+ int smallestSide = Math.min(bitmapWidth, bitmapHeight);
+ float iconScale = Math.min((float) smallestSide
+ / (mAppIconSize + 2 * minOffset), 1f);
+
+ try {
+ Drawable icon = null;
+ int hoffset =
+ (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
+ int yoffset =
+ (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
+ if (iconId > 0)
+ icon = mIconCache.getFullResIcon(packageName, iconId);
+ Resources resources = mLauncher.getResources();
+ if (icon != null) {
+ renderDrawableToBitmap(icon, defaultPreview, hoffset,
+ yoffset, (int) (mAppIconSize * iconScale),
+ (int) (mAppIconSize * iconScale));
+ }
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+
+ // Scale to fit width only - let the widget preview be clipped in the
+ // vertical dimension
+ float scale = 1f;
+ if (bitmapWidth > maxWidth) {
+ scale = maxWidth / (float) bitmapWidth;
+ }
+ if (scale != 1f) {
+ bitmapWidth = (int) (scale * bitmapWidth);
+ bitmapHeight = (int) (scale * bitmapHeight);
+ }
+
+ Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
+ Config.ARGB_8888);
+
+ // Draw the scaled preview into the final bitmap
+ if (widgetPreviewExists) {
+ renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth,
+ bitmapHeight);
+ } else {
+ final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+ final Rect src = mCachedAppWidgetPreviewSrcRect.get();
+ final Rect dest = mCachedAppWidgetPreviewDestRect.get();
+ c.setBitmap(preview);
+ src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
+ dest.set(0, 0, preview.getWidth(), preview.getHeight());
+
+ Paint p = mCachedAppWidgetPreviewPaint.get();
+ if (p == null) {
+ p = new Paint();
+ p.setFilterBitmap(true);
+ mCachedAppWidgetPreviewPaint.set(p);
+ }
+ c.drawBitmap(defaultPreview, src, dest, p);
+ c.setBitmap(null);
+ }
+ return preview;
+ }
+
+ public void syncWidgetPageItems(final int page, final boolean immediate) {
+ int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+
+ // Calculate the dimensions of each cell we are giving to each widget
+ final ArrayList<Object> items = new ArrayList<Object>();
+ int contentWidth = mWidgetSpacingLayout.getContentWidth();
+ final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
+ - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
+ int contentHeight = mWidgetSpacingLayout.getContentHeight();
+ final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
+ - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
+
+ // Prepare the set of widgets to load previews for in the background
+ int offset = (page - mNumAppsPages) * 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
+ final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+ 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);
+ 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);
+ 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;
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
+ GridLayout.spec(iy, GridLayout.LEFT),
+ GridLayout.spec(ix, GridLayout.TOP));
+ lp.width = cellWidth;
+ lp.height = cellHeight;
+ lp.setGravity(Gravity.TOP | Gravity.LEFT);
+ if (ix > 0) lp.leftMargin = mWidgetWidthGap;
+ if (iy > 0) lp.topMargin = mWidgetHeightGap;
+ 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];
+ }
+ if (immediate) {
+ AsyncTaskPageData data = new AsyncTaskPageData(page, items,
+ maxPreviewWidth, maxPreviewHeight, null, null);
+ loadWidgetPreviewsInBackground(null, data);
+ onSyncWidgetPageItems(data);
+ } else {
+ if (mInTransition) {
+ mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
+ } else {
+ prepareLoadWidgetPreviewsTask(page, items,
+ maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
+ }
+ }
+ }
+ });
+ }
+ 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();
+ }
+
+ Object rawInfo = items.get(i);
+ if (rawInfo instanceof AppWidgetProviderInfo) {
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
+ int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info);
+
+ int maxWidth = Math.min(data.maxImageWidth,
+ mWidgetSpacingLayout.estimateCellWidth(cellSpans[0]));
+ int maxHeight = Math.min(data.maxImageHeight,
+ mWidgetSpacingLayout.estimateCellHeight(cellSpans[1]));
+ Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
+ cellSpans[0], cellSpans[1], maxWidth, maxHeight);
+ images.add(b);
+ } else if (rawInfo instanceof ResolveInfo) {
+ // Fill in the shortcuts information
+ ResolveInfo info = (ResolveInfo) rawInfo;
+ images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight));
+ }
+ }
+ }
+
+ private void onSyncWidgetPageItems(AsyncTaskPageData data) {
+ if (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);
+ }
+ }
+
+ layout.createHardwareLayer();
+ invalidate();
+
+ // 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() {
+ removeAllViews();
+ cancelAllTasks();
+
+ Context context = getContext();
+ 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));
+ }
+
+ for (int i = 0; i < mNumAppsPages; ++i) {
+ PagedViewCellLayout layout = new PagedViewCellLayout(context);
+ setupPage(layout);
+ addView(layout);
+ }
+ }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) {
+ if (page < mNumAppsPages) {
+ syncAppsPageItems(page, immediate);
+ } else {
+ syncWidgetPageItems(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);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getPageAt(i);
+ if (v != null) {
+ float scrollProgress = getScrollProgress(screenCenter, v, i);
+
+ float interpolatedProgress =
+ mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
+ float scale = (1 - interpolatedProgress) +
+ interpolatedProgress * TRANSITION_SCALE_FACTOR;
+ float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
+
+ float alpha;
+
+ if (scrollProgress < 0) {
+ alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
+ 1 - Math.abs(scrollProgress)) : 1.0f;
+ } else {
+ // On large screens we need to fade the page as it nears its leftmost position
+ alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
+ }
+
+ v.setCameraDistance(mDensity * CAMERA_DISTANCE);
+ int pageWidth = v.getMeasuredWidth();
+ int pageHeight = v.getMeasuredHeight();
+
+ if (PERFORM_OVERSCROLL_ROTATION) {
+ if (i == 0 && scrollProgress < 0) {
+ // Overscroll to the left
+ v.setPivotX(TRANSITION_PIVOT * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the first page, we don't want the page to have any lateral motion
+ translationX = 0;
+ } else if (i == getChildCount() - 1 && scrollProgress > 0) {
+ // Overscroll to the right
+ v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the last page, we don't want the page to have any lateral motion.
+ translationX = 0;
+ } else {
+ v.setPivotY(pageHeight / 2.0f);
+ v.setPivotX(pageWidth / 2.0f);
+ v.setRotationY(0f);
+ }
+ }
+
+ v.setTranslationX(translationX);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
+ v.setAlpha(alpha);
+
+ // If the view has 0 alpha, we set it to be invisible so as to prevent
+ // it from accepting touches
+ if (alpha == 0) {
+ v.setVisibility(INVISIBLE);
+ } else if (v.getVisibility() != VISIBLE) {
+ v.setVisibility(VISIBLE);
+ }
+ }
+ }
+ }
+
+ protected void overScroll(float amount) {
+ acceleratedOverScroll(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
+ */
+ @Override
+ public void setup(Launcher launcher, DragController dragController) {
+ mLauncher = launcher;
+ mDragController = dragController;
+ }
+ @Override
+ public void zoom(float zoom, boolean animate) {
+ // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed()
+ }
+ @Override
+ public boolean isVisible() {
+ return (getVisibility() == VISIBLE);
+ }
+ @Override
+ public boolean isAnimating() {
+ return false;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ @Override
+ public void setApps(ArrayList<ApplicationInfo> list) {
+ mApps = list;
+ Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
+ updatePageCounts();
+ invalidateOnDataChange();
+ }
+ private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+ // We add it in place, in alphabetical order
+ int count = list.size();
+ for (int i = 0; i < count; ++i) {
+ ApplicationInfo info = list.get(i);
+ int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ }
+ @Override
+ public void addApps(ArrayList<ApplicationInfo> list) {
+ addAppsWithoutInvalidate(list);
+ updatePageCounts();
+ invalidateOnDataChange();
+ }
+ private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
+ ComponentName removeComponent = item.intent.getComponent();
+ int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ ApplicationInfo info = list.get(i);
+ if (info.intent.getComponent().equals(removeComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> 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) {
+ ApplicationInfo info = list.get(i);
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex > -1) {
+ mApps.remove(removeIndex);
+ }
+ }
+ }
+ @Override
+ public void removeApps(ArrayList<ApplicationInfo> list) {
+ removeAppsWithoutInvalidate(list);
+ updatePageCounts();
+ invalidateOnDataChange();
+ }
+ @Override
+ public void updateApps(ArrayList<ApplicationInfo> 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.
+ removeAppsWithoutInvalidate(list);
+ addAppsWithoutInvalidate(list);
+ updatePageCounts();
+ invalidateOnDataChange();
+ }
+
+ @Override
+ public void reset() {
+ // If we have reset, then we should not continue to restore the previous state
+ mSaveInstanceStateItemIndex = -1;
+
+ AppsCustomizeTabHost tabHost = getTabHost();
+ String tag = tabHost.getCurrentTabTag();
+ if (tag != null) {
+ if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
+ tabHost.setCurrentTabFromContent(ContentType.Applications);
+ }
+ }
+
+ if (mCurrentPage != 0) {
+ invalidatePageData(0);
+ }
+ }
+
+ private AppsCustomizeTabHost getTabHost() {
+ return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
+ }
+
+ @Override
+ public void dumpState() {
+ // TODO: Dump information related to current list of Applications, Widgets, etc.
+ ApplicationInfo.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);
+ }
+ }
+ }
+
+ @Override
+ 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();
+ }
+
+ @Override
+ public void iconPressed(PagedViewIcon icon) {
+ // Reset the previously pressed icon and store a reference to the pressed icon so that
+ // we can reset it on return to Launcher (in Launcher.onResume())
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ }
+ mPressedIcon = icon;
+ }
+
+ public void resetDrawableState() {
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ mPressedIcon = null;
+ }
+ }
+
+ /*
+ * 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;
+ }
+
+ @Override
+ protected String getCurrentPageDescription() {
+ int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ int stringId = R.string.default_scroll_format;
+ int count = 0;
+
+ if (page < mNumAppsPages) {
+ stringId = R.string.apps_customize_apps_scroll_format;
+ count = mNumAppsPages;
+ } else {
+ page -= mNumAppsPages;
+ stringId = R.string.apps_customize_widgets_scroll_format;
+ count = mNumWidgetPages;
+ }
+
+ return String.format(getContext().getString(stringId), page + 1, count);
+ }
+}