• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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