Files
Lawnchair/src/com/android/launcher3/AppsCustomizePagedView.java
T
Adrian Roos 5d2704fbb0 Clean up dead, ugly and wrong code in icon and widget preview loading
Removed and fixed serveral instances of dead code and small
bugs that grew in the widget preloading logic, discovered
while chasing a bug.

Bug: 12525890
Change-Id: I22e03eccecc07b73388cfd3c8462d947601c6e54
2014-03-19 16:27:35 +01:00

1720 lines
69 KiB
Java

/*
* 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.android.launcher3;
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.Canvas;
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.view.animation.DecelerateInterpolator;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
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,
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
}
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;
private PagedViewIcon mPressedIcon;
// Content
private ArrayList<AppInfo> mApps;
private ArrayList<Object> mWidgets;
// Cling
private boolean mHasShownAllAppsCling;
private int mClingFocusedX;
private int mClingFocusedY;
// Caching
private Canvas mCanvas;
private IconCache mIconCache;
// Dimens
private int mContentWidth, mContentHeight;
private int mWidgetCountX, mWidgetCountY;
private int mWidgetWidthGap, mWidgetHeightGap;
private PagedViewCellLayout mWidgetSpacingLayout;
private int mNumAppsPages;
private int mNumWidgetPages;
private Rect mAllAppsPadding = new Rect();
// 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;
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>();
private Rect mTmpRect = new Rect();
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();
mCanvas = new Canvas();
mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
// Save the default widget preview background
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
mWidgetWidthGap = mWidgetHeightGap = grid.edgeMarginPx;
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);
}
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) {
mPageLayoutPaddingBottom = 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();
mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
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);
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
// Note we have to shift the items up now that Launcher sits under the status bar
pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
offset[0];
pos[1] += offset[1] - mLauncher.getDragLayer().getPaddingTop();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (!isDataReady()) {
if ((LauncherAppState.isDisableAllApps() || !mApps.isEmpty()) && !mWidgets.isEmpty()) {
setDataIsReady();
setMeasuredDimension(width, height);
onDataReady(width, height);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
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;
}
widget.label = widget.label.trim();
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()) return;
if (v instanceof PagedViewIcon) {
// Animate some feedback to the click
final AppInfo appInfo = (AppInfo) v.getTag();
// Lock the drawable state to pressed until we return to Launcher
if (mPressedIcon != null) {
mPressedIcon.lockDrawableState();
}
mLauncher.onClickPagedViewIcon(v, appInfo);
} else if (v instanceof PagedViewWidget) {
// 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().onDragStartedWithItem(v);
mLauncher.getWorkspace().beginDragShared(v, this);
}
Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
Bundle options = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect);
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher,
info.componentName, null);
float density = 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,
mTmpRect.left - xPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
mTmpRect.top - yPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
mTmpRect.right - xPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
mTmpRect.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();
// Options will be null for platforms with JB or lower, so this serves as an
// SDK level check.
if (options == null) {
if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
mWidgetLoadingId, info.componentName)) {
mWidgetCleanupState = WIDGET_BOUND;
}
} else {
if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
mWidgetLoadingId, info.componentName, 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.componentName,
createWidgetInfo.previewImage, createWidgetInfo.icon, 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 = Bitmap.createBitmap(icon.getIntrinsicWidth(),
icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(preview);
mCanvas.save();
WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0,
icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
mCanvas.restore();
mCanvas.setBitmap(null);
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 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()) {
// 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) && !(target instanceof Folder))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
// drop in Workspace
mLauncher.getWorkspace().removeExtraEmptyScreen(true, new Runnable() {
@Override
public void run() {
mLauncher.exitSpringLoadedDragMode();
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();
}
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.setMinimumWidth(getPageContentWidth());
layout.measure(widthSpec, heightSpec);
layout.setPadding(mAllAppsPadding.left, mAllAppsPadding.top, mAllAppsPadding.right,
mAllAppsPadding.bottom);
setVisibilityOnChildren(layout, View.VISIBLE);
}
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);
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;
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);
layout.setMinimumWidth(getPageContentWidth());
layout.measure(widthSpec, heightSpec);
}
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 = mContentWidth;
final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
- ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
int contentHeight = mContentHeight;
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 * 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, 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;
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);
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];
}
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) {
final boolean isRtl = isLayoutRtl();
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;
float translationX;
float maxScrollProgress = Math.max(0, scrollProgress);
float minScrollProgress = Math.min(0, scrollProgress);
if (isRtl) {
translationX = maxScrollProgress * v.getMeasuredWidth();
interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
} else {
translationX = minScrollProgress * v.getMeasuredWidth();
interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
}
float scale = (1 - interpolatedProgress) +
interpolatedProgress * TRANSITION_SCALE_FACTOR;
float alpha;
if (isRtl && (scrollProgress > 0)) {
alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
} else if (!isRtl && (scrollProgress < 0)) {
alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
} 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) {
float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
if (i == 0 && isOverscrollingFirstPage) {
// Overscroll to the left
v.setPivotX(xPivot * 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 && isOverscrollingLastPage) {
// Overscroll to the right
v.setPivotX((1 - xPivot) * 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);
}
}
}
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) {
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
*/
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.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;
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);
}
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();
}
@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;
}
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);
}
}