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