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