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

Commit fec78497 authored by Winson Chung's avatar Winson Chung
Browse files

Support launching an intent based drag into split

- Extend support for launching intent drags into splitscreen in
  addition to crafted app drags sourced from SystemUI
- Unlike app drag drags which are consumed for the whole display,
  intent drags only invoke split from the side edges of the screen,
  so we need to manipulate the touchable region of the shell drop
  target based on the drag data in the current drag session

This reverts commit 41dc803ab1e79305304b3ad67e0ef8e35c33332e.
Bug: 320797628
Test: atest WMShellUnitTests
Test: https://recall.googleplex.com/projects/e3f080d7-2818-43f0-a087-405000b8fdf5/sessions/e88a0470-6850-43a0-abfa-3366b7c7c6a4

Change-Id: I3c0946ae06ae03c21c189c1df1dba3d47db77eb9
parent 2dd09e2c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -101,6 +101,10 @@
    <dimen name="split_divider_bar_width">10dp</dimen>
    <dimen name="split_divider_corner_size">42dp</dimen>

    <!-- The distance from the edge of the screen to invoke splitscreen when the user is dragging
         an intent that can be launched into split. -->
    <dimen name="drag_launchable_intent_edge_margin">48dp</dimen>

    <!-- One-Handed Mode -->
    <!-- Threshold for dragging distance to enable one-handed mode -->
    <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+3 −5
Original line number Diff line number Diff line
@@ -59,7 +59,6 @@ import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
@@ -316,12 +315,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
                    return false;
                }
                // TODO(b/290391688): Also update the session data with task stack changes
                InstanceId loggerSessionId = mLogger.logStart(event);
                pd.activeDragCount++;
                pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
                pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
                        mDisplayController.getDisplayLayout(displayId), event.getClipData());
                pd.dragSession.update();
                pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
                pd.activeDragCount++;
                pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
                setDropTargetWindowVisibility(pd, View.VISIBLE);
                notifyListeners(l -> {
                    l.onDragStarted();
+12 −8
Original line number Diff line number Diff line
@@ -53,17 +53,21 @@ public class DragAndDropEventLogger {
    /**
     * Logs the start of a drag.
     */
    public InstanceId logStart(DragEvent event) {
        final ClipDescription description = event.getClipDescription();
        final ClipData data = event.getClipData();
        final ClipData.Item item = data.getItemAt(0);
        mInstanceId = item.getIntent().getParcelableExtra(
                ClipDescription.EXTRA_LOGGING_INSTANCE_ID);
    public InstanceId logStart(DragSession session) {
        mInstanceId = session.appData != null
                ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID,
                        InstanceId.class)
                : null;
        if (mInstanceId == null) {
            mInstanceId = mIdSequence.newInstanceId();
        }
        mActivityInfo = item.getActivityInfo();
        log(getStartEnum(description), mActivityInfo);
        mActivityInfo = session.activityInfo;
        if (session.appData != null) {
            log(getStartEnum(session.getClipDescription()), mActivityInfo);
        } else {
            // TODO(b/255649902): Update this once we have a new enum
            log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo);
        }
        return mInstanceId;
    }

+58 −14
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_SHORTCUT_ID;
import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.Intent.EXTRA_USER;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;

import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -52,9 +54,11 @@ import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;

import androidx.annotation.IntDef;
@@ -63,8 +67,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.lang.annotation.Retention;
@@ -104,7 +110,9 @@ public class DragAndDropPolicy {
    void start(DragSession session, InstanceId loggerSessionId) {
        mLoggerSessionId = loggerSessionId;
        mSession = session;
        RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
        RectF disallowHitRegion = mSession.appData != null
                ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
                : null;
        if (disallowHitRegion == null) {
            mDisallowHitRegion.setEmpty();
        } else {
@@ -223,7 +231,7 @@ public class DragAndDropPolicy {
    }

    @VisibleForTesting
    void handleDrop(Target target, ClipData data) {
    void handleDrop(Target target) {
        if (target == null || !mTargets.contains(target)) {
            return;
        }
@@ -238,40 +246,76 @@ public class DragAndDropPolicy {
            mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
        }

        final ClipDescription description = data.getDescription();
        final Intent dragData = mSession.dragData;
        startClipDescription(description, dragData, position);
        if (mSession.appData != null) {
            launchApp(mSession, position);
        } else {
            launchIntent(mSession, position);
        }
    }

    private void startClipDescription(ClipDescription description, Intent intent,
            @SplitPosition int position) {
    /**
     * Launches an app provided by SysUI.
     */
    private void launchApp(DragSession session, @SplitPosition int position) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
                position);
        final ClipDescription description = session.getClipDescription();
        final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
        final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
        final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
        baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
        final Bundle opts = baseActivityOpts.toBundle();
        if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
            opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
        if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
            opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
        }
        // Put BAL flags to avoid activity start aborted.
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
        final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
        final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);

        if (isTask) {
            final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
            final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
            mStarter.startTask(taskId, position, opts);
        } else if (isShortcut) {
            final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
            final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
            final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
            final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
            mStarter.startShortcut(packageName, id, position, opts, user);
        } else {
            final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
            final PendingIntent launchIntent =
                    session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
            if (Build.IS_DEBUGGABLE) {
                if (!user.equals(launchIntent.getCreatorUserHandle())) {
                    Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
                }
            }
            mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
                    position, opts);
        }
    }

    /**
     * Launches an intent sender provided by an application.
     */
    private void launchIntent(DragSession session, @SplitPosition int position) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
                position);
        final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
        baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
        // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
        // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
        baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
                | FLAG_ACTIVITY_MULTIPLE_TASK);

        final Bundle opts = baseActivityOpts.toBundle();
        // Put BAL flags to avoid activity start aborted.
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);

        mStarter.startIntent(session.launchableIntent,
                session.launchableIntent.getCreatorUserHandle().getIdentifier(),
                null /* fillIntent */, position, opts);
    }

    /**
     * Interface for actually committing the task launches.
     */
+79 −6
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;

import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -38,12 +40,15 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
@@ -65,7 +70,8 @@ import java.util.ArrayList;
/**
 * Coordinates the visible drop targets for the current drag within a single display.
 */
public class DragLayout extends LinearLayout {
public class DragLayout extends LinearLayout
        implements ViewTreeObserver.OnComputeInternalInsetsListener {

    // While dragging the status bar is hidden.
    private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -90,7 +96,9 @@ public class DragLayout extends LinearLayout {

    private int mDisplayMargin;
    private int mDividerSize;
    private int mLaunchIntentEdgeMargin;
    private Insets mInsets = Insets.NONE;
    private Region mTouchableRegion;

    private boolean mIsShowing;
    private boolean mHasDropped;
@@ -106,10 +114,11 @@ public class DragLayout extends LinearLayout {
        mStatusBarManager = context.getSystemService(StatusBarManager.class);
        mLastConfiguration.setTo(context.getResources().getConfiguration());

        mDisplayMargin = context.getResources().getDimensionPixelSize(
                R.dimen.drop_layout_display_margin);
        mDividerSize = context.getResources().getDimensionPixelSize(
                R.dimen.split_divider_bar_width);
        final Resources res = context.getResources();
        mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin);
        mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width);
        mLaunchIntentEdgeMargin =
                res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin);

        // Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when
        // showing the highlight.
@@ -130,6 +139,66 @@ public class DragLayout extends LinearLayout {
        updateContainerMargins(mIsLeftRightSplit);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mTouchableRegion = Region.obtain();
        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
        mTouchableRegion.recycle();
    }

    @Override
    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) {
        if (mSession != null && mSession.launchableIntent != null) {
            inOutInfo.touchableRegion.set(mTouchableRegion);
            inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        updateTouchableRegion();
    }

    /**
     * Updates the touchable region, this should be called after any configuration changes have
     * been applied.
     */
    private void updateTouchableRegion() {
        mTouchableRegion.setEmpty();
        if (mSession != null && mSession.launchableIntent != null) {
            final int width = getMeasuredWidth();
            final int height = getMeasuredHeight();
            if (mIsLeftRightSplit) {
                mTouchableRegion.union(
                        new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height));
                mTouchableRegion.union(
                        new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width,
                                height));
            } else {
                mTouchableRegion.union(
                        new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin));
                mTouchableRegion.union(
                        new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width,
                                height));
            }
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                    "Updating drag layout width=%d height=%d touchable region=%s",
                    width, height, mTouchableRegion);

            // Reapply insets to update the touchable region
            requestApplyInsets();
        }
    }


    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
@@ -164,6 +233,7 @@ public class DragLayout extends LinearLayout {
            mDropZoneView2.onThemeChange();
        }
        mLastConfiguration.setTo(newConfig);
        requestLayout();
    }

    private void updateContainerMarginsForSingleTask() {
@@ -242,6 +312,7 @@ public class DragLayout extends LinearLayout {
            mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
            updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
        }
        requestLayout();
    }

    private void updateDropZoneSizesForSingleTask() {
@@ -392,7 +463,7 @@ public class DragLayout extends LinearLayout {
        mHasDropped = true;

        // Process the drop
        mPolicy.handleDrop(mCurrentTarget, event.getClipData());
        mPolicy.handleDrop(mCurrentTarget);

        // Start animating the drop UI out with the drag surface
        hide(event, dropCompleteCallback);
@@ -505,5 +576,7 @@ public class DragLayout extends LinearLayout {
        pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
        pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
        pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
        pw.println(innerPrefix + "mInsets=" + mInsets);
        pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion);
    }
}
Loading