Resize preview for correct clipping

Before, the remote view for the widget in launcher is reused to generate a new preview. However, measuring the view without changing the scale would cause strange clippings. This CL sets the scale of the widget views by manually computing the size ratio.

Change ag/19572297 is necessary before a complete clean-up.

Test: Create a weather widget on first screen -> go to Wallpaper & style -> App grid -> tap on a different grid and verify that the clipping is correct
Fix: 228328759
Change-Id: I8242d3bcfcf30ec924552c1320e22f8a3592f1c1
This commit is contained in:
Sihua Ma
2022-07-18 12:43:56 -07:00
parent fa13629da8
commit e04aa207f2
5 changed files with 132 additions and 14 deletions

View File

@@ -38,11 +38,13 @@ import android.util.SparseArray;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
@@ -57,6 +59,9 @@ public class DeviceProfile {
private static final int DEFAULT_DOT_SIZE = 100;
private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
// Ratio of empty space, qsb should take up to appear visually centered.
private final float mQsbCenterFactor;
@@ -204,7 +209,7 @@ public class DeviceProfile {
public int overviewGridSideMargin;
// Widgets
public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
private final ViewScaleProvider mViewScaleProvider;
// Drop Target
public int dropTargetBarSizePx;
@@ -240,7 +245,8 @@ public class DeviceProfile {
/** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
boolean transposeLayoutWithOrientation, boolean useTwoPanels, boolean isGestureMode) {
boolean transposeLayoutWithOrientation, boolean useTwoPanels, boolean isGestureMode,
@NonNull final ViewScaleProvider viewScaleProvider) {
this.inv = inv;
this.isLandscape = windowBounds.isLandscape();
@@ -473,6 +479,8 @@ public class DeviceProfile {
flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
R.dimen.drag_flingToDeleteMinVelocity);
mViewScaleProvider = viewScaleProvider;
// This is done last, after iconSizePx is calculated above.
mDotRendererWorkSpace = createDotRenderer(iconSizePx, dotRendererCache);
mDotRendererAllApps = createDotRenderer(allAppsIconSizePx, dotRendererCache);
@@ -669,13 +677,18 @@ public class DeviceProfile {
.setMultiWindowMode(true)
.build();
profile.hideWorkspaceLabelsIfNotEnoughSpace();
// We use these scales to measure and layout the widgets using their full invariant profile
// sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
if (appWidgetScaleX != 1 || appWidgetScaleY != 1) {
final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY);
profile = profile.toBuilder(context)
.setViewScaleProvider(i -> p)
.build();
}
profile.hideWorkspaceLabelsIfNotEnoughSpace();
return profile;
}
@@ -1242,6 +1255,19 @@ public class DeviceProfile {
+ getOverviewActionsClaimedSpaceBelow();
}
/**
* Takes the View and return the scales of width and height depending on the DeviceProfile
* specifications
*
* @param itemInfo The tag of the widget view
* @return A PointF instance with the x set to be the scale of width, and y being the scale of
* height
*/
@NonNull
public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) {
return mViewScaleProvider.getScaleFromItemInfo(itemInfo);
}
/**
* @return the bounds for which the open folders should be contained within
*/
@@ -1551,6 +1577,22 @@ public class DeviceProfile {
}
}
/**
* Handler that deals with ItemInfo of the views for the DeviceProfile
*/
@FunctionalInterface
public interface ViewScaleProvider {
/**
* Get the scales from the view
*
* @param itemInfo The tag of the widget view
* @return PointF instance containing the scale information, or null if using the default
* app widget scale of this device profile.
*/
@NonNull
PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo);
}
public static class Builder {
private Context mContext;
private InvariantDeviceProfile mInv;
@@ -1562,6 +1604,7 @@ public class DeviceProfile {
private boolean mIsMultiWindowMode = false;
private Boolean mTransposeLayoutWithOrientation;
private Boolean mIsGestureMode;
private ViewScaleProvider mViewScaleProvider = null;
private SparseArray<DotRenderer> mDotRendererCache;
@@ -1601,6 +1644,19 @@ public class DeviceProfile {
return this;
}
/**
* Set the viewScaleProvider for the builder
*
* @param viewScaleProvider The viewScaleProvider to be set for the
* DeviceProfile
* @return This builder
*/
@NonNull
public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) {
mViewScaleProvider = viewScaleProvider;
return this;
}
public DeviceProfile build() {
if (mWindowBounds == null) {
throw new IllegalArgumentException("Window bounds not set");
@@ -1614,9 +1670,12 @@ public class DeviceProfile {
if (mDotRendererCache == null) {
mDotRendererCache = new SparseArray<>();
}
if (mViewScaleProvider == null) {
mViewScaleProvider = DEFAULT_PROVIDER;
}
return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels,
mIsGestureMode);
mIsGestureMode, mViewScaleProvider);
}
}

View File

@@ -25,6 +25,7 @@ import static com.android.launcher3.CellLayout.WORKSPACE;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
@@ -32,6 +33,7 @@ import android.view.ViewGroup;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
@@ -109,8 +111,9 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
if (child instanceof NavigableAppWidgetHostView) {
DeviceProfile profile = mActivity.getDeviceProfile();
((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpace, mTempRect);
appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace, null);
@@ -133,8 +136,9 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
if (child instanceof NavigableAppWidgetHostView) {
((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
dp.appWidgetScale.x, dp.appWidgetScale.y, mBorderSpace, mTempRect);
appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace, null);
@@ -187,8 +191,9 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
// Scale and center the widget to fit within its cells.
DeviceProfile profile = mActivity.getDeviceProfile();
float scaleX = profile.appWidgetScale.x;
float scaleY = profile.appWidgetScale.y;
final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
float scaleX = appWidgetScale.x;
float scaleY = appWidgetScale.y;
nahv.setScaleToFit(Math.min(scaleX, scaleY));
nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,

View File

@@ -44,6 +44,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -369,7 +370,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
float scale = 1;
if (isWidget) {
DeviceProfile profile = mLauncher.getDeviceProfile();
scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
final PointF appWidgetScale = profile.getAppWidgetScale(null);
scale = Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
}
size[0] = r.width();
size[1] = r.height();
@@ -2883,7 +2885,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
r.top -= widgetPadding.top;
r.bottom += widgetPadding.bottom;
}
Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
PointF appWidgetScale = profile.getAppWidgetScale(null);
Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
}
mTempFXY[0] = r.left;

View File

@@ -20,6 +20,7 @@ import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -36,6 +37,7 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -55,6 +57,7 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.TextClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
@@ -100,6 +103,7 @@ import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LocalColorExtractor;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
import java.util.Collections;
@@ -173,6 +177,7 @@ public class LauncherPreviewRenderer extends ContextWrapper
private final Context mContext;
private final InvariantDeviceProfile mIdp;
private final DeviceProfile mDp;
private final DeviceProfile mDpOrig;
private final Rect mInsets;
private final WorkspaceItemInfo mWorkspaceItemInfo;
private final LayoutInflater mHomeElementInflater;
@@ -192,7 +197,16 @@ public class LauncherPreviewRenderer extends ContextWrapper
mUiHandler = new Handler(Looper.getMainLooper());
mContext = context;
mIdp = idp;
mDp = idp.getDeviceProfile(context).copy(context);
mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider(
this::getAppWidgetScale).build();
if (context instanceof PreviewContext) {
Context tempContext = ((PreviewContext) context).getBaseContext();
mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile
.getCurrentGridName(tempContext)).getDeviceProfile(tempContext)
.copy(tempContext);
} else {
mDpOrig = mDp;
}
WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
.getCurrentWindowMetrics().getWindowInsets();
@@ -390,6 +404,41 @@ public class LauncherPreviewRenderer extends ContextWrapper
addInScreenFromBind(view, info);
}
@NonNull
private PointF getAppWidgetScale(@Nullable ItemInfo itemInfo) {
if (!(itemInfo instanceof LauncherAppWidgetInfo)) {
return DEFAULT_SCALE;
}
LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) itemInfo;
final Size launcherWidgetSize = mLauncherWidgetSpanInfo.get(info.appWidgetId);
if (launcherWidgetSize == null) {
return DEFAULT_SCALE;
}
final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig,
launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight());
final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY);
final Rect previewInset = new Rect();
final Rect origInset = new Rect();
// When the setup() is called for the LayoutParams, insets are added to the width
// and height of the view. This is not accounted for in WidgetSizes and is handled
// here.
if (mDp.shouldInsetWidgets()) {
previewInset.set(mDp.inv.defaultWidgetPadding);
} else {
previewInset.setEmpty();
}
if (mDpOrig.shouldInsetWidgets()) {
origInset.set(mDpOrig.inv.defaultWidgetPadding);
} else {
origInset.setEmpty();
}
return new PointF((float) newSize.getWidth() / (origSize.getWidth()
+ origInset.left + origInset.right),
(float) newSize.getHeight() / (origSize.getHeight()
+ origInset.top + origInset.bottom));
}
private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
CellLayout screen = mWorkspaceScreens.get(info.screenId);
View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info);

View File

@@ -20,6 +20,7 @@ import android.graphics.PointF
import android.graphics.Rect
import android.util.SparseArray
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER;
import com.android.launcher3.util.DisplayController.Info
import com.android.launcher3.util.WindowBounds
import org.junit.Before
@@ -61,7 +62,8 @@ abstract class DeviceProfileBaseTest {
isMultiWindowMode,
transposeLayoutWithOrientation,
useTwoPanels,
isGestureMode
isGestureMode,
DEFAULT_PROVIDER
)
protected fun initializeVarsForPhone(isGestureMode: Boolean = true,