| /* |
| * 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.launcher2; |
| |
| 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.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.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.launcher.R; |
| import com.android.launcher2.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) { |
| 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 (generatedImages != null) { |
| if (cancelled) { |
| for (int i = 0; i < generatedImages.size(); i++) { |
| WidgetPreviewLoader.releaseBitmap(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; |
| } |
| |
| /** |
| * 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 |
| } |
| |
| // 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 IconCache mIconCache; |
| |
| // Dimens |
| private int mContentWidth; |
| private int mMaxAppCellCountX, mMaxAppCellCountY; |
| private int mWidgetCountX, mWidgetCountY; |
| private int mWidgetWidthGap, mWidgetHeightGap; |
| 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; |
| |
| 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(); |
| |
| // 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(); |
| |
| WidgetPreviewLoader mWidgetPreviewLoader; |
| |
| 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 |
| 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); |
| } |
| |
| /** 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); |
| } |
| // Temp hack for now: only use the max cell count Y for widget layout |
| int maxWidgetCellCountY = maxCellCountY; |
| if (mMaxAppCellCountY > -1) { |
| maxWidgetCellCountY = Math.min(maxWidgetCellCountY, 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.calculateCellCount(width, height, maxCellCountX, maxWidgetCellCountY); |
| 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 |
| // 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(); |
| 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() { |
| // 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.isAllAppsVisible() || |
| 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 |
| 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 = mWidgetPreviewLoader.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], |
| mWidgetPreviewLoader.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()) { |
| // 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); |
| } |
| |
| 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); |
| } |
| }); |
| |
| // 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); |
| } |
| |
| 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.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]; |
| } |
| |
| if (mWidgetPreviewLoader == null) { |
| mWidgetPreviewLoader = new WidgetPreviewLoader( |
| maxPreviewWidth, maxPreviewHeight, mLauncher, mWidgetSpacingLayout); |
| } |
| 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(); |
| } |
| |
| images.add(mWidgetPreviewLoader.getPreview(items.get(i))); |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| 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() { |
| 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) { |
| 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<ApplicationInfo> list) { |
| mApps = list; |
| Collections.sort(mApps, LauncherModel.getAppNameComparator()); |
| 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.getAppNameComparator()); |
| if (index < 0) { |
| mApps.add(-(index + 1), info); |
| } |
| } |
| } |
| 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 int findAppByPackage(List<ApplicationInfo> list, String packageName) { |
| int length = list.size(); |
| for (int i = 0; i < length; ++i) { |
| ApplicationInfo info = list.get(i); |
| if (ItemInfo.getPackageName(info.intent).equals(packageName)) { |
| 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); |
| } |
| } |
| } |
| private void removeAppsWithPackageNameWithoutInvalidate(ArrayList<String> packageNames) { |
| // loop through all the package names and remove apps that have the same package name |
| for (String pn : packageNames) { |
| int removeIndex = findAppByPackage(mApps, pn); |
| while (removeIndex > -1) { |
| mApps.remove(removeIndex); |
| removeIndex = findAppByPackage(mApps, pn); |
| } |
| } |
| } |
| public void removeApps(ArrayList<String> packageNames) { |
| removeAppsWithPackageNameWithoutInvalidate(packageNames); |
| updatePageCounts(); |
| invalidateOnDataChange(); |
| } |
| 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(); |
| } |
| |
| 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. |
| 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); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |