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

Commit 5b8c1b7b authored by mpodolian's avatar mpodolian
Browse files

[2/3] Use DragToBubbleController to show shell drags bubble drop targets

Updated logic to use DragToBubbleController to show drop target views
when launcher icon is being dragged over the bubble bar drop zones

Bug: 411505605
Flag: com.android.wm.shell.enable_create_any_bubble
Test: DragAndDropControllerTest
Test: Manual.
- Set gesture navigation mode
- Go to the application
- Drag icon from the taskbar towards the bubble bar drop zone
- Observe expanded view drop target appears fades in
- Drop icon to the bubble bar drop target, so app bubble is created
- Drag another icon from the taskbar towards bubble bar drop zone on the
opposite side
- Observe bubble bar is animated to that side
- Drop icon to the opposite side
- Observe second app icon is created
- Drag bubble bar to the dismiss target and perform the same test for
the 3 buttons navigation mode

Change-Id: I9e15492b1dc1708768003068c8dbe9af41721761
parent 001ec46e
Loading
Loading
Loading
Loading
+1 −59
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -104,7 +103,6 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.bubbles.appinfo.BubbleAppInfoProvider;
import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper;
import com.android.wm.shell.common.DisplayController;
@@ -131,7 +129,6 @@ import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.shared.bubbles.ContextUtils;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -167,7 +164,7 @@ import java.util.function.IntConsumer;
 */
public class BubbleController implements ConfigurationChangeListener,
        RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider,
        BubbleBarDragListener, BubbleTaskUnfoldTransitionMerger {
        BubbleTaskUnfoldTransitionMerger {

    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;

@@ -951,61 +948,6 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    @Override
    public void onDragItemOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) {
        if (bubbleBarLocation == null) return;
        if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
            if (mBubbleStateListener != null) {
                mBubbleStateListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
            }
            showBubbleBarExpandedViewDropTarget(bubbleBarLocation);
        }
    }

    @Override
    public void onItemDraggedOutsideBubbleBarDropZone() {
        if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
            if (mBubbleStateListener != null) {
                mBubbleStateListener.onItemDraggedOutsideBubbleBarDropZone();
            }
            hideBubbleBarExpandedViewDropTarget();
        }
    }

    @Override
    public void onItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
            Intent itemIntent) {
        hideBubbleBarExpandedViewDropTarget();
        ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent
                .getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO);
        if (shortcutInfo != null) {
            expandStackAndSelectBubble(shortcutInfo, location);
            return;
        }
        UserHandle user = (UserHandle) itemIntent.getExtra(Intent.EXTRA_USER);
        PendingIntent pendingIntent = (PendingIntent) itemIntent
                .getExtra(ClipDescription.EXTRA_PENDING_INTENT);
        if (pendingIntent != null && user != null) {
            expandStackAndSelectBubble(pendingIntent, user, location);
        }
    }

    @Override
    public Map<BubbleBarLocation, Rect> getBubbleBarDropZones(int l, int t, int r, int b) {
        Map<BubbleBarLocation, Rect> result = new HashMap<>();
        if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
            // TODO(b/393172431) : Utilise DragZoneFactory once it is ready
            final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize(
                    R.dimen.bubble_bar_drop_zone_side_size);
            int top = b - bubbleBarDropZoneSideSize;
            result.put(BubbleBarLocation.LEFT,
                    new Rect(l, top, l + bubbleBarDropZoneSideSize, b));
            result.put(BubbleBarLocation.RIGHT,
                    new Rect(r - bubbleBarDropZoneSideSize, top, r, b));
        }
        return result;
    }

    private void showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation) {
        ensureBubbleViewsAndWindowCreated();
        if (mLayerView != null) {
+0 −40
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.
 */

package com.android.wm.shell.bubbles.bar

import android.content.Intent
import android.graphics.Rect
import com.android.wm.shell.shared.bubbles.BubbleBarLocation

/** Controller that takes care of the bubble bar drag events. */
interface BubbleBarDragListener {

    /** Called when the drag event is over the bubble bar drop zone. */
    fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation)

    /** Called when the drag event leaves the bubble bar drop zone. */
    fun onItemDraggedOutsideBubbleBarDropZone()

    /** Called when the drop event happens over the bubble bar drop zone. */
    fun onItemDroppedOverBubbleBarDragZone(location: BubbleBarLocation, itemIntent: Intent)

    /**
     * Returns mapping of the bubble bar locations to the corresponding
     * [rect][android.graphics.Rect] zone.
     */
    fun getBubbleBarDropZones(l: Int, t: Int, r: Int, b: Int): Map<BubbleBarLocation, Rect>
}
+10 −8
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ import com.android.wm.shell.bubbles.BubbleTaskUnfoldTransitionMerger;
import com.android.wm.shell.bubbles.BubbleTransitions;
import com.android.wm.shell.bubbles.appinfo.BubbleAppInfoProvider;
import com.android.wm.shell.bubbles.appinfo.PackageManagerBubbleAppInfoProvider;
import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.bar.DragToBubbleController;
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -1775,7 +1775,7 @@ public abstract class WMShellModule {
            IconProvider iconProvider,
            GlobalDragListener globalDragListener,
            Transitions transitions,
            Lazy<BubbleController> bubbleControllerLazy,
            Lazy<DragToBubbleController> dragToBubbleControllerLazy,
            @ShellMainThread ShellExecutor mainExecutor,
            DesktopState desktopState) {
        return new DragAndDropController(
@@ -1789,16 +1789,18 @@ public abstract class WMShellModule {
                iconProvider,
                globalDragListener,
                transitions,
                new Lazy<>() {
                    @Override
                    public BubbleBarDragListener get() {
                        return bubbleControllerLazy.get();
                    }
                },
                dragToBubbleControllerLazy,
                mainExecutor,
                desktopState);
    }

    @WMSingleton
    @Provides
    static DragToBubbleController getDragToBubbleController(Context context,
            BubblePositioner bubblePositioner, BubbleController bubbleController) {
        return new DragToBubbleController(context, bubblePositioner, bubbleController);
    }

    //
    // Misc
    //
+13 −6
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.bar.DragToBubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -105,7 +105,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
    private final Transitions mTransitions;
    private final DesktopState mDesktopState;
    private SplitScreenController mSplitScreen;
    private Lazy<BubbleBarDragListener> mBubbleBarDragController;
    private Lazy<DragToBubbleController> mDragToBubbleController;
    private ShellExecutor mMainExecutor;
    private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();

@@ -148,7 +148,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
            IconProvider iconProvider,
            GlobalDragListener globalDragListener,
            Transitions transitions,
            Lazy<BubbleBarDragListener> bubbleBarDragController,
            Lazy<DragToBubbleController> dragToBubbleControllerLazy,
            ShellExecutor mainExecutor,
            DesktopState desktopState) {
        mContext = context;
@@ -160,7 +160,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
        mIconProvider = iconProvider;
        mGlobalDragListener = globalDragListener;
        mTransitions = transitions;
        mBubbleBarDragController = bubbleBarDragController;
        mDragToBubbleController = dragToBubbleControllerLazy;
        mMainExecutor = mainExecutor;
        mDesktopState = desktopState;
        shellInit.addInitCallback(this::onInit, this);
@@ -181,6 +181,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
        mShellTaskOrganizer.addTaskVanishedListener(this);
        mShellCommandHandler.addDumpCallback(this::dump, this);
        mGlobalDragListener.setListener(this);
        addListener(mDragToBubbleController.get());
    }

    private ExternalInterfaceBinder createExternalInterface() {
@@ -250,13 +251,19 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
        layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        layoutParams.setFitInsetsTypes(0);
        layoutParams.setTitle("ShellDropTarget");

        FrameLayout rootView = (FrameLayout) LayoutInflater.from(context).inflate(
                R.layout.global_drop_target, null);
        rootView.setOnDragListener(this);
        rootView.setVisibility(View.INVISIBLE);
        DragToBubbleController dragToBubbleController = null;
        boolean isPrimaryDisplay = context.getDisplayId() == displayId;
        // only add bubble bar drop targets on primary display
        if (isPrimaryDisplay) {
            dragToBubbleController = mDragToBubbleController.get();
            rootView.addView(dragToBubbleController.getDropTargetContainer());
        }
        DragLayoutProvider dragLayout = new DragLayout(context, mSplitScreen,
                mBubbleBarDragController.get(), mIconProvider);
                dragToBubbleController, mIconProvider);
        dragLayout.addDraggingView(rootView);
        try {
            wm.addView(rootView, layoutParams);
+46 −53
Original line number Diff line number Diff line
@@ -36,9 +36,12 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@@ -47,6 +50,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -65,12 +69,12 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.bar.DragToBubbleController;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.io.PrintWriter;
@@ -108,9 +112,8 @@ public class DragLayout extends LinearLayout
    private boolean mIsLeftRightSplit;

    private SplitDragPolicy.Target mCurrentTarget = null;
    private final BubbleBarDragListener mBubbleBarDragListener;
    private final Map<BubbleBarLocation, Rect> mBubbleBarLocations = new HashMap<>();
    private BubbleBarLocation mCurrentBubbleBarTarget = null;
    private final @Nullable DragToBubbleController mDragToBubbleController;
    private boolean mIsOverBubblesDropZone = false;
    private DropZoneView mDropZoneView1;
    private DropZoneView mDropZoneView2;
    private int mDisplayMargin;
@@ -134,12 +137,12 @@ public class DragLayout extends LinearLayout
    @SuppressLint("WrongConstant")
    public DragLayout(Context context,
            SplitScreenController splitScreenController,
            BubbleBarDragListener bubbleBarDragListener,
            @Nullable DragToBubbleController dragToBubbleController,
            IconProvider iconProvider) {
        super(context);
        mSplitScreenController = splitScreenController;
        mIconProvider = iconProvider;
        mBubbleBarDragListener = bubbleBarDragListener;
        mDragToBubbleController = dragToBubbleController;
        mPolicy = new SplitDragPolicy(context, splitScreenController, this);
        mStatusBarManager = context.getSystemService(StatusBarManager.class);
        mLastConfiguration.setTo(context.getResources().getConfiguration());
@@ -195,12 +198,6 @@ public class DragLayout extends LinearLayout
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        updateTouchableRegion();
        updateBubbleBarRegions(l, t, r, b);
    }

    private void updateBubbleBarRegions(int l, int t, int r, int b) {
        mBubbleBarLocations.clear();
        mBubbleBarLocations.putAll(mBubbleBarDragListener.getBubbleBarDropZones(l, t, r, b));
    }

    /**
@@ -604,39 +601,15 @@ public class DragLayout extends LinearLayout
    }

    private boolean interceptBubbleBarEvent(int x, int y) {
        BubbleBarLocation bubbleBarLocation = getBubbleBarLocation(x, y);
        boolean isOverTheBubbleBar = bubbleBarLocation != null;
        if (mCurrentBubbleBarTarget != bubbleBarLocation) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current bubble bar location: %s",
                    isOverTheBubbleBar);
            mCurrentBubbleBarTarget = bubbleBarLocation;
            if (isOverTheBubbleBar) {
                mBubbleBarDragListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
                if (mCurrentTarget != null) {
        boolean interceptBubbleBarEvent = mDragToBubbleController != null
                && mDragToBubbleController.onDragUpdate(x, y);
        if (interceptBubbleBarEvent && !mIsOverBubblesDropZone && mCurrentTarget != null) {
            // only animate for no target if we first enter bubble bar drop zone and have a target
            animateToNoTarget();
            mCurrentTarget = null;
        }
            } else {
                mBubbleBarDragListener.onItemDraggedOutsideBubbleBarDropZone();
            }
            //TODO(b/388894910): handle accessibility
        }
        return isOverTheBubbleBar;
    }

    @Nullable
    private BubbleBarLocation getBubbleBarLocation(int x, int y) {
        Intent appData = mSession.appData;
        if (appData == null) {
            // there is no app data, so drop event over the bubble bar can not be handled
            return null;
        }
        for (BubbleBarLocation location : mBubbleBarLocations.keySet()) {
            if (mBubbleBarLocations.get(location).contains(x, y)) {
                return location;
            }
        }
        return null;
        mIsOverBubblesDropZone = interceptBubbleBarEvent;
        return interceptBubbleBarEvent;
    }

    private void animateToNoTarget() {
@@ -663,10 +636,10 @@ public class DragLayout extends LinearLayout
                    mSession = null;
            }
        });
        if (mCurrentBubbleBarTarget != null) {
        if (mIsOverBubblesDropZone) {
            // bubble bar is still showing drop target, notify bubbles of drag cancel
            mCurrentBubbleBarTarget = null;
            mBubbleBarDragListener.onItemDraggedOutsideBubbleBarDropZone();
            mIsOverBubblesDropZone = false;
            Objects.requireNonNull(mDragToBubbleController).onDragEnded();
        }
        // Reset the state if we previously force-ignore the bottom margin
        mDropZoneView1.setForceIgnoreBottomMargin(false);
@@ -683,19 +656,19 @@ public class DragLayout extends LinearLayout
     */
    public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
            @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
        final boolean handledDrop = mCurrentTarget != null || mCurrentBubbleBarTarget != null;
        final boolean handledDrop = mCurrentTarget != null || mIsOverBubblesDropZone;
        mHasDropped = true;
        Intent appData = mSession.appData;

        // Process the drop exclusive by DropTarget OR by the BubbleBar
        if (mCurrentTarget != null) {
            mPolicy.onDropped(mCurrentTarget, hideTaskToken);
        } else if (appData != null && mCurrentBubbleBarTarget != null
        } else if (appData != null
                && mIsOverBubblesDropZone
                && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
            mBubbleBarDragListener.onItemDroppedOverBubbleBarDragZone(mCurrentBubbleBarTarget,
                    appData);
            handleDropOnBubbleBar(appData, Objects.requireNonNull(mDragToBubbleController));
        }
        mCurrentBubbleBarTarget = null;
        mIsOverBubblesDropZone = false;

        // Start animating the drop UI out with the drag surface
        hide(event, dropCompleteCallback);
@@ -705,6 +678,26 @@ public class DragLayout extends LinearLayout
        return handledDrop;
    }

    private void handleDropOnBubbleBar(Intent appData,
            DragToBubbleController dragToBubbleController) {
        ShortcutInfo shortcutInfo = appData.getParcelableExtra(
                DragAndDropConstants.EXTRA_SHORTCUT_INFO,
                ShortcutInfo.class
        );
        if (shortcutInfo != null) {
            dragToBubbleController.onItemDropped(shortcutInfo);
            return;
        }
        UserHandle user = appData.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
        PendingIntent pendingIntent = appData.getParcelableExtra(
                ClipDescription.EXTRA_PENDING_INTENT,
                PendingIntent.class
        );
        if (pendingIntent != null && user != null) {
            dragToBubbleController.onItemDropped(pendingIntent, user);
        }
    }

    private void hideDragSurface(@NonNull SurfaceControl dragSurface) {
        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
        final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
Loading