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