WidgetCell & LiveWidgetPreview cleanup

The fixes are:
1. For widgets shown in TableRow, aligns them center vertically.
   This is useful when widgets of different height are shown in
   the same row.
2. Remove background color from WidgetCell
3. Re-enable swipe in FullWidgetsSheet
4. Use the WidgetCell image as the single & long press area because
   1. Previews are now scaled to their default size. The touch area
      should already be larger for widgets that are bigger than 2.4
      cells
   2. WidgetCells also render description. If the user long press the
      description area, the drag view will not align in the middle of
      the user touch / drag area.

Test: Drag-n-drop widgets from FullWidgetsSheet, BottomWidgetsSheet
      and pin widget flow.
      In FullWidgetsSheet, swipe left and right to switch work /
      personal tab.

Video: https://drive.google.com/file/d/1Ur7PwF1a7iwGGRTZczfc0BSVu-Ta6-Vm/view?usp=sharing

Bug: 179797520
Change-Id: I6fe4bc88d1e1b35b1819d8d7f046105f5ed27043
This commit is contained in:
Steven Ng 2021-03-05 20:20:24 +00:00
parent d60299e19f
commit ae6b34811b
12 changed files with 132 additions and 218 deletions

View File

@ -46,7 +46,7 @@
android:background="?android:attr/colorPrimaryDark"
android:theme="?attr/widgetsTheme">
<com.android.launcher3.dragndrop.LivePreviewWidgetCell
<com.android.launcher3.widget.WidgetCell
android:id="@+id/widget_cell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -59,7 +59,7 @@
<include layout="@layout/widget_cell_content" />
</com.android.launcher3.dragndrop.LivePreviewWidgetCell>
</com.android.launcher3.widget.WidgetCell>
</FrameLayout>
</LinearLayout>
</ScrollView>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.dragndrop.LivePreviewWidgetCell
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
android:paddingVertical="@dimen/widget_cell_vertical_padding"
android:layout_weight="1"
android:orientation="vertical"
android:focusable="true"
android:background="?android:attr/colorPrimaryDark"
android:gravity="center_horizontal">
<include layout="@layout/widget_cell_content" />
</com.android.launcher3.dragndrop.LivePreviewWidgetCell>

View File

@ -22,7 +22,6 @@
android:layout_weight="1"
android:orientation="vertical"
android:focusable="true"
android:background="?android:attr/colorPrimaryDark"
android:gravity="center_horizontal">
<include layout="@layout/widget_cell_content" />

View File

@ -96,8 +96,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
private static final int MIN_FLING_VELOCITY = 250;
private boolean mFreeScroll = false;
/** If {@code false}, disable swipe gesture to switch between pages. */
private boolean mSwipeGestureEnabled = true;
protected final int mFlingThresholdVelocity;
protected final int mEasyFlingThresholdVelocity;
@ -859,14 +857,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
}
}
/**
* If {@code enableSwipeGesture} is {@code true}, enables swipe gesture to navigate between
* pages. Otherwise, disables the navigation gesture.
*/
public void setSwipeGestureEnabled(boolean swipeGestureEnabled) {
mSwipeGestureEnabled = swipeGestureEnabled;
}
/**
* {@inheritDoc}
*/
@ -889,8 +879,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
* scrolling there.
*/
if (!mSwipeGestureEnabled) return false;
// Skip touch handling if there are no pages to swipe
if (getChildCount() <= 0) return false;

View File

@ -58,6 +58,7 @@ import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetImageView;
import com.android.launcher3.widget.WidgetManagerHelper;
@ -78,7 +79,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
private LauncherAppState mApp;
private InvariantDeviceProfile mIdp;
private LivePreviewWidgetCell mWidgetCell;
private WidgetCell mWidgetCell;
// Widget request specific options.
private LauncherAppWidgetHost mAppWidgetHost;
@ -117,8 +118,9 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
}
}
mWidgetCell.setOnTouchListener(this);
mWidgetCell.setOnLongClickListener(this);
WidgetImageView preview = mWidgetCell.findViewById(R.id.widget_preview);
preview.setOnTouchListener(this);
preview.setOnLongClickListener(this);
// savedInstanceState is null when the activity is created the first time (i.e., avoids
// duplicate logging during rotation)

View File

@ -1,153 +0,0 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.Utilities.ATLEAST_S;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
/**
* Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
*/
public class LivePreviewWidgetCell extends WidgetCell {
private RemoteViews mPreview;
private AppWidgetHostView mPreviewAppWidgetHostView;
public LivePreviewWidgetCell(Context context) {
this(context, null);
}
public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setPreview(RemoteViews view) {
mPreview = view;
}
public RemoteViews getPreview() {
return mPreview;
}
/** Resets any resource. This should be called before recycling this view. */
@Override
public void clear() {
super.clear();
mPreview = null;
mPreviewAppWidgetHostView = null;
}
@Override
public void ensurePreview() {
if (mPreview != null && mActiveRequest == null) {
Bitmap preview = generateFromRemoteViews(
mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
if (preview != null) {
applyPreview(preview);
return;
}
}
if (mPreviewAppWidgetHostView != null) {
Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
mItem.widgetInfo, mPreviewWidth, new int[1]);
if (preview != null) {
applyPreview(preview);
return;
}
}
super.ensurePreview();
}
@Override
public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
if (ATLEAST_S
&& mPreview == null
&& item.widgetInfo != null
&& item.widgetInfo.previewLayout != Resources.ID_NULL) {
mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
item.widgetInfo.clone());
// A hack to force the initial layout to be the preview layout since there is no API for
// rendering a preview layout for work profile apps yet. For non-work profile layout, a
// proper solution is to use RemoteViews(PackageName, LayoutId).
launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
launcherAppWidgetProviderInfo);
mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
0, /* bottom= */ 0);
mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
}
super.applyFromCellItem(item, loader);
}
/**
* Generates a bitmap by inflating {@param views}.
* @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
*
* TODO: Consider moving this to the background thread.
*/
public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
try {
return generateFromView(activity, views.apply(activity, new FrameLayout(activity)),
info, previewSize, preScaledWidthOut);
} catch (Exception e) {
return null;
}
}
private static Bitmap generateFromView(BaseActivity activity, View v,
LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
DeviceProfile dp = activity.getDeviceProfile();
int viewWidth = dp.cellWidthPx * info.spanX;
int viewHeight = dp.cellHeightPx * info.spanY;
v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
viewWidth = v.getMeasuredWidth();
viewHeight = v.getMeasuredHeight();
v.layout(0, 0, viewWidth, viewHeight);
preScaledWidthOut[0] = viewWidth;
final int bitmapWidth, bitmapHeight;
final float scale;
if (viewWidth > previewSize) {
scale = ((float) previewSize) / viewWidth;
bitmapWidth = previewSize;
bitmapHeight = (int) (viewHeight * scale);
} else {
scale = 1;
bitmapWidth = viewWidth;
bitmapHeight = viewHeight;
}
return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
c.scale(scale, scale);
v.draw(c);
});
}
}

View File

@ -30,7 +30,6 @@ import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@ -86,6 +85,8 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView
if (v instanceof WidgetCell) {
return beginDraggingWidget((WidgetCell) v);
} else if (v.getParent() instanceof WidgetCell) {
return beginDraggingWidget((WidgetCell) v.getParent());
}
return true;
}
@ -101,9 +102,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView
}
PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
if (v instanceof LivePreviewWidgetCell) {
dragHelper.setPreview(((LivePreviewWidgetCell) v).getPreview());
}
dragHelper.setPreview(v.getPreview());
int[] loc = new int[2];
getPopupContainer().getLocationInDragLayer(image, loc);

View File

@ -35,7 +35,6 @@ import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.LauncherIcons;
@ -92,7 +91,7 @@ public class PendingItemDragHelper extends DragPreviewProvider {
int[] previewSizeBeforeScale = new int[1];
if (mPreview != null) {
preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
preview = WidgetCell.generateFromRemoteViews(launcher, mPreview,
createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
}
if (preview == null) {

View File

@ -18,7 +18,9 @@ package com.android.launcher3.widget;
import static com.android.launcher3.Utilities.ATLEAST_S;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.CancellationSignal;
import android.util.AttributeSet;
@ -28,7 +30,9 @@ import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.launcher3.BaseActivity;
@ -37,6 +41,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.model.WidgetItem;
/**
@ -85,6 +90,9 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
protected final DeviceProfile mDeviceProfile;
private final CheckLongPressHelper mLongPressHelper;
private RemoteViews mPreview;
private AppWidgetHostView mPreviewAppWidgetHostView;
public WidgetCell(Context context) {
this(context, null);
}
@ -123,6 +131,14 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
mWidgetDescription = findViewById(R.id.widget_description);
}
public void setPreview(RemoteViews view) {
mPreview = view;
}
public RemoteViews getPreview() {
return mPreview;
}
/**
* Called to clear the view and free attached resources. (e.g., {@link Bitmap}
*/
@ -142,9 +158,13 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
mActiveRequest.cancel();
mActiveRequest = null;
}
mPreview = null;
mPreviewAppWidgetHostView = null;
}
public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
applyPreviewLayout(item);
mItem = item;
mWidgetName.setText(mItem.label);
mWidgetDims.setText(getContext().getString(R.string.widget_dims_format,
@ -169,6 +189,27 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
}
}
private void applyPreviewLayout(WidgetItem item) {
if (ATLEAST_S
&& mPreview == null
&& item.widgetInfo != null
&& item.widgetInfo.previewLayout != Resources.ID_NULL) {
mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
item.widgetInfo.clone());
// A hack to force the initial layout to be the preview layout since there is no API for
// rendering a preview layout for work profile apps yet. For non-work profile layout, a
// proper solution is to use RemoteViews(PackageName, LayoutId).
launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
launcherAppWidgetProviderInfo);
mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
0, /* bottom= */ 0);
mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
}
}
public WidgetImageView getWidgetView() {
return mWidgetImage;
}
@ -217,6 +258,23 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
}
public void ensurePreview() {
if (mPreview != null && mActiveRequest == null) {
Bitmap preview = generateFromRemoteViews(
mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
if (preview != null) {
applyPreview(preview);
return;
}
}
if (mPreviewAppWidgetHostView != null) {
Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
mItem.widgetInfo, mPreviewWidth, new int[1]);
if (preview != null) {
applyPreview(preview);
return;
}
}
if (mActiveRequest != null) {
return;
}
@ -273,4 +331,53 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
super.onInitializeAccessibilityNodeInfo(info);
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
}
/**
* Generates a bitmap by inflating {@param views}.
* @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
*
* TODO: Consider moving this to the background thread.
*/
public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
try {
return generateFromView(activity, views.apply(activity, new FrameLayout(activity)),
info, previewSize, preScaledWidthOut);
} catch (Exception e) {
return null;
}
}
private static Bitmap generateFromView(BaseActivity activity, View v,
LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
DeviceProfile dp = activity.getDeviceProfile();
int viewWidth = dp.cellWidthPx * info.spanX;
int viewHeight = dp.cellHeightPx * info.spanY;
v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
viewWidth = v.getMeasuredWidth();
viewHeight = v.getMeasuredHeight();
v.layout(0, 0, viewWidth, viewHeight);
preScaledWidthOut[0] = viewWidth;
final int bitmapWidth, bitmapHeight;
final float scale;
if (viewWidth > previewSize) {
scale = ((float) previewSize) / viewWidth;
bitmapWidth = previewSize;
bitmapHeight = (int) (viewHeight * scale);
} else {
scale = 1;
bitmapWidth = viewWidth;
bitmapHeight = viewHeight;
}
return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
c.scale(scale, scale);
v.draw(c);
});
}
}

View File

@ -24,6 +24,7 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IntProperty;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -38,7 +39,6 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
@ -140,6 +140,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
TableRow tableRow = new TableRow(getContext());
tableRow.setGravity(Gravity.CENTER_VERTICAL);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
@ -153,11 +154,12 @@ public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
}
protected WidgetCell addItemCell(ViewGroup parent) {
LivePreviewWidgetCell widget = (LivePreviewWidgetCell) LayoutInflater.from(
getContext()).inflate(R.layout.live_preview_widget_cell, parent, false);
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
.inflate(R.layout.widget_cell, parent, false);
widget.setOnClickListener(this);
widget.setOnLongClickListener(this);
WidgetImageView preview = widget.findViewById(R.id.widget_preview);
preview.setOnClickListener(this);
preview.setOnLongClickListener(this);
widget.setAnimatePreview(false);
parent.addView(widget);

View File

@ -110,9 +110,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
if (mHasWorkProfile) {
mViewPager = findViewById(R.id.widgets_view_pager);
// Temporarily disable swipe gesture until widgets list horizontal scrollviews per
// app are replaced by gird views.
mViewPager.setSwipeGestureEnabled(false);
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);

View File

@ -17,6 +17,7 @@ package com.android.launcher3.widget.picker;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@ -30,6 +31,7 @@ import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetImageView;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.util.WidgetsTableUtils;
@ -144,6 +146,7 @@ public final class WidgetsListTableViewHolderBinder
tableRow = (TableRow) table.getChildAt(i);
} else {
tableRow = new TableRow(table.getContext());
tableRow.setGravity(Gravity.CENTER_VERTICAL);
table.addView(tableRow);
}
if (tableRow.getChildCount() > widgetItems.size()) {
@ -153,10 +156,11 @@ public final class WidgetsListTableViewHolderBinder
} else {
for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) {
WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
R.layout.live_preview_widget_cell, tableRow, false);
R.layout.widget_cell, tableRow, false);
// set up touch.
widget.setOnClickListener(mIconClickListener);
widget.setOnLongClickListener(mIconLongClickListener);
WidgetImageView preview = widget.findViewById(R.id.widget_preview);
preview.setOnClickListener(mIconClickListener);
preview.setOnLongClickListener(mIconLongClickListener);
tableRow.addView(widget);
}
}