AppsCustomizePagedView.java revision 6a70e9fc3c62cc83d6abe59323d622dc6cd224a7
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 java.util.ArrayList; 20import java.util.Collections; 21import java.util.List; 22 23import org.xmlpull.v1.XmlPullParser; 24 25import android.animation.Animator; 26import android.animation.AnimatorListenerAdapter; 27import android.animation.ObjectAnimator; 28import android.animation.PropertyValuesHolder; 29import android.app.WallpaperManager; 30import android.appwidget.AppWidgetManager; 31import android.appwidget.AppWidgetProviderInfo; 32import android.content.ComponentName; 33import android.content.Context; 34import android.content.Intent; 35import android.content.pm.ActivityInfo; 36import android.content.pm.PackageManager; 37import android.content.pm.ResolveInfo; 38import android.content.res.Resources; 39import android.content.res.TypedArray; 40import android.content.res.XmlResourceParser; 41import android.graphics.Bitmap; 42import android.graphics.Bitmap.Config; 43import android.graphics.Canvas; 44import android.graphics.Rect; 45import android.graphics.drawable.Drawable; 46import android.util.AttributeSet; 47import android.util.Log; 48import android.util.LruCache; 49import android.util.Slog; 50import android.util.TypedValue; 51import android.util.Xml; 52import android.view.LayoutInflater; 53import android.view.View; 54import android.view.ViewGroup; 55import android.view.animation.DecelerateInterpolator; 56import android.view.animation.LinearInterpolator; 57import android.widget.ImageView; 58import android.widget.TextView; 59 60import com.android.launcher.R; 61 62public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements 63 AllAppsView, View.OnClickListener, DragSource { 64 static final String LOG_TAG = "AppsCustomizePagedView"; 65 66 /** 67 * The different content types that this paged view can show. 68 */ 69 public enum ContentType { 70 Applications, 71 Widgets, 72 Wallpapers 73 } 74 75 // Refs 76 private Launcher mLauncher; 77 private DragController mDragController; 78 private final LayoutInflater mLayoutInflater; 79 private final PackageManager mPackageManager; 80 81 // Content 82 private ContentType mContentType; 83 private ArrayList<ApplicationInfo> mApps; 84 private List<Object> mWidgets; 85 private List<ResolveInfo> mWallpapers; 86 87 // Caching 88 private Drawable mDefaultWidgetBackground; 89 private final int sWidgetPreviewCacheSize = 1 * 1024 * 1024; // 1 MiB 90 private LruCache<Object, Bitmap> mWidgetPreviewCache; 91 private IconCache mIconCache; 92 93 // Dimens 94 private int mContentWidth; 95 private int mMaxWidgetSpan, mMinWidgetSpan; 96 private int mCellWidthGap, mCellHeightGap; 97 private int mWidgetCountX, mWidgetCountY; 98 private int mWallpaperCountX, mWallpaperCountY; 99 private final int mWidgetPreviewIconPaddedDimension; 100 private final float sWidgetPreviewIconPaddingPercentage = 0.25f; 101 private PagedViewCellLayout mWidgetSpacingLayout; 102 103 // Animations 104 private final float ANIMATION_SCALE = 0.5f; 105 private final int TRANSLATE_ANIM_DURATION = 400; 106 private final int DROP_ANIM_DURATION = 200; 107 108 public AppsCustomizePagedView(Context context, AttributeSet attrs) { 109 super(context, attrs); 110 mLayoutInflater = LayoutInflater.from(context); 111 mPackageManager = context.getPackageManager(); 112 mContentType = ContentType.Applications; 113 mApps = new ArrayList<ApplicationInfo>(); 114 mWidgets = new ArrayList<Object>(); 115 mWallpapers = new ArrayList<ResolveInfo>(); 116 mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); 117 mWidgetPreviewCache = new LruCache<Object, Bitmap>(sWidgetPreviewCacheSize) { 118 protected int sizeOf(Object key, Bitmap value) { 119 return value.getByteCount(); 120 } 121 }; 122 123 // Save the default widget preview background 124 Resources resources = context.getResources(); 125 mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview); 126 127 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0); 128 mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6); 129 mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4); 130 a.recycle(); 131 a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); 132 mCellWidthGap = 133 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 10); 134 mCellHeightGap = 135 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 10); 136 mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); 137 mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); 138 mWallpaperCountX = a.getInt(R.styleable.AppsCustomizePagedView_wallpaperCountX, 2); 139 mWallpaperCountY = a.getInt(R.styleable.AppsCustomizePagedView_wallpaperCountY, 2); 140 a.recycle(); 141 142 // Create a dummy page that we can use to approximate the cell dimensions of widgets and 143 // the content width (to be used by our parent) 144 mWidgetSpacingLayout = new PagedViewCellLayout(context); 145 setupPage(mWidgetSpacingLayout); 146 mContentWidth = mWidgetSpacingLayout.getContentWidth(); 147 148 // The max widget span is the length N, such that NxN is the largest bounds that the widget 149 // preview can be before applying the widget scaling 150 mMinWidgetSpan = 1; 151 mMaxWidgetSpan = 3; 152 153 // The padding on the non-matched dimension for the default widget preview icons 154 // (top + bottom) 155 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 156 mWidgetPreviewIconPaddedDimension = 157 (int) (iconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage))); 158 } 159 160 @Override 161 protected void init() { 162 super.init(); 163 mCenterPagesVertically = false; 164 165 Context context = getContext(); 166 Resources r = context.getResources(); 167 setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); 168 } 169 170 public void onPackagesUpdated() { 171 // Get the list of widgets and shortcuts 172 mWidgets.clear(); 173 mWidgets.addAll(AppWidgetManager.getInstance(mLauncher).getInstalledProviders()); 174 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 175 mWidgets.addAll(mPackageManager.queryIntentActivities(shortcutsIntent, 0)); 176 Collections.sort(mWidgets, 177 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); 178 179 // Get the list of wallpapers 180 Intent wallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER); 181 mWallpapers = mPackageManager.queryIntentActivities(wallpapersIntent, 182 PackageManager.GET_META_DATA); 183 Collections.sort(mWallpapers, 184 new LauncherModel.ShortcutNameComparator(mPackageManager)); 185 } 186 187 /** 188 * Animates the given item onto the center of a home screen, and then scales the item to 189 * look as though it's disappearing onto that screen. 190 */ 191 private void animateItemOntoScreen(View dragView, 192 final CellLayout layout, final ItemInfo info) { 193 // On the phone, we only want to fade the widget preview out 194 float[] position = new float[2]; 195 position[0] = layout.getWidth() / 2; 196 position[1] = layout.getHeight() / 2; 197 198 mLauncher.getWorkspace().mapPointFromChildToSelf(layout, position); 199 200 int dragViewWidth = dragView.getMeasuredWidth(); 201 int dragViewHeight = dragView.getMeasuredHeight(); 202 float heightOffset = 0; 203 float widthOffset = 0; 204 205 if (dragView instanceof ImageView) { 206 Drawable d = ((ImageView) dragView).getDrawable(); 207 int width = d.getIntrinsicWidth(); 208 int height = d.getIntrinsicHeight(); 209 210 if ((1.0 * width / height) >= (1.0f * dragViewWidth) / dragViewHeight) { 211 float f = (dragViewWidth / (width * 1.0f)); 212 heightOffset = ANIMATION_SCALE * (dragViewHeight - f * height) / 2; 213 } else { 214 float f = (dragViewHeight / (height * 1.0f)); 215 widthOffset = ANIMATION_SCALE * (dragViewWidth - f * width) / 2; 216 } 217 } 218 final float toX = position[0] - dragView.getMeasuredWidth() / 2 + widthOffset; 219 final float toY = position[1] - dragView.getMeasuredHeight() / 2 + heightOffset; 220 221 final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer); 222 final View dragCopy = dragLayer.createDragView(dragView); 223 dragCopy.setAlpha(1.0f); 224 225 // Translate the item to the center of the appropriate home screen 226 animateIntoPosition(dragCopy, toX, toY, null); 227 228 // The drop-onto-screen animation begins a bit later, but ends at the same time. 229 final int startDelay = TRANSLATE_ANIM_DURATION - DROP_ANIM_DURATION; 230 231 // Scale down the icon and fade out the alpha 232 animateDropOntoScreen(dragCopy, info, DROP_ANIM_DURATION, startDelay); 233 } 234 235 /** 236 * Animation which scales the view down and animates its alpha, making it appear to disappear 237 * onto a home screen. 238 */ 239 private void animateDropOntoScreen( 240 final View view, final ItemInfo info, int duration, int delay) { 241 final DragLayer dragLayer = (DragLayer) mLauncher.findViewById(R.id.drag_layer); 242 final CellLayout layout = mLauncher.getWorkspace().getCurrentDropLayout(); 243 244 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, 245 PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f), 246 PropertyValuesHolder.ofFloat("scaleX", ANIMATION_SCALE), 247 PropertyValuesHolder.ofFloat("scaleY", ANIMATION_SCALE)); 248 anim.setInterpolator(new LinearInterpolator()); 249 if (delay > 0) { 250 anim.setStartDelay(delay); 251 } 252 anim.setDuration(duration); 253 anim.addListener(new AnimatorListenerAdapter() { 254 public void onAnimationEnd(Animator animation) { 255 dragLayer.removeView(view); 256 mLauncher.addExternalItemToScreen(info, layout); 257 info.dropPos = null; 258 } 259 }); 260 anim.start(); 261 } 262 263 /** 264 * Animates the x,y position of the view, and optionally execute a Runnable on animation end. 265 */ 266 private void animateIntoPosition( 267 View view, float toX, float toY, final Runnable endRunnable) { 268 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, 269 PropertyValuesHolder.ofFloat("x", toX), 270 PropertyValuesHolder.ofFloat("y", toY)); 271 anim.setInterpolator(new DecelerateInterpolator(2.5f)); 272 anim.setDuration(TRANSLATE_ANIM_DURATION); 273 if (endRunnable != null) { 274 anim.addListener(new AnimatorListenerAdapter() { 275 @Override 276 public void onAnimationEnd(Animator animation) { 277 endRunnable.run(); 278 } 279 }); 280 } 281 anim.start(); 282 } 283 284 @Override 285 public void onClick(View v) { 286 if (v instanceof PagedViewIcon) { 287 // Animate some feedback to the click 288 final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); 289 animateClickFeedback(v, new Runnable() { 290 @Override 291 public void run() { 292 mLauncher.startActivitySafely(appInfo.intent, appInfo); 293 } 294 }); 295 } else if (v instanceof PagedViewWidget) { 296 final ResolveInfo info = (ResolveInfo) v.getTag(); 297 if (mWallpapers.contains(info)) { 298 // Start the wallpaper picker 299 animateClickFeedback(v, new Runnable() { 300 @Override 301 public void run() { 302 // add the shortcut 303 Intent createWallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER); 304 ComponentName name = new ComponentName(info.activityInfo.packageName, 305 info.activityInfo.name); 306 createWallpapersIntent.setComponent(name); 307 mLauncher.processWallpaper(createWallpapersIntent); 308 } 309 }); 310 } else { 311 // Add the widget to the current workspace screen 312 Workspace w = mLauncher.getWorkspace(); 313 int currentWorkspaceScreen = mLauncher.getCurrentWorkspaceScreen(); 314 final CellLayout cl = (CellLayout) w.getChildAt(currentWorkspaceScreen); 315 final View dragView = v.findViewById(R.id.widget_preview); 316 final ItemInfo itemInfo = (ItemInfo) v.getTag(); 317 animateClickFeedback(v, new Runnable() { 318 @Override 319 public void run() { 320 cl.calculateSpans(itemInfo); 321 if (cl.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY)) { 322 if (LauncherApplication.isScreenLarge()) { 323 animateItemOntoScreen(dragView, cl, itemInfo); 324 } else { 325 mLauncher.addExternalItemToScreen(itemInfo, cl); 326 itemInfo.dropPos = null; 327 } 328 329 // Hide the pane so we can see the workspace we dropped on 330 mLauncher.showWorkspace(true); 331 } else { 332 mLauncher.showOutOfSpaceMessage(); 333 } 334 } 335 }); 336 } 337 } 338 } 339 340 /* 341 * PagedViewWithDraggableItems implementation 342 */ 343 @Override 344 protected void determineDraggingStart(android.view.MotionEvent ev) { 345 // Disable dragging by pulling an app down for now. 346 } 347 private void beginDraggingApplication(View v) { 348 // Make a copy of the ApplicationInfo 349 ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag()); 350 351 // Show the uninstall button if the app is uninstallable. 352 if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0) { 353 DeleteZone allAppsDeleteZone = (DeleteZone) 354 mLauncher.findViewById(R.id.all_apps_delete_zone); 355 allAppsDeleteZone.setDragAndDropEnabled(true); 356 357 if ((appInfo.flags & ApplicationInfo.UPDATED_SYSTEM_APP_FLAG) != 0) { 358 allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps_system_app); 359 } else { 360 allAppsDeleteZone.setText(R.string.delete_zone_label_all_apps); 361 } 362 } 363 364 // Show the info button 365 ApplicationInfoDropTarget allAppsInfoButton = 366 (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target); 367 allAppsInfoButton.setDragAndDropEnabled(true); 368 369 // Compose the drag image (top compound drawable, index is 1) 370 final TextView tv = (TextView) v; 371 final Drawable icon = tv.getCompoundDrawables()[1]; 372 Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), 373 Bitmap.Config.ARGB_8888); 374 Canvas c = new Canvas(b); 375 c.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop()); 376 icon.draw(c); 377 378 // Compose the visible rect of the drag image 379 Rect dragRect = null; 380 if (v instanceof TextView) { 381 int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size); 382 int top = v.getPaddingTop(); 383 int left = (b.getWidth() - iconSize) / 2; 384 int right = left + iconSize; 385 int bottom = top + iconSize; 386 dragRect = new Rect(left, top, right, bottom); 387 } 388 389 // Start the drag 390 mLauncher.lockScreenOrientation(); 391 mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b); 392 mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect); 393 b.recycle(); 394 } 395 private void beginDraggingWidget(View v) { 396 // Get the widget preview as the drag representation 397 ImageView image = (ImageView) v.findViewById(R.id.widget_preview); 398 PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); 399 400 // Compose the drag image 401 Bitmap b; 402 Drawable preview = image.getDrawable(); 403 int w = preview.getIntrinsicWidth(); 404 int h = preview.getIntrinsicHeight(); 405 if (createItemInfo instanceof PendingAddWidgetInfo) { 406 PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo; 407 int[] spanXY = CellLayout.rectToCell(getResources(), 408 createWidgetInfo.minWidth, createWidgetInfo.minHeight, null); 409 createItemInfo.spanX = spanXY[0]; 410 createItemInfo.spanY = spanXY[1]; 411 412 b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 413 renderDrawableToBitmap(preview, b, 0, 0, w, h, 1, 1); 414 } else { 415 // Workaround for the fact that we don't keep the original ResolveInfo associated with 416 // the shortcut around. To get the icon, we just render the preview image (which has 417 // the shortcut icon) to a new drag bitmap that clips the non-icon space. 418 b = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension, 419 mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888); 420 Canvas c = new Canvas(b); 421 preview.draw(c); 422 createItemInfo.spanX = createItemInfo.spanY = 1; 423 } 424 425 // Start the drag 426 mLauncher.lockScreenOrientation(); 427 mLauncher.getWorkspace().onDragStartedWithItemSpans(createItemInfo.spanX, 428 createItemInfo.spanY, b); 429 mDragController.startDrag(image, b, this, createItemInfo, 430 DragController.DRAG_ACTION_COPY, null); 431 b.recycle(); 432 } 433 @Override 434 protected boolean beginDragging(View v) { 435 if (!super.beginDragging(v)) return false; 436 437 // Hide the pane so that the user can drop onto the workspace, we must do this first, 438 // due to how the drop target layout is computed when we start dragging to the workspace. 439 mLauncher.showWorkspace(true); 440 441 if (v instanceof PagedViewIcon) { 442 beginDraggingApplication(v); 443 } else if (v instanceof PagedViewWidget) { 444 beginDraggingWidget(v); 445 } 446 447 return true; 448 } 449 private void endDragging(boolean success) { 450 post(new Runnable() { 451 // Once the drag operation has fully completed, hence the post, we want to disable the 452 // deleteZone and the appInfoButton in all apps, and re-enable the instance which 453 // live in the workspace 454 public void run() { 455 // if onDestroy was called on Launcher, we might have already deleted the 456 // all apps delete zone / info button, so check if they are null 457 DeleteZone allAppsDeleteZone = 458 (DeleteZone) mLauncher.findViewById(R.id.all_apps_delete_zone); 459 ApplicationInfoDropTarget allAppsInfoButton = 460 (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.all_apps_info_target); 461 462 if (allAppsDeleteZone != null) allAppsDeleteZone.setDragAndDropEnabled(false); 463 if (allAppsInfoButton != null) allAppsInfoButton.setDragAndDropEnabled(false); 464 } 465 }); 466 mLauncher.getWorkspace().onDragStopped(success); 467 mLauncher.unlockScreenOrientation(); 468 } 469 470 /* 471 * DragSource implementation 472 */ 473 @Override 474 public void onDragViewVisible() {} 475 @Override 476 public void onDropCompleted(View target, Object dragInfo, boolean success) { 477 endDragging(success); 478 479 // Display an error message if the drag failed due to there not being enough space on the 480 // target layout we were dropping on. 481 if (!success) { 482 boolean showOutOfSpaceMessage = false; 483 if (target instanceof Workspace) { 484 int currentScreen = mLauncher.getCurrentWorkspaceScreen(); 485 Workspace workspace = (Workspace) target; 486 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); 487 ItemInfo itemInfo = (ItemInfo) dragInfo; 488 if (layout != null) { 489 layout.calculateSpans(itemInfo); 490 showOutOfSpaceMessage = 491 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); 492 } 493 } 494 // TODO-APPS_CUSTOMIZE: We need to handle this for folders as well later. 495 if (showOutOfSpaceMessage) { 496 mLauncher.showOutOfSpaceMessage(); 497 } 498 } 499 } 500 501 public void setContentType(ContentType type) { 502 mContentType = type; 503 setCurrentPage(0); 504 invalidatePageData(); 505 } 506 507 /* 508 * Apps PagedView implementation 509 */ 510 private void setVisibilityOnChildren(ViewGroup layout, int visibility) { 511 int childCount = layout.getChildCount(); 512 for (int i = 0; i < childCount; ++i) { 513 layout.getChildAt(i).setVisibility(visibility); 514 } 515 } 516 private void setupPage(PagedViewCellLayout layout) { 517 layout.setCellCount(mCellCountX, mCellCountY); 518 layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 519 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 520 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 521 522 // Note: We force a measure here to get around the fact that when we do layout calculations 523 // immediately after syncing, we don't have a proper width. That said, we already know the 524 // expected page width, so we can actually optimize by hiding all the TextView-based 525 // children that are expensive to measure, and let that happen naturally later. 526 setVisibilityOnChildren(layout, View.GONE); 527 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 528 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 529 layout.setMinimumWidth(getPageContentWidth()); 530 layout.measure(widthSpec, heightSpec); 531 setVisibilityOnChildren(layout, View.VISIBLE); 532 } 533 public void syncAppsPages() { 534 // Ensure that we have the right number of pages 535 Context context = getContext(); 536 int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); 537 for (int i = 0; i < numPages; ++i) { 538 PagedViewCellLayout layout = new PagedViewCellLayout(context); 539 setupPage(layout); 540 addView(layout); 541 } 542 } 543 public void syncAppsPageItems(int page) { 544 // ensure that we have the right number of items on the pages 545 int numPages = getPageCount(); 546 int numCells = mCellCountX * mCellCountY; 547 int startIndex = page * numCells; 548 int endIndex = Math.min(startIndex + numCells, mApps.size()); 549 PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page); 550 layout.removeAllViewsOnPage(); 551 for (int i = startIndex; i < endIndex; ++i) { 552 ApplicationInfo info = mApps.get(i); 553 PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( 554 R.layout.apps_customize_application, layout, false); 555 icon.applyFromApplicationInfo( 556 info, mPageViewIconCache, true, isHardwareAccelerated() && (numPages > 1)); 557 icon.setOnClickListener(this); 558 icon.setOnLongClickListener(this); 559 icon.setOnTouchListener(this); 560 561 int index = i - startIndex; 562 int x = index % mCellCountX; 563 int y = index / mCellCountX; 564 layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); 565 } 566 } 567 /* 568 * Widgets PagedView implementation 569 */ 570 private void setupPage(PagedViewGridLayout layout) { 571 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 572 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 573 574 // Note: We force a measure here to get around the fact that when we do layout calculations 575 // immediately after syncing, we don't have a proper width. That said, we already know the 576 // expected page width, so we can actually optimize by hiding all the TextView-based 577 // children that are expensive to measure, and let that happen naturally later. 578 setVisibilityOnChildren(layout, View.GONE); 579 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 580 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 581 layout.setMinimumWidth(getPageContentWidth()); 582 layout.measure(widthSpec, heightSpec); 583 setVisibilityOnChildren(layout, View.VISIBLE); 584 } 585 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 586 float scaleX, float scaleY) { 587 Canvas c = new Canvas(); 588 if (bitmap != null) c.setBitmap(bitmap); 589 c.save(); 590 c.scale(scaleX, scaleY); 591 Rect oldBounds = d.copyBounds(); 592 d.setBounds(x, y, x + w, y + h); 593 d.draw(c); 594 d.setBounds(oldBounds); // Restore the bounds 595 c.restore(); 596 } 597 private FastBitmapDrawable getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) { 598 // Return the cached version if necessary 599 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 600 if (cachedBitmap != null) { 601 return new FastBitmapDrawable(cachedBitmap); 602 } 603 604 Resources resources = mLauncher.getResources(); 605 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 606 // We only need to make it wide enough so as not allow the preview to be scaled 607 int expectedWidth = cellWidth; 608 int expectedHeight = mWidgetPreviewIconPaddedDimension; 609 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 610 611 // Render the icon 612 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 613 Drawable icon = mIconCache.getFullResIcon(info, mPackageManager); 614 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, 615 mWidgetPreviewIconPaddedDimension, mWidgetPreviewIconPaddedDimension, 1f, 1f); 616 renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f); 617 FastBitmapDrawable iconDrawable = new FastBitmapDrawable(preview); 618 iconDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 619 mWidgetPreviewCache.put(info, preview); 620 return iconDrawable; 621 } 622 private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, 623 int cellVSpan, int cellWidth, int cellHeight) { 624 // Return the cached version if necessary 625 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 626 if (cachedBitmap != null) { 627 return new FastBitmapDrawable(cachedBitmap); 628 } 629 630 // Calculate the size of the drawable 631 cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan)); 632 cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan)); 633 int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan); 634 int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan); 635 636 // Scale down the bitmap to fit the space 637 float widgetPreviewScale = (float) cellWidth / expectedWidth; 638 expectedWidth = (int) (widgetPreviewScale * expectedWidth); 639 expectedHeight = (int) (widgetPreviewScale * expectedHeight); 640 641 // Load the preview image if possible 642 String packageName = info.provider.getPackageName(); 643 Drawable drawable = null; 644 FastBitmapDrawable newDrawable = null; 645 if (info.previewImage != 0) { 646 drawable = mPackageManager.getDrawable(packageName, info.previewImage, null); 647 if (drawable == null) { 648 Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon) 649 + " for provider: " + info.provider); 650 } else { 651 // Scale down the preview to the dimensions we want 652 int imageWidth = drawable.getIntrinsicWidth(); 653 int imageHeight = drawable.getIntrinsicHeight(); 654 float aspect = (float) imageWidth / imageHeight; 655 int newWidth = imageWidth; 656 int newHeight = imageHeight; 657 if (aspect > 1f) { 658 newWidth = expectedWidth; 659 newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth)); 660 } else { 661 newHeight = expectedHeight; 662 newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight)); 663 } 664 665 Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888); 666 renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f); 667 newDrawable = new FastBitmapDrawable(preview); 668 newDrawable.setBounds(0, 0, newWidth, newHeight); 669 mWidgetPreviewCache.put(info, preview); 670 } 671 } 672 673 // Generate a preview image if we couldn't load one 674 if (drawable == null) { 675 Resources resources = mLauncher.getResources(); 676 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 677 678 // Specify the dimensions of the bitmap 679 if (info.minWidth >= info.minHeight) { 680 expectedWidth = cellWidth; 681 expectedHeight = mWidgetPreviewIconPaddedDimension; 682 } else { 683 // Note that in vertical widgets, we might not have enough space due to the text 684 // label, so be conservative and use the width as a height bound 685 expectedWidth = mWidgetPreviewIconPaddedDimension; 686 expectedHeight = cellWidth; 687 } 688 689 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 690 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth, 691 expectedHeight, 1f,1f); 692 693 // Draw the icon in the top left corner 694 try { 695 Drawable icon = null; 696 if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null); 697 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application); 698 699 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 700 renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f); 701 } catch (Resources.NotFoundException e) {} 702 703 newDrawable = new FastBitmapDrawable(preview); 704 newDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 705 mWidgetPreviewCache.put(info, preview); 706 } 707 return newDrawable; 708 } 709 public void syncWidgetPages() { 710 // Ensure that we have the right number of pages 711 Context context = getContext(); 712 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 713 int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage); 714 for (int i = 0; i < numPages; ++i) { 715 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 716 mWidgetCountY); 717 setupPage(layout); 718 addView(layout); 719 } 720 } 721 public void syncWidgetPageItems(int page) { 722 PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page); 723 layout.removeAllViews(); 724 725 // Calculate the dimensions of each cell we are giving to each widget 726 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 727 int offset = page * numWidgetsPerPage; 728 int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap 729 - ((mWidgetCountX - 1) * mCellWidthGap)) / mWidgetCountX); 730 int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap 731 - ((mWidgetCountY - 1) * mCellHeightGap)) / mWidgetCountY); 732 for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) { 733 Object rawInfo = mWidgets.get(offset + i); 734 PendingAddItemInfo createItemInfo = null; 735 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 736 R.layout.apps_customize_widget, layout, false); 737 if (rawInfo instanceof AppWidgetProviderInfo) { 738 // Fill in the widget information 739 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 740 createItemInfo = new PendingAddWidgetInfo(info, null, null); 741 final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth, 742 info.minHeight, null); 743 FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1], 744 cellWidth, cellHeight); 745 widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false); 746 widget.setTag(createItemInfo); 747 } else if (rawInfo instanceof ResolveInfo) { 748 // Fill in the shortcuts information 749 ResolveInfo info = (ResolveInfo) rawInfo; 750 createItemInfo = new PendingAddItemInfo(); 751 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 752 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 753 info.activityInfo.name); 754 FastBitmapDrawable preview = getShortcutPreview(info, cellWidth, cellHeight); 755 widget.applyFromResolveInfo(mPackageManager, info, preview, null, false); 756 widget.setTag(createItemInfo); 757 } 758 widget.setOnClickListener(this); 759 widget.setOnLongClickListener(this); 760 widget.setOnTouchListener(this); 761 762 // Layout each widget 763 int ix = i % mWidgetCountX; 764 int iy = i / mWidgetCountX; 765 PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth, 766 cellHeight); 767 lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap); 768 lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap); 769 layout.addView(widget, lp); 770 } 771 } 772 773 /* 774 * This method fetches an xml file specified in the manifest identified by 775 * WallpaperManager.WALLPAPER_PREVIEW_META_DATA). The xml file specifies 776 * an image which will be used as the wallpaper preview for an activity 777 * which responds to ACTION_SET_WALLPAPER. This image is returned and used 778 * in the customize drawer. 779 */ 780 private Drawable parseWallpaperPreviewXml(ResolveInfo ri) { 781 ActivityInfo activityInfo = ri.activityInfo; 782 XmlResourceParser parser = null; 783 ComponentName component = new ComponentName(ri.activityInfo.packageName, 784 ri.activityInfo.name); 785 try { 786 parser = activityInfo.loadXmlMetaData(mPackageManager, 787 WallpaperManager.WALLPAPER_PREVIEW_META_DATA); 788 if (parser == null) { 789 Slog.w(LOG_TAG, "No " + WallpaperManager.WALLPAPER_PREVIEW_META_DATA 790 + " meta-data for " + "wallpaper provider '" + component + '\''); 791 return null; 792 } 793 794 AttributeSet attrs = Xml.asAttributeSet(parser); 795 796 int type; 797 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 798 && type != XmlPullParser.START_TAG) { 799 // drain whitespace, comments, etc. 800 } 801 802 String nodeName = parser.getName(); 803 if (!"wallpaper-preview".equals(nodeName)) { 804 Slog.w(LOG_TAG, "Meta-data does not start with wallpaper-preview tag for " 805 + "wallpaper provider '" + component + '\''); 806 return null; 807 } 808 809 // If metaData was null, we would have returned earlier when getting 810 // the parser No need to do the check here 811 Resources res = mPackageManager.getResourcesForApplication( 812 activityInfo.applicationInfo); 813 814 TypedArray sa = res.obtainAttributes(attrs, 815 com.android.internal.R.styleable.WallpaperPreviewInfo); 816 817 TypedValue value = sa.peekValue( 818 com.android.internal.R.styleable.WallpaperPreviewInfo_staticWallpaperPreview); 819 if (value == null) return null; 820 821 return res.getDrawable(value.resourceId); 822 } catch (Exception e) { 823 Slog.w(LOG_TAG, "XML parsing failed for wallpaper provider '" + component + '\'', e); 824 return null; 825 } finally { 826 if (parser != null) parser.close(); 827 } 828 } 829 private FastBitmapDrawable getWallpaperPreview(ResolveInfo info, int cellWidth, int cellHeight){ 830 // Return the cached version if necessary 831 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 832 if (cachedBitmap != null) { 833 return new FastBitmapDrawable(cachedBitmap); 834 } 835 836 // Get the preview 837 Resources resources = getContext().getResources(); 838 Drawable wallpaperPreview = parseWallpaperPreviewXml(info); 839 Drawable wallpaperIcon = null; 840 int expectedWidth; 841 int expectedHeight; 842 if (wallpaperPreview != null) { 843 expectedWidth = wallpaperPreview.getIntrinsicWidth(); 844 expectedHeight = wallpaperPreview.getIntrinsicHeight(); 845 } else { 846 wallpaperPreview = mDefaultWidgetBackground; 847 expectedWidth = expectedHeight = Math.min(cellWidth, cellHeight); 848 849 // Draw the icon in the top left corner 850 String packageName = info.activityInfo.packageName; 851 try { 852 if (info.icon > 0) { 853 wallpaperIcon = mPackageManager.getDrawable(packageName, info.icon, null); 854 } 855 if (wallpaperIcon == null) { 856 wallpaperIcon = resources.getDrawable(R.drawable.ic_launcher_application); 857 } 858 } catch (Resources.NotFoundException e) {} 859 } 860 861 // Create the bitmap 862 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 863 renderDrawableToBitmap(wallpaperPreview, preview, 0, 0, expectedWidth, expectedHeight, 864 1f, 1f); 865 if (wallpaperIcon != null) { 866 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 867 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 868 renderDrawableToBitmap(wallpaperIcon, preview, offset, offset, iconSize, iconSize, 869 1f, 1f); 870 } 871 872 FastBitmapDrawable previewDrawable = new FastBitmapDrawable(preview); 873 previewDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 874 mWidgetPreviewCache.put(info, preview); 875 return previewDrawable; 876 } 877 /* 878 * Wallpapers PagedView implementation 879 */ 880 public void syncWallpaperPages() { 881 // Ensure that we have the right number of pages 882 Context context = getContext(); 883 int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY; 884 int numPages = (int) Math.ceil(mWallpapers.size() / (float) numWidgetsPerPage); 885 for (int i = 0; i < numPages; ++i) { 886 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWallpaperCountX, 887 mWallpaperCountY); 888 setupPage(layout); 889 addView(layout); 890 } 891 } 892 public void syncWallpaperPageItems(int page) { 893 PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page); 894 layout.removeAllViews(); 895 896 // Calculate the dimensions of each cell we are giving to each widget 897 int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY; 898 int offset = page * numWidgetsPerPage; 899 int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap 900 - ((mWallpaperCountX - 1) * mCellWidthGap)) / mWallpaperCountX); 901 int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap 902 - ((mWallpaperCountY - 1) * mCellHeightGap)) / mWallpaperCountY); 903 for (int i = 0; i < Math.min(numWidgetsPerPage, mWallpapers.size() - offset); ++i) { 904 ResolveInfo info = mWallpapers.get(offset + i); 905 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 906 R.layout.apps_customize_wallpaper, layout, false); 907 908 // Fill in the shortcuts information 909 FastBitmapDrawable preview = getWallpaperPreview(info, cellWidth, cellHeight); 910 widget.applyFromResolveInfo(mPackageManager, info, preview, null, false); 911 widget.setTag(info); 912 widget.setOnClickListener(this); 913 widget.setOnLongClickListener(this); 914 widget.setOnTouchListener(this); 915 916 // Layout each widget 917 int ix = i % mWallpaperCountX; 918 int iy = i / mWallpaperCountX; 919 PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth, 920 cellHeight); 921 lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap); 922 lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap); 923 layout.addView(widget, lp); 924 } 925 } 926 927 @Override 928 public void syncPages() { 929 removeAllViews(); 930 switch (mContentType) { 931 case Applications: 932 syncAppsPages(); 933 break; 934 case Widgets: 935 syncWidgetPages(); 936 break; 937 case Wallpapers: 938 syncWallpaperPages(); 939 break; 940 } 941 } 942 @Override 943 public void syncPageItems(int page) { 944 switch (mContentType) { 945 case Applications: 946 syncAppsPageItems(page); 947 break; 948 case Widgets: 949 syncWidgetPageItems(page); 950 break; 951 case Wallpapers: 952 syncWallpaperPageItems(page); 953 break; 954 } 955 } 956 957 /** 958 * Used by the parent to get the content width to set the tab bar to 959 * @return 960 */ 961 public int getPageContentWidth() { 962 return mContentWidth; 963 } 964 965 /* 966 * AllAppsView implementation 967 */ 968 @Override 969 public void setup(Launcher launcher, DragController dragController) { 970 mLauncher = launcher; 971 mDragController = dragController; 972 } 973 @Override 974 public void zoom(float zoom, boolean animate) { 975 // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() 976 } 977 @Override 978 public boolean isVisible() { 979 return (getVisibility() == VISIBLE); 980 } 981 @Override 982 public boolean isAnimating() { 983 return false; 984 } 985 @Override 986 public void setApps(ArrayList<ApplicationInfo> list) { 987 mApps = list; 988 Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); 989 invalidatePageData(); 990 } 991 private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 992 // We add it in place, in alphabetical order 993 int count = list.size(); 994 for (int i = 0; i < count; ++i) { 995 ApplicationInfo info = list.get(i); 996 int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); 997 if (index < 0) { 998 mApps.add(-(index + 1), info); 999 } 1000 } 1001 } 1002 @Override 1003 public void addApps(ArrayList<ApplicationInfo> list) { 1004 addAppsWithoutInvalidate(list); 1005 invalidatePageData(); 1006 } 1007 private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { 1008 ComponentName removeComponent = item.intent.getComponent(); 1009 int length = list.size(); 1010 for (int i = 0; i < length; ++i) { 1011 ApplicationInfo info = list.get(i); 1012 if (info.intent.getComponent().equals(removeComponent)) { 1013 return i; 1014 } 1015 } 1016 return -1; 1017 } 1018 private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1019 // loop through all the apps and remove apps that have the same component 1020 int length = list.size(); 1021 for (int i = 0; i < length; ++i) { 1022 ApplicationInfo info = list.get(i); 1023 int removeIndex = findAppByComponent(mApps, info); 1024 if (removeIndex > -1) { 1025 mApps.remove(removeIndex); 1026 mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info)); 1027 } 1028 } 1029 } 1030 @Override 1031 public void removeApps(ArrayList<ApplicationInfo> list) { 1032 removeAppsWithoutInvalidate(list); 1033 invalidatePageData(); 1034 } 1035 @Override 1036 public void updateApps(ArrayList<ApplicationInfo> list) { 1037 // We remove and re-add the updated applications list because it's properties may have 1038 // changed (ie. the title), and this will ensure that the items will be in their proper 1039 // place in the list. 1040 removeAppsWithoutInvalidate(list); 1041 addAppsWithoutInvalidate(list); 1042 invalidatePageData(); 1043 } 1044 @Override 1045 public void reset() { 1046 if (mContentType != ContentType.Applications) { 1047 // Reset to the first page of the Apps pane 1048 AppsCustomizeTabHost tabs = (AppsCustomizeTabHost) 1049 mLauncher.findViewById(R.id.apps_customize_pane); 1050 tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications)); 1051 } else { 1052 setCurrentPage(0); 1053 invalidatePageData(); 1054 } 1055 } 1056 @Override 1057 public void dumpState() { 1058 // TODO: Dump information related to current list of Applications, Widgets, etc. 1059 ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps); 1060 dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets); 1061 } 1062 private void dumpAppWidgetProviderInfoList(String tag, String label, 1063 List<Object> list) { 1064 Log.d(tag, label + " size=" + list.size()); 1065 for (Object i: list) { 1066 if (i instanceof AppWidgetProviderInfo) { 1067 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1068 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1069 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1070 + " initialLayout=" + info.initialLayout 1071 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1072 } else if (i instanceof ResolveInfo) { 1073 ResolveInfo info = (ResolveInfo) i; 1074 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1075 + info.icon); 1076 } 1077 } 1078 } 1079 @Override 1080 public void surrender() { 1081 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1082 // should stop this now. 1083 } 1084} 1085