1 /* 2 * Copyright (C) 2015 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.launcher3.widget; 18 19 import static android.view.View.MeasureSpec.makeMeasureSpec; 20 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 21 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 22 23 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; 24 import static com.android.launcher3.Utilities.ATLEAST_S; 25 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING; 26 27 import android.content.Context; 28 import android.graphics.Bitmap; 29 import android.graphics.drawable.Drawable; 30 import android.os.Process; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.util.Size; 34 import android.view.Gravity; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.ViewPropertyAnimator; 39 import android.view.accessibility.AccessibilityNodeInfo; 40 import android.widget.FrameLayout; 41 import android.widget.ImageView; 42 import android.widget.LinearLayout; 43 import android.widget.RemoteViews; 44 import android.widget.TextView; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.Nullable; 48 49 import com.android.launcher3.CheckLongPressHelper; 50 import com.android.launcher3.DeviceProfile; 51 import com.android.launcher3.Launcher; 52 import com.android.launcher3.R; 53 import com.android.launcher3.icons.FastBitmapDrawable; 54 import com.android.launcher3.icons.RoundDrawableWrapper; 55 import com.android.launcher3.icons.cache.HandlerRunnable; 56 import com.android.launcher3.model.WidgetItem; 57 import com.android.launcher3.views.ActivityContext; 58 import com.android.launcher3.widget.util.WidgetSizes; 59 60 import java.util.function.Consumer; 61 62 /** 63 * Represents the individual cell of the widget inside the widget tray. The preview is drawn 64 * horizontally centered, and scaled down if needed. 65 * 66 * This view does not support padding. Since the image is scaled down to fit the view, padding will 67 * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth 68 * transition from the view to drag view, so when adding padding support, DnD would need to 69 * consider the appropriate scaling factor. 70 */ 71 public class WidgetCell extends LinearLayout { 72 73 private static final String TAG = "WidgetCell"; 74 private static final boolean DEBUG = false; 75 76 private static final int FADE_IN_DURATION_MS = 90; 77 78 /** Widget cell width is calculated by multiplying this factor to grid cell width. */ 79 private static final float WIDTH_SCALE = 3f; 80 81 /** Widget preview width is calculated by multiplying this factor to the widget cell width. */ 82 private static final float PREVIEW_SCALE = 0.8f; 83 84 /** 85 * The maximum dimension that can be used as the size in 86 * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int)}. 87 * 88 * <p>This is equal to (1 << MeasureSpec.MODE_SHIFT) - 1. 89 */ 90 private static final int MAX_MEASURE_SPEC_DIMENSION = (1 << 30) - 1; 91 92 /** 93 * The target preview width, in pixels, of a widget or a shortcut. 94 * 95 * <p>The actual preview width may be smaller than or equal to this value subjected to scaling. 96 */ 97 protected int mTargetPreviewWidth; 98 99 /** 100 * The target preview height, in pixels, of a widget or a shortcut. 101 * 102 * <p>The actual preview height may be smaller than or equal to this value subjected to scaling. 103 */ 104 protected int mTargetPreviewHeight; 105 106 protected int mPresetPreviewSize; 107 108 private int mCellSize; 109 110 /** 111 * The scale of the preview container. 112 */ 113 private float mPreviewContainerScale = 1f; 114 115 private FrameLayout mWidgetImageContainer; 116 private WidgetImageView mWidgetImage; 117 private ImageView mWidgetBadge; 118 private TextView mWidgetName; 119 private TextView mWidgetDims; 120 private TextView mWidgetDescription; 121 122 protected WidgetItem mItem; 123 124 private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader; 125 126 protected HandlerRunnable mActiveRequest; 127 private boolean mAnimatePreview = true; 128 129 protected final ActivityContext mActivity; 130 private final CheckLongPressHelper mLongPressHelper; 131 private final float mEnforcedCornerRadius; 132 133 private RemoteViews mRemoteViewsPreview; 134 private NavigableAppWidgetHostView mAppWidgetHostViewPreview; 135 private float mAppWidgetHostViewScale = 1f; 136 private int mSourceContainer = CONTAINER_WIDGETS_TRAY; 137 WidgetCell(Context context)138 public WidgetCell(Context context) { 139 this(context, null); 140 } 141 WidgetCell(Context context, AttributeSet attrs)142 public WidgetCell(Context context, AttributeSet attrs) { 143 this(context, attrs, 0); 144 } 145 WidgetCell(Context context, AttributeSet attrs, int defStyle)146 public WidgetCell(Context context, AttributeSet attrs, int defStyle) { 147 super(context, attrs, defStyle); 148 149 mActivity = ActivityContext.lookupContext(context); 150 mWidgetPreviewLoader = new DatabaseWidgetPreviewLoader(context); 151 mLongPressHelper = new CheckLongPressHelper(this); 152 mLongPressHelper.setLongPressTimeoutFactor(1); 153 154 setContainerWidth(); 155 setWillNotDraw(false); 156 setClipToPadding(false); 157 setAccessibilityDelegate(mActivity.getAccessibilityDelegate()); 158 mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context); 159 } 160 setContainerWidth()161 private void setContainerWidth() { 162 mCellSize = (int) (mActivity.getDeviceProfile().allAppsIconSizePx * WIDTH_SCALE); 163 mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE); 164 mTargetPreviewWidth = mTargetPreviewHeight = mPresetPreviewSize; 165 } 166 167 @Override onFinishInflate()168 protected void onFinishInflate() { 169 super.onFinishInflate(); 170 171 mWidgetImageContainer = findViewById(R.id.widget_preview_container); 172 mWidgetImage = findViewById(R.id.widget_preview); 173 mWidgetBadge = findViewById(R.id.widget_badge); 174 mWidgetName = findViewById(R.id.widget_name); 175 mWidgetDims = findViewById(R.id.widget_dims); 176 mWidgetDescription = findViewById(R.id.widget_description); 177 } 178 setRemoteViewsPreview(RemoteViews view)179 public void setRemoteViewsPreview(RemoteViews view) { 180 mRemoteViewsPreview = view; 181 } 182 183 @Nullable getRemoteViewsPreview()184 public RemoteViews getRemoteViewsPreview() { 185 return mRemoteViewsPreview; 186 } 187 188 /** Returns the app widget host view scale, which is a value between [0f, 1f]. */ getAppWidgetHostViewScale()189 public float getAppWidgetHostViewScale() { 190 return mAppWidgetHostViewScale; 191 } 192 193 /** 194 * Called to clear the view and free attached resources. (e.g., {@link Bitmap} 195 */ clear()196 public void clear() { 197 if (DEBUG) { 198 Log.d(TAG, "reset called on:" + mWidgetName.getText()); 199 } 200 mWidgetImage.animate().cancel(); 201 mWidgetImage.setDrawable(null); 202 mWidgetImage.setVisibility(View.VISIBLE); 203 mWidgetBadge.setImageDrawable(null); 204 mWidgetBadge.setVisibility(View.GONE); 205 mWidgetName.setText(null); 206 mWidgetDims.setText(null); 207 mWidgetDescription.setText(null); 208 mWidgetDescription.setVisibility(GONE); 209 mTargetPreviewWidth = mTargetPreviewHeight = mPresetPreviewSize; 210 211 if (mActiveRequest != null) { 212 mActiveRequest.cancel(); 213 mActiveRequest = null; 214 } 215 mRemoteViewsPreview = null; 216 if (mAppWidgetHostViewPreview != null) { 217 mWidgetImageContainer.removeView(mAppWidgetHostViewPreview); 218 } 219 mAppWidgetHostViewPreview = null; 220 mAppWidgetHostViewScale = 1f; 221 mItem = null; 222 } 223 setSourceContainer(int sourceContainer)224 public void setSourceContainer(int sourceContainer) { 225 this.mSourceContainer = sourceContainer; 226 } 227 228 /** 229 * Applies the item to this view 230 */ applyFromCellItem(WidgetItem item)231 public void applyFromCellItem(WidgetItem item) { 232 applyFromCellItem(item, 1f); 233 } 234 235 /** 236 * Applies the item to this view 237 */ applyFromCellItem(WidgetItem item, float previewScale)238 public void applyFromCellItem(WidgetItem item, float previewScale) { 239 applyFromCellItem(item, previewScale, this::applyPreview, null); 240 } 241 242 /** 243 * Applies the item to this view 244 * @param item item to apply 245 * @param previewScale factor to scale the preview 246 * @param callback callback when preview is loaded in case the preview is being loaded or cached 247 * @param cachedPreview previously cached preview bitmap is present 248 */ applyFromCellItem(WidgetItem item, float previewScale, @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview)249 public void applyFromCellItem(WidgetItem item, float previewScale, 250 @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) { 251 // setPreviewSize 252 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 253 Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item); 254 mTargetPreviewWidth = widgetSize.getWidth(); 255 mTargetPreviewHeight = widgetSize.getHeight(); 256 mPreviewContainerScale = previewScale; 257 258 applyPreviewOnAppWidgetHostView(item); 259 260 Context context = getContext(); 261 mItem = item; 262 mWidgetName.setText(mItem.label); 263 mWidgetName.setContentDescription( 264 context.getString(R.string.widget_preview_context_description, mItem.label)); 265 mWidgetDims.setText(context.getString(R.string.widget_dims_format, 266 mItem.spanX, mItem.spanY)); 267 mWidgetDims.setContentDescription(context.getString( 268 R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY)); 269 if (ATLEAST_S && mItem.widgetInfo != null) { 270 CharSequence description = mItem.widgetInfo.loadDescription(context); 271 if (description != null && description.length() > 0) { 272 mWidgetDescription.setText(description); 273 mWidgetDescription.setVisibility(VISIBLE); 274 } else { 275 mWidgetDescription.setVisibility(GONE); 276 } 277 } 278 279 if (item.activityInfo != null) { 280 setTag(new PendingAddShortcutInfo(item.activityInfo)); 281 } else { 282 setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer)); 283 } 284 285 ensurePreviewWithCallback(callback, cachedPreview); 286 } 287 288 private static class ScaledAppWidgetHostView extends LauncherAppWidgetHostView { 289 private boolean mKeepOrigForDragging = true; 290 ScaledAppWidgetHostView(Context context)291 ScaledAppWidgetHostView(Context context) { 292 super(context); 293 } 294 295 /** 296 * Set if the view will keep its original scale when dragged 297 * @param isKeepOrig True if keep original scale when dragged, false otherwise 298 */ setKeepOrigForDragging(boolean isKeepOrig)299 public void setKeepOrigForDragging(boolean isKeepOrig) { 300 mKeepOrigForDragging = isKeepOrig; 301 } 302 303 /** 304 * @return True if the view is set to preserve original scale when dragged, false otherwise 305 */ isKeepOrigForDragging()306 public boolean isKeepOrigForDragging() { 307 return mKeepOrigForDragging; 308 } 309 310 @Override startDrag()311 public void startDrag() { 312 super.startDrag(); 313 if (!isKeepOrigForDragging()) { 314 // restore to original scale when being dragged, if set to do so 315 setScaleToFit(1.0f); 316 } 317 // When the drag start, translations need to be set to zero to center the view 318 getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING, 0f, 0f); 319 } 320 } 321 applyPreviewOnAppWidgetHostView(WidgetItem item)322 private void applyPreviewOnAppWidgetHostView(WidgetItem item) { 323 if (mRemoteViewsPreview != null) { 324 mAppWidgetHostViewPreview = createAppWidgetHostView(getContext()); 325 setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo, 326 mRemoteViewsPreview); 327 return; 328 } 329 330 if (!item.hasPreviewLayout()) return; 331 332 Context context = getContext(); 333 // If the context is a Launcher activity, DragView will show mAppWidgetHostViewPreview as 334 // a preview during drag & drop. And thus, we should use LauncherAppWidgetHostView, which 335 // supports applying local color extraction during drag & drop. 336 mAppWidgetHostViewPreview = isLauncherContext(context) 337 ? new ScaledAppWidgetHostView(context) 338 : createAppWidgetHostView(context); 339 LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo = 340 LauncherAppWidgetProviderInfo.fromProviderInfo(context, item.widgetInfo.clone()); 341 // A hack to force the initial layout to be the preview layout since there is no API for 342 // rendering a preview layout for work profile apps yet. For non-work profile layout, a 343 // proper solution is to use RemoteViews(PackageName, LayoutId). 344 launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout; 345 setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, 346 launcherAppWidgetProviderInfo, /* remoteViews= */ null); 347 } 348 setAppWidgetHostViewPreview( NavigableAppWidgetHostView appWidgetHostViewPreview, LauncherAppWidgetProviderInfo providerInfo, @Nullable RemoteViews remoteViews)349 private void setAppWidgetHostViewPreview( 350 NavigableAppWidgetHostView appWidgetHostViewPreview, 351 LauncherAppWidgetProviderInfo providerInfo, 352 @Nullable RemoteViews remoteViews) { 353 appWidgetHostViewPreview.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 354 appWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1, providerInfo); 355 appWidgetHostViewPreview.updateAppWidget(remoteViews); 356 } 357 getWidgetView()358 public WidgetImageView getWidgetView() { 359 return mWidgetImage; 360 } 361 362 @Nullable getAppWidgetHostViewPreview()363 public NavigableAppWidgetHostView getAppWidgetHostViewPreview() { 364 return mAppWidgetHostViewPreview; 365 } 366 setAnimatePreview(boolean shouldAnimate)367 public void setAnimatePreview(boolean shouldAnimate) { 368 mAnimatePreview = shouldAnimate; 369 } 370 applyPreview(Bitmap bitmap)371 private void applyPreview(Bitmap bitmap) { 372 if (bitmap != null) { 373 Drawable drawable = new RoundDrawableWrapper( 374 new FastBitmapDrawable(bitmap), mEnforcedCornerRadius); 375 376 // Scale down the preview size if it's wider than the cell. 377 float scale = 1f; 378 if (mTargetPreviewWidth > 0) { 379 float maxWidth = mTargetPreviewWidth; 380 float previewWidth = drawable.getIntrinsicWidth() * mPreviewContainerScale; 381 scale = Math.min(maxWidth / previewWidth, 1); 382 } 383 setContainerSize( 384 Math.round(drawable.getIntrinsicWidth() * scale * mPreviewContainerScale), 385 Math.round(drawable.getIntrinsicHeight() * scale * mPreviewContainerScale)); 386 mWidgetImage.setDrawable(drawable); 387 mWidgetImage.setVisibility(View.VISIBLE); 388 if (mAppWidgetHostViewPreview != null) { 389 removeView(mAppWidgetHostViewPreview); 390 mAppWidgetHostViewPreview = null; 391 } 392 } 393 394 if (mAnimatePreview) { 395 mWidgetImageContainer.setAlpha(0f); 396 ViewPropertyAnimator anim = mWidgetImageContainer.animate(); 397 anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS); 398 } else { 399 mWidgetImageContainer.setAlpha(1f); 400 } 401 if (mActiveRequest != null) { 402 mActiveRequest.cancel(); 403 mActiveRequest = null; 404 } 405 } 406 407 /** Used to show the badge when the widget is in the recommended section 408 */ showBadge()409 public void showBadge() { 410 if (Process.myUserHandle().equals(mItem.user)) { 411 mWidgetBadge.setVisibility(View.GONE); 412 } else { 413 mWidgetBadge.setVisibility(View.VISIBLE); 414 mWidgetBadge.setImageResource(R.drawable.ic_work_app_badge); 415 } 416 } 417 setContainerSize(int width, int height)418 private void setContainerSize(int width, int height) { 419 LayoutParams layoutParams = (LayoutParams) mWidgetImageContainer.getLayoutParams(); 420 layoutParams.width = width; 421 layoutParams.height = height; 422 mWidgetImageContainer.setLayoutParams(layoutParams); 423 } 424 425 /** 426 * Ensures that the preview is already loaded or being loaded. If the preview is not loaded, 427 * it applies the provided cachedPreview. If that is null, it starts a loader and notifies the 428 * callback on successful load. 429 */ ensurePreviewWithCallback(Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview)430 private void ensurePreviewWithCallback(Consumer<Bitmap> callback, 431 @Nullable Bitmap cachedPreview) { 432 if (mAppWidgetHostViewPreview != null) { 433 int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale); 434 int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale); 435 setContainerSize(containerWidth, containerHeight); 436 boolean shouldMeasureAndScale = false; 437 if (mAppWidgetHostViewPreview.getChildCount() == 1) { 438 View widgetContent = mAppWidgetHostViewPreview.getChildAt(0); 439 ViewGroup.LayoutParams layoutParams = widgetContent.getLayoutParams(); 440 // We only scale preview if both the width & height of the outermost view group are 441 // not set to MATCH_PARENT. 442 shouldMeasureAndScale = 443 layoutParams.width != MATCH_PARENT && layoutParams.height != MATCH_PARENT; 444 if (shouldMeasureAndScale) { 445 setNoClip(mWidgetImageContainer); 446 setNoClip(mAppWidgetHostViewPreview); 447 mAppWidgetHostViewScale = measureAndComputeWidgetPreviewScale(); 448 } 449 } 450 451 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 452 mTargetPreviewWidth, mTargetPreviewHeight, Gravity.FILL); 453 mAppWidgetHostViewPreview.setLayoutParams(params); 454 455 if (!shouldMeasureAndScale 456 && mAppWidgetHostViewPreview instanceof ScaledAppWidgetHostView) { 457 // If the view is not measured & scaled, at least one side will match the grid size, 458 // so it should be safe to restore the original scale once it is dragged. 459 ScaledAppWidgetHostView tempView = 460 (ScaledAppWidgetHostView) mAppWidgetHostViewPreview; 461 tempView.setKeepOrigForDragging(false); 462 tempView.setScaleToFit(mPreviewContainerScale); 463 } else if (!shouldMeasureAndScale) { 464 mAppWidgetHostViewPreview.setScaleToFit(mPreviewContainerScale); 465 } else { 466 mAppWidgetHostViewPreview.setScaleToFit(mAppWidgetHostViewScale); 467 } 468 mAppWidgetHostViewPreview.getTranslateDelegate().setTranslation( 469 INDEX_WIDGET_CENTERING, 470 -(params.width - (params.width * mPreviewContainerScale)) / 2.0f, 471 -(params.height - (params.height * mPreviewContainerScale)) / 2.0f); 472 mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0); 473 mWidgetImage.setVisibility(View.GONE); 474 applyPreview(null); 475 return; 476 } 477 if (cachedPreview != null) { 478 applyPreview(cachedPreview); 479 return; 480 } 481 if (mActiveRequest != null) { 482 return; 483 } 484 mActiveRequest = mWidgetPreviewLoader.loadPreview( 485 mItem, new Size(mTargetPreviewWidth, mTargetPreviewHeight), callback); 486 } 487 488 @Override onTouchEvent(MotionEvent ev)489 public boolean onTouchEvent(MotionEvent ev) { 490 super.onTouchEvent(ev); 491 mLongPressHelper.onTouchEvent(ev); 492 return true; 493 } 494 495 @Override cancelLongPress()496 public void cancelLongPress() { 497 super.cancelLongPress(); 498 mLongPressHelper.cancelLongPress(); 499 } 500 createAppWidgetHostView(Context context)501 private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) { 502 return new NavigableAppWidgetHostView(context) { 503 @Override 504 protected boolean shouldAllowDirectClick() { 505 return false; 506 } 507 }; 508 } 509 510 private static boolean isLauncherContext(Context context) { 511 return ActivityContext.lookupContext(context) instanceof Launcher; 512 } 513 514 @Override 515 public CharSequence getAccessibilityClassName() { 516 return WidgetCell.class.getName(); 517 } 518 519 @Override 520 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 521 super.onInitializeAccessibilityNodeInfo(info); 522 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 523 } 524 525 private static void setNoClip(ViewGroup view) { 526 view.setClipChildren(false); 527 view.setClipToPadding(false); 528 } 529 530 private float measureAndComputeWidgetPreviewScale() { 531 if (mAppWidgetHostViewPreview.getChildCount() != 1) { 532 return 1f; 533 } 534 535 // Measure the largest possible width & height that the app widget wants to display. 536 mAppWidgetHostViewPreview.measure( 537 makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED), 538 makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED)); 539 if (mRemoteViewsPreview != null) { 540 // If RemoteViews contains multiple sizes, the best fit sized RemoteViews will be 541 // selected in onLayout. To work out the right measurement, let's layout and then 542 // measure again. 543 mAppWidgetHostViewPreview.layout( 544 /* left= */ 0, 545 /* top= */ 0, 546 /* right= */ mTargetPreviewWidth, 547 /* bottom= */ mTargetPreviewHeight); 548 mAppWidgetHostViewPreview.measure( 549 makeMeasureSpec(mTargetPreviewWidth, MeasureSpec.UNSPECIFIED), 550 makeMeasureSpec(mTargetPreviewHeight, MeasureSpec.UNSPECIFIED)); 551 552 } 553 View widgetContent = mAppWidgetHostViewPreview.getChildAt(0); 554 int appWidgetContentWidth = widgetContent.getMeasuredWidth(); 555 int appWidgetContentHeight = widgetContent.getMeasuredHeight(); 556 if (appWidgetContentWidth == 0 || appWidgetContentHeight == 0) { 557 return 1f; 558 } 559 560 // If the width / height of the widget content is set to wrap content, overrides the width / 561 // height with the measured dimension. This avoids incorrect measurement after scaling. 562 FrameLayout.LayoutParams layoutParam = 563 (FrameLayout.LayoutParams) widgetContent.getLayoutParams(); 564 if (layoutParam.width == WRAP_CONTENT) { 565 layoutParam.width = widgetContent.getMeasuredWidth(); 566 } 567 if (layoutParam.height == WRAP_CONTENT) { 568 layoutParam.height = widgetContent.getMeasuredHeight(); 569 } 570 widgetContent.setLayoutParams(layoutParam); 571 572 int horizontalPadding = mAppWidgetHostViewPreview.getPaddingStart() 573 + mAppWidgetHostViewPreview.getPaddingEnd(); 574 int verticalPadding = mAppWidgetHostViewPreview.getPaddingTop() 575 + mAppWidgetHostViewPreview.getPaddingBottom(); 576 return Math.min( 577 (mTargetPreviewWidth - horizontalPadding) * mPreviewContainerScale 578 / appWidgetContentWidth, 579 (mTargetPreviewHeight - verticalPadding) * mPreviewContainerScale 580 / appWidgetContentHeight); 581 } 582 } 583