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