AppsCustomizePagedView.java revision 46af2e89164b391b7a0049c6ce9048a2b7a7f644
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.isScreenXLarge()) { 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 isHardwareAccelerated() && (numPages > 1)); 566 } 567 } 568 /* 569 * Widgets PagedView implementation 570 */ 571 private void setupPage(PagedViewGridLayout layout) { 572 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 573 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 574 575 // Note: We force a measure here to get around the fact that when we do layout calculations 576 // immediately after syncing, we don't have a proper width. That said, we already know the 577 // expected page width, so we can actually optimize by hiding all the TextView-based 578 // children that are expensive to measure, and let that happen naturally later. 579 setVisibilityOnChildren(layout, View.GONE); 580 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 581 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 582 layout.setMinimumWidth(getPageContentWidth()); 583 layout.measure(widthSpec, heightSpec); 584 setVisibilityOnChildren(layout, View.VISIBLE); 585 } 586 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 587 float scaleX, float scaleY) { 588 Canvas c = new Canvas(); 589 if (bitmap != null) c.setBitmap(bitmap); 590 c.save(); 591 c.scale(scaleX, scaleY); 592 Rect oldBounds = d.copyBounds(); 593 d.setBounds(x, y, x + w, y + h); 594 d.draw(c); 595 d.setBounds(oldBounds); // Restore the bounds 596 c.restore(); 597 } 598 private FastBitmapDrawable getShortcutPreview(ResolveInfo info, int cellWidth, int cellHeight) { 599 // Return the cached version if necessary 600 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 601 if (cachedBitmap != null) { 602 return new FastBitmapDrawable(cachedBitmap); 603 } 604 605 Resources resources = mLauncher.getResources(); 606 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 607 // We only need to make it wide enough so as not allow the preview to be scaled 608 int expectedWidth = cellWidth; 609 int expectedHeight = mWidgetPreviewIconPaddedDimension; 610 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 611 612 // Render the icon 613 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 614 Drawable icon = mIconCache.getFullResIcon(info, mPackageManager); 615 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, 616 mWidgetPreviewIconPaddedDimension, mWidgetPreviewIconPaddedDimension, 1f, 1f); 617 renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f); 618 FastBitmapDrawable iconDrawable = new FastBitmapDrawable(preview); 619 iconDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 620 mWidgetPreviewCache.put(info, preview); 621 return iconDrawable; 622 } 623 private FastBitmapDrawable getWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, 624 int cellVSpan, int cellWidth, int cellHeight) { 625 // Return the cached version if necessary 626 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 627 if (cachedBitmap != null) { 628 return new FastBitmapDrawable(cachedBitmap); 629 } 630 631 // Calculate the size of the drawable 632 cellHSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellHSpan)); 633 cellVSpan = Math.max(mMinWidgetSpan, Math.min(mMaxWidgetSpan, cellVSpan)); 634 int expectedWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan); 635 int expectedHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan); 636 637 // Scale down the bitmap to fit the space 638 float widgetPreviewScale = (float) cellWidth / expectedWidth; 639 expectedWidth = (int) (widgetPreviewScale * expectedWidth); 640 expectedHeight = (int) (widgetPreviewScale * expectedHeight); 641 642 // Load the preview image if possible 643 String packageName = info.provider.getPackageName(); 644 Drawable drawable = null; 645 FastBitmapDrawable newDrawable = null; 646 if (info.previewImage != 0) { 647 drawable = mPackageManager.getDrawable(packageName, info.previewImage, null); 648 if (drawable == null) { 649 Log.w(LOG_TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon) 650 + " for provider: " + info.provider); 651 } else { 652 // Scale down the preview to the dimensions we want 653 int imageWidth = drawable.getIntrinsicWidth(); 654 int imageHeight = drawable.getIntrinsicHeight(); 655 float aspect = (float) imageWidth / imageHeight; 656 int newWidth = imageWidth; 657 int newHeight = imageHeight; 658 if (aspect > 1f) { 659 newWidth = expectedWidth; 660 newHeight = (int) (imageHeight * ((float) expectedWidth / imageWidth)); 661 } else { 662 newHeight = expectedHeight; 663 newWidth = (int) (imageWidth * ((float) expectedHeight / imageHeight)); 664 } 665 666 Bitmap preview = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888); 667 renderDrawableToBitmap(drawable, preview, 0, 0, newWidth, newHeight, 1f, 1f); 668 newDrawable = new FastBitmapDrawable(preview); 669 newDrawable.setBounds(0, 0, newWidth, newHeight); 670 mWidgetPreviewCache.put(info, preview); 671 } 672 } 673 674 // Generate a preview image if we couldn't load one 675 if (drawable == null) { 676 Resources resources = mLauncher.getResources(); 677 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 678 679 // Specify the dimensions of the bitmap 680 if (info.minWidth >= info.minHeight) { 681 expectedWidth = cellWidth; 682 expectedHeight = mWidgetPreviewIconPaddedDimension; 683 } else { 684 // Note that in vertical widgets, we might not have enough space due to the text 685 // label, so be conservative and use the width as a height bound 686 expectedWidth = mWidgetPreviewIconPaddedDimension; 687 expectedHeight = cellWidth; 688 } 689 690 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 691 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, expectedWidth, 692 expectedHeight, 1f,1f); 693 694 // Draw the icon in the top left corner 695 try { 696 Drawable icon = null; 697 if (info.icon > 0) icon = mPackageManager.getDrawable(packageName, info.icon, null); 698 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application); 699 700 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 701 renderDrawableToBitmap(icon, preview, offset, offset, iconSize, iconSize, 1f, 1f); 702 } catch (Resources.NotFoundException e) {} 703 704 newDrawable = new FastBitmapDrawable(preview); 705 newDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 706 mWidgetPreviewCache.put(info, preview); 707 } 708 return newDrawable; 709 } 710 public void syncWidgetPages() { 711 // Ensure that we have the right number of pages 712 Context context = getContext(); 713 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 714 int numPages = (int) Math.ceil(mWidgets.size() / (float) numWidgetsPerPage); 715 for (int i = 0; i < numPages; ++i) { 716 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 717 mWidgetCountY); 718 setupPage(layout); 719 addView(layout); 720 } 721 } 722 public void syncWidgetPageItems(int page) { 723 PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page); 724 layout.removeAllViews(); 725 726 // Calculate the dimensions of each cell we are giving to each widget 727 int numWidgetsPerPage = mWidgetCountX * mWidgetCountY; 728 int offset = page * numWidgetsPerPage; 729 int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap 730 - ((mWidgetCountX - 1) * mCellWidthGap)) / mWidgetCountX); 731 int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap 732 - ((mWidgetCountY - 1) * mCellHeightGap)) / mWidgetCountY); 733 for (int i = 0; i < Math.min(numWidgetsPerPage, mWidgets.size() - offset); ++i) { 734 Object rawInfo = mWidgets.get(offset + i); 735 PendingAddItemInfo createItemInfo = null; 736 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 737 R.layout.apps_customize_widget, layout, false); 738 if (rawInfo instanceof AppWidgetProviderInfo) { 739 // Fill in the widget information 740 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 741 createItemInfo = new PendingAddWidgetInfo(info, null, null); 742 final int[] cellSpans = CellLayout.rectToCell(getResources(), info.minWidth, 743 info.minHeight, null); 744 FastBitmapDrawable preview = getWidgetPreview(info, cellSpans[0], cellSpans[1], 745 cellWidth, cellHeight); 746 widget.applyFromAppWidgetProviderInfo(info, preview, -1, cellSpans, null, false); 747 widget.setTag(createItemInfo); 748 } else if (rawInfo instanceof ResolveInfo) { 749 // Fill in the shortcuts information 750 ResolveInfo info = (ResolveInfo) rawInfo; 751 createItemInfo = new PendingAddItemInfo(); 752 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 753 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 754 info.activityInfo.name); 755 FastBitmapDrawable preview = getShortcutPreview(info, cellWidth, cellHeight); 756 widget.applyFromResolveInfo(mPackageManager, info, preview, null, false); 757 widget.setTag(createItemInfo); 758 } 759 widget.setOnClickListener(this); 760 widget.setOnLongClickListener(this); 761 widget.setOnTouchListener(this); 762 763 // Layout each widget 764 int ix = i % mWidgetCountX; 765 int iy = i / mWidgetCountX; 766 PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth, 767 cellHeight); 768 lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap); 769 lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap); 770 layout.addView(widget, lp); 771 } 772 } 773 774 /* 775 * This method fetches an xml file specified in the manifest identified by 776 * WallpaperManager.WALLPAPER_PREVIEW_META_DATA). The xml file specifies 777 * an image which will be used as the wallpaper preview for an activity 778 * which responds to ACTION_SET_WALLPAPER. This image is returned and used 779 * in the customize drawer. 780 */ 781 private Drawable parseWallpaperPreviewXml(ResolveInfo ri) { 782 ActivityInfo activityInfo = ri.activityInfo; 783 XmlResourceParser parser = null; 784 ComponentName component = new ComponentName(ri.activityInfo.packageName, 785 ri.activityInfo.name); 786 try { 787 parser = activityInfo.loadXmlMetaData(mPackageManager, 788 WallpaperManager.WALLPAPER_PREVIEW_META_DATA); 789 if (parser == null) { 790 Slog.w(LOG_TAG, "No " + WallpaperManager.WALLPAPER_PREVIEW_META_DATA 791 + " meta-data for " + "wallpaper provider '" + component + '\''); 792 return null; 793 } 794 795 AttributeSet attrs = Xml.asAttributeSet(parser); 796 797 int type; 798 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 799 && type != XmlPullParser.START_TAG) { 800 // drain whitespace, comments, etc. 801 } 802 803 String nodeName = parser.getName(); 804 if (!"wallpaper-preview".equals(nodeName)) { 805 Slog.w(LOG_TAG, "Meta-data does not start with wallpaper-preview tag for " 806 + "wallpaper provider '" + component + '\''); 807 return null; 808 } 809 810 // If metaData was null, we would have returned earlier when getting 811 // the parser No need to do the check here 812 Resources res = mPackageManager.getResourcesForApplication( 813 activityInfo.applicationInfo); 814 815 TypedArray sa = res.obtainAttributes(attrs, 816 com.android.internal.R.styleable.WallpaperPreviewInfo); 817 818 TypedValue value = sa.peekValue( 819 com.android.internal.R.styleable.WallpaperPreviewInfo_staticWallpaperPreview); 820 if (value == null) return null; 821 822 return res.getDrawable(value.resourceId); 823 } catch (Exception e) { 824 Slog.w(LOG_TAG, "XML parsing failed for wallpaper provider '" + component + '\'', e); 825 return null; 826 } finally { 827 if (parser != null) parser.close(); 828 } 829 } 830 private FastBitmapDrawable getWallpaperPreview(ResolveInfo info, int cellWidth, int cellHeight){ 831 // Return the cached version if necessary 832 Bitmap cachedBitmap = mWidgetPreviewCache.get(info); 833 if (cachedBitmap != null) { 834 return new FastBitmapDrawable(cachedBitmap); 835 } 836 837 // Get the preview 838 Resources resources = getContext().getResources(); 839 Drawable wallpaperPreview = parseWallpaperPreviewXml(info); 840 Drawable wallpaperIcon = null; 841 int expectedWidth; 842 int expectedHeight; 843 if (wallpaperPreview != null) { 844 expectedWidth = wallpaperPreview.getIntrinsicWidth(); 845 expectedHeight = wallpaperPreview.getIntrinsicHeight(); 846 } else { 847 wallpaperPreview = mDefaultWidgetBackground; 848 expectedWidth = expectedHeight = Math.min(cellWidth, cellHeight); 849 850 // Draw the icon in the top left corner 851 String packageName = info.activityInfo.packageName; 852 try { 853 if (info.icon > 0) { 854 wallpaperIcon = mPackageManager.getDrawable(packageName, info.icon, null); 855 } 856 if (wallpaperIcon == null) { 857 wallpaperIcon = resources.getDrawable(R.drawable.ic_launcher_application); 858 } 859 } catch (Resources.NotFoundException e) {} 860 } 861 862 // Create the bitmap 863 Bitmap preview = Bitmap.createBitmap(expectedWidth, expectedHeight, Config.ARGB_8888); 864 renderDrawableToBitmap(wallpaperPreview, preview, 0, 0, expectedWidth, expectedHeight, 865 1f, 1f); 866 if (wallpaperIcon != null) { 867 int iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 868 int offset = (int) (iconSize * sWidgetPreviewIconPaddingPercentage); 869 renderDrawableToBitmap(wallpaperIcon, preview, offset, offset, iconSize, iconSize, 870 1f, 1f); 871 } 872 873 FastBitmapDrawable previewDrawable = new FastBitmapDrawable(preview); 874 previewDrawable.setBounds(0, 0, expectedWidth, expectedHeight); 875 mWidgetPreviewCache.put(info, preview); 876 return previewDrawable; 877 } 878 /* 879 * Wallpapers PagedView implementation 880 */ 881 public void syncWallpaperPages() { 882 // Ensure that we have the right number of pages 883 Context context = getContext(); 884 int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY; 885 int numPages = (int) Math.ceil(mWallpapers.size() / (float) numWidgetsPerPage); 886 for (int i = 0; i < numPages; ++i) { 887 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWallpaperCountX, 888 mWallpaperCountY); 889 setupPage(layout); 890 addView(layout); 891 } 892 } 893 public void syncWallpaperPageItems(int page) { 894 PagedViewGridLayout layout = (PagedViewGridLayout) getChildAt(page); 895 layout.removeAllViews(); 896 897 // Calculate the dimensions of each cell we are giving to each widget 898 int numWidgetsPerPage = mWallpaperCountX * mWallpaperCountY; 899 int offset = page * numWidgetsPerPage; 900 int cellWidth = ((mWidgetSpacingLayout.getContentWidth() - mPageLayoutWidthGap 901 - ((mWallpaperCountX - 1) * mCellWidthGap)) / mWallpaperCountX); 902 int cellHeight = ((mWidgetSpacingLayout.getContentHeight() - mPageLayoutHeightGap 903 - ((mWallpaperCountY - 1) * mCellHeightGap)) / mWallpaperCountY); 904 for (int i = 0; i < Math.min(numWidgetsPerPage, mWallpapers.size() - offset); ++i) { 905 ResolveInfo info = mWallpapers.get(offset + i); 906 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 907 R.layout.apps_customize_wallpaper, layout, false); 908 909 // Fill in the shortcuts information 910 FastBitmapDrawable preview = getWallpaperPreview(info, cellWidth, cellHeight); 911 widget.applyFromResolveInfo(mPackageManager, info, preview, null, false); 912 widget.setTag(info); 913 widget.setOnClickListener(this); 914 widget.setOnLongClickListener(this); 915 widget.setOnTouchListener(this); 916 917 // Layout each widget 918 int ix = i % mWallpaperCountX; 919 int iy = i / mWallpaperCountX; 920 PagedViewGridLayout.LayoutParams lp = new PagedViewGridLayout.LayoutParams(cellWidth, 921 cellHeight); 922 lp.leftMargin = (ix * cellWidth) + (ix * mCellWidthGap); 923 lp.topMargin = (iy * cellHeight) + (iy * mCellHeightGap); 924 layout.addView(widget, lp); 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 case Wallpapers: 939 syncWallpaperPages(); 940 break; 941 } 942 } 943 @Override 944 public void syncPageItems(int page) { 945 switch (mContentType) { 946 case Applications: 947 syncAppsPageItems(page); 948 break; 949 case Widgets: 950 syncWidgetPageItems(page); 951 break; 952 case Wallpapers: 953 syncWallpaperPageItems(page); 954 break; 955 } 956 } 957 958 /** 959 * Used by the parent to get the content width to set the tab bar to 960 * @return 961 */ 962 public int getPageContentWidth() { 963 return mContentWidth; 964 } 965 966 /* 967 * AllAppsView implementation 968 */ 969 @Override 970 public void setup(Launcher launcher, DragController dragController) { 971 mLauncher = launcher; 972 mDragController = dragController; 973 } 974 @Override 975 public void zoom(float zoom, boolean animate) { 976 // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() 977 } 978 @Override 979 public boolean isVisible() { 980 return (getVisibility() == VISIBLE); 981 } 982 @Override 983 public boolean isAnimating() { 984 return false; 985 } 986 @Override 987 public void setApps(ArrayList<ApplicationInfo> list) { 988 mApps = list; 989 Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); 990 invalidatePageData(); 991 } 992 private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 993 // We add it in place, in alphabetical order 994 int count = list.size(); 995 for (int i = 0; i < count; ++i) { 996 ApplicationInfo info = list.get(i); 997 int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); 998 if (index < 0) { 999 mApps.add(-(index + 1), info); 1000 } 1001 } 1002 } 1003 @Override 1004 public void addApps(ArrayList<ApplicationInfo> list) { 1005 addAppsWithoutInvalidate(list); 1006 invalidatePageData(); 1007 } 1008 private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { 1009 ComponentName removeComponent = item.intent.getComponent(); 1010 int length = list.size(); 1011 for (int i = 0; i < length; ++i) { 1012 ApplicationInfo info = list.get(i); 1013 if (info.intent.getComponent().equals(removeComponent)) { 1014 return i; 1015 } 1016 } 1017 return -1; 1018 } 1019 private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1020 // loop through all the apps and remove apps that have the same component 1021 int length = list.size(); 1022 for (int i = 0; i < length; ++i) { 1023 ApplicationInfo info = list.get(i); 1024 int removeIndex = findAppByComponent(mApps, info); 1025 if (removeIndex > -1) { 1026 mApps.remove(removeIndex); 1027 mPageViewIconCache.removeOutline(new PagedViewIconCache.Key(info)); 1028 } 1029 } 1030 } 1031 @Override 1032 public void removeApps(ArrayList<ApplicationInfo> list) { 1033 removeAppsWithoutInvalidate(list); 1034 invalidatePageData(); 1035 } 1036 @Override 1037 public void updateApps(ArrayList<ApplicationInfo> list) { 1038 // We remove and re-add the updated applications list because it's properties may have 1039 // changed (ie. the title), and this will ensure that the items will be in their proper 1040 // place in the list. 1041 removeAppsWithoutInvalidate(list); 1042 addAppsWithoutInvalidate(list); 1043 invalidatePageData(); 1044 } 1045 @Override 1046 public void reset() { 1047 if (mContentType != ContentType.Applications) { 1048 // Reset to the first page of the Apps pane 1049 AppsCustomizeTabHost tabs = (AppsCustomizeTabHost) 1050 mLauncher.findViewById(R.id.apps_customize_pane); 1051 tabs.setCurrentTabByTag(tabs.getTabTagForContentType(ContentType.Applications)); 1052 } else { 1053 setCurrentPage(0); 1054 invalidatePageData(); 1055 } 1056 } 1057 @Override 1058 public void dumpState() { 1059 // TODO: Dump information related to current list of Applications, Widgets, etc. 1060 ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps); 1061 dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets); 1062 } 1063 private void dumpAppWidgetProviderInfoList(String tag, String label, 1064 List<Object> list) { 1065 Log.d(tag, label + " size=" + list.size()); 1066 for (Object i: list) { 1067 if (i instanceof AppWidgetProviderInfo) { 1068 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1069 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1070 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1071 + " initialLayout=" + info.initialLayout 1072 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1073 } else if (i instanceof ResolveInfo) { 1074 ResolveInfo info = (ResolveInfo) i; 1075 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1076 + info.icon); 1077 } 1078 } 1079 } 1080 @Override 1081 public void surrender() { 1082 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1083 // should stop this now. 1084 } 1085} 1086