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