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