Avoid flicker to drop a widget that needs a config activity.

When dropping a widget that requires an config activity, drop a
PendingAppWidgetHostView to workspace

Adb command to try the feature:

adb shell device_config put launcher com.android.launcher3.enable_add_app_widget_via_config_activity_v2 true

Fix: 284236964
Test: manual
Flag: aconfig launcher.enable_add_app_widget_via_config_activity_v2 DISABLED
Change-Id: Ifd0be5c607a388cf8a8f6d77b46c03112e3e599f
This commit is contained in:
Fengjiang Li
2024-01-26 10:00:47 -08:00
parent 8ca8368730
commit 8bd66ee82d
6 changed files with 165 additions and 34 deletions
+10
View File
@@ -83,6 +83,9 @@ flag {
namespace: "launcher"
description: "Enables full width two pane widget picker for tablets in landscape and portrait"
bug: "315055849"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
@@ -156,6 +159,13 @@ flag {
bug: "318410881"
}
flag {
name: "enable_add_app_widget_via_config_activity_v2"
namespace: "launcher"
description: "When adding app widget through config activity, directly add it to workspace to reduce flicker"
bug: "284236964"
}
flag {
name: "use_activity_overlay"
namespace: "launcher"
+106 -31
View File
@@ -27,6 +27,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2;
import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
@@ -116,6 +117,8 @@ import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -829,7 +832,7 @@ public class Launcher extends StatefulActivity<LauncherState>
announceForAccessibility(R.string.item_added_to_workspace);
break;
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(appWidgetId, info, null, null);
completeAddAppWidget(appWidgetId, info, null, null, false, null);
break;
case REQUEST_RECONFIGURE_APPWIDGET:
getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
@@ -1016,11 +1019,18 @@ public class Launcher extends StatefulActivity<LauncherState>
AppWidgetHostView boundWidget = null;
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
final AppWidgetHostView layout = mAppWidgetHolder.createView(appWidgetId,
requestArgs.getWidgetHandler().getProviderInfo(this));
// Now that we are exiting the config activity with RESULT_OK.
// If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we can retrieve the
// PendingAppWidgetHostView from LauncherWidgetHolder (it was added to
// LauncherWidgetHolder when starting the config activity).
final AppWidgetHostView layout = enableAddAppWidgetViaConfigActivityV2()
? getWorkspace().getWidgetForAppWidgetId(appWidgetId)
: mAppWidgetHolder.createView(appWidgetId,
requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = () -> {
completeAddAppWidget(appWidgetId, requestArgs, layout, null);
completeAddAppWidget(appWidgetId, requestArgs, layout, null, false, null);
if (!isInState(EDIT_MODE)) {
mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
@@ -1450,14 +1460,15 @@ public class Launcher extends StatefulActivity<LauncherState>
*/
@Thunk
void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
@Nullable AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo,
boolean showPendingWidget, @Nullable Bitmap widgetPreviewBitmap) {
if (appWidgetInfo == null) {
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId,
itemInfo.getTargetComponent());
}
if (hostView == null) {
if (hostView == null && !showPendingWidget) {
// Perform actual inflation because we're live
hostView = mAppWidgetHolder.createView(appWidgetId, appWidgetInfo);
}
@@ -1471,39 +1482,71 @@ public class Launcher extends StatefulActivity<LauncherState>
launcherInfo.minSpanX = itemInfo.minSpanX;
launcherInfo.minSpanY = itemInfo.minSpanY;
launcherInfo.user = appWidgetInfo.getProfile();
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(itemInfo);
if (showPendingWidget) {
launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
PendingAppWidgetHostView pendingAppWidgetHostView =
new PendingAppWidgetHostView(this, launcherInfo, appWidgetInfo);
pendingAppWidgetHostView.setPreviewBitmap(widgetPreviewBitmap);
hostView = pendingAppWidgetHostView;
} else if (hostView instanceof PendingAppWidgetHostView) {
((PendingAppWidgetHostView) hostView).setPreviewBitmap(null);
// User has selected a widget config and exited the config activity, we can trigger
// re-inflation of PendingAppWidgetHostView to replace it with
// LauncherAppWidgetHostView in workspace.
completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
// Show resize frame on the newly inflated LauncherAppWidgetHostView.
LauncherAppWidgetHostView reInflatedHostView =
getWorkspace().getWidgetForAppWidgetId(appWidgetId);
showWidgetResizeFrame(
reInflatedHostView,
(LauncherAppWidgetInfo) reInflatedHostView.getTag(),
presenterPos);
return;
}
if (itemInfo instanceof PendingAddWidgetInfo) {
launcherInfo.sourceContainer = ((PendingAddWidgetInfo) itemInfo).sourceContainer;
} else if (itemInfo instanceof PendingRequestArgs) {
launcherInfo.sourceContainer =
((PendingRequestArgs) itemInfo).getWidgetSourceContainer();
}
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(itemInfo);
getModelWriter().addItemToDatabase(launcherInfo,
itemInfo.container, presenterPos.screenId, presenterPos.cellX, presenterPos.cellY);
hostView.setVisibility(View.VISIBLE);
mItemInflater.prepareAppWidget(hostView, launcherInfo);
mWorkspace.addInScreen(hostView, launcherInfo);
if (!enableAddAppWidgetViaConfigActivityV2() || hostView.getParent() == null) {
mWorkspace.addInScreen(hostView, launcherInfo);
}
announceForAccessibility(R.string.item_added_to_workspace);
// Show the widget resize frame.
if (hostView instanceof LauncherAppWidgetHostView) {
final LauncherAppWidgetHostView launcherHostView = (LauncherAppWidgetHostView) hostView;
CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId);
if (mStateManager.getState() == NORMAL) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
} else {
mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if ((mPrevLauncherState == SPRING_LOADED || mPrevLauncherState == EDIT_MODE)
&& finalState == NORMAL) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
mStateManager.removeStateListener(this);
}
showWidgetResizeFrame(launcherHostView, launcherInfo, presenterPos);
}
}
/** Show widget resize frame. */
private void showWidgetResizeFrame(
LauncherAppWidgetHostView launcherHostView,
LauncherAppWidgetInfo launcherInfo,
CellPos presenterPos) {
CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId);
if (mStateManager.getState() == NORMAL) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
} else {
mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if ((mPrevLauncherState == SPRING_LOADED || mPrevLauncherState == EDIT_MODE)
&& finalState == NORMAL) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
mStateManager.removeStateListener(this);
}
});
}
}
});
}
}
@@ -1760,19 +1803,39 @@ public class Launcher extends StatefulActivity<LauncherState>
addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
}
/**
* If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we always add widget
* host view to workspace, otherwise we only add widget to host view if config activity is
* not started.
*/
void addAppWidgetImpl(int appWidgetId, ItemInfo info,
AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
if (!addFlowHandler.startConfigActivity(this, appWidgetId, info,
REQUEST_CREATE_APPWIDGET)) {
// If the configuration flow was not started, add the widget
final boolean isActivityStarted = addFlowHandler.startConfigActivity(
this, appWidgetId, info, REQUEST_CREATE_APPWIDGET);
// Exit spring loaded mode if necessary after adding the widget
Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
: () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this));
mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
if (!enableAddAppWidgetViaConfigActivityV2() && isActivityStarted) {
return;
}
// If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled and config activity is
// started, we should remove the dropped AppWidgetHostView from drag layer and extract the
// Bitmap that shows the preview. Then pass the Bitmap to completeAddAppWidget() to create
// a PendingWidgetHostView.
Bitmap widgetPreviewBitmap = null;
if (isActivityStarted) {
DragView dropView = getDragLayer().clearAnimatedView();
if (dropView != null && dropView.containsAppWidgetHostView()) {
widgetPreviewBitmap = getBitmapFromView(dropView.getContentView());
}
}
// Exit spring loaded mode if necessary after adding the widget
Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
: () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
widgetPreviewBitmap);
mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
}
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
@@ -2373,6 +2436,18 @@ public class Launcher extends StatefulActivity<LauncherState>
return null;
}
/** Convert a {@link View} to {@link Bitmap}. */
private static Bitmap getBitmapFromView(@Nullable View view) {
if (view == null) {
return null;
}
Bitmap returnedBitmap =
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
view.draw(canvas);
return returnedBitmap;
}
/**
* Returns the first view matching the operator in the given ViewGroups, or null if none.
* Forward iteration matters.
+1 -1
View File
@@ -2988,7 +2988,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
}
public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
final Runnable onCompleteRunnable, int animationType, final View finalView,
final Runnable onCompleteRunnable, int animationType, @Nullable final View finalView,
boolean external) {
int[] finalPos = new int[2];
float scaleXY[] = new float[2];
@@ -42,6 +42,8 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DropTargetBar;
@@ -388,7 +390,13 @@ public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverla
mDropAnim.start();
}
public void clearAnimatedView() {
/**
* Remove the drop view and end the drag animation.
*
* @return {@link DragView} that is removed.
*/
@Nullable
public DragView clearAnimatedView() {
if (mDropAnim != null) {
mDropAnim.cancel();
}
@@ -396,8 +404,10 @@ public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverla
if (mDropView != null) {
mDragController.onDeferredEndDrag(mDropView);
}
DragView ret = mDropView;
mDropView = null;
invalidate();
return ret;
}
public View getAnimatedView() {
@@ -30,6 +30,7 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -563,6 +564,11 @@ public abstract class DragView<T extends Context & ActivityContext> extends Fram
return mContentViewParent;
}
/** Return true if {@link mContent} is a {@link AppWidgetHostView}. */
public boolean containsAppWidgetHostView() {
return mContent instanceof AppWidgetHostView;
}
private static class SpringFloatValue {
private static final FloatPropertyCompat<SpringFloatValue> VALUE =
@@ -16,13 +16,20 @@
package com.android.launcher3.widget;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
@@ -85,8 +92,12 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
private boolean mDrawableSizeChanged;
private final TextPaint mPaint;
private final Paint mPreviewPaint;
private Layout mSetupTextLayout;
@Nullable private Bitmap mPreviewBitmap;
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
@Nullable LauncherAppWidgetProviderInfo appWidget) {
this(context, info, appWidget,
@@ -116,6 +127,15 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
mDrawableSizeChanged = true;
}
/** Set {@link Bitmap} of widget preview. */
public void setPreviewBitmap(@Nullable Bitmap previewBitmap) {
if (this.mPreviewBitmap == previewBitmap) {
return;
}
this.mPreviewBitmap = previewBitmap;
invalidate();
}
private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
@@ -130,11 +150,16 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
mPreviewPaint = new Paint(ANTI_ALIAS_FLAG | DITHER_FLAG | FILTER_BITMAP_FLAG);
setWillNotDraw(false);
setBackgroundResource(R.drawable.pending_widget_bg);
}
@Override
public AppWidgetProviderInfo getAppWidgetInfo() {
return mAppwidget;
}
@Override
public void updateAppWidget(RemoteViews remoteViews) {
checkIfRestored();
@@ -393,6 +418,11 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
@Override
protected void onDraw(Canvas canvas) {
if (mPreviewBitmap != null
&& (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0) {
canvas.drawBitmap(mPreviewBitmap, 0, 0, mPreviewPaint);
return;
}
if (mCenterDrawable == null) {
// Nothing to draw
return;