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