AppsCustomizePagedView.java revision ef3dd6efe03c988be1fe980fbed75333017e6760
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.AppWidgetHostView; 23import android.appwidget.AppWidgetManager; 24import android.appwidget.AppWidgetProviderInfo; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 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.MaskFilter; 37import android.graphics.Paint; 38import android.graphics.PorterDuff; 39import android.graphics.Rect; 40import android.graphics.TableMaskFilter; 41import android.graphics.drawable.Drawable; 42import android.os.AsyncTask; 43import android.os.Handler; 44import android.os.HandlerThread; 45import android.os.Process; 46import android.util.AttributeSet; 47import android.util.Log; 48import android.view.Gravity; 49import android.view.KeyEvent; 50import android.view.LayoutInflater; 51import android.view.MotionEvent; 52import android.view.View; 53import android.view.ViewConfiguration; 54import android.view.ViewGroup; 55import android.view.animation.AccelerateInterpolator; 56import android.view.animation.DecelerateInterpolator; 57import android.widget.GridLayout; 58import android.widget.ImageView; 59import android.widget.Toast; 60 61import com.android.launcher.R; 62import com.android.launcher2.DropTarget.DragObject; 63 64import java.util.ArrayList; 65import java.util.Collections; 66import java.util.Iterator; 67import java.util.List; 68 69/** 70 * A simple callback interface which also provides the results of the task. 71 */ 72interface AsyncTaskCallback { 73 void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); 74} 75 76/** 77 * The data needed to perform either of the custom AsyncTasks. 78 */ 79class AsyncTaskPageData { 80 enum Type { 81 LoadWidgetPreviewData 82 } 83 84 AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR, 85 AsyncTaskCallback postR) { 86 page = p; 87 items = l; 88 sourceImages = si; 89 generatedImages = new ArrayList<Bitmap>(); 90 maxImageWidth = maxImageHeight = -1; 91 doInBackgroundCallback = bgR; 92 postExecuteCallback = postR; 93 } 94 AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR, 95 AsyncTaskCallback postR) { 96 page = p; 97 items = l; 98 generatedImages = new ArrayList<Bitmap>(); 99 maxImageWidth = cw; 100 maxImageHeight = ch; 101 doInBackgroundCallback = bgR; 102 postExecuteCallback = postR; 103 } 104 void cleanup(boolean cancelled) { 105 // Clean up any references to source/generated bitmaps 106 if (sourceImages != null) { 107 if (cancelled) { 108 for (Bitmap b : sourceImages) { 109 b.recycle(); 110 } 111 } 112 sourceImages.clear(); 113 } 114 if (generatedImages != null) { 115 if (cancelled) { 116 for (Bitmap b : generatedImages) { 117 b.recycle(); 118 } 119 } 120 generatedImages.clear(); 121 } 122 } 123 int page; 124 ArrayList<Object> items; 125 ArrayList<Bitmap> sourceImages; 126 ArrayList<Bitmap> generatedImages; 127 int maxImageWidth; 128 int maxImageHeight; 129 AsyncTaskCallback doInBackgroundCallback; 130 AsyncTaskCallback postExecuteCallback; 131} 132 133/** 134 * A generic template for an async task used in AppsCustomize. 135 */ 136class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> { 137 AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) { 138 page = p; 139 threadPriority = Process.THREAD_PRIORITY_DEFAULT; 140 dataType = ty; 141 } 142 @Override 143 protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { 144 if (params.length != 1) return null; 145 // Load each of the widget previews in the background 146 params[0].doInBackgroundCallback.run(this, params[0]); 147 return params[0]; 148 } 149 @Override 150 protected void onPostExecute(AsyncTaskPageData result) { 151 // All the widget previews are loaded, so we can just callback to inflate the page 152 result.postExecuteCallback.run(this, result); 153 } 154 155 void setThreadPriority(int p) { 156 threadPriority = p; 157 } 158 void syncThreadPriority() { 159 Process.setThreadPriority(threadPriority); 160 } 161 162 // The page that this async task is associated with 163 AsyncTaskPageData.Type dataType; 164 int page; 165 int threadPriority; 166} 167 168/** 169 * The Apps/Customize page that displays all the applications, widgets, and shortcuts. 170 */ 171public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements 172 AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource, 173 PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener { 174 static final String LOG_TAG = "AppsCustomizePagedView"; 175 176 /** 177 * The different content types that this paged view can show. 178 */ 179 public enum ContentType { 180 Applications, 181 Widgets 182 } 183 184 // Refs 185 private Launcher mLauncher; 186 private DragController mDragController; 187 private final LayoutInflater mLayoutInflater; 188 private final PackageManager mPackageManager; 189 190 // Save and Restore 191 private int mSaveInstanceStateItemIndex = -1; 192 private PagedViewIcon mPressedIcon; 193 194 // Content 195 private ArrayList<ApplicationInfo> mApps; 196 private ArrayList<Object> mWidgets; 197 198 // Cling 199 private boolean mHasShownAllAppsCling; 200 private int mClingFocusedX; 201 private int mClingFocusedY; 202 203 // Caching 204 private Canvas mCanvas; 205 private Drawable mDefaultWidgetBackground; 206 private IconCache mIconCache; 207 208 // Dimens 209 private int mContentWidth; 210 private int mAppIconSize; 211 private int mMaxAppCellCountX, mMaxAppCellCountY; 212 private int mWidgetCountX, mWidgetCountY; 213 private int mWidgetWidthGap, mWidgetHeightGap; 214 private final int mWidgetPreviewIconPaddedDimension; 215 private final float sWidgetPreviewIconPaddingPercentage = 0.25f; 216 private PagedViewCellLayout mWidgetSpacingLayout; 217 private int mNumAppsPages; 218 private int mNumWidgetPages; 219 220 // Relating to the scroll and overscroll effects 221 Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f); 222 private static float CAMERA_DISTANCE = 6500; 223 private static float TRANSITION_SCALE_FACTOR = 0.74f; 224 private static float TRANSITION_PIVOT = 0.65f; 225 private static float TRANSITION_MAX_ROTATION = 22; 226 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 227 private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); 228 private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); 229 230 // Previews & outlines 231 ArrayList<AppsCustomizeAsyncTask> mRunningTasks; 232 private static final int sPageSleepDelay = 200; 233 234 private Runnable mInflateWidgetRunnable = null; 235 private Runnable mBindWidgetRunnable = null; 236 static final int WIDGET_NO_CLEANUP_REQUIRED = -1; 237 static final int WIDGET_BOUND = 0; 238 static final int WIDGET_INFLATED = 1; 239 int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; 240 int mWidgetLoadingId = -1; 241 PendingAddWidgetInfo mCreateWidgetInfo = null; 242 243 public AppsCustomizePagedView(Context context, AttributeSet attrs) { 244 super(context, attrs); 245 mLayoutInflater = LayoutInflater.from(context); 246 mPackageManager = context.getPackageManager(); 247 mApps = new ArrayList<ApplicationInfo>(); 248 mWidgets = new ArrayList<Object>(); 249 mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); 250 mCanvas = new Canvas(); 251 mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>(); 252 253 // Save the default widget preview background 254 Resources resources = context.getResources(); 255 mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo); 256 mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); 257 258 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); 259 mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1); 260 mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1); 261 mWidgetWidthGap = 262 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0); 263 mWidgetHeightGap = 264 a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0); 265 mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); 266 mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); 267 mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0); 268 mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); 269 a.recycle(); 270 mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); 271 272 // The padding on the non-matched dimension for the default widget preview icons 273 // (top + bottom) 274 mWidgetPreviewIconPaddedDimension = 275 (int) (mAppIconSize * (1 + (2 * sWidgetPreviewIconPaddingPercentage))); 276 mFadeInAdjacentScreens = false; 277 } 278 279 @Override 280 protected void init() { 281 super.init(); 282 mCenterPagesVertically = false; 283 284 Context context = getContext(); 285 Resources r = context.getResources(); 286 setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); 287 } 288 289 @Override 290 protected void onUnhandledTap(MotionEvent ev) { 291 if (LauncherApplication.isScreenLarge()) { 292 // Dismiss AppsCustomize if we tap 293 mLauncher.showWorkspace(true); 294 } 295 } 296 297 /** Returns the item index of the center item on this page so that we can restore to this 298 * item index when we rotate. */ 299 private int getMiddleComponentIndexOnCurrentPage() { 300 int i = -1; 301 if (getPageCount() > 0) { 302 int currentPage = getCurrentPage(); 303 if (currentPage < mNumAppsPages) { 304 PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage); 305 PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout(); 306 int numItemsPerPage = mCellCountX * mCellCountY; 307 int childCount = childrenLayout.getChildCount(); 308 if (childCount > 0) { 309 i = (currentPage * numItemsPerPage) + (childCount / 2); 310 } 311 } else { 312 int numApps = mApps.size(); 313 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage); 314 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 315 int childCount = layout.getChildCount(); 316 if (childCount > 0) { 317 i = numApps + 318 ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2); 319 } 320 } 321 } 322 return i; 323 } 324 325 /** Get the index of the item to restore to if we need to restore the current page. */ 326 int getSaveInstanceStateIndex() { 327 if (mSaveInstanceStateItemIndex == -1) { 328 mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage(); 329 } 330 return mSaveInstanceStateItemIndex; 331 } 332 333 /** Returns the page in the current orientation which is expected to contain the specified 334 * item index. */ 335 int getPageForComponent(int index) { 336 if (index < 0) return 0; 337 338 if (index < mApps.size()) { 339 int numItemsPerPage = mCellCountX * mCellCountY; 340 return (index / numItemsPerPage); 341 } else { 342 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 343 return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage); 344 } 345 } 346 347 /** 348 * This differs from isDataReady as this is the test done if isDataReady is not set. 349 */ 350 private boolean testDataReady() { 351 // We only do this test once, and we default to the Applications page, so we only really 352 // have to wait for there to be apps. 353 // TODO: What if one of them is validly empty 354 return !mApps.isEmpty() && !mWidgets.isEmpty(); 355 } 356 357 /** Restores the page for an item at the specified index */ 358 void restorePageForIndex(int index) { 359 if (index < 0) return; 360 mSaveInstanceStateItemIndex = index; 361 } 362 363 private void updatePageCounts() { 364 mNumWidgetPages = (int) Math.ceil(mWidgets.size() / 365 (float) (mWidgetCountX * mWidgetCountY)); 366 mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); 367 } 368 369 protected void onDataReady(int width, int height) { 370 // Note that we transpose the counts in portrait so that we get a similar layout 371 boolean isLandscape = getResources().getConfiguration().orientation == 372 Configuration.ORIENTATION_LANDSCAPE; 373 int maxCellCountX = Integer.MAX_VALUE; 374 int maxCellCountY = Integer.MAX_VALUE; 375 if (LauncherApplication.isScreenLarge()) { 376 maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() : 377 LauncherModel.getCellCountY()); 378 maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() : 379 LauncherModel.getCellCountX()); 380 } 381 if (mMaxAppCellCountX > -1) { 382 maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX); 383 } 384 if (mMaxAppCellCountY > -1) { 385 maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY); 386 } 387 388 // Now that the data is ready, we can calculate the content width, the number of cells to 389 // use for each page 390 mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 391 mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 392 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 393 mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY); 394 mCellCountX = mWidgetSpacingLayout.getCellCountX(); 395 mCellCountY = mWidgetSpacingLayout.getCellCountY(); 396 updatePageCounts(); 397 398 // Force a measure to update recalculate the gaps 399 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 400 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 401 mWidgetSpacingLayout.measure(widthSpec, heightSpec); 402 mContentWidth = mWidgetSpacingLayout.getContentWidth(); 403 404 AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost(); 405 final boolean hostIsTransitioning = host.isTransitioning(); 406 407 // Restore the page 408 int page = getPageForComponent(mSaveInstanceStateItemIndex); 409 invalidatePageData(Math.max(0, page), hostIsTransitioning); 410 411 // Show All Apps cling if we are finished transitioning, otherwise, we will try again when 412 // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be 413 // returned while animating) 414 if (!hostIsTransitioning) { 415 post(new Runnable() { 416 @Override 417 public void run() { 418 showAllAppsCling(); 419 } 420 }); 421 } 422 } 423 424 void showAllAppsCling() { 425 if (!mHasShownAllAppsCling && isDataReady() && testDataReady()) { 426 mHasShownAllAppsCling = true; 427 // Calculate the position for the cling punch through 428 int[] offset = new int[2]; 429 int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); 430 mLauncher.getDragLayer().getLocationInDragLayer(this, offset); 431 // PagedViews are centered horizontally but top aligned 432 pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + 433 offset[0]; 434 pos[1] += offset[1]; 435 mLauncher.showFirstRunAllAppsCling(pos); 436 } 437 } 438 439 @Override 440 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 441 int width = MeasureSpec.getSize(widthMeasureSpec); 442 int height = MeasureSpec.getSize(heightMeasureSpec); 443 if (!isDataReady()) { 444 if (testDataReady()) { 445 setDataIsReady(); 446 setMeasuredDimension(width, height); 447 onDataReady(width, height); 448 } 449 } 450 451 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 452 } 453 454 public void onPackagesUpdated() { 455 // TODO: this isn't ideal, but we actually need to delay here. This call is triggered 456 // by a broadcast receiver, and in order for it to work correctly, we need to know that 457 // the AppWidgetService has already received and processed the same broadcast. Since there 458 // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally, 459 // we should have a more precise way of ensuring the AppWidgetService is up to date. 460 postDelayed(new Runnable() { 461 public void run() { 462 updatePackages(); 463 } 464 }, 500); 465 } 466 467 public void updatePackages() { 468 // Get the list of widgets and shortcuts 469 boolean wasEmpty = mWidgets.isEmpty(); 470 mWidgets.clear(); 471 List<AppWidgetProviderInfo> widgets = 472 AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); 473 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 474 List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); 475 for (AppWidgetProviderInfo widget : widgets) { 476 if (widget.minWidth > 0 && widget.minHeight > 0) { 477 mWidgets.add(widget); 478 } else { 479 Log.e(LOG_TAG, "Widget " + widget.provider + " has invalid dimensions (" + 480 widget.minWidth + ", " + widget.minHeight + ")"); 481 } 482 } 483 mWidgets.addAll(shortcuts); 484 Collections.sort(mWidgets, 485 new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); 486 updatePageCounts(); 487 488 if (wasEmpty) { 489 // The next layout pass will trigger data-ready if both widgets and apps are set, so request 490 // a layout to do this test and invalidate the page data when ready. 491 if (testDataReady()) requestLayout(); 492 } else { 493 cancelAllTasks(); 494 invalidatePageData(); 495 } 496 } 497 498 @Override 499 public void onClick(View v) { 500 // When we have exited all apps or are in transition, disregard clicks 501 if (!mLauncher.isAllAppsCustomizeOpen() || 502 mLauncher.getWorkspace().isSwitchingState()) return; 503 504 if (v instanceof PagedViewIcon) { 505 // Animate some feedback to the click 506 final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); 507 mLauncher.startActivitySafely(appInfo.intent, appInfo); 508 509 // Lock the drawable state to pressed until we return to Launcher 510 if (mPressedIcon != null) { 511 mPressedIcon.lockDrawableState(); 512 } 513 } else if (v instanceof PagedViewWidget) { 514 // Let the user know that they have to long press to add a widget 515 Toast.makeText(getContext(), R.string.long_press_widget_to_add, 516 Toast.LENGTH_SHORT).show(); 517 518 // Create a little animation to show that the widget can move 519 float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 520 final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); 521 AnimatorSet bounce = new AnimatorSet(); 522 ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY); 523 tyuAnim.setDuration(125); 524 ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f); 525 tydAnim.setDuration(100); 526 bounce.play(tyuAnim).before(tydAnim); 527 bounce.setInterpolator(new AccelerateInterpolator()); 528 bounce.start(); 529 } 530 } 531 532 public boolean onKey(View v, int keyCode, KeyEvent event) { 533 return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event); 534 } 535 536 /* 537 * PagedViewWithDraggableItems implementation 538 */ 539 @Override 540 protected void determineDraggingStart(android.view.MotionEvent ev) { 541 // Disable dragging by pulling an app down for now. 542 } 543 544 private void beginDraggingApplication(View v) { 545 mLauncher.getWorkspace().onDragStartedWithItem(v); 546 mLauncher.getWorkspace().beginDragShared(v, this); 547 } 548 549 private void loadWidgetInBackground(final PendingAddWidgetInfo info) { 550 final AppWidgetProviderInfo pInfo = info.info; 551 if (pInfo.configure != null) { 552 return; 553 } 554 555 mBindWidgetRunnable = new Runnable() { 556 @Override 557 public void run() { 558 mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); 559 AppWidgetManager.getInstance(mLauncher).bindAppWidgetId(mWidgetLoadingId, 560 info.componentName); 561 mWidgetCleanupState = WIDGET_BOUND; 562 } 563 }; 564 post(mBindWidgetRunnable); 565 566 mInflateWidgetRunnable = new Runnable() { 567 @Override 568 public void run() { 569 AppWidgetHostView hostView = 570 mLauncher.getAppWidgetHost().createView(mContext, mWidgetLoadingId, pInfo); 571 info.boundWidget = hostView; 572 mWidgetCleanupState = WIDGET_INFLATED; 573 hostView.setVisibility(INVISIBLE); 574 mLauncher.getDragLayer().addView(hostView); 575 } 576 }; 577 post(mInflateWidgetRunnable); 578 } 579 580 @Override 581 public void onShortPress(View v) { 582 // We are anticipating a long press, and we use this time to load bind and instantiate 583 // the widget. This will need to be cleaned up if it turns out no long press occurs. 584 mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); 585 loadWidgetInBackground(mCreateWidgetInfo); 586 } 587 588 @Override 589 public void cleanUpShortPress(View v) { 590 PendingAddWidgetInfo info = mCreateWidgetInfo; 591 mCreateWidgetInfo = null; 592 if (mWidgetCleanupState >= 0 && mWidgetLoadingId != -1) { 593 mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); 594 } 595 if (mWidgetCleanupState == WIDGET_BOUND) { 596 removeCallbacks(mInflateWidgetRunnable); 597 } else if (mWidgetCleanupState == WIDGET_INFLATED) { 598 AppWidgetHostView widget = info.boundWidget; 599 int widgetId = widget.getAppWidgetId(); 600 mLauncher.getAppWidgetHost().deleteAppWidgetId(widgetId); 601 mLauncher.getDragLayer().removeView(widget); 602 } 603 mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; 604 mWidgetLoadingId = -1; 605 } 606 607 private void beginDraggingWidget(View v) { 608 mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; 609 mWidgetLoadingId = -1; 610 611 // Get the widget preview as the drag representation 612 ImageView image = (ImageView) v.findViewById(R.id.widget_preview); 613 PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); 614 615 // Compose the drag image 616 Bitmap preview; 617 Bitmap outline; 618 if (createItemInfo instanceof PendingAddWidgetInfo) { 619 PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo; 620 createItemInfo = createWidgetInfo; 621 mCreateWidgetInfo = null; 622 int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null); 623 int[] size = mLauncher.getWorkspace().estimateItemSize(spanXY[0], 624 spanXY[1], createWidgetInfo, true); 625 createItemInfo.spanX = spanXY[0]; 626 createItemInfo.spanY = spanXY[1]; 627 628 preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage, 629 createWidgetInfo.icon, spanXY[0], spanXY[1], size[0], size[1]); 630 } else { 631 // Workaround for the fact that we don't keep the original ResolveInfo associated with 632 // the shortcut around. To get the icon, we just render the preview image (which has 633 // the shortcut icon) to a new drag bitmap that clips the non-icon space. 634 preview = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension, 635 mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888); 636 Drawable d = image.getDrawable(); 637 mCanvas.setBitmap(preview); 638 d.draw(mCanvas); 639 mCanvas.setBitmap(null); 640 createItemInfo.spanX = createItemInfo.spanY = 1; 641 } 642 643 // We use a custom alpha clip table for the default widget previews 644 Paint alphaClipPaint = null; 645 if (createItemInfo instanceof PendingAddWidgetInfo) { 646 if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) { 647 MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255); 648 alphaClipPaint = new Paint(); 649 alphaClipPaint.setMaskFilter(alphaClipTable); 650 } 651 } 652 653 // Save the preview for the outline generation, then dim the preview 654 outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(), 655 false); 656 657 // Start the drag 658 alphaClipPaint = null; 659 mLauncher.lockScreenOrientationOnLargeUI(); 660 mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint); 661 mDragController.startDrag(image, preview, this, createItemInfo, 662 DragController.DRAG_ACTION_COPY, null); 663 outline.recycle(); 664 preview.recycle(); 665 } 666 667 @Override 668 protected boolean beginDragging(final View v) { 669 if (!super.beginDragging(v)) return false; 670 671 if (v instanceof PagedViewIcon) { 672 beginDraggingApplication(v); 673 } else if (v instanceof PagedViewWidget) { 674 beginDraggingWidget(v); 675 } 676 677 // We delay entering spring-loaded mode slightly to make sure the UI 678 // thready is free of any work. 679 postDelayed(new Runnable() { 680 @Override 681 public void run() { 682 // We don't enter spring-loaded mode if the drag has been cancelled 683 if (mLauncher.getDragController().isDragging()) { 684 // Dismiss the cling 685 mLauncher.dismissAllAppsCling(null); 686 687 // Reset the alpha on the dragged icon before we drag 688 resetDrawableState(); 689 690 // Go into spring loaded mode (must happen before we startDrag()) 691 mLauncher.enterSpringLoadedDragMode(); 692 } 693 } 694 },150); 695 696 return true; 697 } 698 699 private void endDragging(View target, boolean success) { 700 mLauncher.getWorkspace().onDragStopped(success); 701 if (!success || (target != mLauncher.getWorkspace() && 702 !(target instanceof DeleteDropTarget))) { 703 // Exit spring loaded mode if we have not successfully dropped or have not handled the 704 // drop in Workspace 705 mLauncher.exitSpringLoadedDragMode(); 706 } 707 mLauncher.unlockScreenOrientationOnLargeUI(); 708 } 709 710 @Override 711 public void onDropCompleted(View target, DragObject d, boolean success) { 712 endDragging(target, success); 713 714 // Display an error message if the drag failed due to there not being enough space on the 715 // target layout we were dropping on. 716 if (!success) { 717 boolean showOutOfSpaceMessage = false; 718 if (target instanceof Workspace) { 719 int currentScreen = mLauncher.getCurrentWorkspaceScreen(); 720 Workspace workspace = (Workspace) target; 721 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); 722 ItemInfo itemInfo = (ItemInfo) d.dragInfo; 723 if (layout != null) { 724 layout.calculateSpans(itemInfo); 725 showOutOfSpaceMessage = 726 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); 727 } 728 } 729 if (showOutOfSpaceMessage) { 730 mLauncher.showOutOfSpaceMessage(); 731 } 732 d.deferDragViewCleanupPostAnimation = false; 733 } 734 } 735 736 @Override 737 protected void onDetachedFromWindow() { 738 super.onDetachedFromWindow(); 739 cancelAllTasks(); 740 } 741 742 public void clearAllWidgetPages() { 743 cancelAllTasks(); 744 int count = getChildCount(); 745 for (int i = 0; i < count; i++) { 746 View v = getPageAt(i); 747 if (v instanceof PagedViewGridLayout) { 748 ((PagedViewGridLayout) v).removeAllViewsOnPage(); 749 mDirtyPageContent.set(i, true); 750 } 751 } 752 } 753 754 private void cancelAllTasks() { 755 // Clean up all the async tasks 756 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 757 while (iter.hasNext()) { 758 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 759 task.cancel(false); 760 iter.remove(); 761 } 762 } 763 764 public void setContentType(ContentType type) { 765 if (type == ContentType.Widgets) { 766 invalidatePageData(mNumAppsPages, true); 767 } else if (type == ContentType.Applications) { 768 invalidatePageData(0, true); 769 } 770 } 771 772 protected void snapToPage(int whichPage, int delta, int duration) { 773 super.snapToPage(whichPage, delta, duration); 774 updateCurrentTab(whichPage); 775 776 // Update the thread priorities given the direction lookahead 777 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 778 while (iter.hasNext()) { 779 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 780 int pageIndex = task.page + mNumAppsPages; 781 if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) || 782 (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) { 783 task.setThreadPriority(getThreadPriorityForPage(pageIndex)); 784 } else { 785 task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); 786 } 787 } 788 } 789 790 private void updateCurrentTab(int currentPage) { 791 AppsCustomizeTabHost tabHost = getTabHost(); 792 if (tabHost != null) { 793 String tag = tabHost.getCurrentTabTag(); 794 if (tag != null) { 795 if (currentPage >= mNumAppsPages && 796 !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) { 797 tabHost.setCurrentTabFromContent(ContentType.Widgets); 798 } else if (currentPage < mNumAppsPages && 799 !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { 800 tabHost.setCurrentTabFromContent(ContentType.Applications); 801 } 802 } 803 } 804 } 805 806 /* 807 * Apps PagedView implementation 808 */ 809 private void setVisibilityOnChildren(ViewGroup layout, int visibility) { 810 int childCount = layout.getChildCount(); 811 for (int i = 0; i < childCount; ++i) { 812 layout.getChildAt(i).setVisibility(visibility); 813 } 814 } 815 private void setupPage(PagedViewCellLayout layout) { 816 layout.setCellCount(mCellCountX, mCellCountY); 817 layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); 818 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 819 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 820 821 // Note: We force a measure here to get around the fact that when we do layout calculations 822 // immediately after syncing, we don't have a proper width. That said, we already know the 823 // expected page width, so we can actually optimize by hiding all the TextView-based 824 // children that are expensive to measure, and let that happen naturally later. 825 setVisibilityOnChildren(layout, View.GONE); 826 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 827 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 828 layout.setMinimumWidth(getPageContentWidth()); 829 layout.measure(widthSpec, heightSpec); 830 setVisibilityOnChildren(layout, View.VISIBLE); 831 } 832 833 public void syncAppsPageItems(int page, boolean immediate) { 834 // ensure that we have the right number of items on the pages 835 int numCells = mCellCountX * mCellCountY; 836 int startIndex = page * numCells; 837 int endIndex = Math.min(startIndex + numCells, mApps.size()); 838 PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page); 839 840 layout.removeAllViewsOnPage(); 841 ArrayList<Object> items = new ArrayList<Object>(); 842 ArrayList<Bitmap> images = new ArrayList<Bitmap>(); 843 for (int i = startIndex; i < endIndex; ++i) { 844 ApplicationInfo info = mApps.get(i); 845 PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( 846 R.layout.apps_customize_application, layout, false); 847 icon.applyFromApplicationInfo(info, true, this); 848 icon.setOnClickListener(this); 849 icon.setOnLongClickListener(this); 850 icon.setOnTouchListener(this); 851 icon.setOnKeyListener(this); 852 853 int index = i - startIndex; 854 int x = index % mCellCountX; 855 int y = index / mCellCountX; 856 layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); 857 858 items.add(info); 859 images.add(info.iconBitmap); 860 } 861 862 layout.createHardwareLayers(); 863 } 864 865 /** 866 * A helper to return the priority for loading of the specified widget page. 867 */ 868 private int getWidgetPageLoadPriority(int page) { 869 // If we are snapping to another page, use that index as the target page index 870 int toPage = mCurrentPage; 871 if (mNextPage > -1) { 872 toPage = mNextPage; 873 } 874 875 // We use the distance from the target page as an initial guess of priority, but if there 876 // are no pages of higher priority than the page specified, then bump up the priority of 877 // the specified page. 878 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 879 int minPageDiff = Integer.MAX_VALUE; 880 while (iter.hasNext()) { 881 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 882 minPageDiff = Math.abs(task.page + mNumAppsPages - toPage); 883 } 884 885 int rawPageDiff = Math.abs(page - toPage); 886 return rawPageDiff - Math.min(rawPageDiff, minPageDiff); 887 } 888 /** 889 * Return the appropriate thread priority for loading for a given page (we give the current 890 * page much higher priority) 891 */ 892 private int getThreadPriorityForPage(int page) { 893 // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below 894 int pageDiff = getWidgetPageLoadPriority(page); 895 if (pageDiff <= 0) { 896 return Process.THREAD_PRIORITY_LESS_FAVORABLE; 897 } else if (pageDiff <= 1) { 898 return Process.THREAD_PRIORITY_LOWEST; 899 } else { 900 return Process.THREAD_PRIORITY_LOWEST; 901 } 902 } 903 private int getSleepForPage(int page) { 904 int pageDiff = getWidgetPageLoadPriority(page); 905 return Math.max(0, pageDiff * sPageSleepDelay); 906 } 907 /** 908 * Creates and executes a new AsyncTask to load a page of widget previews. 909 */ 910 private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets, 911 int cellWidth, int cellHeight, int cellCountX) { 912 913 // Prune all tasks that are no longer needed 914 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 915 while (iter.hasNext()) { 916 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 917 int taskPage = task.page + mNumAppsPages; 918 if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || 919 taskPage > getAssociatedUpperPageBound(mCurrentPage)) { 920 task.cancel(false); 921 iter.remove(); 922 } else { 923 task.setThreadPriority(getThreadPriorityForPage(taskPage)); 924 } 925 } 926 927 // We introduce a slight delay to order the loading of side pages so that we don't thrash 928 final int sleepMs = getSleepForPage(page + mNumAppsPages); 929 AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, 930 new AsyncTaskCallback() { 931 @Override 932 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 933 try { 934 try { 935 Thread.sleep(sleepMs); 936 } catch (Exception e) {} 937 loadWidgetPreviewsInBackground(task, data); 938 } finally { 939 if (task.isCancelled()) { 940 data.cleanup(true); 941 } 942 } 943 } 944 }, 945 new AsyncTaskCallback() { 946 @Override 947 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { 948 try { 949 mRunningTasks.remove(task); 950 if (task.isCancelled()) return; 951 onSyncWidgetPageItems(data); 952 } finally { 953 data.cleanup(task.isCancelled()); 954 } 955 } 956 }); 957 958 // Ensure that the task is appropriately prioritized and runs in parallel 959 AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, 960 AsyncTaskPageData.Type.LoadWidgetPreviewData); 961 t.setThreadPriority(getThreadPriorityForPage(page + mNumAppsPages)); 962 t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); 963 mRunningTasks.add(t); 964 } 965 966 /* 967 * Widgets PagedView implementation 968 */ 969 private void setupPage(PagedViewGridLayout layout) { 970 layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, 971 mPageLayoutPaddingRight, mPageLayoutPaddingBottom); 972 973 // Note: We force a measure here to get around the fact that when we do layout calculations 974 // immediately after syncing, we don't have a proper width. 975 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 976 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 977 layout.setMinimumWidth(getPageContentWidth()); 978 layout.measure(widthSpec, heightSpec); 979 } 980 981 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { 982 renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f, 0xFFFFFFFF); 983 } 984 985 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 986 float scale, int multiplyColor) { 987 if (bitmap != null) { 988 Canvas c = new Canvas(bitmap); 989 c.scale(scale, scale); 990 Rect oldBounds = d.copyBounds(); 991 d.setBounds(x, y, x + w, y + h); 992 d.draw(c); 993 d.setBounds(oldBounds); // Restore the bounds 994 c.setBitmap(null); 995 } 996 } 997 private Bitmap getShortcutPreview(ResolveInfo info) { 998 // Render the background 999 int offset = 0; 1000 int bitmapSize = mAppIconSize; 1001 Bitmap preview = Bitmap.createBitmap(bitmapSize, bitmapSize, Config.ARGB_8888); 1002 1003 // Render the icon 1004 Drawable icon = mIconCache.getFullResIcon(info); 1005 renderDrawableToBitmap(icon, preview, offset, offset, mAppIconSize, mAppIconSize); 1006 return preview; 1007 } 1008 1009 private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId, 1010 int cellHSpan, int cellVSpan, int maxWidth, int maxHeight) { 1011 // Load the preview image if possible 1012 String packageName = provider.getPackageName(); 1013 if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; 1014 if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; 1015 1016 Drawable drawable = null; 1017 if (previewImage != 0) { 1018 drawable = mPackageManager.getDrawable(packageName, previewImage, null); 1019 if (drawable == null) { 1020 Log.w(LOG_TAG, "Can't load widget preview drawable 0x" + 1021 Integer.toHexString(previewImage) + " for provider: " + provider); 1022 } 1023 } 1024 1025 int bitmapWidth; 1026 int bitmapHeight; 1027 boolean widgetPreviewExists = (drawable != null); 1028 if (widgetPreviewExists) { 1029 bitmapWidth = drawable.getIntrinsicWidth(); 1030 bitmapHeight = drawable.getIntrinsicHeight(); 1031 1032 // Cap the size so widget previews don't appear larger than the actual widget 1033 maxWidth = Math.min(maxWidth, mWidgetSpacingLayout.estimateCellWidth(cellHSpan)); 1034 maxHeight = Math.min(maxHeight, mWidgetSpacingLayout.estimateCellHeight(cellVSpan)); 1035 } else { 1036 // Determine the size of the bitmap for the preview image we will generate 1037 // TODO: This actually uses the apps customize cell layout params, where as we make want 1038 // the Workspace params for more accuracy. 1039 bitmapWidth = mWidgetSpacingLayout.estimateCellWidth(cellHSpan); 1040 bitmapHeight = mWidgetSpacingLayout.estimateCellHeight(cellVSpan); 1041 if (cellHSpan == cellVSpan) { 1042 // For square widgets, we just have a fixed size for 1x1 and larger-than-1x1 1043 int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); 1044 if (cellHSpan <= 1) { 1045 bitmapWidth = bitmapHeight = mAppIconSize + 2 * minOffset; 1046 } else { 1047 bitmapWidth = bitmapHeight = mAppIconSize + 4 * minOffset; 1048 } 1049 } 1050 } 1051 1052 float scale = 1f; 1053 if (bitmapWidth > maxWidth) { 1054 scale = maxWidth / (float) bitmapWidth; 1055 } 1056 if (bitmapHeight * scale > maxHeight) { 1057 scale = maxHeight / (float) bitmapHeight; 1058 } 1059 if (scale != 1f) { 1060 bitmapWidth = (int) (scale * bitmapWidth); 1061 bitmapHeight = (int) (scale * bitmapHeight); 1062 } 1063 1064 Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888); 1065 1066 if (widgetPreviewExists) { 1067 renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, bitmapHeight); 1068 } else { 1069 // Generate a preview image if we couldn't load one 1070 int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); 1071 int smallestSide = Math.min(bitmapWidth, bitmapHeight); 1072 float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), 1f); 1073 if (cellHSpan != 1 || cellVSpan != 1) { 1074 renderDrawableToBitmap(mDefaultWidgetBackground, preview, 0, 0, bitmapWidth, 1075 bitmapHeight); 1076 } 1077 1078 // Draw the icon in the top left corner 1079 try { 1080 Drawable icon = null; 1081 int hoffset = (int) (bitmapWidth / 2 - mAppIconSize * iconScale / 2); 1082 int yoffset = (int) (bitmapHeight / 2 - mAppIconSize * iconScale / 2); 1083 if (iconId > 0) icon = mIconCache.getFullResIcon(packageName, iconId); 1084 Resources resources = mLauncher.getResources(); 1085 if (icon == null) icon = resources.getDrawable(R.drawable.ic_launcher_application); 1086 1087 renderDrawableToBitmap(icon, preview, hoffset, yoffset, 1088 (int) (mAppIconSize * iconScale), 1089 (int) (mAppIconSize * iconScale)); 1090 } catch (Resources.NotFoundException e) {} 1091 } 1092 return preview; 1093 } 1094 1095 public void syncWidgetPageItems(final int page, final boolean immediate) { 1096 int numItemsPerPage = mWidgetCountX * mWidgetCountY; 1097 1098 // Calculate the dimensions of each cell we are giving to each widget 1099 final ArrayList<Object> items = new ArrayList<Object>(); 1100 int contentWidth = mWidgetSpacingLayout.getContentWidth(); 1101 final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight 1102 - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); 1103 int contentHeight = mWidgetSpacingLayout.getContentHeight(); 1104 final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom 1105 - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); 1106 1107 // Prepare the set of widgets to load previews for in the background 1108 int offset = page * numItemsPerPage; 1109 for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) { 1110 items.add(mWidgets.get(i)); 1111 } 1112 1113 // Prepopulate the pages with the other widget info, and fill in the previews later 1114 final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages); 1115 layout.setColumnCount(layout.getCellCountX()); 1116 for (int i = 0; i < items.size(); ++i) { 1117 Object rawInfo = items.get(i); 1118 PendingAddItemInfo createItemInfo = null; 1119 PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( 1120 R.layout.apps_customize_widget, layout, false); 1121 if (rawInfo instanceof AppWidgetProviderInfo) { 1122 // Fill in the widget information 1123 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 1124 createItemInfo = new PendingAddWidgetInfo(info, null, null); 1125 int[] cellSpans = mLauncher.getSpanForWidget(info, null); 1126 widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans); 1127 widget.setTag(createItemInfo); 1128 widget.setShortPressListener(this); 1129 } else if (rawInfo instanceof ResolveInfo) { 1130 // Fill in the shortcuts information 1131 ResolveInfo info = (ResolveInfo) rawInfo; 1132 createItemInfo = new PendingAddItemInfo(); 1133 createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 1134 createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, 1135 info.activityInfo.name); 1136 widget.applyFromResolveInfo(mPackageManager, info); 1137 widget.setTag(createItemInfo); 1138 } 1139 widget.setOnClickListener(this); 1140 widget.setOnLongClickListener(this); 1141 widget.setOnTouchListener(this); 1142 widget.setOnKeyListener(this); 1143 1144 // Layout each widget 1145 int ix = i % mWidgetCountX; 1146 int iy = i / mWidgetCountX; 1147 GridLayout.LayoutParams lp = new GridLayout.LayoutParams( 1148 GridLayout.spec(iy, GridLayout.LEFT), 1149 GridLayout.spec(ix, GridLayout.TOP)); 1150 lp.width = cellWidth; 1151 lp.height = cellHeight; 1152 lp.setGravity(Gravity.TOP | Gravity.LEFT); 1153 if (ix > 0) lp.leftMargin = mWidgetWidthGap; 1154 if (iy > 0) lp.topMargin = mWidgetHeightGap; 1155 layout.addView(widget, lp); 1156 } 1157 1158 // wait until a call on onLayout to start loading, because 1159 // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out 1160 // TODO: can we do a measure/layout immediately? 1161 layout.setOnLayoutListener(new Runnable() { 1162 public void run() { 1163 // Load the widget previews 1164 int maxPreviewWidth = cellWidth; 1165 int maxPreviewHeight = cellHeight; 1166 if (layout.getChildCount() > 0) { 1167 PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0); 1168 int[] maxSize = w.getPreviewSize(); 1169 maxPreviewWidth = maxSize[0]; 1170 maxPreviewHeight = maxSize[1]; 1171 } 1172 if (immediate) { 1173 AsyncTaskPageData data = new AsyncTaskPageData(page, items, 1174 maxPreviewWidth, maxPreviewHeight, null, null); 1175 loadWidgetPreviewsInBackground(null, data); 1176 onSyncWidgetPageItems(data); 1177 } else { 1178 prepareLoadWidgetPreviewsTask(page, items, 1179 maxPreviewWidth, maxPreviewHeight, mWidgetCountX); 1180 } 1181 } 1182 }); 1183 } 1184 private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, 1185 AsyncTaskPageData data) { 1186 // loadWidgetPreviewsInBackground can be called without a task to load a set of widget 1187 // previews synchronously 1188 if (task != null) { 1189 // Ensure that this task starts running at the correct priority 1190 task.syncThreadPriority(); 1191 } 1192 1193 // Load each of the widget/shortcut previews 1194 ArrayList<Object> items = data.items; 1195 ArrayList<Bitmap> images = data.generatedImages; 1196 int count = items.size(); 1197 for (int i = 0; i < count; ++i) { 1198 if (task != null) { 1199 // Ensure we haven't been cancelled yet 1200 if (task.isCancelled()) break; 1201 // Before work on each item, ensure that this task is running at the correct 1202 // priority 1203 task.syncThreadPriority(); 1204 } 1205 1206 Object rawInfo = items.get(i); 1207 if (rawInfo instanceof AppWidgetProviderInfo) { 1208 AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; 1209 int[] cellSpans = mLauncher.getSpanForWidget(info, null); 1210 Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon, 1211 cellSpans[0], cellSpans[1], data.maxImageWidth, data.maxImageHeight); 1212 images.add(b); 1213 } else if (rawInfo instanceof ResolveInfo) { 1214 // Fill in the shortcuts information 1215 ResolveInfo info = (ResolveInfo) rawInfo; 1216 images.add(getShortcutPreview(info)); 1217 } 1218 } 1219 } 1220 private void onSyncWidgetPageItems(AsyncTaskPageData data) { 1221 int page = data.page; 1222 PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages); 1223 1224 ArrayList<Object> items = data.items; 1225 int count = items.size(); 1226 for (int i = 0; i < count; ++i) { 1227 PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); 1228 if (widget != null) { 1229 Bitmap preview = data.generatedImages.get(i); 1230 widget.applyPreview(new FastBitmapDrawable(preview), i); 1231 } 1232 } 1233 1234 layout.createHardwareLayer(); 1235 invalidate(); 1236 1237 // Update all thread priorities 1238 Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); 1239 while (iter.hasNext()) { 1240 AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); 1241 int pageIndex = task.page + mNumAppsPages; 1242 task.setThreadPriority(getThreadPriorityForPage(pageIndex)); 1243 } 1244 } 1245 1246 @Override 1247 public void syncPages() { 1248 removeAllViews(); 1249 cancelAllTasks(); 1250 1251 Context context = getContext(); 1252 for (int j = 0; j < mNumWidgetPages; ++j) { 1253 PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, 1254 mWidgetCountY); 1255 setupPage(layout); 1256 addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT, 1257 LayoutParams.MATCH_PARENT)); 1258 } 1259 1260 for (int i = 0; i < mNumAppsPages; ++i) { 1261 PagedViewCellLayout layout = new PagedViewCellLayout(context); 1262 setupPage(layout); 1263 addView(layout); 1264 } 1265 } 1266 1267 @Override 1268 public void syncPageItems(int page, boolean immediate) { 1269 if (page < mNumAppsPages) { 1270 syncAppsPageItems(page, immediate); 1271 } else { 1272 syncWidgetPageItems(page - mNumAppsPages, immediate); 1273 } 1274 } 1275 1276 // We want our pages to be z-ordered such that the further a page is to the left, the higher 1277 // it is in the z-order. This is important to insure touch events are handled correctly. 1278 View getPageAt(int index) { 1279 return getChildAt(getChildCount() - index - 1); 1280 } 1281 1282 @Override 1283 protected int indexToPage(int index) { 1284 return getChildCount() - index - 1; 1285 } 1286 1287 // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. 1288 @Override 1289 protected void screenScrolled(int screenCenter) { 1290 super.screenScrolled(screenCenter); 1291 1292 for (int i = 0; i < getChildCount(); i++) { 1293 View v = getPageAt(i); 1294 if (v != null) { 1295 float scrollProgress = getScrollProgress(screenCenter, v, i); 1296 1297 float interpolatedProgress = 1298 mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); 1299 float scale = (1 - interpolatedProgress) + 1300 interpolatedProgress * TRANSITION_SCALE_FACTOR; 1301 float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); 1302 1303 float alpha; 1304 1305 if (!LauncherApplication.isScreenLarge() || scrollProgress < 0) { 1306 alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( 1307 1 - Math.abs(scrollProgress)) : 1.0f; 1308 } else { 1309 // On large screens we need to fade the page as it nears its leftmost position 1310 alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); 1311 } 1312 1313 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 1314 int pageWidth = v.getMeasuredWidth(); 1315 int pageHeight = v.getMeasuredHeight(); 1316 1317 if (PERFORM_OVERSCROLL_ROTATION) { 1318 if (i == 0 && scrollProgress < 0) { 1319 // Overscroll to the left 1320 v.setPivotX(TRANSITION_PIVOT * pageWidth); 1321 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1322 scale = 1.0f; 1323 alpha = 1.0f; 1324 // On the first page, we don't want the page to have any lateral motion 1325 translationX = 0; 1326 } else if (i == getChildCount() - 1 && scrollProgress > 0) { 1327 // Overscroll to the right 1328 v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); 1329 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 1330 scale = 1.0f; 1331 alpha = 1.0f; 1332 // On the last page, we don't want the page to have any lateral motion. 1333 translationX = 0; 1334 } else { 1335 v.setPivotY(pageHeight / 2.0f); 1336 v.setPivotX(pageWidth / 2.0f); 1337 v.setRotationY(0f); 1338 } 1339 } 1340 1341 v.setTranslationX(translationX); 1342 v.setScaleX(scale); 1343 v.setScaleY(scale); 1344 v.setAlpha(alpha); 1345 1346 // If the view has 0 alpha, we set it to be invisible so as to prevent 1347 // it from accepting touches 1348 if (alpha < ViewConfiguration.ALPHA_THRESHOLD) { 1349 v.setVisibility(INVISIBLE); 1350 } else if (v.getVisibility() != VISIBLE) { 1351 v.setVisibility(VISIBLE); 1352 } 1353 } 1354 } 1355 } 1356 1357 protected void overScroll(float amount) { 1358 acceleratedOverScroll(amount); 1359 } 1360 1361 /** 1362 * Used by the parent to get the content width to set the tab bar to 1363 * @return 1364 */ 1365 public int getPageContentWidth() { 1366 return mContentWidth; 1367 } 1368 1369 @Override 1370 protected void onPageEndMoving() { 1371 super.onPageEndMoving(); 1372 1373 // We reset the save index when we change pages so that it will be recalculated on next 1374 // rotation 1375 mSaveInstanceStateItemIndex = -1; 1376 } 1377 1378 /* 1379 * AllAppsView implementation 1380 */ 1381 @Override 1382 public void setup(Launcher launcher, DragController dragController) { 1383 mLauncher = launcher; 1384 mDragController = dragController; 1385 } 1386 @Override 1387 public void zoom(float zoom, boolean animate) { 1388 // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() 1389 } 1390 @Override 1391 public boolean isVisible() { 1392 return (getVisibility() == VISIBLE); 1393 } 1394 @Override 1395 public boolean isAnimating() { 1396 return false; 1397 } 1398 @Override 1399 public void setApps(ArrayList<ApplicationInfo> list) { 1400 mApps = list; 1401 Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); 1402 updatePageCounts(); 1403 1404 // The next layout pass will trigger data-ready if both widgets and apps are set, so 1405 // request a layout to do this test and invalidate the page data when ready. 1406 if (testDataReady()) requestLayout(); 1407 } 1408 private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1409 // We add it in place, in alphabetical order 1410 int count = list.size(); 1411 for (int i = 0; i < count; ++i) { 1412 ApplicationInfo info = list.get(i); 1413 int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); 1414 if (index < 0) { 1415 mApps.add(-(index + 1), info); 1416 } 1417 } 1418 } 1419 @Override 1420 public void addApps(ArrayList<ApplicationInfo> list) { 1421 addAppsWithoutInvalidate(list); 1422 updatePageCounts(); 1423 invalidatePageData(); 1424 } 1425 private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { 1426 ComponentName removeComponent = item.intent.getComponent(); 1427 int length = list.size(); 1428 for (int i = 0; i < length; ++i) { 1429 ApplicationInfo info = list.get(i); 1430 if (info.intent.getComponent().equals(removeComponent)) { 1431 return i; 1432 } 1433 } 1434 return -1; 1435 } 1436 private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { 1437 // loop through all the apps and remove apps that have the same component 1438 int length = list.size(); 1439 for (int i = 0; i < length; ++i) { 1440 ApplicationInfo info = list.get(i); 1441 int removeIndex = findAppByComponent(mApps, info); 1442 if (removeIndex > -1) { 1443 mApps.remove(removeIndex); 1444 } 1445 } 1446 } 1447 @Override 1448 public void removeApps(ArrayList<ApplicationInfo> list) { 1449 removeAppsWithoutInvalidate(list); 1450 updatePageCounts(); 1451 invalidatePageData(); 1452 } 1453 @Override 1454 public void updateApps(ArrayList<ApplicationInfo> list) { 1455 // We remove and re-add the updated applications list because it's properties may have 1456 // changed (ie. the title), and this will ensure that the items will be in their proper 1457 // place in the list. 1458 removeAppsWithoutInvalidate(list); 1459 addAppsWithoutInvalidate(list); 1460 updatePageCounts(); 1461 1462 invalidatePageData(); 1463 } 1464 1465 @Override 1466 public void reset() { 1467 // If we have reset, then we should not continue to restore the previous state 1468 mSaveInstanceStateItemIndex = -1; 1469 1470 AppsCustomizeTabHost tabHost = getTabHost(); 1471 String tag = tabHost.getCurrentTabTag(); 1472 if (tag != null) { 1473 if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { 1474 tabHost.setCurrentTabFromContent(ContentType.Applications); 1475 } 1476 } 1477 1478 if (mCurrentPage != 0) { 1479 invalidatePageData(0); 1480 } 1481 } 1482 1483 private AppsCustomizeTabHost getTabHost() { 1484 return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane); 1485 } 1486 1487 @Override 1488 public void dumpState() { 1489 // TODO: Dump information related to current list of Applications, Widgets, etc. 1490 ApplicationInfo.dumpApplicationInfoList(LOG_TAG, "mApps", mApps); 1491 dumpAppWidgetProviderInfoList(LOG_TAG, "mWidgets", mWidgets); 1492 } 1493 1494 private void dumpAppWidgetProviderInfoList(String tag, String label, 1495 ArrayList<Object> list) { 1496 Log.d(tag, label + " size=" + list.size()); 1497 for (Object i: list) { 1498 if (i instanceof AppWidgetProviderInfo) { 1499 AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; 1500 Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage 1501 + " resizeMode=" + info.resizeMode + " configure=" + info.configure 1502 + " initialLayout=" + info.initialLayout 1503 + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); 1504 } else if (i instanceof ResolveInfo) { 1505 ResolveInfo info = (ResolveInfo) i; 1506 Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" 1507 + info.icon); 1508 } 1509 } 1510 } 1511 1512 @Override 1513 public void surrender() { 1514 // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we 1515 // should stop this now. 1516 1517 // Stop all background tasks 1518 cancelAllTasks(); 1519 } 1520 1521 @Override 1522 public void iconPressed(PagedViewIcon icon) { 1523 // Reset the previously pressed icon and store a reference to the pressed icon so that 1524 // we can reset it on return to Launcher (in Launcher.onResume()) 1525 if (mPressedIcon != null) { 1526 mPressedIcon.resetDrawableState(); 1527 } 1528 mPressedIcon = icon; 1529 } 1530 1531 public void resetDrawableState() { 1532 if (mPressedIcon != null) { 1533 mPressedIcon.resetDrawableState(); 1534 mPressedIcon = null; 1535 } 1536 } 1537 1538 /* 1539 * We load an extra page on each side to prevent flashes from scrolling and loading of the 1540 * widget previews in the background with the AsyncTasks. 1541 */ 1542 final static int sLookBehindPageCount = 2; 1543 final static int sLookAheadPageCount = 2; 1544 protected int getAssociatedLowerPageBound(int page) { 1545 final int count = getChildCount(); 1546 int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); 1547 int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0); 1548 return windowMinIndex; 1549 } 1550 protected int getAssociatedUpperPageBound(int page) { 1551 final int count = getChildCount(); 1552 int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); 1553 int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1), 1554 count - 1); 1555 return windowMaxIndex; 1556 } 1557 1558 @Override 1559 protected String getCurrentPageDescription() { 1560 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 1561 int stringId = R.string.default_scroll_format; 1562 int count = 0; 1563 1564 if (page < mNumAppsPages) { 1565 stringId = R.string.apps_customize_apps_scroll_format; 1566 count = mNumAppsPages; 1567 } else { 1568 page -= mNumAppsPages; 1569 stringId = R.string.apps_customize_widgets_scroll_format; 1570 count = mNumWidgetPages; 1571 } 1572 1573 return String.format(mContext.getString(stringId), page + 1, count); 1574 } 1575} 1576