Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8bd66ee8 authored by Fengjiang Li's avatar Fengjiang Li
Browse files

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
parent 8ca83687
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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,

            // 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,24 +1482,57 @@ 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);
        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;
            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);
@@ -1505,7 +1549,6 @@ public class Launcher extends StatefulActivity<LauncherState>
            });
        }
    }
    }

    private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;

@@ -1760,20 +1803,40 @@ 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);

        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.getProviderInfo(this), addFlowHandler.needsConfigure(),
                widgetPreviewBitmap);
        mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
    }
    }

    public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
            int[] cell, int spanX, int spanY) {
@@ -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
Original line number Diff line number Diff line
@@ -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];
+11 −1
Original line number Diff line number Diff line
@@ -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() {
+6 −0
Original line number Diff line number Diff line
@@ -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 =
Loading