1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher3; 18 19import android.animation.AnimatorSet; 20import android.animation.ValueAnimator; 21import android.appwidget.AppWidgetHostView; 22import android.appwidget.AppWidgetManager; 23import android.appwidget.AppWidgetProviderInfo; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.content.res.Resources; 29import android.content.res.TypedArray; 30import android.graphics.Bitmap; 31import android.graphics.Canvas; 32import android.graphics.Point; 33import android.graphics.Rect; 34import android.graphics.drawable.Drawable; 35import android.os.AsyncTask; 36import android.os.Build; 37import android.os.Bundle; 38import android.os.Process; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.view.Gravity; 42import android.view.KeyEvent; 43import android.view.LayoutInflater; 44import android.view.View; 45import android.view.ViewGroup; 46import android.view.animation.AccelerateInterpolator; 47import android.view.animation.DecelerateInterpolator; 48import android.widget.GridLayout; 49import android.widget.ImageView; 50import android.widget.Toast; 51 52import com.android.launcher3.DropTarget.DragObject; 53 54import java.util.ArrayList; 55import java.util.Collections; 56import java.util.Iterator; 57import java.util.List; 58 59/** 60 * A simple callback interface which also provides the results of the task. 61 */ 62interface AsyncTaskCallback { 63 void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); 64} 65 66/** 67 * The data needed to perform either of the custom AsyncTasks. 68 */ 69class AsyncTaskPageData { 70 enum Type { 71 LoadWidgetPreviewData 72 } 73 74 AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR, 75 AsyncTaskCallback postR, WidgetPreviewLoader w) { 76 page = p; 77 items = l; 78 generatedImages = new ArrayList<Bitmap>(); 79 maxImageWidth = cw; 80 maxImageHeight = ch; 81 doInBackgroundCallback = bgR; 82 postExecuteCallback = postR; 83 widgetPreviewLoader = w; 84 } 85 void cleanup(boolean cancelled) { 86 // Clean up any references to source/generated bitmaps 87 if (generatedImages != null) { 88 if (cancelled) { 89 for (int i = 0; i < generatedImages.size(); i++) { 90 widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i)); 91 } 92 } 93 generatedImages.clear(); 94 } 95 } 96 int page; 97 ArrayList<Object> items; 98 ArrayList<Bitmap> sourceImages; 99 ArrayList<Bitmap> generatedImages; 100 int maxImageWidth; 101 int maxImageHeight; 102 AsyncTaskCallback doInBackgroundCallback; 103 AsyncTaskCallback postExecuteCallback; 104 WidgetPreviewLoader widgetPreviewLoader; 105} 106 107/** 108 * A generic template for an async task used in AppsCustomize. 109 */ 110class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> { 111 AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) { 112 page = p; 113 threadPriority = Process.THREAD_PRIORITY_DEFAULT; 114 dataType = ty; 115 } 116 @Override 117 protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { 118 if (params.length != 1) return null; 119 // Load each of the widget previews in the background 120 params[0].doInBackgroundCallback.run(this, params[0]); 121 return params[0]; 122 } 123 @Override 124 protected void onPostExecute(AsyncTaskPageData result) { 125 // All the widget previews are loaded, so we can just callback to inflate the page 126 result.postExecuteCallback.run(this, result); 127 } 128 129 void setThreadPriority(int p) { 130 threadPriority = p; 131 } 132 void syncThreadPriority() { 133 Process.setThreadPriority(threadPriority); 134 } 135 136 // The page that this async task is associated with 137 AsyncTaskPageData.Type dataType; 138 int page; 139 int threadPriority; 140} 141 142/** 143 * The Apps/Customize page that displays all the applications, widgets, and shortcuts. 144 */ 145public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements 146 View.OnClickListener, View.OnKeyListener, DragSource, 147 PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener, 148 LauncherTransitionable { 149 static final String TAG = "AppsCustomizePagedView"; 150 151 /** 152 * The different content types that this paged view can show. 153 */ 154 public enum ContentType { 155 Applications, 156 Widgets 157 } 158 private ContentType mContentType = ContentType.Applications; 159 160 // Refs 161 private Launcher mLauncher; 162 private DragController mDragController; 163 private final LayoutInflater mLayoutInflater; 164 private final PackageManager mPackageManager; 165 166 // Save and Restore 167 private int mSaveInstanceStateItemIndex = -1; 168 private PagedViewIcon mPressedIcon; 169 170 // Content 171 private ArrayList<AppInfo> mApps; 172 private ArrayList<Object> mWidgets; 173 174 // Cling 175 private boolean mHasShownAllAppsCling; 176 private int mClingFocusedX; 177 private int mClingFocusedY; 178 179 // Caching 180 private Canvas mCanvas; 181 private IconCache mIconCache; 182 183 // Dimens 184 private int mContentWidth, mContentHeight; 185 private int mWidgetCountX, mWidgetCountY; 186 private int mWidgetWidthGap, mWidgetHeightGap; 187 private PagedViewCellLayout mWidgetSpacingLayout; 188 private int mNumAppsPages; 189 private int mNumWidgetPages; 190 191 // Relating to the scroll and overscroll effects 192 Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f); 193 private static float CAMERA_DISTANCE = 6500; 194 private static float TRANSITION_SCALE_FACTOR = 0.74f; 195 private static float TRANSITION_PIVOT = 0.65f; 196 private static float TRANSITION_MAX_ROTATION = 22; 197 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 198 private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); 199 private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); 200 201 public static boolean DISABLE_ALL_APPS = false; 202 203 // Previews & outlines 204 ArrayList<AppsCustomizeAsyncTask> mRunningTasks; 205 private static final int sPageSleepDelay = 200; 206 207 private Runnable mInflateWidgetRunnable = null; 208 private Runnable mBindWidgetRunnable = null; 209 static final int WIDGET_NO_CLEANUP_REQUIRED = -1; 210 static final int WIDGET_PRELOAD_PENDING = 0; 211 static final int WIDGET_BOUND = 1; 212 static final int WIDGET_INFLATED = 2; 213 int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; 214 int mWidgetLoadingId = -1; 215 PendingAddWidgetInfo mCreateWidgetInfo = null; 216 private boolean mDraggingWidget = false; 217 218 private Toast mWidgetInstructionToast; 219 220 // Deferral of loading widget previews during launcher transitions 221 private boolean mInTransition; 222 private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems = 223 new ArrayList<AsyncTaskPageData>(); 224 private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks = 225 new ArrayList<Runnable>(); 226 227 private Rect mTmpRect = new Rect(); 228 229 // Used for drawing shortcut previews 230 BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); 231 PaintCache mCachedShortcutPreviewPaint = new PaintCache(); 232 CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); 233 234 // Used for drawing widget previews 235 CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); 236 RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); 237 RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); 238 PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); 239 240 WidgetPreviewLoader mWidgetPreviewLoader; 241 242 private boolean mInBulkBind; 243 private boolean mNeedToUpdatePageCountsAndInvalidateData; 244 245 public AppsCustomizePagedView(Context context, AttributeSet attrs) { 246 super(context, attrs); 247 mLayoutInflater = LayoutInflater.from(context); 248 mPackageManager = context.getPackageManager(); 249 mApps = new ArrayList<AppInfo>(); 250 mWidgets = new ArrayList<Object>(); 251 mIconCache = (LauncherAppState.getInstance()).getIconCache(); 252 mCanvas = new Canvas(); 253 mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>(); 254 255 // Save the default widget preview background 256 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); 257 LauncherAppState app = LauncherAppState.getInstance(); 258 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 259 mWidgetWidthGap = mWidgetHeightGap = grid.edgeMarginPx; 260 mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); 261 mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); 262 mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0); 263 mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); 264 a.recycle(); 265 mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); 266 267 // The padding on the non-matched dimension for the default widget preview icons 268 // (top + bottom) 269 mFadeInAdjacentScreens = false; 270 271 // Unless otherwise specified this view is important for accessibility. 272 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 273 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 274 } 275 } 276 277 @Override 278 protected void init() { 279 super.init(); 280 mCenterPagesVertically = false; 281 282 Context context = getContext(); 283 Resources r = context.getResources(); 284 setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); 285 } 286 287 public void onFinishInflate() { 288 super.onFinishInflate(); 289 290 LauncherAppState app = LauncherAppState.getInstance(); 291 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 292 setPadding(grid.edgeMarginPx, 2 * grid.edgeMarginPx, 293 grid.edgeMarginPx, 2 * grid.edgeMarginPx); 294 } 295 296 /** Returns the item index of the center item on this page so that we can restore to this 297 * item index when we rotate. */ 298 private int getMiddleComponentIndexOnCurrentPage() { 299 int i = -1; 300 if (getPageCount() > 0) { 301 int currentPage = getCurrentPage(); 302 if (mContentType == ContentType.Applications) { 303 AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(currentPage); 304 ShortcutAndWidgetContainer childrenLayout = layout.getShortcutsAndWidgets(); 305 int numItemsPerPage = mCellCountX * mCellCountY; 306 int childCount = childrenLayout.getChildCount(); 307 if (childCount > 0) { 308 i = (currentPage * numItemsPerPage) + (childCount / 2); 309 } 310 } else if (mContentType == ContentType.Widgets) { 311 int numApps = mApps.size(); 312 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage); 313 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 314 int childCount = layout.getChildCount(); 315 if (childCount > 0) { 316 i = numApps + 317 (currentPage * numItemsPerPage) + (childCount / 2); 318 } 319 } else { 320 throw new RuntimeException("Invalid ContentType"); 321 } 322 } 323 return i; 324 } 325 326 /** Get the index of the item to restore to if we need to restore the current page. */ 327 int getSaveInstanceStateIndex() { 328 if (mSaveInstanceStateItemIndex == -1) { 329 mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage(); 330 } 331 return mSaveInstanceStateItemIndex; 332 } 333 334 /** Returns the page in the current orientation which is expected to contain the specified 335 * item index. */ 336 int getPageForComponent(int index) { 337 if (index < 0) return 0; 338 339 if (index < mApps.size()) { 340 int numItemsPerPage = mCellCountX * mCellCountY; 341 return (index / numItemsPerPage); 342 } else { 343 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 344 return (index - mApps.size()) / numItemsPerPage; 345 } 346 } 347 348 /** Restores the page for an item at the specified index */ 349 void restorePageForIndex(int index) { 350 if (index < 0) return; 351 mSaveInstanceStateItemIndex = index; 352 } 353 354 private void updatePageCounts() { 355 mNumWidgetPages = (int) Math.ceil(mWidgets.size() / 356 (float) (mWidgetCountX * mWidgetCountY)); 357 mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); 358 } 359 360 protected void onDataReady(int width, int height) { 361 if (mWidgetPreviewLoader == null) { 362 mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher); 363 } 364 365 // Now that the data is ready, we can calculate the content width, the number of cells to 366 // use for each page 367 LauncherAppState app = LauncherAppState.getInstance(); 368 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 369 mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 370 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 371 mCellCountX = (int) grid.allAppsNumCols; 372 mCellCountY = (int) grid.allAppsNumRows; 373 updatePageCounts(); 374 375 // Force a measure to update recalculate the gaps 376 mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 377 mContentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); 378 int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST); 379 int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST); 380 mWidgetSpacingLayout.measure(widthSpec, heightSpec); 381 382 AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost(); 383 final boolean hostIsTransitioning = host.isTransitioning(); 384 385 // Restore the page 386 int page = getPageForComponent(mSaveInstanceStateItemIndex); 387 invalidatePageData(Math.max(0, page), hostIsTransitioning); 388 389 // Show All Apps cling if we are finished transitioning, otherwise, we will try again when 390 // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be 391 // returned while animating) 392 if (!hostIsTransitioning) { 393 post(new Runnable() { 394 @Override 395 public void run() { 396 showAllAppsCling(); 397 } 398 }); 399 } 400 } 401 402 void showAllAppsCling() { 403 if (!mHasShownAllAppsCling && isDataReady()) { 404 mHasShownAllAppsCling = true; 405 // Calculate the position for the cling punch through 406 int[] offset = new int[2]; 407 int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); 408 mLauncher.getDragLayer().getLocationInDragLayer(this, offset); 409 // PagedViews are centered horizontally but top aligned 410 // Note we have to shift the items up now that Launcher sits under the status bar 411 pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + 412 offset[0]; 413 pos[1] += offset[1] - mLauncher.getDragLayer().getPaddingTop(); 414 } 415 } 416 417 @Override 418 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 419 int width = MeasureSpec.getSize(widthMeasureSpec); 420 int height = MeasureSpec.getSize(heightMeasureSpec); 421 if (!isDataReady()) { 422 if ((DISABLE_ALL_APPS || !mApps.isEmpty()) && !mWidgets.isEmpty()) { 423 setDataIsReady(); 424 setMeasuredDimension(width, height); 425 onDataReady(width, height); 426 } 427 } 428 429 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 430 } 431 432 public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) { 433 LauncherAppState app = LauncherAppState.getInstance(); 434 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 435 436 // Get the list of widgets and shortcuts 437 mWidgets.clear(); 438 for (Object o : widgetsAndShortcuts) { 439 if (o instanceof AppWidgetProviderInfo) { 440 AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o; 441 if (!app.shouldShowAppOrWidgetProvider(widget.provider)) { 442 continue; 443 } 444 widget.label = widget.label.trim(); 445 if (widget.minWidth > 0 && widget.minHeight > 0) { 446 // Ensure that all widgets we show can be added on a workspace of this size 447 int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget); 448 int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget); 449 int minSpanX = Math.min(spanXY[0], minSpanXY[0]); 450 int minSpanY = Math.min(spanXY[1], minSpanXY[1]); 451 if (minSpanX <= (int) grid.numColumns && 452 minSpanY <= (int) grid.numRows) { 453 mWidgets.add(widget); 454 } else { 455 Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" + 456 widget.minWidth + ", " + widget.minHeight + ")"); 457 } 458 } else { 459 Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" + 460 widget.minWidth + ", " + widget.minHeight + ")"); 461 } 462 } else { 463 // just add shortcuts 464 mWidgets.add(o); 465 } 466 } 467 updatePageCountsAndInvalidateData(); 468 } 469 470 public void setBulkBind(boolean bulkBind) { 471 if (bulkBind) { 472 mInBulkBind = true; 473 } else { 474 mInBulkBind = false; 475 if (mNeedToUpdatePageCountsAndInvalidateData) { 476 updatePageCountsAndInvalidateData(); 477 } 478 } 479 } 480 481 private void updatePageCountsAndInvalidateData() { 482 if (mInBulkBind) { 483 mNeedToUpdatePageCountsAndInvalidateData = true; 484 } else { 485 updatePageCounts(); 486 invalidateOnDataChange(); 487 mNeedToUpdatePageCountsAndInvalidateData = false; 488 } 489 } 490 491 @Override 492 public void onClick(View v) { 493 // When we have exited all apps or are in transition, disregard clicks 494 if (!mLauncher.isAllAppsVisible() || 495 mLauncher.getWorkspace().isSwitchingState()) return; 496 497 if (v instanceof PagedViewIcon) { 498 // Animate some feedback to the click 499 final AppInfo appInfo = (AppInfo) v.getTag(); 500 501 // Lock the drawable state to pressed until we return to Launcher 502 if (mPressedIcon != null) { 503 mPressedIcon.lockDrawableState(); 504 } 505 mLauncher.startActivitySafely(v, appInfo.intent, appInfo); 506 mLauncher.getStats().recordLaunch(appInfo.intent); 507 } else if (v instanceof PagedViewWidget) { 508 // Let the user know that they have to long press to add a widget 509 if (mWidgetInstructionToast != null) { 510 mWidgetInstructionToast.cancel(); 511 } 512 mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add, 513 Toast.LENGTH_SHORT); 514 mWidgetInstructionToast.show(); 515 516 // Create a little animation to show that the widget can move 517 float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 518 final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); 519 AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet(); 520 ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY); 521 tyuAnim.setDuration(125); 522 ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f); 523 tydAnim.setDuration(100); 524 bounce.play(tyuAnim).before(tydAnim); 525 bounce.setInterpolator(new AccelerateInterpolator()); 526 bounce.start(); 527 } 528 } 529 530 public boolean onKey(View v, int keyCode, KeyEvent event) { 531 return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event); 532 } 533 534 /* 535 * PagedViewWithDraggableItems implementation 536 */ 537 @Override 538 protected void determineDraggingStart(android.view.MotionEvent ev) { 539 // Disable dragging by pulling an app down for now. 540 } 541 542 private void beginDraggingApplication(View v) { 543 mLauncher.getWorkspace().onDragStartedWithItem(v); 544 mLauncher.getWorkspace().beginDragShared(v, this); 545 } 546 547 Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) { 548 Bundle options = null; 549 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 550 AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect); 551 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher, 552 info.componentName, null); 553 554 float density = getResources().getDisplayMetrics().density; 555 int xPaddingDips = (int) ((padding.left + padding.right) / density); 556 int yPaddingDips = (int) ((padding.top + padding.bottom) / density); 557 558 options = new Bundle(); 559 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 560 mTmpRect.left - xPaddingDips); 561 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 562 mTmpRect.top - yPaddingDips); 563 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 564 mTmpRect.right - xPaddingDips); 565 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 566 mTmpRect.bottom - yPaddingDips); 567 } 568 return options; 569 } 570 571 private void preloadWidget(final PendingAddWidgetInfo info) { 572 final AppWidgetProviderInfo pInfo = info.info; 573 final Bundle options = getDefaultOptionsForWidget(mLauncher, info); 574 575 if (pInfo.configure != null) { 576 info.bindOptions = options; 577 return; 578 } 579 580 mWidgetCleanupState = WIDGET_PRELOAD_PENDING; 581 mBindWidgetRunnable = new Runnable() { 582 @Override 583 public void run() { 584 mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); 585 // Options will be null for platforms with JB or lower, so this serves as an 586 // SDK level check. 587 if (options == null) { 588 if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed( 589 mWidgetLoadingId, info.componentName)) { 590 mWidgetCleanupState = WIDGET_BOUND; 591 } 592 } else { 593 if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed( 594 mWidgetLoadingId, info.componentName, options)) { 595 mWidgetCleanupState = WIDGET_BOUND; 596 } 597 } 598 } 599 }; 600 post(mBindWidgetRunnable); 601 602 mInflateWidgetRunnable = new Runnable() { 603 @Override 604 public void run() { 605 if (mWidgetCleanupState != WIDGET_BOUND) { 606 return; 607 } 608 AppWidgetHostView hostView = mLauncher. 609 getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo); 610 info.boundWidget = hostView; 611 mWidgetCleanupState = WIDGET_INFLATED; 612 hostView.setVisibility(INVISIBLE); 613 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX, 614 info.spanY, info, false); 615 616 // We want the first widget layout to be the correct size. This will be important 617 // for width size reporting to the AppWidgetManager. 618 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], 619 unScaledSize[1]); 620 lp.x = lp.y = 0; 621 lp.customPosition = true; 622 hostView.setLayoutParams(lp); 623 mLauncher.getDragLayer().addView(hostView); 624 } 625 }; 626 post(mInflateWidgetRunnable); 627 } 628 629 @Override 630 public void onShortPress(View v) { 631 // We are anticipating a long press, and we use this time to load bind and instantiate 632 // the widget. This will need to be cleaned up if it turns out no long press occurs. 633 if (mCreateWidgetInfo != null) { 634 // Just in case the cleanup process wasn't properly executed. This shouldn't happen. 635 cleanupWidgetPreloading(false); 636 } 637 mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); 638 preloadWidget(mCreateWidgetInfo); 639 } 640 641 private void cleanupWidgetPreloading(boolean widgetWasAdded) { 642 if (!widgetWasAdded) { 643 // If the widget was not added, we may need to do further cleanup. 644 PendingAddWidgetInfo info = mCreateWidgetInfo; 645 mCreateWidgetInfo = null; 646 647 if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) { 648 // We never did any preloading, so just remove pending callbacks to do so 649 removeCallbacks(mBindWidgetRunnable); 650 removeCallbacks(mInflateWidgetRunnable); 651 } else if (mWidgetCleanupState == WIDGET_BOUND) { 652 // Delete the widget id which was allocated 653 if (mWidgetLoadingId != -1) { 654 mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); 655 } 656 657 // We never got around to inflating the widget, so remove the callback to do so. 658 removeCallbacks(mInflateWidgetRunnable); 659 } else if (mWidgetCleanupState == WIDGET_INFLATED) { 660 // Delete the widget id which was allocated 661 if (mWidgetLoadingId != -1) { 662 mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); 663 } 664 665 // The widget was inflated and added to the DragLayer -- remove it. 666 AppWidgetHostView widget = info.boundWidget; 667 mLauncher.getDragLayer().removeView(widget); 668 } 669 } 670 mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; 671 mWidgetLoadingId = -1; 672 mCreateWidgetInfo = null; 673 PagedViewWidget.resetShortPressTarget(); 674 } 675 676 @Override 677 public void cleanUpShortPress(View v) { 678 if (!mDraggingWidget) { 679 cleanupWidgetPreloading(false); 680 } 681 } 682 683 private boolean beginDraggingWidget(View v) { 684 mDraggingWidget = true; 685 // Get the widget preview as the drag representation 686 ImageView image = (ImageView) v.findViewById(R.id.widget_preview); 687 PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); 688 689 // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and 690 // we abort the drag. 691 if (image.getDrawable() == null) { 692 mDraggingWidget = false; 693 return false; 694 } 695 696 // Compose the drag image 697 Bitmap preview; 698 Bitmap outline; 699 float scale = 1f; 700 Point previewPadding = null; 701 702 if (createItemInfo instanceof PendingAddWidgetInfo) { 703 // This can happen in some weird cases involving multi-touch. We can't start dragging 704 // the widget if this is null, so we break out. 705 if (mCreateWidgetInfo == null) { 706 return false; 707 } 708 709 PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo; 710 createItemInfo = createWidgetInfo; 711 int spanX = createItemInfo.spanX; 712 int spanY = createItemInfo.spanY; 713 int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY, 714 createWidgetInfo, true); 715 716 FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable(); 717 float minScale = 1.25f; 718 int maxWidth, maxHeight; 719 maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]); 720 maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]); 721 722 int[] previewSizeBeforeScale = new int[1]; 723 724 preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName, 725 createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY, 726 maxWidth, maxHeight, null, previewSizeBeforeScale); 727 728 // Compare the size of the drag preview to the preview in the AppsCustomize tray 729 int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0], 730 mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX)); 731 scale = previewWidthInAppsCustomize / (float) preview.getWidth(); 732 733 // The bitmap in the AppsCustomize tray is always the the same size, so there 734 // might be extra pixels around the preview itself - this accounts for that 735 if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) { 736 int padding = 737 (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2; 738 previewPadding = new Point(padding, 0); 739 } 740 } else { 741 PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag(); 742 Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo); 743 preview = Bitmap.createBitmap(icon.getIntrinsicWidth(), 744 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 745 746 mCanvas.setBitmap(preview); 747 mCanvas.save(); 748 WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0, 749 icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 750 mCanvas.restore(); 751 mCanvas.setBitmap(null); 752 createItemInfo.spanX = createItemInfo.spanY = 1; 753 } 754 755 // Don't clip alpha values for the drag outline if we're using the default widget preview 756 boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo && 757 (((PendingAddWidgetInfo) createItemInfo).previewImage == 0)); 758 759 // Save the preview for the outline generation, then dim the preview 760 outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(), 761 false); 762 763 // Start the drag 764 mLauncher.lockScreenOrientation(); 765 mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha); 766 mDragController.startDrag(image, preview, this, createItemInfo, 767 DragController.DRAG_ACTION_COPY, previewPadding, scale); 768 outline.recycle(); 769 preview.recycle(); 770 return true; 771 } 772 773 @Override 774 protected boolean beginDragging(final View v) { 775 if (!super.beginDragging(v)) return false; 776 777 if (v instanceof PagedViewIcon) { 778 beginDraggingApplication(v); 779 } else if (v instanceof PagedViewWidget) { 780 if (!beginDraggingWidget(v)) { 781 return false; 782 } 783 } 784 785 // We delay entering spring-loaded mode slightly to make sure the UI 786 // thready is free of any work. 787 postDelayed(new Runnable() { 788 @Override 789 public void run() { 790 // We don't enter spring-loaded mode if the drag has been cancelled 791 if (mLauncher.getDragController().isDragging()) { 792 // Reset the alpha on the dragged icon before we drag 793 resetDrawableState(); 794 795 // Go into spring loaded mode (must happen before we startDrag()) 796 mLauncher.enterSpringLoadedDragMode(); 797 } 798 } 799 }, 150); 800 801 return true; 802 } 803 804 /** 805 * Clean up after dragging. 806 * 807 * @param target where the item was dragged to (can be null if the item was flung) 808 */ 809 private void endDragging(View target, boolean isFlingToDelete, boolean success) { 810 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && 811 !(target instanceof DeleteDropTarget))) { 812 // Exit spring loaded mode if we have not successfully dropped or have not handled the 813 // drop in Workspace 814 mLauncher.exitSpringLoadedDragMode(); 815 } 816 mLauncher.unlockScreenOrientation(false); 817 } 818 819 @Override 820 public View getContent() { 821 return null; 822 } 823 824 @Override 825 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { 826 mInTransition = true; 827 if (toWorkspace) { 828 cancelAllTasks(); 829 } 830 } 831 832 @Override 833 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 834 } 835 836 @Override 837 public void onLauncherTransitionStep(Launcher l, float t) { 838 } 839 840 @Override 841 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 842 mInTransition = false; 843 for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) { 844 onSyncWidgetPageItems(d); 845 } 846 mDeferredSyncWidgetPageItems.clear(); 847 for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) { 848 r.run(); 849 } 850 mDeferredPrepareLoadWidgetPreviewsTasks.clear(); 851 mForceDrawAllChildrenNextFrame = !toWorkspace; 852 } 853 854 @Override 855 public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, 856 boolean success) { 857 // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling 858 if (isFlingToDelete) return; 859 860 endDragging(target, false, success); 861 862 // Display an error message if the drag failed due to there not being enough space on the 863 // target layout we were dropping on. 864 if (!success) { 865 boolean showOutOfSpaceMessage = false; 866 if (target instanceof Workspace) { 867 int currentScreen = mLauncher.getCurrentWorkspaceScreen(); 868 Workspace workspace = (Workspace) target; 869 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); 870 ItemInfo itemInfo = (ItemInfo) d.dragInfo; 871 if (layout != null) { 872 layout.calculateSpans(itemInfo); 873 showOutOfSpaceMessage = 874 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); 875 } 876 } 877 if (showOutOfSpaceMessage) { 878 mLauncher.showOutOfSpaceMessage(false); 879 } 880 881 d.deferDragViewCleanupPostAnimation = false; 882 } 883 cleanupWidgetPreloading(success); 884 mDraggingWidget = false; 885 } 886 887 @Override 888 public void onFlingToDeleteCompleted() { 889 // We just dismiss the drag when we fling, so cleanup here 890 endDragging(null, true, true); 891 cleanupWidgetPreloading(false); 892 mDraggingWidget = false; 893 } 894 895 @Override 896 public boolean supportsFlingToDelete() { 897 return true; 898 } 899 900 @Override 901 protected void onDetachedFromWindow() { 902 super.onDetachedFromWindow(); 903 cancelAllTasks(); 904 } 905 906 public void clearAllWidgetPages() { 907 cancelAllTasks(); 908 int count = getChildCount(); 909 for (int i = 0; i < count; i++) { 910 View v = getPageAt(i); 911 if (v instanceof PagedViewGridLayout) { 912 ((PagedViewGridLayout) v).removeAllViewsOnPage(); 913 mDirtyPageContent.set(i, true); 914 } 915 } 916 } 917 918 private void cancelAllTasks() { 919 // Clean up all the async tasks 920 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 921 while (iter.hasNext()) { 922 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 923 task.cancel(false); 924 iter.remove(); 925 mDirtyPageContent.set(task.page, true); 926 927 // We've already preallocated the views for the data to load into, so clear them as well 928 View v = getPageAt(task.page); 929 if (v instanceof PagedViewGridLayout) { 930 ((PagedViewGridLayout) v).removeAllViewsOnPage(); 931 } 932 } 933 mDeferredSyncWidgetPageItems.clear(); 934 mDeferredPrepareLoadWidgetPreviewsTasks.clear(); 935 } 936 937 public void setContentType(ContentType type) { 938 int page = getCurrentPage(); 939 if (mContentType != type) { 940 page = 0; 941 } 942 mContentType = type; 943 invalidatePageData(page, true); 944 } 945 946 public ContentType getContentType() { 947 return mContentType; 948 } 949 950 protected void snapToPage(int whichPage, int delta, int duration) { 951 super.snapToPage(whichPage, delta, duration); 952 953 // Update the thread priorities given the direction lookahead 954 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 955 while (iter.hasNext()) { 956 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 957 int pageIndex = task.page; 958 if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) || 959 (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) { 960 task.setThreadPriority(getThreadPriorityForPage(pageIndex)); 961 } else { 962 task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); 963 } 964 } 965 } 966 967 /* 968 * Apps PagedView implementation 969 */ 970 private void setVisibilityOnChildren(ViewGroup layout, int visibility) { 971 int childCount = layout.getChildCount(); 972 for (int i = 0; i < childCount; ++i) { 973 layout.getChildAt(i).setVisibility(visibility); 974 } 975 } 976 private void setupPage(AppsCustomizeCellLayout layout) { 977 layout.setGridSize(mCellCountX, mCellCountY); 978 979 // Note: We force a measure here to get around the fact that when we do layout calculations 980 // immediately after syncing, we don't have a proper width. That said, we already know the 981 // expected page width, so we can actually optimize by hiding all the TextView-based 982 // children that are expensive to measure, and let that happen naturally later. 983 setVisibilityOnChildren(layout, View.GONE); 984 int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST); 985 int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST); 986 layout.setMinimumWidth(getPageContentWidth()); 987 layout.measure(widthSpec, heightSpec); 988 setVisibilityOnChildren(layout, View.VISIBLE); 989 } 990 991 public void syncAppsPageItems(int page, boolean immediate) { 992 // ensure that we have the right number of items on the pages 993 final boolean isRtl = isLayoutRtl(); 994 int numCells = mCellCountX * mCellCountY; 995 int startIndex = page * numCells; 996 int endIndex = Math.min(startIndex + numCells, mApps.size()); 997 AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page); 998 999 layout.removeAllViewsOnPage(); 1000 ArrayList<Object> items = new ArrayList<Object>(); 1001 ArrayList<Bitmap> images = new ArrayList<Bitmap>(); 1002 for (int i = startIndex; i < endIndex; ++i) { 1003 AppInfo info = mApps.get(i); 1004 PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( 1005 R.layout.apps_customize_application, layout, false); 1006 icon.applyFromApplicationInfo(info, true, this); 1007 icon.setOnClickListener(this); 1008 icon.setOnLongClickListener(this); 1009 icon.setOnTouchListener(this); 1010 icon.setOnKeyListener(this); 1011 1012 int index = i - startIndex; 1013 int x = index % mCellCountX; 1014 int y = index / mCellCountX; 1015 if (isRtl) { 1016 x = mCellCountX - x - 1; 1017 } 1018 layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false); 1019 1020 items.add(info); 1021 images.add(info.iconBitmap); 1022 } 1023 1024 enableHwLayersOnVisiblePages(); 1025 } 1026 1027 /** 1028 * A helper to return the priority for loading of the specified widget page. 1029 */ 1030 private int getWidgetPageLoadPriority(int page) { 1031 // If we are snapping to another page, use that index as the target page index 1032 int toPage = mCurrentPage; 1033 if (mNextPage > -1) { 1034 toPage = mNextPage; 1035 } 1036 1037 // We use the distance from the target page as an initial guess of priority, but if there 1038 // are no pages of higher priority than the page specified, then bump up the priority of 1039 // the specified page. 1040 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 1041 int minPageDiff = Integer.MAX_VALUE; 1042 while (iter.hasNext()) { 1043 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 1044 minPageDiff = Math.abs(task.page - toPage); 1045 } 1046 1047 int rawPageDiff = Math.abs(page - toPage); 1048 return rawPageDiff - Math.min(rawPageDiff, minPageDiff); 1049 } 1050 /** 1051 * Return the appropriate thread priority for loading for a given page (we give the current 1052 * page much higher priority) 1053 */ 1054 private int getThreadPriorityForPage(int page) { 1055 // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below 1056 int pageDiff = getWidgetPageLoadPriority(page); 1057 if (pageDiff <= 0) { 1058 return Process.THREAD_PRIORITY_LESS_FAVORABLE; 1059 } else if (pageDiff <= 1) { 1060 return Process.THREAD_PRIORITY_LOWEST; 1061 } else { 1062 return Process.THREAD_PRIORITY_LOWEST; 1063 } 1064 } 1065 private int getSleepForPage(int page) { 1066 int pageDiff = getWidgetPageLoadPriority(page); 1067 return Math.max(0, pageDiff * sPageSleepDelay); 1068 } 1069 /** 1070 * Creates and executes a new AsyncTask to load a page of widget previews. 1071 */ 1072 private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, 1073 int cellWidth, int cellHeight, int cellCountX) { 1074 1075 // Prune all tasks that are no longer needed 1076 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 1077 while (iter.hasNext()) { 1078 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 1079 int taskPage = task.page; 1080 if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || 1081 taskPage > getAssociatedUpperPageBound(mCurrentPage)) { 1082 task.cancel(false); 1083 iter.remove(); 1084 } else { 1085 task.setThreadPriority(getThreadPriorityForPage(taskPage)); 1086 } 1087 } 1088 1089 // We introduce a slight delay to order the loading of side pages so that we don't thrash 1090 final int sleepMs = getSleepForPage(page); 1091 AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, 1092 new AsyncTaskCallback() { 1093 @Override 1094 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 1095 try { 1096 try { 1097 Thread.sleep(sleepMs); 1098 } catch (Exception e) {} 1099 loadWidgetPreviewsInBackground(task, data); 1100 } finally { 1101 if (task.isCancelled()) { 1102 data.cleanup(true); 1103 } 1104 } 1105 } 1106 }, 1107 new AsyncTaskCallback() { 1108 @Override 1109 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 1110 mRunningTasks.remove(task); 1111 if (task.isCancelled()) return; 1112 // do cleanup inside onSyncWidgetPageItems 1113 onSyncWidgetPageItems(data); 1114 } 1115 }, mWidgetPreviewLoader); 1116 1117 // Ensure that the task is appropriately prioritized and runs in parallel 1118 AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, 1119 AsyncTaskPageData.Type.LoadWidgetPreviewData); 1120 t.setThreadPriority(getThreadPriorityForPage(page)); 1121 t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); 1122 mRunningTasks.add(t); 1123 } 1124 1125 /* 1126 * Widgets PagedView implementation 1127 */ 1128 private void setupPage(PagedViewGridLayout layout) { 1129 // Note: We force a measure here to get around the fact that when we do layout calculations 1130 // immediately after syncing, we don't have a proper width. 1131 int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST); 1132 int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST); 1133 layout.setMinimumWidth(getPageContentWidth()); 1134 layout.measure(widthSpec, heightSpec); 1135 } 1136 1137 public void syncWidgetPageItems(final int page, final boolean immediate) { 1138 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 1139 1140 // Calculate the dimensions of each cell we are giving to each widget 1141 final ArrayList<Object> items = new ArrayList<Object>(); 1142 int contentWidth = mContentWidth; 1143 final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight 1144 - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); 1145 int contentHeight = mContentHeight; 1146 final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom 1147 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); 1148 1149 // Prepare the set of widgets to load previews for in the background 1150 int offset = page * numItemsPerPage; 1151 for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) { 1152 items.add(mWidgets.get(i)); 1153 } 1154 1155 // Prepopulate the pages with the other widget info, and fill in the previews later 1156 final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); 1157 layout.setColumnCount(layout.getCellCountX()); 1158 for (int i = 0; i < items.size(); ++i) { 1159 Object rawInfo = items.get(i); 1160 PendingAddItemInfo createItemInfo = null; 1161 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 1162 R.layout.apps_customize_widget, layout, false); 1163 if (rawInfo instanceof AppWidgetProviderInfo) { 1164 // Fill in the widget information 1165 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 1166 createItemInfo = new PendingAddWidgetInfo(info, null, null); 1167 1168 // Determine the widget spans and min resize spans. 1169 int[] spanXY = Launcher.getSpanForWidget(mLauncher, info); 1170 createItemInfo.spanX = spanXY[0]; 1171 createItemInfo.spanY = spanXY[1]; 1172 int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info); 1173 createItemInfo.minSpanX = minSpanXY[0]; 1174 createItemInfo.minSpanY = minSpanXY[1]; 1175 1176 widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader); 1177 widget.setTag(createItemInfo); 1178 widget.setShortPressListener(this); 1179 } else if (rawInfo instanceof ResolveInfo) { 1180 // Fill in the shortcuts information 1181 ResolveInfo info = (ResolveInfo) rawInfo; 1182 createItemInfo = new PendingAddShortcutInfo(info.activityInfo); 1183 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 1184 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 1185 info.activityInfo.name); 1186 widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader); 1187 widget.setTag(createItemInfo); 1188 } 1189 widget.setOnClickListener(this); 1190 widget.setOnLongClickListener(this); 1191 widget.setOnTouchListener(this); 1192 widget.setOnKeyListener(this); 1193 1194 // Layout each widget 1195 int ix = i % mWidgetCountX; 1196 int iy = i / mWidgetCountX; 1197 GridLayout.LayoutParams lp = new GridLayout.LayoutParams( 1198 GridLayout.spec(iy, GridLayout.START), 1199 GridLayout.spec(ix, GridLayout.TOP)); 1200 lp.width = cellWidth; 1201 lp.height = cellHeight; 1202 lp.setGravity(Gravity.TOP | Gravity.START); 1203 if (ix > 0) lp.leftMargin = mWidgetWidthGap; 1204 if (iy > 0) lp.topMargin = mWidgetHeightGap; 1205 layout.addView(widget, lp); 1206 } 1207 1208 // wait until a call on onLayout to start loading, because 1209 // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out 1210 // TODO: can we do a measure/layout immediately? 1211 layout.setOnLayoutListener(new Runnable() { 1212 public void run() { 1213 // Load the widget previews 1214 int maxPreviewWidth = cellWidth; 1215 int maxPreviewHeight = cellHeight; 1216 if (layout.getChildCount() > 0) { 1217 PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0); 1218 int[] maxSize = w.getPreviewSize(); 1219 maxPreviewWidth = maxSize[0]; 1220 maxPreviewHeight = maxSize[1]; 1221 } 1222 1223 mWidgetPreviewLoader.setPreviewSize( 1224 maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout); 1225 if (immediate) { 1226 AsyncTaskPageData data = new AsyncTaskPageData(page, items, 1227 maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader); 1228 loadWidgetPreviewsInBackground(null, data); 1229 onSyncWidgetPageItems(data); 1230 } else { 1231 if (mInTransition) { 1232 mDeferredPrepareLoadWidgetPreviewsTasks.add(this); 1233 } else { 1234 prepareLoadWidgetPreviewsTask(page, items, 1235 maxPreviewWidth, maxPreviewHeight, mWidgetCountX); 1236 } 1237 } 1238 layout.setOnLayoutListener(null); 1239 } 1240 }); 1241 } 1242 private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, 1243 AsyncTaskPageData data) { 1244 // loadWidgetPreviewsInBackground can be called without a task to load a set of widget 1245 // previews synchronously 1246 if (task != null) { 1247 // Ensure that this task starts running at the correct priority 1248 task.syncThreadPriority(); 1249 } 1250 1251 // Load each of the widget/shortcut previews 1252 ArrayList<Object> items = data.items; 1253 ArrayList<Bitmap> images = data.generatedImages; 1254 int count = items.size(); 1255 for (int i = 0; i < count; ++i) { 1256 if (task != null) { 1257 // Ensure we haven't been cancelled yet 1258 if (task.isCancelled()) break; 1259 // Before work on each item, ensure that this task is running at the correct 1260 // priority 1261 task.syncThreadPriority(); 1262 } 1263 1264 images.add(mWidgetPreviewLoader.getPreview(items.get(i))); 1265 } 1266 } 1267 1268 private void onSyncWidgetPageItems(AsyncTaskPageData data) { 1269 if (mInTransition) { 1270 mDeferredSyncWidgetPageItems.add(data); 1271 return; 1272 } 1273 try { 1274 int page = data.page; 1275 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); 1276 1277 ArrayList<Object> items = data.items; 1278 int count = items.size(); 1279 for (int i = 0; i < count; ++i) { 1280 PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); 1281 if (widget != null) { 1282 Bitmap preview = data.generatedImages.get(i); 1283 widget.applyPreview(new FastBitmapDrawable(preview), i); 1284 } 1285 } 1286 1287 enableHwLayersOnVisiblePages(); 1288 1289 // Update all thread priorities 1290 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 1291 while (iter.hasNext()) { 1292 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 1293 int pageIndex = task.page; 1294 task.setThreadPriority(getThreadPriorityForPage(pageIndex)); 1295 } 1296 } finally { 1297 data.cleanup(false); 1298 } 1299 } 1300 1301 @Override 1302 public void syncPages() { 1303 disablePagedViewAnimations(); 1304 1305 removeAllViews(); 1306 cancelAllTasks(); 1307 1308 Context context = getContext(); 1309 if (mContentType == ContentType.Applications) { 1310 for (int i = 0; i < mNumAppsPages; ++i) { 1311 AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context); 1312 setupPage(layout); 1313 addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, 1314 LayoutParams.MATCH_PARENT)); 1315 } 1316 } else if (mContentType == ContentType.Widgets) { 1317 for (int j = 0; j < mNumWidgetPages; ++j) { 1318 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 1319 mWidgetCountY); 1320 setupPage(layout); 1321 addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, 1322 LayoutParams.MATCH_PARENT)); 1323 } 1324 } else { 1325 throw new RuntimeException("Invalid ContentType"); 1326 } 1327 1328 enablePagedViewAnimations(); 1329 } 1330 1331 @Override 1332 public void syncPageItems(int page, boolean immediate) { 1333 if (mContentType == ContentType.Widgets) { 1334 syncWidgetPageItems(page, immediate); 1335 } else { 1336 syncAppsPageItems(page, immediate); 1337 } 1338 } 1339 1340 // We want our pages to be z-ordered such that the further a page is to the left, the higher 1341 // it is in the z-order. This is important to insure touch events are handled correctly. 1342 View getPageAt(int index) { 1343 return getChildAt(indexToPage(index)); 1344 } 1345 1346 @Override 1347 protected int indexToPage(int index) { 1348 return getChildCount() - index - 1; 1349 } 1350 1351 // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. 1352 @Override 1353 protected void screenScrolled(int screenCenter) { 1354 final boolean isRtl = isLayoutRtl(); 1355 super.screenScrolled(screenCenter); 1356 1357 for (int i = 0; i < getChildCount(); i++) { 1358 View v = getPageAt(i); 1359 if (v != null) { 1360 float scrollProgress = getScrollProgress(screenCenter, v, i); 1361 1362 float interpolatedProgress; 1363 float translationX; 1364 float maxScrollProgress = Math.max(0, scrollProgress); 1365 float minScrollProgress = Math.min(0, scrollProgress); 1366 1367 if (isRtl) { 1368 translationX = maxScrollProgress * v.getMeasuredWidth(); 1369 interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress)); 1370 } else { 1371 translationX = minScrollProgress * v.getMeasuredWidth(); 1372 interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress)); 1373 } 1374 float scale = (1 - interpolatedProgress) + 1375 interpolatedProgress * TRANSITION_SCALE_FACTOR; 1376 1377 float alpha; 1378 if (isRtl && (scrollProgress > 0)) { 1379 alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress)); 1380 } else if (!isRtl && (scrollProgress < 0)) { 1381 alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress)); 1382 } else { 1383 // On large screens we need to fade the page as it nears its leftmost position 1384 alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); 1385 } 1386 1387 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 1388 int pageWidth = v.getMeasuredWidth(); 1389 int pageHeight = v.getMeasuredHeight(); 1390 1391 if (PERFORM_OVERSCROLL_ROTATION) { 1392 float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT; 1393 boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0; 1394 boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0; 1395 1396 if (i == 0 && isOverscrollingFirstPage) { 1397 // Overscroll to the left 1398 v.setPivotX(xPivot * pageWidth); 1399 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1400 scale = 1.0f; 1401 alpha = 1.0f; 1402 // On the first page, we don't want the page to have any lateral motion 1403 translationX = 0; 1404 } else if (i == getChildCount() - 1 && isOverscrollingLastPage) { 1405 // Overscroll to the right 1406 v.setPivotX((1 - xPivot) * pageWidth); 1407 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1408 scale = 1.0f; 1409 alpha = 1.0f; 1410 // On the last page, we don't want the page to have any lateral motion. 1411 translationX = 0; 1412 } else { 1413 v.setPivotY(pageHeight / 2.0f); 1414 v.setPivotX(pageWidth / 2.0f); 1415 v.setRotationY(0f); 1416 } 1417 } 1418 1419 v.setTranslationX(translationX); 1420 v.setScaleX(scale); 1421 v.setScaleY(scale); 1422 v.setAlpha(alpha); 1423 1424 // If the view has 0 alpha, we set it to be invisible so as to prevent 1425 // it from accepting touches 1426 if (alpha == 0) { 1427 v.setVisibility(INVISIBLE); 1428 } else if (v.getVisibility() != VISIBLE) { 1429 v.setVisibility(VISIBLE); 1430 } 1431 } 1432 } 1433 1434 enableHwLayersOnVisiblePages(); 1435 } 1436 1437 private void enableHwLayersOnVisiblePages() { 1438 final int screenCount = getChildCount(); 1439 1440 getVisiblePages(mTempVisiblePagesRange); 1441 int leftScreen = mTempVisiblePagesRange[0]; 1442 int rightScreen = mTempVisiblePagesRange[1]; 1443 int forceDrawScreen = -1; 1444 if (leftScreen == rightScreen) { 1445 // make sure we're caching at least two pages always 1446 if (rightScreen < screenCount - 1) { 1447 rightScreen++; 1448 forceDrawScreen = rightScreen; 1449 } else if (leftScreen > 0) { 1450 leftScreen--; 1451 forceDrawScreen = leftScreen; 1452 } 1453 } else { 1454 forceDrawScreen = leftScreen + 1; 1455 } 1456 1457 for (int i = 0; i < screenCount; i++) { 1458 final View layout = (View) getPageAt(i); 1459 if (!(leftScreen <= i && i <= rightScreen && 1460 (i == forceDrawScreen || shouldDrawChild(layout)))) { 1461 layout.setLayerType(LAYER_TYPE_NONE, null); 1462 } 1463 } 1464 1465 for (int i = 0; i < screenCount; i++) { 1466 final View layout = (View) getPageAt(i); 1467 1468 if (leftScreen <= i && i <= rightScreen && 1469 (i == forceDrawScreen || shouldDrawChild(layout))) { 1470 if (layout.getLayerType() != LAYER_TYPE_HARDWARE) { 1471 layout.setLayerType(LAYER_TYPE_HARDWARE, null); 1472 } 1473 } 1474 } 1475 } 1476 1477 protected void overScroll(float amount) { 1478 acceleratedOverScroll(amount); 1479 } 1480 1481 /** 1482 * Used by the parent to get the content width to set the tab bar to 1483 * @return 1484 */ 1485 public int getPageContentWidth() { 1486 return mContentWidth; 1487 } 1488 1489 @Override 1490 protected void onPageEndMoving() { 1491 super.onPageEndMoving(); 1492 mForceDrawAllChildrenNextFrame = true; 1493 // We reset the save index when we change pages so that it will be recalculated on next 1494 // rotation 1495 mSaveInstanceStateItemIndex = -1; 1496 } 1497 1498 /* 1499 * AllAppsView implementation 1500 */ 1501 public void setup(Launcher launcher, DragController dragController) { 1502 mLauncher = launcher; 1503 mDragController = dragController; 1504 } 1505 1506 /** 1507 * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can 1508 * appropriately determine when to invalidate the PagedView page data. In cases where the data 1509 * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the 1510 * next onMeasure() pass, which will trigger an invalidatePageData() itself. 1511 */ 1512 private void invalidateOnDataChange() { 1513 if (!isDataReady()) { 1514 // The next layout pass will trigger data-ready if both widgets and apps are set, so 1515 // request a layout to trigger the page data when ready. 1516 requestLayout(); 1517 } else { 1518 cancelAllTasks(); 1519 invalidatePageData(); 1520 } 1521 } 1522 1523 public void setApps(ArrayList<AppInfo> list) { 1524 if (!DISABLE_ALL_APPS) { 1525 mApps = list; 1526 Collections.sort(mApps, LauncherModel.getAppNameComparator()); 1527 updatePageCountsAndInvalidateData(); 1528 } 1529 } 1530 private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) { 1531 // We add it in place, in alphabetical order 1532 int count = list.size(); 1533 for (int i = 0; i < count; ++i) { 1534 AppInfo info = list.get(i); 1535 int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator()); 1536 if (index < 0) { 1537 mApps.add(-(index + 1), info); 1538 } 1539 } 1540 } 1541 public void addApps(ArrayList<AppInfo> list) { 1542 if (!DISABLE_ALL_APPS) { 1543 addAppsWithoutInvalidate(list); 1544 updatePageCountsAndInvalidateData(); 1545 } 1546 } 1547 private int findAppByComponent(List<AppInfo> list, AppInfo item) { 1548 ComponentName removeComponent = item.intent.getComponent(); 1549 int length = list.size(); 1550 for (int i = 0; i < length; ++i) { 1551 AppInfo info = list.get(i); 1552 if (info.intent.getComponent().equals(removeComponent)) { 1553 return i; 1554 } 1555 } 1556 return -1; 1557 } 1558 private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) { 1559 // loop through all the apps and remove apps that have the same component 1560 int length = list.size(); 1561 for (int i = 0; i < length; ++i) { 1562 AppInfo info = list.get(i); 1563 int removeIndex = findAppByComponent(mApps, info); 1564 if (removeIndex > -1) { 1565 mApps.remove(removeIndex); 1566 } 1567 } 1568 } 1569 public void removeApps(ArrayList<AppInfo> appInfos) { 1570 if (!DISABLE_ALL_APPS) { 1571 removeAppsWithoutInvalidate(appInfos); 1572 updatePageCountsAndInvalidateData(); 1573 } 1574 } 1575 public void updateApps(ArrayList<AppInfo> list) { 1576 // We remove and re-add the updated applications list because it's properties may have 1577 // changed (ie. the title), and this will ensure that the items will be in their proper 1578 // place in the list. 1579 if (!DISABLE_ALL_APPS) { 1580 removeAppsWithoutInvalidate(list); 1581 addAppsWithoutInvalidate(list); 1582 updatePageCountsAndInvalidateData(); 1583 } 1584 } 1585 1586 public void reset() { 1587 // If we have reset, then we should not continue to restore the previous state 1588 mSaveInstanceStateItemIndex = -1; 1589 1590 AppsCustomizeTabHost tabHost = getTabHost(); 1591 String tag = tabHost.getCurrentTabTag(); 1592 if (tag != null) { 1593 if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { 1594 tabHost.setCurrentTabFromContent(ContentType.Applications); 1595 } 1596 } 1597 1598 if (mCurrentPage != 0) { 1599 invalidatePageData(0); 1600 } 1601 } 1602 1603 private AppsCustomizeTabHost getTabHost() { 1604 return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane); 1605 } 1606 1607 public void dumpState() { 1608 // TODO: Dump information related to current list of Applications, Widgets, etc. 1609 AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps); 1610 dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets); 1611 } 1612 1613 private void dumpAppWidgetProviderInfoList(String tag, String label, 1614 ArrayList<Object> list) { 1615 Log.d(tag, label + " size=" + list.size()); 1616 for (Object i: list) { 1617 if (i instanceof AppWidgetProviderInfo) { 1618 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1619 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1620 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1621 + " initialLayout=" + info.initialLayout 1622 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1623 } else if (i instanceof ResolveInfo) { 1624 ResolveInfo info = (ResolveInfo) i; 1625 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1626 + info.icon); 1627 } 1628 } 1629 } 1630 1631 public void surrender() { 1632 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1633 // should stop this now. 1634 1635 // Stop all background tasks 1636 cancelAllTasks(); 1637 } 1638 1639 @Override 1640 public void iconPressed(PagedViewIcon icon) { 1641 // Reset the previously pressed icon and store a reference to the pressed icon so that 1642 // we can reset it on return to Launcher (in Launcher.onResume()) 1643 if (mPressedIcon != null) { 1644 mPressedIcon.resetDrawableState(); 1645 } 1646 mPressedIcon = icon; 1647 } 1648 1649 public void resetDrawableState() { 1650 if (mPressedIcon != null) { 1651 mPressedIcon.resetDrawableState(); 1652 mPressedIcon = null; 1653 } 1654 } 1655 1656 /* 1657 * We load an extra page on each side to prevent flashes from scrolling and loading of the 1658 * widget previews in the background with the AsyncTasks. 1659 */ 1660 final static int sLookBehindPageCount = 2; 1661 final static int sLookAheadPageCount = 2; 1662 protected int getAssociatedLowerPageBound(int page) { 1663 final int count = getChildCount(); 1664 int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); 1665 int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0); 1666 return windowMinIndex; 1667 } 1668 protected int getAssociatedUpperPageBound(int page) { 1669 final int count = getChildCount(); 1670 int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); 1671 int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1), 1672 count - 1); 1673 return windowMaxIndex; 1674 } 1675 1676 protected String getCurrentPageDescription() { 1677 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 1678 int stringId = R.string.default_scroll_format; 1679 int count = 0; 1680 1681 if (mContentType == ContentType.Applications) { 1682 stringId = R.string.apps_customize_apps_scroll_format; 1683 count = mNumAppsPages; 1684 } else if (mContentType == ContentType.Widgets) { 1685 stringId = R.string.apps_customize_widgets_scroll_format; 1686 count = mNumWidgetPages; 1687 } else { 1688 throw new RuntimeException("Invalid ContentType"); 1689 } 1690 1691 return String.format(getContext().getString(stringId), page + 1, count); 1692 } 1693} 1694