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