AppsCustomizePagedView.java revision 4e076545e4ccdbd3c045a3fa33869a2b7519a0cc
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.Rect; 37import android.graphics.drawable.Drawable; 38import android.os.AsyncTask; 39import android.os.Process; 40import android.util.AttributeSet; 41import android.util.Log; 42import android.view.LayoutInflater; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.animation.AccelerateInterpolator; 46import android.widget.ImageView; 47import android.widget.TextView; 48import android.widget.Toast; 49 50import com.android.launcher.R; 51import com.android.launcher2.DropTarget.DragObject; 52 53import java.util.ArrayList; 54import java.util.Collections; 55import java.util.Iterator; 56import java.util.List; 57 58/** 59 * A simple callback interface which also provides the results of the task. 60 */ 61interface AsyncTaskCallback { 62 void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); 63} 64 65/** 66 * The data needed to perform either of the custom AsyncTasks. 67 */ 68class AsyncTaskPageData { 69 AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR, 70 AsyncTaskCallback postR) { 71 page = p; 72 items = l; 73 sourceImages = si; 74 generatedImages = new ArrayList<Bitmap>(); 75 cellWidth = cellHeight = -1; 76 doInBackgroundCallback = bgR; 77 postExecuteCallback = postR; 78 } 79 AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR, 80 AsyncTaskCallback postR) { 81 page = p; 82 items = l; 83 generatedImages = new ArrayList<Bitmap>(); 84 cellWidth = cw; 85 cellHeight = ch; 86 doInBackgroundCallback = bgR; 87 postExecuteCallback = postR; 88 } 89 int page; 90 ArrayList<Object> items; 91 ArrayList<Bitmap> sourceImages; 92 ArrayList<Bitmap> generatedImages; 93 int cellWidth; 94 int cellHeight; 95 AsyncTaskCallback doInBackgroundCallback; 96 AsyncTaskCallback postExecuteCallback; 97} 98 99/** 100 * A generic template for an async task used in AppsCustomize. 101 */ 102class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> { 103 AppsCustomizeAsyncTask(int p, AppsCustomizePagedView.ContentType t) { 104 page = p; 105 pageContentType = t; 106 threadPriority = Process.THREAD_PRIORITY_DEFAULT; 107 } 108 @Override 109 protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { 110 if (params.length != 1) return null; 111 // Load each of the widget previews in the background 112 params[0].doInBackgroundCallback.run(this, params[0]); 113 return params[0]; 114 } 115 @Override 116 protected void onPostExecute(AsyncTaskPageData result) { 117 // All the widget previews are loaded, so we can just callback to inflate the page 118 result.postExecuteCallback.run(this, result); 119 } 120 121 void setThreadPriority(int p) { 122 threadPriority = p; 123 } 124 void syncThreadPriority() { 125 Process.setThreadPriority(threadPriority); 126 } 127 128 // The page that this async task is associated with 129 int page; 130 AppsCustomizePagedView.ContentType pageContentType; 131 int threadPriority; 132} 133 134/** 135 * The Apps/Customize page that displays all the applications, widgets, and shortcuts. 136 */ 137public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements 138 AllAppsView, View.OnClickListener, DragSource { 139 static final String LOG_TAG = "AppsCustomizePagedView"; 140 141 /** 142 * The different content types that this paged view can show. 143 */ 144 public enum ContentType { 145 Applications, 146 Widgets 147 } 148 149 // Refs 150 private Launcher mLauncher; 151 private DragController mDragController; 152 private final LayoutInflater mLayoutInflater; 153 private final PackageManager mPackageManager; 154 155 // Content 156 private ContentType mContentType; 157 private ArrayList<ApplicationInfo> mApps; 158 private List<Object> mWidgets; 159 160 // Caching 161 private Canvas mCanvas; 162 private Drawable mDefaultWidgetBackground; 163 private IconCache mIconCache; 164 165 // Dimens 166 private int mContentWidth; 167 private int mMaxWidgetSpan, mMinWidgetSpan; 168 private int mWidgetWidthGap, mWidgetHeightGap; 169 private int mWidgetCountX, mWidgetCountY; 170 private final int mWidgetPreviewIconPaddedDimension; 171 private final float sWidgetPreviewIconPaddingPercentage = 0.25f; 172 private PagedViewCellLayout mWidgetSpacingLayout; 173 174 // Previews & outlines 175 ArrayList<AppsCustomizeAsyncTask> mRunningTasks; 176 private HolographicOutlineHelper mHolographicOutlineHelper; 177 178 public AppsCustomizePagedView(Context context, AttributeSet attrs) { 179 super(context, attrs); 180 mLayoutInflater = LayoutInflater.from(context); 181 mPackageManager = context.getPackageManager(); 182 mContentType = ContentType.Applications; 183 mApps = new ArrayList<ApplicationInfo>(); 184 mWidgets = new ArrayList<Object>(); 185 mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); 186 mHolographicOutlineHelper = new HolographicOutlineHelper(); 187 mCanvas = new Canvas(); 188 mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>(); 189 190 // Save the default widget preview background 191 Resources resources = context.getResources(); 192 mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview); 193 194 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0); 195 // TODO-APPS_CUSTOMIZE: remove these unnecessary attrs after 196 mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6); 197 mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4); 198 a.recycle(); 199 a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); 200 mWidgetWidthGap = 201 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0); 202 mWidgetHeightGap = 203 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0); 204 mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); 205 mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); 206 a.recycle(); 207 mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); 208 209 // The max widget span is the length N, such that NxN is the largest bounds that the widget 210 // preview can be before applying the widget scaling 211 mMinWidgetSpan = 1; 212 mMaxWidgetSpan = 3; 213 214 // The padding on the non-matched dimension for the default widget preview icons 215 // (top + bottom) 216 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 217 mWidgetPreviewIconPaddedDimension = 218 (int) (iconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage))); 219 } 220 221 @Override 222 protected void init() { 223 super.init(); 224 mCenterPagesVertically = false; 225 226 Context context = getContext(); 227 Resources r = context.getResources(); 228 setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); 229 } 230 231 @Override 232 protected void onWallpaperTap(android.view.MotionEvent ev) { 233 mLauncher.showWorkspace(true); 234 } 235 236 /** 237 * This differs from isDataReady as this is the test done if isDataReady is not set. 238 */ 239 private boolean testDataReady() { 240 return !mApps.isEmpty() && !mWidgets.isEmpty(); 241 } 242 243 protected void onDataReady(int width, int height) { 244 // Note that we transpose the counts in portrait so that we get a similar layout 245 boolean isLandscape = getResources().getConfiguration().orientation == 246 Configuration.ORIENTATION_LANDSCAPE; 247 int maxCellCountX = Integer.MAX_VALUE; 248 int maxCellCountY = Integer.MAX_VALUE; 249 if (LauncherApplication.isScreenLarge()) { 250 maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() : 251 LauncherModel.getCellCountY()); 252 maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() : 253 LauncherModel.getCellCountX()); 254 } 255 256 // Now that the data is ready, we can calculate the content width, the number of cells to 257 // use for each page 258 mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 259 mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 260 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 261 mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY); 262 mCellCountX = mWidgetSpacingLayout.getCellCountX(); 263 mCellCountY = mWidgetSpacingLayout.getCellCountY(); 264 mWidgetCountX = Math.max(1, (int) Math.round(mCellCountX / 2f)); 265 mWidgetCountY = Math.max(1, (int) Math.round(mCellCountY / 3f)); 266 mContentWidth = mWidgetSpacingLayout.getContentWidth(); 267 268 invalidatePageData(); 269 } 270 271 @Override 272 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 273 int width = MeasureSpec.getSize(widthMeasureSpec); 274 int height = MeasureSpec.getSize(heightMeasureSpec); 275 if (!isDataReady()) { 276 if (testDataReady()) { 277 setDataIsReady(); 278 setMeasuredDimension(width, height); 279 onDataReady(width, height); 280 } 281 } 282 283 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 284 } 285 286 /** Removes and returns the ResolveInfo with the specified ComponentName */ 287 private ResolveInfo removeResolveInfoWithComponentName(List<ResolveInfo> list, 288 ComponentName cn) { 289 Iterator<ResolveInfo> iter = list.iterator(); 290 while (iter.hasNext()) { 291 ResolveInfo rinfo = iter.next(); 292 ActivityInfo info = rinfo.activityInfo; 293 ComponentName c = new ComponentName(info.packageName, info.name); 294 if (c.equals(cn)) { 295 iter.remove(); 296 return rinfo; 297 } 298 } 299 return null; 300 } 301 302 public void onPackagesUpdated() { 303 // Get the list of widgets and shortcuts 304 mWidgets.clear(); 305 List<AppWidgetProviderInfo> widgets = 306 AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); 307 Collections.sort(widgets, 308 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); 309 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 310 List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); 311 Collections.sort(shortcuts, 312 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); 313 mWidgets.addAll(widgets); 314 mWidgets.addAll(shortcuts); 315 316 // The next layout pass will trigger data-ready if both widgets and apps are set, so request 317 // a layout to do this test and invalidate the page data when ready. 318 if (testDataReady()) requestLayout(); 319 } 320 321 @Override 322 public void onClick(View v) { 323 if (v instanceof PagedViewIcon) { 324 // Animate some feedback to the click 325 final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); 326 animateClickFeedback(v, new Runnable() { 327 @Override 328 public void run() { 329 mLauncher.startActivitySafely(appInfo.intent, appInfo); 330 } 331 }); 332 } else if (v instanceof PagedViewWidget) { 333 // Let the user know that they have to long press to add a widget 334 Toast.makeText(getContext(), R.string.long_press_widget_to_add, 335 Toast.LENGTH_SHORT).show(); 336 337 // Create a little animation to show that the widget can move 338 float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 339 final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); 340 AnimatorSet bounce = new AnimatorSet(); 341 ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY); 342 tyuAnim.setDuration(125); 343 ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f); 344 tydAnim.setDuration(100); 345 bounce.play(tyuAnim).before(tydAnim); 346 bounce.setInterpolator(new AccelerateInterpolator()); 347 bounce.start(); 348 } 349 } 350 351 /* 352 * PagedViewWithDraggableItems implementation 353 */ 354 @Override 355 protected void determineDraggingStart(android.view.MotionEvent ev) { 356 // Disable dragging by pulling an app down for now. 357 } 358 private void beginDraggingApplication(View v) { 359 // Make a copy of the ApplicationInfo 360 ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag()); 361 362 // Compose the drag image (top compound drawable, index is 1) 363 final TextView tv = (TextView) v; 364 final Drawable icon = tv.getCompoundDrawables()[1]; 365 Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), 366 Bitmap.Config.ARGB_8888); 367 mCanvas.setBitmap(b); 368 mCanvas.save(); 369 mCanvas.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop()); 370 icon.draw(mCanvas); 371 mCanvas.restore(); 372 373 // Compose the visible rect of the drag image 374 Rect dragRect = null; 375 if (v instanceof TextView) { 376 int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size); 377 int top = v.getPaddingTop(); 378 int left = (b.getWidth() - iconSize) / 2; 379 int right = left + iconSize; 380 int bottom = top + iconSize; 381 dragRect = new Rect(left, top, right, bottom); 382 } 383 384 // Start the drag 385 mLauncher.lockScreenOrientation(); 386 mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b); 387 mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect); 388 b.recycle(); 389 } 390 private void beginDraggingWidget(View v) { 391 // Get the widget preview as the drag representation 392 ImageView image = (ImageView) v.findViewById(R.id.widget_preview); 393 PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); 394 395 // Compose the drag image 396 Bitmap b; 397 Drawable preview = image.getDrawable(); 398 int w = preview.getIntrinsicWidth(); 399 int h = preview.getIntrinsicHeight(); 400 if (createItemInfo instanceof PendingAddWidgetInfo) { 401 PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo; 402 int[] spanXY = CellLayout.rectToCell(getResources(), 403 createWidgetInfo.minWidth, createWidgetInfo.minHeight, null); 404 createItemInfo.spanX = spanXY[0]; 405 createItemInfo.spanY = spanXY[1]; 406 407 b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 408 renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1); 409 } else { 410 // Workaround for the fact that we don't keep the original ResolveInfo associated with 411 // the shortcut around. To get the icon, we just render the preview image (which has 412 // the shortcut icon) to a new drag bitmap that clips the non-icon space. 413 b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension, 414 mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888); 415 mCanvas.setBitmap(b); 416 mCanvas.save(); 417 preview.draw(mCanvas); 418 mCanvas.restore(); 419 createItemInfo.spanX = createItemInfo.spanY = 1; 420 } 421 422 // Start the drag 423 mLauncher.lockScreenOrientation(); 424 mLauncher.getWorkspace().onDragStartedWithItemSpans(createItemInfo.spanX, 425 createItemInfo.spanY, b); 426 mDragController.startDrag(image, b, this, createItemInfo, 427 DragController.DRAG_ACTION_COPY, null); 428 b.recycle(); 429 } 430 @Override 431 protected boolean beginDragging(View v) { 432 if (!super.beginDragging(v)) return false; 433 434 435 if (v instanceof PagedViewIcon) { 436 beginDraggingApplication(v); 437 } else if (v instanceof PagedViewWidget) { 438 beginDraggingWidget(v); 439 } 440 441 // Go into spring loaded mode 442 int currentPageIndex = mLauncher.getWorkspace().getCurrentPage(); 443 CellLayout currentPage = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPageIndex); 444 mLauncher.enterSpringLoadedDragMode(currentPage); 445 return true; 446 } 447 private void endDragging(boolean success) { 448 mLauncher.exitSpringLoadedDragMode(); 449 mLauncher.getWorkspace().onDragStopped(success); 450 mLauncher.unlockScreenOrientation(); 451 452 } 453 454 /* 455 * DragSource implementation 456 */ 457 @Override 458 public void onDragViewVisible() {} 459 @Override 460 public void onDropCompleted(View target, DragObject d, boolean success) { 461 endDragging(success); 462 463 // Display an error message if the drag failed due to there not being enough space on the 464 // target layout we were dropping on. 465 if (!success) { 466 boolean showOutOfSpaceMessage = false; 467 if (target instanceof Workspace) { 468 int currentScreen = mLauncher.getCurrentWorkspaceScreen(); 469 Workspace workspace = (Workspace) target; 470 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); 471 ItemInfo itemInfo = (ItemInfo) d.dragInfo; 472 if (layout != null) { 473 layout.calculateSpans(itemInfo); 474 showOutOfSpaceMessage = 475 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); 476 } 477 } 478 // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later. 479 if (showOutOfSpaceMessage) { 480 mLauncher.showOutOfSpaceMessage(); 481 } 482 } 483 } 484 485 public void setContentType(ContentType type) { 486 mContentType = type; 487 setCurrentPage(0); 488 invalidatePageData(); 489 } 490 491 public boolean isContentType(ContentType type) { 492 return (mContentType == type); 493 } 494 495 /* 496 * Apps PagedView implementation 497 */ 498 private void setVisibilityOnChildren(ViewGroup layout, int visibility) { 499 int childCount = layout.getChildCount(); 500 for (int i = 0; i < childCount; ++i) { 501 layout.getChildAt(i).setVisibility(visibility); 502 } 503 } 504 private void setupPage(PagedViewCellLayout layout) { 505 layout.setCellCount(mCellCountX, mCellCountY); 506 layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 507 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 508 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 509 510 // Note: We force a measure here to get around the fact that when we do layout calculations 511 // immediately after syncing, we don't have a proper width. That said, we already know the 512 // expected page width, so we can actually optimize by hiding all the TextView-based 513 // children that are expensive to measure, and let that happen naturally later. 514 setVisibilityOnChildren(layout, View.GONE); 515 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 516 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 517 layout.setMinimumWidth(getPageContentWidth()); 518 layout.measure(widthSpec, heightSpec); 519 setVisibilityOnChildren(layout, View.VISIBLE); 520 } 521 public void syncAppsPages() { 522 // Ensure that we have the right number of pages 523 Context context = getContext(); 524 int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); 525 for (int i = 0; i < numPages; ++i) { 526 PagedViewCellLayout layout = new PagedViewCellLayout(context); 527 setupPage(layout); 528 addView(layout); 529 } 530 } 531 public void syncAppsPageItems(int page) { 532 // ensure that we have the right number of items on the pages 533 int numPages = getPageCount(); 534 int numCells = mCellCountX * mCellCountY; 535 int startIndex = page * numCells; 536 int endIndex = Math.min(startIndex + numCells, mApps.size()); 537 PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page); 538 layout.removeAllViewsOnPage(); 539 ArrayList<Object> items = new ArrayList<Object>(); 540 ArrayList<Bitmap> images = new ArrayList<Bitmap>(); 541 for (int i = startIndex; i < endIndex; ++i) { 542 ApplicationInfo info = mApps.get(i); 543 PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( 544 R.layout.apps_customize_application, layout, false); 545 icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper); 546 icon.setOnClickListener(this); 547 icon.setOnLongClickListener(this); 548 icon.setOnTouchListener(this); 549 550 int index = i - startIndex; 551 int x = index % mCellCountX; 552 int y = index / mCellCountX; 553 layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); 554 555 items.add(info); 556 images.add(info.iconBitmap); 557 } 558 559 // Create the hardware layers 560 layout.allowHardwareLayerCreation(); 561 layout.createHardwareLayers(); 562 563 prepareGenerateHoloOutlinesTask(page, items, images); 564 } 565 566 /** 567 * Return the appropriate thread priority for loading for a given page (we give the current 568 * page much higher priority) 569 */ 570 private int getThreadPriorityForPage(int page) { 571 // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below 572 int pageDiff = Math.abs(page - mCurrentPage); 573 if (pageDiff <= 0) { 574 // return Process.THREAD_PRIORITY_DEFAULT; 575 return Process.THREAD_PRIORITY_MORE_FAVORABLE; 576 } else if (pageDiff <= 1) { 577 // return Process.THREAD_PRIORITY_BACKGROUND; 578 return Process.THREAD_PRIORITY_DEFAULT; 579 } else { 580 // return Process.THREAD_PRIORITY_LOWEST; 581 return Process.THREAD_PRIORITY_DEFAULT; 582 } 583 } 584 /** 585 * Creates and executes a new AsyncTask to load a page of widget previews. 586 */ 587 private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, 588 int cellWidth, int cellHeight) { 589 // Prune all tasks that are no longer needed 590 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 591 while (iter.hasNext()) { 592 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 593 int taskPage = task.page; 594 if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || 595 taskPage > getAssociatedUpperPageBound(mCurrentPage)) { 596 task.cancel(false); 597 iter.remove(); 598 } else { 599 task.setThreadPriority(getThreadPriorityForPage(taskPage)); 600 } 601 } 602 603 AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, 604 new AsyncTaskCallback() { 605 @Override 606 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 607 // Ensure that this task starts running at the correct priority 608 task.syncThreadPriority(); 609 610 // Load each of the widget/shortcut previews 611 ArrayList<Object> items = data.items; 612 ArrayList<Bitmap> images = data.generatedImages; 613 int count = items.size(); 614 int cellWidth = data.cellWidth; 615 int cellHeight = data.cellHeight; 616 for (int i = 0; i < count && !task.isCancelled(); ++i) { 617 // Before work on each item, ensure that this task is running at the correct 618 // priority 619 task.syncThreadPriority(); 620 621 Object rawInfo = items.get(i); 622 if (rawInfo instanceof AppWidgetProviderInfo) { 623 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 624 int[] cellSpans = CellLayout.rectToCell(getResources(), 625 info.minWidth, info.minHeight, null); 626 images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1], 627 cellWidth, cellHeight)); 628 } else if (rawInfo instanceof ResolveInfo) { 629 // Fill in the shortcuts information 630 ResolveInfo info = (ResolveInfo) rawInfo; 631 images.add(getShortcutPreview(info, cellWidth, cellHeight)); 632 } 633 } 634 } 635 }, 636 new AsyncTaskCallback() { 637 @Override 638 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 639 mRunningTasks.remove(task); 640 if (task.page > getPageCount()) return; 641 if (task.pageContentType != mContentType) return; 642 onSyncWidgetPageItems(data); 643 } 644 }); 645 646 // Ensure that the task is appropriately prioritized and runs in parallel 647 AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, mContentType); 648 t.setThreadPriority(getThreadPriorityForPage(page)); 649 t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); 650 mRunningTasks.add(t); 651 } 652 /** 653 * Creates and executes a new AsyncTask to load the outlines for a page of content. 654 */ 655 private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items, 656 ArrayList<Bitmap> images) { 657 AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images, 658 new AsyncTaskCallback() { 659 @Override 660 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 661 // Ensure that this task starts running at the correct priority 662 task.syncThreadPriority(); 663 664 ArrayList<Bitmap> images = data.generatedImages; 665 ArrayList<Bitmap> srcImages = data.sourceImages; 666 int count = srcImages.size(); 667 Canvas c = new Canvas(); 668 for (int i = 0; i < count && !task.isCancelled(); ++i) { 669 // Before work on each item, ensure that this task is running at the correct 670 // priority 671 task.syncThreadPriority(); 672 673 Bitmap b = srcImages.get(i); 674 Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(), 675 Bitmap.Config.ARGB_8888); 676 677 c.setBitmap(outline); 678 c.save(); 679 c.drawBitmap(b, 0, 0, null); 680 c.restore(); 681 682 images.add(outline); 683 } 684 } 685 }, 686 new AsyncTaskCallback() { 687 @Override 688 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 689 mRunningTasks.remove(task); 690 if (task.page > getPageCount()) return; 691 if (task.pageContentType != mContentType) return; 692 onHolographicPageItemsLoaded(data); 693 } 694 }); 695 696 // Ensure that the outline task always runs in the background, serially 697 AppsCustomizeAsyncTask t = 698 new AppsCustomizeAsyncTask(page, mContentType); 699 t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 700 t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData); 701 mRunningTasks.add(t); 702 } 703 704 /* 705 * Widgets PagedView implementation 706 */ 707 private void setupPage(PagedViewGridLayout layout) { 708 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 709 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 710 711 // Note: We force a measure here to get around the fact that when we do layout calculations 712 // immediately after syncing, we don't have a proper width. That said, we already know the 713 // expected page width, so we can actually optimize by hiding all the TextView-based 714 // children that are expensive to measure, and let that happen naturally later. 715 setVisibilityOnChildren(layout, View.GONE); 716 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 717 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 718 layout.setMinimumWidth(getPageContentWidth()); 719 layout.measure(widthSpec, heightSpec); 720 setVisibilityOnChildren(layout, View.VISIBLE); 721 } 722 private synchronized void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 723 float scaleX, float scaleY) { 724 if (bitmap != null) { 725 Canvas c = new Canvas(bitmap); 726 c.scale(scaleX, scaleY); 727 Rect oldBounds = d.copyBounds(); 728 d.setBounds(x, y, x + w, y + h); 729 d.draw(c); 730 d.setBounds(oldBounds); // Restore the bounds 731 } 732 } 733 private Bitmap getShortcutPreview(ResolveInfo info, int cellWidth, 734 int cellHeight) { 735 Resources resources = mLauncher.getResources(); 736 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 737 // We only need to make it wide enough so as not allow the preview to be scaled 738 int expectedWidth = cellWidth; 739 int expectedHeight = mWidgetPreviewIconPaddedDimension; 740 741 // Render the icon 742 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 743 Drawable icon = mIconCache.getFullResIcon(info, mPackageManager); 744 renderDrawableToBitmap(icon, preview, 0, 0, iconSize, iconSize, 1f, 1f); 745 return preview; 746 } 747 private Bitmap getWidgetPreview(AppWidgetProviderInfo info, 748 int cellHSpan, int cellVSpan, int cellWidth, int cellHeight) { 749 750 // Calculate the size of the drawable 751 cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan)); 752 cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan)); 753 int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan); 754 int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan); 755 756 // Scale down the bitmap to fit the space 757 float widgetPreviewScale = (float) cellWidth / expectedWidth; 758 expectedWidth = (int) (widgetPreviewScale * expectedWidth); 759 expectedHeight = (int) (widgetPreviewScale * expectedHeight); 760 761 // Load the preview image if possible 762 String packageName = info.provider.getPackageName(); 763 Drawable drawable = null; 764 Bitmap preview = null; 765 if (info.previewImage != 0) { 766 drawable = mPackageManager.getDrawable(packageName, info.previewImage, null); 767 if (drawable == null) { 768 Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon) 769 + " for provider: " + info.provider); 770 } else { 771 // Scale down the preview to the dimensions we want 772 int imageWidth = drawable.getIntrinsicWidth(); 773 int imageHeight = drawable.getIntrinsicHeight(); 774 float aspect = (float) imageWidth / imageHeight; 775 int newWidth = imageWidth; 776 int newHeight = imageHeight; 777 if (aspect > 1f) { 778 newWidth = expectedWidth; 779 newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth)); 780 } else { 781 newHeight = expectedHeight; 782 newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight)); 783 } 784 785 preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888); 786 renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f); 787 } 788 } 789 790 // Generate a preview image if we couldn't load one 791 if (drawable == null) { 792 Resources resources = mLauncher.getResources(); 793 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 794 795 // Specify the dimensions of the bitmap 796 if (info.minWidth >= info.minHeight) { 797 expectedWidth = cellWidth; 798 expectedHeight = mWidgetPreviewIconPaddedDimension; 799 } else { 800 // Note that in vertical widgets, we might not have enough space due to the text 801 // label, so be conservative and use the width as a height bound 802 expectedWidth = mWidgetPreviewIconPaddedDimension; 803 expectedHeight = cellWidth; 804 } 805 806 preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 807 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth, 808 expectedHeight, 1f,1f); 809 810 // Draw the icon in the top left corner 811 try { 812 Drawable icon = null; 813 if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null); 814 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application); 815 816 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 817 renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f); 818 } catch (Resources.NotFoundException e) {} 819 } 820 return preview; 821 } 822 public void syncWidgetPages() { 823 // Ensure that we have the right number of pages 824 Context context = getContext(); 825 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 826 int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage); 827 for (int i = 0; i < numPages; ++i) { 828 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 829 mWidgetCountY); 830 setupPage(layout); 831 addView(layout); 832 } 833 } 834 public void syncWidgetPageItems(int page) { 835 // Calculate the dimensions of each cell we are giving to each widget 836 ArrayList<Object> widgets = new ArrayList<Object>(); 837 int cellWidth = ((mWidgetSpacingLayout.getContentWidth() 838 - mPageLayoutPaddingLeft - mPageLayoutPaddingRight 839 - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); 840 int cellHeight = ((mWidgetSpacingLayout.getContentHeight() 841 - mPageLayoutPaddingTop - mPageLayoutPaddingBottom 842 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); 843 844 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 845 int offset = page * numWidgetsPerPage; 846 for (int i = offset; i < Math.min(offset + numWidgetsPerPage, mWidgets.size()); ++i) { 847 widgets.add(mWidgets.get(i)); 848 } 849 850 prepareLoadWidgetPreviewsTask(page, widgets, cellWidth, cellHeight); 851 } 852 private void onSyncWidgetPageItems(AsyncTaskPageData data) { 853 int page = data.page; 854 PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page); 855 layout.removeAllViews(); 856 857 ArrayList<Object> items = data.items; 858 int count = items.size(); 859 int cellWidth = data.cellWidth; 860 int cellHeight = data.cellHeight; 861 for (int i = 0; i < count; ++i) { 862 Object rawInfo = items.get(i); 863 PendingAddItemInfo createItemInfo = null; 864 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 865 R.layout.apps_customize_widget, layout, false); 866 if (rawInfo instanceof AppWidgetProviderInfo) { 867 // Fill in the widget information 868 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 869 createItemInfo = new PendingAddWidgetInfo(info, null, null); 870 int[] cellSpans = CellLayout.rectToCell(getResources(), 871 info.minWidth, info.minHeight, null); 872 FastBitmapDrawable preview = new FastBitmapDrawable(data.generatedImages.get(i)); 873 widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, 874 mHolographicOutlineHelper); 875 widget.setTag(createItemInfo); 876 } else if (rawInfo instanceof ResolveInfo) { 877 // Fill in the shortcuts information 878 ResolveInfo info = (ResolveInfo) rawInfo; 879 createItemInfo = new PendingAddItemInfo(); 880 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 881 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 882 info.activityInfo.name); 883 FastBitmapDrawable preview = new FastBitmapDrawable(data.generatedImages.get(i)); 884 widget.applyFromResolveInfo(mPackageManager, info, preview, 885 mHolographicOutlineHelper); 886 widget.setTag(createItemInfo); 887 } 888 widget.setOnClickListener(this); 889 widget.setOnLongClickListener(this); 890 widget.setOnTouchListener(this); 891 892 // Layout each widget 893 int ix = i % mWidgetCountX; 894 int iy = i / mWidgetCountX; 895 PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth, 896 cellHeight); 897 lp.leftMargin = (ix * cellWidth) + (ix * mWidgetWidthGap); 898 lp.topMargin = (iy * cellHeight) + (iy * mWidgetHeightGap); 899 layout.addView(widget, lp); 900 } 901 902 invalidate(); 903 forceUpdateAdjacentPagesAlpha(); 904 prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages); 905 } 906 private void onHolographicPageItemsLoaded(AsyncTaskPageData data) { 907 // Invalidate early to short-circuit children invalidates 908 invalidate(); 909 910 int page = data.page; 911 ViewGroup layout = (ViewGroup) getChildAt(page); 912 if (layout instanceof PagedViewCellLayout) { 913 PagedViewCellLayout cl = (PagedViewCellLayout) layout; 914 int count = cl.getPageChildCount(); 915 for (int i = 0; i < count; ++i) { 916 PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i); 917 icon.setHolographicOutline(data.generatedImages.get(i)); 918 } 919 } else { 920 int count = layout.getChildCount(); 921 for (int i = 0; i < count; ++i) { 922 View v = layout.getChildAt(i); 923 ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i)); 924 } 925 } 926 } 927 928 @Override 929 public void syncPages() { 930 removeAllViews(); 931 switch (mContentType) { 932 case Applications: 933 syncAppsPages(); 934 break; 935 case Widgets: 936 syncWidgetPages(); 937 break; 938 } 939 } 940 @Override 941 public void syncPageItems(int page) { 942 switch (mContentType) { 943 case Applications: 944 syncAppsPageItems(page); 945 break; 946 case Widgets: 947 syncWidgetPageItems(page); 948 break; 949 } 950 } 951 952 /** 953 * Used by the parent to get the content width to set the tab bar to 954 * @return 955 */ 956 public int getPageContentWidth() { 957 return mContentWidth; 958 } 959 960 @Override 961 protected void onPageBeginMoving() { 962 /* TO BE ENABLED LATER 963 setChildrenDrawnWithCacheEnabled(true); 964 for (int i = 0; i < getChildCount(); ++i) { 965 View v = getChildAt(i); 966 if (v instanceof PagedViewCellLayout) { 967 ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(true); 968 } 969 } 970 */ 971 super.onPageBeginMoving(); 972 } 973 974 @Override 975 protected void onPageEndMoving() { 976 /* TO BE ENABLED LATER 977 for (int i = 0; i < getChildCount(); ++i) { 978 View v = getChildAt(i); 979 if (v instanceof PagedViewCellLayout) { 980 ((PagedViewCellLayout) v).setChildrenDrawingCacheEnabled(false); 981 } 982 } 983 setChildrenDrawnWithCacheEnabled(false); 984 */ 985 super.onPageEndMoving(); 986 } 987 988 /* 989 * AllAppsView implementation 990 */ 991 @Override 992 public void setup(Launcher launcher, DragController dragController) { 993 mLauncher = launcher; 994 mDragController = dragController; 995 } 996 @Override 997 public void zoom(float zoom, boolean animate) { 998 // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() 999 } 1000 @Override 1001 public boolean isVisible() { 1002 return (getVisibility() == VISIBLE); 1003 } 1004 @Override 1005 public boolean isAnimating() { 1006 return false; 1007 } 1008 @Override 1009 public void setApps(ArrayList<ApplicationInfo> list) { 1010 mApps = list; 1011 Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); 1012 1013 // The next layout pass will trigger data-ready if both widgets and apps are set, so request 1014 // a layout to do this test and invalidate the page data when ready. 1015 if (testDataReady()) requestLayout(); 1016 } 1017 private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1018 // We add it in place, in alphabetical order 1019 int count = list.size(); 1020 for (int i = 0; i < count; ++i) { 1021 ApplicationInfo info = list.get(i); 1022 int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); 1023 if (index < 0) { 1024 mApps.add(-(index + 1), info); 1025 } 1026 } 1027 } 1028 @Override 1029 public void addApps(ArrayList<ApplicationInfo> list) { 1030 addAppsWithoutInvalidate(list); 1031 invalidatePageData(); 1032 } 1033 private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { 1034 ComponentName removeComponent = item.intent.getComponent(); 1035 int length = list.size(); 1036 for (int i = 0; i < length; ++i) { 1037 ApplicationInfo info = list.get(i); 1038 if (info.intent.getComponent().equals(removeComponent)) { 1039 return i; 1040 } 1041 } 1042 return -1; 1043 } 1044 private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1045 // loop through all the apps and remove apps that have the same component 1046 int length = list.size(); 1047 for (int i = 0; i < length; ++i) { 1048 ApplicationInfo info = list.get(i); 1049 int removeIndex = findAppByComponent(mApps, info); 1050 if (removeIndex > -1) { 1051 mApps.remove(removeIndex); 1052 } 1053 } 1054 } 1055 @Override 1056 public void removeApps(ArrayList<ApplicationInfo> list) { 1057 removeAppsWithoutInvalidate(list); 1058 invalidatePageData(); 1059 } 1060 @Override 1061 public void updateApps(ArrayList<ApplicationInfo> list) { 1062 // We remove and re-add the updated applications list because it's properties may have 1063 // changed (ie. the title), and this will ensure that the items will be in their proper 1064 // place in the list. 1065 removeAppsWithoutInvalidate(list); 1066 addAppsWithoutInvalidate(list); 1067 invalidatePageData(); 1068 } 1069 @Override 1070 public void reset() { 1071 if (mContentType != ContentType.Applications) { 1072 // Reset to the first page of the Apps pane 1073 AppsCustomizeTabHost tabs = (AppsCustomizeTabHost) 1074 mLauncher.findViewById(R.id.apps_customize_pane); 1075 tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications)); 1076 } else { 1077 setCurrentPage(0); 1078 invalidatePageData(); 1079 } 1080 } 1081 @Override 1082 public void dumpState() { 1083 // TODO: Dump information related to current list of Applications, Widgets, etc. 1084 ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps); 1085 dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets); 1086 } 1087 private void dumpAppWidgetProviderInfoList(String tag, String label, 1088 List<Object> list) { 1089 Log.d(tag, label + " size=" + list.size()); 1090 for (Object i: list) { 1091 if (i instanceof AppWidgetProviderInfo) { 1092 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1093 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1094 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1095 + " initialLayout=" + info.initialLayout 1096 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1097 } else if (i instanceof ResolveInfo) { 1098 ResolveInfo info = (ResolveInfo) i; 1099 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1100 + info.icon); 1101 } 1102 } 1103 } 1104 @Override 1105 public void surrender() { 1106 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1107 // should stop this now. 1108 } 1109 1110 @Override 1111 protected int getPageWidthForScrollingIndicator() { 1112 return getPageContentWidth(); 1113 } 1114 1115 /* 1116 * We load an extra page on each side to prevent flashes from scrolling and loading of the 1117 * widget previews in the background with the AsyncTasks. 1118 */ 1119 protected int getAssociatedLowerPageBound(int page) { 1120 return Math.max(0, page - 2); 1121 } 1122 protected int getAssociatedUpperPageBound(int page) { 1123 final int count = getChildCount(); 1124 return Math.min(page + 2, count - 1); 1125 } 1126} 1127