diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index d379132bcf..25db4d743d 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -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 { @@ -163,6 +166,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" diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 247125469f..07eea32116 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -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; @@ -828,7 +831,7 @@ public class Launcher extends StatefulActivity 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); @@ -1015,11 +1018,18 @@ public class Launcher extends StatefulActivity 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); } @@ -1449,14 +1459,15 @@ public class Launcher extends StatefulActivity */ @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); } @@ -1470,39 +1481,71 @@ public class Launcher extends StatefulActivity 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() { - @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() { + @Override + public void onStateTransitionComplete(LauncherState finalState) { + if ((mPrevLauncherState == SPRING_LOADED || mPrevLauncherState == EDIT_MODE) + && finalState == NORMAL) { + AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout); + mStateManager.removeStateListener(this); } - }); - } + } + }); } } @@ -1759,19 +1802,39 @@ public class Launcher extends StatefulActivity 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, @@ -2366,6 +2429,18 @@ public class Launcher extends StatefulActivity 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. diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 86f31a1576..55416af5c0 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2988,7 +2988,7 @@ public class Workspace extends PagedView } 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]; diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index f18f900593..db693f0fa9 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -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 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 implements LauncherOverla if (mDropView != null) { mDragController.onDeferredEndDrag(mDropView); } + DragView ret = mDropView; mDropView = null; invalidate(); + return ret; } public View getAnimatedView() { diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 1f0735243d..bcee4420b1 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -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 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 VALUE = diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 25979c268d..adf85c70e0 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -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;