AppsCustomizePagedView.java revision e4a647f8793ce7823394db43d6af57f9eab8a340
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher2; 18 19import android.animation.AnimatorSet; 20import android.animation.ObjectAnimator; 21import android.animation.ValueAnimator; 22import android.appwidget.AppWidgetManager; 23import android.appwidget.AppWidgetProviderInfo; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ActivityInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.content.res.Configuration; 31import android.content.res.Resources; 32import android.content.res.TypedArray; 33import android.graphics.Bitmap; 34import android.graphics.Canvas; 35import android.graphics.PorterDuff; 36import android.graphics.Rect; 37import android.graphics.Bitmap.Config; 38import android.graphics.drawable.Drawable; 39import android.os.AsyncTask; 40import android.os.Process; 41import android.util.AttributeSet; 42import android.util.Log; 43import android.view.Gravity; 44import android.view.LayoutInflater; 45import android.view.MotionEvent; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.animation.AccelerateInterpolator; 49import android.view.animation.DecelerateInterpolator; 50import android.widget.GridLayout; 51import android.widget.ImageView; 52import android.widget.Toast; 53 54import com.android.launcher.R; 55import com.android.launcher2.DropTarget.DragObject; 56 57import java.util.ArrayList; 58import java.util.Collections; 59import java.util.Iterator; 60import java.util.List; 61 62/** 63 * A simple callback interface which also provides the results of the task. 64 */ 65interface AsyncTaskCallback { 66 void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); 67} 68 69/** 70 * The data needed to perform either of the custom AsyncTasks. 71 */ 72class AsyncTaskPageData { 73 enum Type { 74 LoadWidgetPreviewData, 75 LoadHolographicIconsData 76 } 77 78 AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR, 79 AsyncTaskCallback postR) { 80 page = p; 81 items = l; 82 sourceImages = si; 83 generatedImages = new ArrayList<Bitmap>(); 84 cellWidth = cellHeight = -1; 85 doInBackgroundCallback = bgR; 86 postExecuteCallback = postR; 87 } 88 AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, int ccx, AsyncTaskCallback bgR, 89 AsyncTaskCallback postR) { 90 page = p; 91 items = l; 92 generatedImages = new ArrayList<Bitmap>(); 93 cellWidth = cw; 94 cellHeight = ch; 95 cellCountX = ccx; 96 doInBackgroundCallback = bgR; 97 postExecuteCallback = postR; 98 } 99 void cleanup(boolean cancelled) { 100 // Clean up any references to source/generated bitmaps 101 if (sourceImages != null) { 102 if (cancelled) { 103 for (Bitmap b : sourceImages) { 104 b.recycle(); 105 } 106 } 107 sourceImages.clear(); 108 } 109 if (generatedImages != null) { 110 if (cancelled) { 111 for (Bitmap b : generatedImages) { 112 b.recycle(); 113 } 114 } 115 generatedImages.clear(); 116 } 117 } 118 int page; 119 ArrayList<Object> items; 120 ArrayList<Bitmap> sourceImages; 121 ArrayList<Bitmap> generatedImages; 122 int cellWidth; 123 int cellHeight; 124 int cellCountX; 125 AsyncTaskCallback doInBackgroundCallback; 126 AsyncTaskCallback postExecuteCallback; 127} 128 129/** 130 * A generic template for an async task used in AppsCustomize. 131 */ 132class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> { 133 AppsCustomizeAsyncTask(int p, AppsCustomizePagedView.ContentType t, AsyncTaskPageData.Type ty) { 134 page = p; 135 pageContentType = t; 136 threadPriority = Process.THREAD_PRIORITY_DEFAULT; 137 dataType = ty; 138 } 139 @Override 140 protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { 141 if (params.length != 1) return null; 142 // Load each of the widget previews in the background 143 params[0].doInBackgroundCallback.run(this, params[0]); 144 return params[0]; 145 } 146 @Override 147 protected void onPostExecute(AsyncTaskPageData result) { 148 // All the widget previews are loaded, so we can just callback to inflate the page 149 result.postExecuteCallback.run(this, result); 150 } 151 152 void setThreadPriority(int p) { 153 threadPriority = p; 154 } 155 void syncThreadPriority() { 156 Process.setThreadPriority(threadPriority); 157 } 158 159 // The page that this async task is associated with 160 AsyncTaskPageData.Type dataType; 161 int page; 162 AppsCustomizePagedView.ContentType pageContentType; 163 int threadPriority; 164} 165 166/** 167 * The Apps/Customize page that displays all the applications, widgets, and shortcuts. 168 */ 169public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements 170 AllAppsView, View.OnClickListener, DragSource { 171 static final String LOG_TAG = "AppsCustomizePagedView"; 172 173 /** 174 * The different content types that this paged view can show. 175 */ 176 public enum ContentType { 177 Applications, 178 Widgets 179 } 180 181 // Refs 182 private Launcher mLauncher; 183 private DragController mDragController; 184 private final LayoutInflater mLayoutInflater; 185 private final PackageManager mPackageManager; 186 187 // Save and Restore 188 private int mSaveInstanceStateItemIndex = -1; 189 private int mRestorePage = -1; 190 191 // Content 192 private ContentType mContentType; 193 private ArrayList<ApplicationInfo> mApps; 194 private ArrayList<Object> mWidgets; 195 196 // Cling 197 private int mClingFocusedX; 198 private int mClingFocusedY; 199 200 // Caching 201 private Canvas mCanvas; 202 private Drawable mDefaultWidgetBackground; 203 private IconCache mIconCache; 204 private int mDragViewMultiplyColor; 205 206 // Dimens 207 private int mContentWidth; 208 private int mAppIconSize; 209 private int mMaxWidgetSpan, mMinWidgetSpan; 210 private int mWidgetCountX, mWidgetCountY; 211 private int mWidgetWidthGap, mWidgetHeightGap; 212 private final int mWidgetPreviewIconPaddedDimension; 213 private final float sWidgetPreviewIconPaddingPercentage = 0.25f; 214 private PagedViewCellLayout mWidgetSpacingLayout; 215 216 // Relating to the scroll and overscroll effects 217 Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f); 218 private static float CAMERA_DISTANCE = 6500; 219 private static float TRANSITION_SCALE_FACTOR = 0.74f; 220 private static float TRANSITION_PIVOT = 0.65f; 221 private static float TRANSITION_MAX_ROTATION = 22; 222 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 223 private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); 224 225 // Previews & outlines 226 ArrayList<AppsCustomizeAsyncTask> mRunningTasks; 227 private HolographicOutlineHelper mHolographicOutlineHelper; 228 private static final int sPageSleepDelay = 200; 229 230 public AppsCustomizePagedView(Context context, AttributeSet attrs) { 231 super(context, attrs); 232 mLayoutInflater = LayoutInflater.from(context); 233 mPackageManager = context.getPackageManager(); 234 mContentType = ContentType.Applications; 235 mApps = new ArrayList<ApplicationInfo>(); 236 mWidgets = new ArrayList<Object>(); 237 mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); 238 mHolographicOutlineHelper = new HolographicOutlineHelper(); 239 mCanvas = new Canvas(); 240 mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>(); 241 242 // Save the default widget preview background 243 Resources resources = context.getResources(); 244 mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo); 245 mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 246 mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color); 247 248 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0); 249 // TODO-APPS_CUSTOMIZE: remove these unnecessary attrs after 250 mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6); 251 mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4); 252 a.recycle(); 253 a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); 254 mWidgetWidthGap = 255 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0); 256 mWidgetHeightGap = 257 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0); 258 mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); 259 mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); 260 mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0); 261 mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); 262 a.recycle(); 263 mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); 264 265 // The max widget span is the length N, such that NxN is the largest bounds that the widget 266 // preview can be before applying the widget scaling 267 mMinWidgetSpan = 1; 268 mMaxWidgetSpan = 3; 269 270 // The padding on the non-matched dimension for the default widget preview icons 271 // (top + bottom) 272 mWidgetPreviewIconPaddedDimension = 273 (int) (mAppIconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage))); 274 mFadeInAdjacentScreens = LauncherApplication.isScreenLarge(); 275 } 276 277 @Override 278 protected void init() { 279 super.init(); 280 mCenterPagesVertically = false; 281 282 Context context = getContext(); 283 Resources r = context.getResources(); 284 setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); 285 } 286 287 @Override 288 protected void onUnhandledTap(MotionEvent ev) { 289 if (LauncherApplication.isScreenLarge()) { 290 // Dismiss AppsCustomize if we tap 291 mLauncher.showWorkspace(true); 292 } 293 } 294 295 /** Returns the item index of the center item on this page so that we can restore to this 296 * item index when we rotate. */ 297 private int getMiddleComponentIndexOnCurrentPage() { 298 int i = -1; 299 if (getPageCount() > 0) { 300 int currentPage = getCurrentPage(); 301 switch (mContentType) { 302 case Applications: { 303 PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage); 304 PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout(); 305 int numItemsPerPage = mCellCountX * mCellCountY; 306 int childCount = childrenLayout.getChildCount(); 307 if (childCount > 0) { 308 i = (currentPage * numItemsPerPage) + (childCount / 2); 309 }} 310 break; 311 case Widgets: { 312 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage); 313 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 314 int childCount = layout.getChildCount(); 315 if (childCount > 0) { 316 i = (currentPage * numItemsPerPage) + (childCount / 2); 317 }} 318 break; 319 } 320 } 321 return i; 322 } 323 324 /** Get the index of the item to restore to if we need to restore the current page. */ 325 int getSaveInstanceStateIndex() { 326 if (mSaveInstanceStateItemIndex == -1) { 327 mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage(); 328 } 329 return mSaveInstanceStateItemIndex; 330 } 331 332 /** Returns the page in the current orientation which is expected to contain the specified 333 * item index. */ 334 int getPageForComponent(int index) { 335 switch (mContentType) { 336 case Applications: { 337 int numItemsPerPage = mCellCountX * mCellCountY; 338 return (index / numItemsPerPage); 339 } 340 case Widgets: { 341 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 342 return (index / numItemsPerPage); 343 }} 344 return -1; 345 } 346 347 /** 348 * This differs from isDataReady as this is the test done if isDataReady is not set. 349 */ 350 private boolean testDataReady() { 351 // We only do this test once, and we default to the Applications page, so we only really 352 // have to wait for there to be apps. 353 if (mContentType == AppsCustomizePagedView.ContentType.Widgets) { 354 return !mApps.isEmpty() && !mWidgets.isEmpty(); 355 } else { 356 return !mApps.isEmpty(); 357 } 358 } 359 360 /** Restores the page for an item at the specified index */ 361 void restorePageForIndex(int index) { 362 if (index < 0) return; 363 364 int page = getPageForComponent(index); 365 if (page > -1) { 366 if (getChildCount() > 0) { 367 invalidatePageData(page); 368 } else { 369 mRestorePage = page; 370 } 371 } 372 } 373 374 protected void onDataReady(int width, int height) { 375 // Note that we transpose the counts in portrait so that we get a similar layout 376 boolean isLandscape = getResources().getConfiguration().orientation == 377 Configuration.ORIENTATION_LANDSCAPE; 378 int maxCellCountX = Integer.MAX_VALUE; 379 int maxCellCountY = Integer.MAX_VALUE; 380 if (LauncherApplication.isScreenLarge()) { 381 maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() : 382 LauncherModel.getCellCountY()); 383 maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() : 384 LauncherModel.getCellCountX()); 385 } 386 387 // Now that the data is ready, we can calculate the content width, the number of cells to 388 // use for each page 389 mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 390 mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 391 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 392 mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY); 393 mCellCountX = mWidgetSpacingLayout.getCellCountX(); 394 mCellCountY = mWidgetSpacingLayout.getCellCountY(); 395 396 // Force a measure to update recalculate the gaps 397 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 398 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 399 mWidgetSpacingLayout.measure(widthSpec, heightSpec); 400 mContentWidth = mWidgetSpacingLayout.getContentWidth(); 401 invalidatePageData(Math.max(0, mRestorePage)); 402 mRestorePage = -1; 403 404 int[] offset = new int[2]; 405 int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); 406 mLauncher.getDragLayer().getLocationInDragLayer(this, offset); 407 pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + offset[0]; 408 pos[1] += (getMeasuredHeight() - mWidgetSpacingLayout.getMeasuredHeight()) / 2 + offset[1]; 409 mLauncher.showFirstRunAllAppsCling(pos); 410 411 } 412 413 @Override 414 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 415 int width = MeasureSpec.getSize(widthMeasureSpec); 416 int height = MeasureSpec.getSize(heightMeasureSpec); 417 if (!isDataReady()) { 418 if (testDataReady()) { 419 setDataIsReady(); 420 setMeasuredDimension(width, height); 421 onDataReady(width, height); 422 } 423 } 424 425 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 426 } 427 428 /** Removes and returns the ResolveInfo with the specified ComponentName */ 429 private ResolveInfo removeResolveInfoWithComponentName(List<ResolveInfo> list, 430 ComponentName cn) { 431 Iterator<ResolveInfo> iter = list.iterator(); 432 while (iter.hasNext()) { 433 ResolveInfo rinfo = iter.next(); 434 ActivityInfo info = rinfo.activityInfo; 435 ComponentName c = new ComponentName(info.packageName, info.name); 436 if (c.equals(cn)) { 437 iter.remove(); 438 return rinfo; 439 } 440 } 441 return null; 442 } 443 444 public void onPackagesUpdated() { 445 // TODO: this isn't ideal, but we actually need to delay here. This call is triggered 446 // by a broadcast receiver, and in order for it to work correctly, we need to know that 447 // the AppWidgetService has already received and processed the same broadcast. Since there 448 // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally, 449 // we should have a more precise way of ensuring the AppWidgetService is up to date. 450 postDelayed(new Runnable() { 451 public void run() { 452 updatePackages(); 453 } 454 }, 500); 455 } 456 457 public void updatePackages() { 458 // Get the list of widgets and shortcuts 459 boolean wasEmpty = mWidgets.isEmpty(); 460 mWidgets.clear(); 461 List<AppWidgetProviderInfo> widgets = 462 AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); 463 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 464 List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); 465 mWidgets.addAll(widgets); 466 mWidgets.addAll(shortcuts); 467 Collections.sort(mWidgets, 468 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); 469 470 if (wasEmpty) { 471 // The next layout pass will trigger data-ready if both widgets and apps are set, so request 472 // a layout to do this test and invalidate the page data when ready. 473 if (testDataReady()) requestLayout(); 474 } else { 475 invalidatePageData(); 476 } 477 } 478 479 @Override 480 public void onClick(View v) { 481 // When we have exited all apps or are in transition, disregard clicks 482 if (!mLauncher.isAllAppsCustomizeOpen() || 483 mLauncher.getWorkspace().isSwitchingState()) return; 484 485 if (v instanceof PagedViewIcon) { 486 // Animate some feedback to the click 487 final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); 488 animateClickFeedback(v, new Runnable() { 489 @Override 490 public void run() { 491 mLauncher.startActivitySafely(appInfo.intent, appInfo); 492 } 493 }); 494 } else if (v instanceof PagedViewWidget) { 495 // Let the user know that they have to long press to add a widget 496 Toast.makeText(getContext(), R.string.long_press_widget_to_add, 497 Toast.LENGTH_SHORT).show(); 498 499 // Create a little animation to show that the widget can move 500 float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 501 final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); 502 AnimatorSet bounce = new AnimatorSet(); 503 ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY); 504 tyuAnim.setDuration(125); 505 ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f); 506 tydAnim.setDuration(100); 507 bounce.play(tyuAnim).before(tydAnim); 508 bounce.setInterpolator(new AccelerateInterpolator()); 509 bounce.start(); 510 } 511 } 512 513 /* 514 * PagedViewWithDraggableItems implementation 515 */ 516 @Override 517 protected void determineDraggingStart(android.view.MotionEvent ev) { 518 // Disable dragging by pulling an app down for now. 519 } 520 521 private void beginDraggingApplication(View v) { 522 mLauncher.getWorkspace().onDragStartedWithItem(v); 523 mLauncher.getWorkspace().beginDragShared(v, this); 524 } 525 526 private void beginDraggingWidget(View v) { 527 // Get the widget preview as the drag representation 528 ImageView image = (ImageView) v.findViewById(R.id.widget_preview); 529 PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); 530 531 // Compose the drag image 532 Bitmap b; 533 Drawable preview = image.getDrawable(); 534 int w = preview.getIntrinsicWidth(); 535 int h = preview.getIntrinsicHeight(); 536 if (createItemInfo instanceof PendingAddWidgetInfo) { 537 PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo; 538 int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null); 539 createItemInfo.spanX = spanXY[0]; 540 createItemInfo.spanY = spanXY[1]; 541 542 b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 543 renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1, mDragViewMultiplyColor); 544 } else { 545 // Workaround for the fact that we don't keep the original ResolveInfo associated with 546 // the shortcut around. To get the icon, we just render the preview image (which has 547 // the shortcut icon) to a new drag bitmap that clips the non-icon space. 548 b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension, 549 mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888); 550 mCanvas.setBitmap(b); 551 mCanvas.save(); 552 preview.draw(mCanvas); 553 mCanvas.restore(); 554 mCanvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY); 555 mCanvas.setBitmap(null); 556 createItemInfo.spanX = createItemInfo.spanY = 1; 557 } 558 559 // Start the drag 560 mLauncher.lockScreenOrientationOnLargeUI(); 561 mLauncher.getWorkspace().onDragStartedWithItemSpans(createItemInfo.spanX, 562 createItemInfo.spanY, b); 563 mDragController.startDrag(image, b, this, createItemInfo, 564 DragController.DRAG_ACTION_COPY, null); 565 b.recycle(); 566 } 567 @Override 568 protected boolean beginDragging(View v) { 569 // Dismiss the cling 570 mLauncher.dismissAllAppsCling(null); 571 572 if (!super.beginDragging(v)) return false; 573 574 // Go into spring loaded mode (must happen before we startDrag()) 575 mLauncher.enterSpringLoadedDragMode(); 576 577 if (v instanceof PagedViewIcon) { 578 beginDraggingApplication(v); 579 } else if (v instanceof PagedViewWidget) { 580 beginDraggingWidget(v); 581 } 582 return true; 583 } 584 private void endDragging(View target, boolean success) { 585 mLauncher.getWorkspace().onDragStopped(success); 586 if (!success || (target != mLauncher.getWorkspace() && 587 !(target instanceof DeleteDropTarget))) { 588 // Exit spring loaded mode if we have not successfully dropped or have not handled the 589 // drop in Workspace 590 mLauncher.exitSpringLoadedDragMode(); 591 } 592 mLauncher.unlockScreenOrientationOnLargeUI(); 593 594 } 595 596 @Override 597 public void onDropCompleted(View target, DragObject d, boolean success) { 598 endDragging(target, success); 599 600 // Display an error message if the drag failed due to there not being enough space on the 601 // target layout we were dropping on. 602 if (!success) { 603 boolean showOutOfSpaceMessage = false; 604 if (target instanceof Workspace) { 605 int currentScreen = mLauncher.getCurrentWorkspaceScreen(); 606 Workspace workspace = (Workspace) target; 607 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); 608 ItemInfo itemInfo = (ItemInfo) d.dragInfo; 609 if (layout != null) { 610 layout.calculateSpans(itemInfo); 611 showOutOfSpaceMessage = 612 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); 613 } 614 } 615 // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later. 616 if (showOutOfSpaceMessage) { 617 mLauncher.showOutOfSpaceMessage(); 618 } 619 } 620 } 621 622 @Override 623 protected void onDetachedFromWindow() { 624 super.onDetachedFromWindow(); 625 626 // Clean up all the async tasks 627 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 628 while (iter.hasNext()) { 629 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 630 task.cancel(false); 631 iter.remove(); 632 } 633 } 634 635 public void setContentType(ContentType type) { 636 mContentType = type; 637 invalidatePageData(0, (type != ContentType.Applications)); 638 } 639 640 public boolean isContentType(ContentType type) { 641 return (mContentType == type); 642 } 643 644 public void setCurrentPageToWidgets() { 645 invalidatePageData(0); 646 } 647 648 /* 649 * Apps PagedView implementation 650 */ 651 private void setVisibilityOnChildren(ViewGroup layout, int visibility) { 652 int childCount = layout.getChildCount(); 653 for (int i = 0; i < childCount; ++i) { 654 layout.getChildAt(i).setVisibility(visibility); 655 } 656 } 657 private void setupPage(PagedViewCellLayout layout) { 658 layout.setCellCount(mCellCountX, mCellCountY); 659 layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 660 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 661 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 662 663 // Note: We force a measure here to get around the fact that when we do layout calculations 664 // immediately after syncing, we don't have a proper width. That said, we already know the 665 // expected page width, so we can actually optimize by hiding all the TextView-based 666 // children that are expensive to measure, and let that happen naturally later. 667 setVisibilityOnChildren(layout, View.GONE); 668 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 669 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 670 layout.setMinimumWidth(getPageContentWidth()); 671 layout.measure(widthSpec, heightSpec); 672 setVisibilityOnChildren(layout, View.VISIBLE); 673 } 674 public void syncAppsPages() { 675 // Ensure that we have the right number of pages 676 Context context = getContext(); 677 int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); 678 for (int i = 0; i < numPages; ++i) { 679 PagedViewCellLayout layout = new PagedViewCellLayout(context); 680 setupPage(layout); 681 addView(layout); 682 } 683 } 684 public void syncAppsPageItems(int page, boolean immediate) { 685 // ensure that we have the right number of items on the pages 686 int numCells = mCellCountX * mCellCountY; 687 int startIndex = page * numCells; 688 int endIndex = Math.min(startIndex + numCells, mApps.size()); 689 PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page); 690 691 layout.removeAllViewsOnPage(); 692 ArrayList<Object> items = new ArrayList<Object>(); 693 ArrayList<Bitmap> images = new ArrayList<Bitmap>(); 694 for (int i = startIndex; i < endIndex; ++i) { 695 ApplicationInfo info = mApps.get(i); 696 PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( 697 R.layout.apps_customize_application, layout, false); 698 icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper); 699 icon.setOnClickListener(this); 700 icon.setOnLongClickListener(this); 701 icon.setOnTouchListener(this); 702 703 int index = i - startIndex; 704 int x = index % mCellCountX; 705 int y = index / mCellCountX; 706 layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); 707 708 items.add(info); 709 images.add(info.iconBitmap); 710 } 711 712 layout.createHardwareLayers(); 713 714 if (mFadeInAdjacentScreens) { 715 prepareGenerateHoloOutlinesTask(page, items, images); 716 } 717 } 718 719 /** 720 * Return the appropriate thread priority for loading for a given page (we give the current 721 * page much higher priority) 722 */ 723 private int getThreadPriorityForPage(int page) { 724 // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below 725 int pageDiff = Math.abs(page - mCurrentPage); 726 if (pageDiff <= 0) { 727 // return Process.THREAD_PRIORITY_DEFAULT; 728 return Process.THREAD_PRIORITY_MORE_FAVORABLE; 729 } else if (pageDiff <= 1) { 730 // return Process.THREAD_PRIORITY_BACKGROUND; 731 return Process.THREAD_PRIORITY_DEFAULT; 732 } else { 733 // return Process.THREAD_PRIORITY_LOWEST; 734 return Process.THREAD_PRIORITY_DEFAULT; 735 } 736 } 737 private int getSleepForPage(int page) { 738 int pageDiff = Math.abs(page - mCurrentPage) - 1; 739 return Math.max(0, pageDiff * sPageSleepDelay); 740 } 741 /** 742 * Creates and executes a new AsyncTask to load a page of widget previews. 743 */ 744 private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, 745 int cellWidth, int cellHeight, int cellCountX) { 746 // Prune all tasks that are no longer needed 747 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 748 while (iter.hasNext()) { 749 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 750 int taskPage = task.page; 751 if ((taskPage == page) || 752 taskPage < getAssociatedLowerPageBound(mCurrentPage) || 753 taskPage > getAssociatedUpperPageBound(mCurrentPage)) { 754 task.cancel(false); 755 iter.remove(); 756 } else { 757 task.setThreadPriority(getThreadPriorityForPage(taskPage)); 758 } 759 } 760 761 // We introduce a slight delay to order the loading of side pages so that we don't thrash 762 final int sleepMs = getSleepForPage(page); 763 AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, 764 cellCountX, new AsyncTaskCallback() { 765 @Override 766 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 767 try { 768 try { 769 Thread.sleep(sleepMs); 770 } catch (Exception e) {} 771 loadWidgetPreviewsInBackground(task, data); 772 } finally { 773 if (task.isCancelled()) { 774 data.cleanup(true); 775 } 776 } 777 } 778 }, 779 new AsyncTaskCallback() { 780 @Override 781 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 782 try { 783 mRunningTasks.remove(task); 784 if (task.isCancelled()) return; 785 if (task.page > getPageCount()) return; 786 if (task.pageContentType != mContentType) return; 787 onSyncWidgetPageItems(data); 788 } finally { 789 data.cleanup(task.isCancelled()); 790 } 791 } 792 }); 793 794 // Ensure that the task is appropriately prioritized and runs in parallel 795 AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, mContentType, 796 AsyncTaskPageData.Type.LoadWidgetPreviewData); 797 t.setThreadPriority(getThreadPriorityForPage(page)); 798 t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); 799 mRunningTasks.add(t); 800 } 801 /** 802 * Creates and executes a new AsyncTask to load the outlines for a page of content. 803 */ 804 private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items, 805 ArrayList<Bitmap> images) { 806 // Prune old tasks for this page 807 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 808 while (iter.hasNext()) { 809 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 810 int taskPage = task.page; 811 if ((taskPage == page) && 812 (task.dataType == AsyncTaskPageData.Type.LoadHolographicIconsData)) { 813 task.cancel(false); 814 iter.remove(); 815 } 816 } 817 818 AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images, 819 new AsyncTaskCallback() { 820 @Override 821 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 822 try { 823 // Ensure that this task starts running at the correct priority 824 task.syncThreadPriority(); 825 826 ArrayList<Bitmap> images = data.generatedImages; 827 ArrayList<Bitmap> srcImages = data.sourceImages; 828 int count = srcImages.size(); 829 Canvas c = new Canvas(); 830 for (int i = 0; i < count && !task.isCancelled(); ++i) { 831 // Before work on each item, ensure that this task is running at the correct 832 // priority 833 task.syncThreadPriority(); 834 835 Bitmap b = srcImages.get(i); 836 Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(), 837 Bitmap.Config.ARGB_8888); 838 839 c.setBitmap(outline); 840 c.save(); 841 c.drawBitmap(b, 0, 0, null); 842 c.restore(); 843 c.setBitmap(null); 844 845 images.add(outline); 846 } 847 } finally { 848 if (task.isCancelled()) { 849 data.cleanup(true); 850 } 851 } 852 } 853 }, 854 new AsyncTaskCallback() { 855 @Override 856 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 857 try { 858 mRunningTasks.remove(task); 859 if (task.isCancelled()) return; 860 if (task.page > getPageCount()) return; 861 if (task.pageContentType != mContentType) return; 862 onHolographicPageItemsLoaded(data); 863 } finally { 864 data.cleanup(task.isCancelled()); 865 } 866 } 867 }); 868 869 // Ensure that the outline task always runs in the background, serially 870 AppsCustomizeAsyncTask t = 871 new AppsCustomizeAsyncTask(page, mContentType, 872 AsyncTaskPageData.Type.LoadHolographicIconsData); 873 t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 874 t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData); 875 mRunningTasks.add(t); 876 } 877 878 /* 879 * Widgets PagedView implementation 880 */ 881 private void setupPage(PagedViewGridLayout layout) { 882 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 883 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 884 885 // Note: We force a measure here to get around the fact that when we do layout calculations 886 // immediately after syncing, we don't have a proper width. 887 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 888 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 889 layout.setMinimumWidth(getPageContentWidth()); 890 layout.measure(widthSpec, heightSpec); 891 } 892 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 893 float scaleX, float scaleY) { 894 renderDrawableToBitmap(d, bitmap, x, y, w, h, scaleX, scaleY, 0xFFFFFFFF); 895 } 896 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 897 float scaleX, float scaleY, int multiplyColor) { 898 if (bitmap != null) { 899 Canvas c = new Canvas(bitmap); 900 c.scale(scaleX, scaleY); 901 Rect oldBounds = d.copyBounds(); 902 d.setBounds(x, y, x + w, y + h); 903 d.draw(c); 904 d.setBounds(oldBounds); // Restore the bounds 905 if (multiplyColor != 0xFFFFFFFF) { 906 c.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY); 907 } 908 c.setBitmap(null); 909 } 910 } 911 private Bitmap getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) { 912 // Render the icon 913 Bitmap preview = Bitmap.createBitmap(cellWidth, mAppIconSize, Config.ARGB_8888); 914 Drawable icon = mIconCache.getFullResIcon(info, mPackageManager); 915 renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize, 1f, 1f); 916 return preview; 917 } 918 private Bitmap getWidgetPreview(AppWidgetProviderInfo info, 919 int cellHSpan, int cellVSpan, int cellWidth, int cellHeight) { 920 921 // Calculate the size of the drawable 922 cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan)); 923 cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan)); 924 int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan); 925 int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan); 926 927 // Scale down the bitmap to fit the space 928 float widgetPreviewScale = (float) cellWidth / expectedWidth; 929 expectedWidth = (int) (widgetPreviewScale * expectedWidth); 930 expectedHeight = (int) (widgetPreviewScale * expectedHeight); 931 932 // Load the preview image if possible 933 String packageName = info.provider.getPackageName(); 934 Drawable drawable = null; 935 Bitmap preview = null; 936 if (info.previewImage != 0) { 937 drawable = mPackageManager.getDrawable(packageName, info.previewImage, null); 938 if (drawable == null) { 939 Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon) 940 + " for provider: " + info.provider); 941 } else { 942 // Scale down the preview to the dimensions we want 943 int imageWidth = drawable.getIntrinsicWidth(); 944 int imageHeight = drawable.getIntrinsicHeight(); 945 float aspect = (float) imageWidth / imageHeight; 946 int newWidth = imageWidth; 947 int newHeight = imageHeight; 948 if (aspect > 1f) { 949 newWidth = expectedWidth; 950 newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth)); 951 } else { 952 newHeight = expectedHeight; 953 newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight)); 954 } 955 956 preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888); 957 renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f); 958 } 959 } 960 961 // Generate a preview image if we couldn't load one 962 if (drawable == null) { 963 Resources resources = mLauncher.getResources(); 964 int bitmapWidth; 965 int bitmapHeight; 966 967 // Specify the dimensions of the bitmap (since we are using a default preview bg with 968 // the full icon, we only imply the aspect ratio of the widget) 969 if (cellHSpan == cellVSpan) { 970 bitmapWidth = bitmapHeight = cellWidth; 971 expectedWidth = expectedHeight = mWidgetPreviewIconPaddedDimension; 972 } else if (cellHSpan >= cellVSpan) { 973 bitmapWidth = expectedWidth = cellWidth; 974 bitmapHeight = expectedHeight = mWidgetPreviewIconPaddedDimension; 975 } else { 976 // Note that in vertical widgets, we might not have enough space due to the text 977 // label, so be conservative and use the width as a height bound 978 bitmapWidth = expectedWidth = mWidgetPreviewIconPaddedDimension; 979 bitmapHeight = expectedHeight = cellWidth; 980 } 981 982 preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888); 983 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth, 984 expectedHeight, 1f, 1f); 985 986 // Draw the icon in the top left corner 987 try { 988 Drawable icon = null; 989 if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null); 990 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application); 991 992 int offset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); 993 renderDrawableToBitmap(icon, preview, offset, offset, 994 mAppIconSize, mAppIconSize, 1f, 1f); 995 } catch (Resources.NotFoundException e) {} 996 } 997 return preview; 998 } 999 public void syncWidgetPages() { 1000 // Ensure that we have the right number of pages 1001 Context context = getContext(); 1002 int numPages = (int) Math.ceil(mWidgets.size() / 1003 (float) (mWidgetCountX * mWidgetCountY)); 1004 for (int j = 0; j < numPages; ++j) { 1005 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 1006 mWidgetCountY); 1007 setupPage(layout); 1008 addView(layout); 1009 } 1010 } 1011 public void syncWidgetPageItems(int page, boolean immediate) { 1012 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 1013 int contentWidth = mWidgetSpacingLayout.getContentWidth(); 1014 int contentHeight = mWidgetSpacingLayout.getContentHeight(); 1015 1016 // Calculate the dimensions of each cell we are giving to each widget 1017 ArrayList<Object> items = new ArrayList<Object>(); 1018 int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight 1019 - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); 1020 int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom 1021 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); 1022 1023 // Prepare the set of widgets to load previews for in the background 1024 int offset = page * numItemsPerPage; 1025 for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) { 1026 items.add(mWidgets.get(i)); 1027 } 1028 1029 // Prepopulate the pages with the other widget info, and fill in the previews later 1030 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); 1031 layout.setColumnCount(layout.getCellCountX()); 1032 for (int i = 0; i < items.size(); ++i) { 1033 Object rawInfo = items.get(i); 1034 PendingAddItemInfo createItemInfo = null; 1035 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 1036 R.layout.apps_customize_widget, layout, false); 1037 if (rawInfo instanceof AppWidgetProviderInfo) { 1038 // Fill in the widget information 1039 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 1040 createItemInfo = new PendingAddWidgetInfo(info, null, null); 1041 int[] cellSpans = mLauncher.getSpanForWidget(info, null); 1042 widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans, 1043 mHolographicOutlineHelper); 1044 widget.setTag(createItemInfo); 1045 } else if (rawInfo instanceof ResolveInfo) { 1046 // Fill in the shortcuts information 1047 ResolveInfo info = (ResolveInfo) rawInfo; 1048 createItemInfo = new PendingAddItemInfo(); 1049 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 1050 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 1051 info.activityInfo.name); 1052 widget.applyFromResolveInfo(mPackageManager, info, mHolographicOutlineHelper); 1053 widget.setTag(createItemInfo); 1054 } 1055 widget.setOnClickListener(this); 1056 widget.setOnLongClickListener(this); 1057 widget.setOnTouchListener(this); 1058 1059 // Layout each widget 1060 int ix = i % mWidgetCountX; 1061 int iy = i / mWidgetCountX; 1062 GridLayout.LayoutParams lp = new GridLayout.LayoutParams( 1063 GridLayout.spec(iy, GridLayout.LEFT), 1064 GridLayout.spec(ix, GridLayout.TOP)); 1065 lp.width = cellWidth; 1066 lp.height = cellHeight; 1067 lp.setGravity(Gravity.TOP | Gravity.LEFT); 1068 if (ix > 0) lp.leftMargin = mWidgetWidthGap; 1069 if (iy > 0) lp.topMargin = mWidgetHeightGap; 1070 layout.addView(widget, lp); 1071 } 1072 1073 // Load the widget previews 1074 if (immediate) { 1075 AsyncTaskPageData data = new AsyncTaskPageData(page, items, cellWidth, cellHeight, 1076 mWidgetCountX, null, null); 1077 loadWidgetPreviewsInBackground(null, data); 1078 onSyncWidgetPageItems(data); 1079 } else { 1080 prepareLoadWidgetPreviewsTask(page, items, cellWidth, cellHeight, mWidgetCountX); 1081 } 1082 } 1083 private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, 1084 AsyncTaskPageData data) { 1085 if (task != null) { 1086 // Ensure that this task starts running at the correct priority 1087 task.syncThreadPriority(); 1088 } 1089 1090 // Load each of the widget/shortcut previews 1091 ArrayList<Object> items = data.items; 1092 ArrayList<Bitmap> images = data.generatedImages; 1093 int count = items.size(); 1094 int cellWidth = data.cellWidth; 1095 int cellHeight = data.cellHeight; 1096 for (int i = 0; i < count; ++i) { 1097 if (task != null) { 1098 // Ensure we haven't been cancelled yet 1099 if (task.isCancelled()) break; 1100 // Before work on each item, ensure that this task is running at the correct 1101 // priority 1102 task.syncThreadPriority(); 1103 } 1104 1105 Object rawInfo = items.get(i); 1106 if (rawInfo instanceof AppWidgetProviderInfo) { 1107 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 1108 int[] cellSpans = mLauncher.getSpanForWidget(info, null); 1109 images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1], 1110 cellWidth, cellHeight)); 1111 } else if (rawInfo instanceof ResolveInfo) { 1112 // Fill in the shortcuts information 1113 ResolveInfo info = (ResolveInfo) rawInfo; 1114 images.add(getShortcutPreview(info, cellWidth, cellHeight)); 1115 } 1116 } 1117 } 1118 private void onSyncWidgetPageItems(AsyncTaskPageData data) { 1119 int page = data.page; 1120 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); 1121 1122 ArrayList<Object> items = data.items; 1123 int count = items.size(); 1124 for (int i = 0; i < count; ++i) { 1125 PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); 1126 if (widget != null) { 1127 widget.applyPreview(new FastBitmapDrawable(data.generatedImages.get(i)), i); 1128 } 1129 } 1130 layout.createHardwareLayer(); 1131 1132 invalidate(); 1133 forceUpdateAdjacentPagesAlpha(); 1134 1135 if (mFadeInAdjacentScreens) { 1136 prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages); 1137 } 1138 } 1139 private void onHolographicPageItemsLoaded(AsyncTaskPageData data) { 1140 // Invalidate early to short-circuit children invalidates 1141 invalidate(); 1142 1143 int page = data.page; 1144 ViewGroup layout = (ViewGroup) getPageAt(page); 1145 if (layout instanceof PagedViewCellLayout) { 1146 PagedViewCellLayout cl = (PagedViewCellLayout) layout; 1147 int count = cl.getPageChildCount(); 1148 if (count != data.generatedImages.size()) return; 1149 for (int i = 0; i < count; ++i) { 1150 PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i); 1151 icon.setHolographicOutline(data.generatedImages.get(i)); 1152 } 1153 } else { 1154 int count = layout.getChildCount(); 1155 if (count != data.generatedImages.size()) return; 1156 for (int i = 0; i < count; ++i) { 1157 View v = layout.getChildAt(i); 1158 ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i)); 1159 } 1160 } 1161 } 1162 1163 @Override 1164 public void syncPages() { 1165 removeAllViews(); 1166 1167 // Remove all background asyc tasks if we are loading content anew 1168 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 1169 while (iter.hasNext()) { 1170 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 1171 task.cancel(false); 1172 iter.remove(); 1173 } 1174 1175 switch (mContentType) { 1176 case Applications: 1177 syncAppsPages(); 1178 break; 1179 case Widgets: 1180 syncWidgetPages(); 1181 break; 1182 } 1183 } 1184 @Override 1185 public void syncPageItems(int page, boolean immediate) { 1186 switch (mContentType) { 1187 case Applications: 1188 syncAppsPageItems(page, immediate); 1189 break; 1190 case Widgets: 1191 syncWidgetPageItems(page, immediate); 1192 break; 1193 } 1194 } 1195 1196 // We want our pages to be z-ordered such that the further a page is to the left, the higher 1197 // it is in the z-order. This is important to insure touch events are handled correctly. 1198 View getPageAt(int index) { 1199 return getChildAt(getChildCount() - index - 1); 1200 } 1201 1202 // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. 1203 @Override 1204 protected void screenScrolled(int screenCenter) { 1205 super.screenScrolled(screenCenter); 1206 1207 for (int i = 0; i < getChildCount(); i++) { 1208 View v = getPageAt(i); 1209 if (v != null) { 1210 float scrollProgress = getScrollProgress(screenCenter, v, i); 1211 1212 float interpolatedProgress = 1213 mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); 1214 float scale = (1 - interpolatedProgress) + 1215 interpolatedProgress * TRANSITION_SCALE_FACTOR; 1216 float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); 1217 1218 float alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( 1219 1 - Math.abs(scrollProgress)) : 1.0f; 1220 1221 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 1222 int pageWidth = v.getMeasuredWidth(); 1223 int pageHeight = v.getMeasuredHeight(); 1224 1225 if (PERFORM_OVERSCROLL_ROTATION) { 1226 if (i == 0 && scrollProgress < 0) { 1227 // Overscroll to the left 1228 v.setPivotX(TRANSITION_PIVOT * pageWidth); 1229 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1230 scale = 1.0f; 1231 alpha = 1.0f; 1232 // On the first page, we don't want the page to have any lateral motion 1233 translationX = getScrollX(); 1234 } else if (i == getChildCount() - 1 && scrollProgress > 0) { 1235 // Overscroll to the right 1236 v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); 1237 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1238 scale = 1.0f; 1239 alpha = 1.0f; 1240 // On the last page, we don't want the page to have any lateral motion. 1241 translationX = getScrollX() - mMaxScrollX; 1242 } else { 1243 v.setPivotY(pageHeight / 2.0f); 1244 v.setPivotX(pageWidth / 2.0f); 1245 v.setRotationY(0f); 1246 } 1247 } 1248 1249 v.setTranslationX(translationX); 1250 v.setScaleX(scale); 1251 v.setScaleY(scale); 1252 v.setAlpha(alpha); 1253 } 1254 } 1255 } 1256 1257 protected void overScroll(float amount) { 1258 acceleratedOverScroll(amount); 1259 } 1260 1261 /** 1262 * Used by the parent to get the content width to set the tab bar to 1263 * @return 1264 */ 1265 public int getPageContentWidth() { 1266 return mContentWidth; 1267 } 1268 1269 @Override 1270 protected void onPageBeginMoving() { 1271 /* TO BE ENABLED LATER 1272 setChildrenDrawnWithCacheEnabled(true); 1273 for (int i = 0; i < getChildCount(); ++i) { 1274 View v = getChildAt(i); 1275 if (v instanceof PagedViewCellLayout) { 1276 ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(true); 1277 } 1278 } 1279 */ 1280 super.onPageBeginMoving(); 1281 } 1282 1283 @Override 1284 protected void onPageEndMoving() { 1285 /* TO BE ENABLED LATER 1286 for (int i = 0; i < getChildCount(); ++i) { 1287 View v = getChildAt(i); 1288 if (v instanceof PagedViewCellLayout) { 1289 ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(false); 1290 } 1291 } 1292 setChildrenDrawnWithCacheEnabled(false); 1293 */ 1294 super.onPageEndMoving(); 1295 1296 // We reset the save index when we change pages so that it will be recalculated on next 1297 // rotation 1298 mSaveInstanceStateItemIndex = -1; 1299 } 1300 1301 /* 1302 * AllAppsView implementation 1303 */ 1304 @Override 1305 public void setup(Launcher launcher, DragController dragController) { 1306 mLauncher = launcher; 1307 mDragController = dragController; 1308 } 1309 @Override 1310 public void zoom(float zoom, boolean animate) { 1311 // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() 1312 } 1313 @Override 1314 public boolean isVisible() { 1315 return (getVisibility() == VISIBLE); 1316 } 1317 @Override 1318 public boolean isAnimating() { 1319 return false; 1320 } 1321 @Override 1322 public void setApps(ArrayList<ApplicationInfo> list) { 1323 mApps = list; 1324 Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); 1325 1326 // The next layout pass will trigger data-ready if both widgets and apps are set, so 1327 // request a layout to do this test and invalidate the page data when ready. 1328 if (testDataReady()) requestLayout(); 1329 } 1330 private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1331 // We add it in place, in alphabetical order 1332 int count = list.size(); 1333 for (int i = 0; i < count; ++i) { 1334 ApplicationInfo info = list.get(i); 1335 int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); 1336 if (index < 0) { 1337 mApps.add(-(index + 1), info); 1338 } 1339 } 1340 } 1341 @Override 1342 public void addApps(ArrayList<ApplicationInfo> list) { 1343 addAppsWithoutInvalidate(list); 1344 invalidatePageData(); 1345 } 1346 private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { 1347 ComponentName removeComponent = item.intent.getComponent(); 1348 int length = list.size(); 1349 for (int i = 0; i < length; ++i) { 1350 ApplicationInfo info = list.get(i); 1351 if (info.intent.getComponent().equals(removeComponent)) { 1352 return i; 1353 } 1354 } 1355 return -1; 1356 } 1357 private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1358 // loop through all the apps and remove apps that have the same component 1359 int length = list.size(); 1360 for (int i = 0; i < length; ++i) { 1361 ApplicationInfo info = list.get(i); 1362 int removeIndex = findAppByComponent(mApps, info); 1363 if (removeIndex > -1) { 1364 mApps.remove(removeIndex); 1365 } 1366 } 1367 } 1368 @Override 1369 public void removeApps(ArrayList<ApplicationInfo> list) { 1370 removeAppsWithoutInvalidate(list); 1371 invalidatePageData(); 1372 } 1373 @Override 1374 public void updateApps(ArrayList<ApplicationInfo> list) { 1375 // We remove and re-add the updated applications list because it's properties may have 1376 // changed (ie. the title), and this will ensure that the items will be in their proper 1377 // place in the list. 1378 removeAppsWithoutInvalidate(list); 1379 addAppsWithoutInvalidate(list); 1380 invalidatePageData(); 1381 } 1382 1383 @Override 1384 public void reset() { 1385 if (mContentType != ContentType.Applications) { 1386 // Reset to the first page of the Apps pane 1387 AppsCustomizeTabHost tabs = (AppsCustomizeTabHost) 1388 mLauncher.findViewById(R.id.apps_customize_pane); 1389 tabs.selectAppsTab(); 1390 } else if (getCurrentPage() != 0) { 1391 invalidatePageData(0); 1392 } 1393 } 1394 @Override 1395 public void dumpState() { 1396 // TODO: Dump information related to current list of Applications, Widgets, etc. 1397 ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps); 1398 dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets); 1399 } 1400 private void dumpAppWidgetProviderInfoList(String tag, String label, 1401 ArrayList<Object> list) { 1402 Log.d(tag, label + " size=" + list.size()); 1403 for (Object i: list) { 1404 if (i instanceof AppWidgetProviderInfo) { 1405 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1406 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1407 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1408 + " initialLayout=" + info.initialLayout 1409 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1410 } else if (i instanceof ResolveInfo) { 1411 ResolveInfo info = (ResolveInfo) i; 1412 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1413 + info.icon); 1414 } 1415 } 1416 } 1417 @Override 1418 public void surrender() { 1419 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1420 // should stop this now. 1421 } 1422 1423 /* 1424 * We load an extra page on each side to prevent flashes from scrolling and loading of the 1425 * widget previews in the background with the AsyncTasks. 1426 */ 1427 protected int getAssociatedLowerPageBound(int page) { 1428 return Math.max(0, page - 2); 1429 } 1430 protected int getAssociatedUpperPageBound(int page) { 1431 final int count = getChildCount(); 1432 return Math.min(page + 2, count - 1); 1433 } 1434 1435 @Override 1436 protected String getCurrentPageDescription() { 1437 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 1438 int stringId = R.string.default_scroll_format; 1439 switch (mContentType) { 1440 case Applications: 1441 stringId = R.string.apps_customize_apps_scroll_format; 1442 break; 1443 case Widgets: 1444 stringId = R.string.apps_customize_widgets_scroll_format; 1445 break; 1446 } 1447 return String.format(mContext.getString(stringId), page + 1, getChildCount()); 1448 } 1449} 1450