• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.draganddrop;
18 
19 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
20 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
21 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
25 import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
26 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
27 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
28 import static android.content.Intent.EXTRA_PACKAGE_NAME;
29 import static android.content.Intent.EXTRA_SHORTCUT_ID;
30 import static android.content.Intent.EXTRA_TASK_ID;
31 import static android.content.Intent.EXTRA_USER;
32 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
33 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
34 
35 import static com.android.wm.shell.Flags.enableFlexibleSplit;
36 import static com.android.wm.shell.draganddrop.DragLayout.DEBUG_LAYOUT;
37 import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN;
38 import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
39 import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
40 import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
41 import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
42 import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
43 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
44 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
45 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
46 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
47 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
48 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
49 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
50 
51 import android.animation.Animator;
52 import android.animation.AnimatorSet;
53 import android.animation.ObjectAnimator;
54 import android.app.ActivityOptions;
55 import android.app.ActivityTaskManager;
56 import android.app.PendingIntent;
57 import android.content.ActivityNotFoundException;
58 import android.content.ClipDescription;
59 import android.content.Context;
60 import android.content.Intent;
61 import android.content.pm.LauncherApps;
62 import android.graphics.Insets;
63 import android.graphics.Rect;
64 import android.graphics.RectF;
65 import android.os.Build;
66 import android.os.Bundle;
67 import android.os.RemoteException;
68 import android.os.UserHandle;
69 import android.util.Log;
70 import android.util.Slog;
71 import android.view.View;
72 import android.window.WindowContainerToken;
73 
74 import androidx.annotation.IntDef;
75 import androidx.annotation.NonNull;
76 import androidx.annotation.Nullable;
77 import androidx.annotation.VisibleForTesting;
78 
79 import com.android.internal.logging.InstanceId;
80 import com.android.internal.protolog.ProtoLog;
81 import com.android.wm.shell.R;
82 import com.android.wm.shell.draganddrop.anim.DropTargetAnimSupplier;
83 import com.android.wm.shell.draganddrop.anim.HoverAnimProps;
84 import com.android.wm.shell.draganddrop.anim.TwoFiftyFiftyTargetAnimator;
85 import com.android.wm.shell.protolog.ShellProtoLogGroup;
86 import com.android.wm.shell.shared.split.SplitScreenConstants;
87 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
88 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
89 import com.android.wm.shell.splitscreen.SplitScreenController;
90 
91 import java.lang.annotation.Retention;
92 import java.lang.annotation.RetentionPolicy;
93 import java.util.ArrayList;
94 import java.util.HashMap;
95 import java.util.List;
96 import java.util.Map;
97 import java.util.function.BiConsumer;
98 
99 import kotlin.Pair;
100 
101 /**
102  * The policy for handling drag and drop operations to shell.
103  */
104 public class SplitDragPolicy implements DropTarget {
105 
106     private static final String TAG = SplitDragPolicy.class.getSimpleName();
107 
108     private final Context mContext;
109     // Used only for launching a fullscreen task (or as a fallback if there is no split starter)
110     private final Starter mFullscreenStarter;
111     // Used for launching tasks into splitscreen
112     private final Starter mSplitscreenStarter;
113     private final DragZoneAnimator mDragZoneAnimator;
114     private final SplitScreenController mSplitScreen;
115     private ArrayList<SplitDragPolicy.Target> mTargets = new ArrayList<>();
116     private final RectF mDisallowHitRegion = new RectF();
117     /**
118      * Maps a given SnapPosition to an array where each index of the array represents one
119      * of the targets that are being hovered over, in order (Left to Right, Top to Bottom).
120      * Ex: 4 drop targets when we're in 50/50 split
121      * 2_50_50 => [ [AnimPropsTarget1, AnimPropsTarget2, AnimPropsTarget3, AnimPropsTarget4],
122      *              ... // hovering over target 2,
123      *              ... // hovering over target 3,
124      *              ... // hovering over target 4
125      *            ]
126      */
127     private final Map<Integer, List<List<HoverAnimProps>>> mHoverAnimProps = new HashMap();
128 
129     private InstanceId mLoggerSessionId;
130     private DragSession mSession;
131     @Nullable
132     private Target mCurrentHoverTarget;
133     /** This variable is a temporary placeholder, will be queried on drag start. */
134     private int mCurrentSnapPosition = -1;
135 
SplitDragPolicy(Context context, SplitScreenController splitScreen, DragZoneAnimator dragZoneAnimator)136     public SplitDragPolicy(Context context, SplitScreenController splitScreen,
137             DragZoneAnimator dragZoneAnimator) {
138         this(context, splitScreen, new DefaultStarter(context), dragZoneAnimator);
139     }
140 
141     @VisibleForTesting
SplitDragPolicy(Context context, SplitScreenController splitScreen, Starter fullscreenStarter, DragZoneAnimator dragZoneAnimator)142     SplitDragPolicy(Context context, SplitScreenController splitScreen,
143             Starter fullscreenStarter, DragZoneAnimator dragZoneAnimator) {
144         mContext = context;
145         mSplitScreen = splitScreen;
146         mFullscreenStarter = fullscreenStarter;
147         mSplitscreenStarter = splitScreen;
148         mDragZoneAnimator = dragZoneAnimator;
149     }
150 
151     /**
152      * Starts a new drag session with the given initial drag data.
153      */
start(DragSession session, InstanceId loggerSessionId)154     public void start(DragSession session, InstanceId loggerSessionId) {
155         mLoggerSessionId = loggerSessionId;
156         mSession = session;
157         RectF disallowHitRegion = mSession.appData != null
158                 ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
159                 : null;
160         if (disallowHitRegion == null) {
161             mDisallowHitRegion.setEmpty();
162         } else {
163             mDisallowHitRegion.set(disallowHitRegion);
164         }
165     }
166 
167     /**
168      * Returns the number of targets.
169      */
170     @Override
getNumTargets()171     public int getNumTargets() {
172         return mTargets.size();
173     }
174 
175     /**
176      * Returns the target's regions based on the current state of the device and display.
177      */
178     @NonNull
179     @Override
getTargets(@onNull Insets insets)180     public ArrayList<Target> getTargets(@NonNull Insets insets) {
181         mTargets.clear();
182         if (mSession == null) {
183             // Return early if this isn't an app drag
184             return mTargets;
185         }
186 
187         final int w = mSession.displayLayout.width();
188         final int h = mSession.displayLayout.height();
189         final int iw = w - insets.left - insets.right;
190         final int ih = h - insets.top - insets.bottom;
191         final int l = insets.left;
192         final int t = insets.top;
193         final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
194         final Rect fullscreenDrawRegion = new Rect(displayRegion);
195         final Rect fullscreenHitRegion = new Rect(displayRegion);
196         final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
197         final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
198         final float dividerWidth = mContext.getResources().getDimensionPixelSize(
199                 R.dimen.split_divider_bar_width);
200         // We allow splitting if we are already in split-screen or the running task is a standard
201         // task in fullscreen mode.
202         final boolean allowSplit = inSplitScreen
203                 || (mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
204                         && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN);
205         if (allowSplit) {
206             if (enableFlexibleSplit()) {
207                 // TODO(b/349828130) get this from split screen controller, expose the SnapTarget object
208                 //  entirely and then pull out the SnapPosition
209                 @SplitScreenConstants.SnapPosition int snapPosition = SNAP_TO_2_50_50;
210                 final Rect startHitRegion = new Rect();
211                 final Rect endHitRegion = new Rect();
212                 if (!inSplitScreen) {
213                     // Currently in fullscreen, split in half
214                     final Rect startBounds = new Rect();
215                     final Rect endBounds = new Rect();
216                     mSplitScreen.getStageBounds(startBounds, endBounds);
217                     startBounds.intersect(displayRegion);
218                     endBounds.intersect(displayRegion);
219 
220                     if (isLeftRightSplit) {
221                         displayRegion.splitVertically(startHitRegion, endHitRegion);
222                     } else {
223                         displayRegion.splitHorizontally(startHitRegion, endHitRegion);
224                     }
225 
226                     mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds,
227                             SPLIT_INDEX_0));
228                     mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds,
229                             SPLIT_INDEX_1));
230                 } else {
231                     // TODO(b/349828130), move this into init function and/or the insets updating
232                     //  callback
233                     DropTargetAnimSupplier supplier = null;
234                     switch (snapPosition) {
235                         case SNAP_TO_2_50_50:
236                             supplier = new TwoFiftyFiftyTargetAnimator();
237                         break;
238                         case SplitScreenConstants.SNAP_TO_2_33_66:
239                             break;
240                         case SplitScreenConstants.SNAP_TO_2_66_33:
241                             break;
242                         case SplitScreenConstants.SNAP_TO_END_AND_DISMISS:
243                             break;
244                         case SplitScreenConstants.SNAP_TO_MINIMIZE:
245                             break;
246                         case SplitScreenConstants.SNAP_TO_NONE:
247                             break;
248                         case SplitScreenConstants.SNAP_TO_START_AND_DISMISS:
249                             break;
250                         default:
251                     }
252 
253                     Pair<List<Target>, List<List<HoverAnimProps>>> targetsAnims =
254                             supplier.getTargets(mSession.displayLayout,
255                                     insets, isLeftRightSplit, mContext.getResources());
256                     mTargets = new ArrayList<>(targetsAnims.getFirst());
257                     mHoverAnimProps.put(SNAP_TO_2_50_50, targetsAnims.getSecond());
258                     assert(mTargets.size() == targetsAnims.getSecond().size());
259                     if (DEBUG_LAYOUT) {
260                         for (List<HoverAnimProps> props : targetsAnims.getSecond()) {
261                             StringBuilder sb = new StringBuilder();
262                             for (HoverAnimProps hap : props) {
263                                 sb.append(hap).append("\n");
264                             }
265                             sb.append("\n");
266                             Log.d(TAG, sb.toString());
267                         }
268                     }
269                 }
270             } else {
271                 // Already split, allow replacing existing split task
272                 final Rect topOrLeftBounds = new Rect();
273                 final Rect bottomOrRightBounds = new Rect();
274                 mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
275                 topOrLeftBounds.intersect(displayRegion);
276                 bottomOrRightBounds.intersect(displayRegion);
277 
278                 if (isLeftRightSplit) {
279                     final Rect leftHitRegion = new Rect();
280                     final Rect rightHitRegion = new Rect();
281 
282                     // If we have existing split regions use those bounds, otherwise split it 50/50
283                     if (inSplitScreen) {
284                         // The bounds of the existing split will have a divider bar, the hit region
285                         // should include that space. Find the center of the divider bar:
286                         float centerX = topOrLeftBounds.right + (dividerWidth / 2);
287                         // Now set the hit regions using that center.
288                         leftHitRegion.set(displayRegion);
289                         leftHitRegion.right = (int) centerX;
290                         rightHitRegion.set(displayRegion);
291                         rightHitRegion.left = (int) centerX;
292                     } else {
293                         displayRegion.splitVertically(leftHitRegion, rightHitRegion);
294                     }
295 
296                     mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds,
297                             SPLIT_INDEX_UNDEFINED));
298                     mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds,
299                             SPLIT_INDEX_UNDEFINED));
300                 } else {
301                     final Rect topHitRegion = new Rect();
302                     final Rect bottomHitRegion = new Rect();
303 
304                     // If we have existing split regions use those bounds, otherwise split it 50/50
305                     if (inSplitScreen) {
306                         // The bounds of the existing split will have a divider bar, the hit region
307                         // should include that space. Find the center of the divider bar:
308                         float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
309                         // Now set the hit regions using that center.
310                         topHitRegion.set(displayRegion);
311                         topHitRegion.bottom = (int) centerX;
312                         bottomHitRegion.set(displayRegion);
313                         bottomHitRegion.top = (int) centerX;
314                     } else {
315                         displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
316                     }
317 
318                     mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds,
319                             SPLIT_INDEX_UNDEFINED));
320                     mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds,
321                             SPLIT_INDEX_UNDEFINED));
322                 }
323             }
324         } else {
325             // Split-screen not allowed, so only show the fullscreen target
326             mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion, -1));
327         }
328         return mTargets;
329     }
330 
331     /**
332      * Returns the target at the given position based on the targets previously calculated.
333      */
334     @Nullable
getTargetAtLocation(int x, int y)335     public Target getTargetAtLocation(int x, int y) {
336         if (mDisallowHitRegion.contains(x, y)) {
337             return null;
338         }
339         for (int i = mTargets.size() - 1; i >= 0; i--) {
340             SplitDragPolicy.Target t = mTargets.get(i);
341             if (enableFlexibleSplit() && mCurrentHoverTarget != null) {
342                 // If we're in flexible split, the targets themselves animate, so we have to rely
343                 // on the view's animated position for subsequent drag coordinates which we also
344                 // cache in HoverAnimProps.
345                 List<List<HoverAnimProps>> hoverAnimPropTargets =
346                         mHoverAnimProps.get(mCurrentSnapPosition);
347                 for (HoverAnimProps animProps :
348                         hoverAnimPropTargets.get(mCurrentHoverTarget.index)) {
349                     if (animProps.getHoverRect() != null &&
350                             animProps.getHoverRect().contains(x, y)) {
351                         return animProps.getTarget();
352                     }
353                 }
354 
355             }
356 
357             if (t.hitRegion.contains(x, y)) {
358                 return t;
359             }
360         }
361         return null;
362     }
363 
364     /**
365      * Handles the drop on a given {@param target}.  If a {@param hideTaskToken} is set, then the
366      * handling of the drop will attempt to hide the given task as a part of the same window
367      * container transaction if possible.
368      */
369     @VisibleForTesting
onDropped(Target target, @Nullable WindowContainerToken hideTaskToken)370     public void onDropped(Target target, @Nullable WindowContainerToken hideTaskToken) {
371         if (target == null || !mTargets.contains(target)) {
372             return;
373         }
374 
375         final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
376 
377         @SplitPosition int position = SPLIT_POSITION_UNDEFINED;
378         if (target.type != TYPE_FULLSCREEN && mSplitScreen != null) {
379             // Update launch options for the split side we are targeting.
380             position = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
381             // Add some data for logging splitscreen once it is invoked
382             mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
383         }
384 
385         final Starter starter = target.type == TYPE_FULLSCREEN
386                 ? mFullscreenStarter
387                 : mSplitscreenStarter;
388         if (mSession.appData != null) {
389             launchApp(mSession, starter, position, hideTaskToken, target.index);
390         } else {
391             launchIntent(mSession, starter, position, hideTaskToken, target.index);
392         }
393 
394         if (enableFlexibleSplit()) {
395             reset();
396         }
397     }
398 
399     /**
400      * Launches an app provided by SysUI.
401      */
launchApp(DragSession session, Starter starter, @SplitPosition int position, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int splitIndex)402     private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
403             @Nullable WindowContainerToken hideTaskToken, @SplitIndex int splitIndex) {
404         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
405                 "Launching app data at position=%d index=%d",
406                 position, splitIndex);
407         final ClipDescription description = session.getClipDescription();
408         final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
409         final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
410         final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
411         baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
412         // Put BAL flags to avoid activity start aborted.
413         baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
414                 MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
415         final Bundle opts = baseActivityOpts.toBundle();
416         if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
417             opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
418         }
419         final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
420 
421         if (isTask) {
422             final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
423             starter.startTask(taskId, position, opts, hideTaskToken);
424         } else if (isShortcut) {
425             if (hideTaskToken != null) {
426                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
427                         "Can not hide task token with starting shortcut");
428             }
429             final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
430             final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
431             starter.startShortcut(packageName, id, position, opts, user);
432         } else {
433             final PendingIntent launchIntent =
434                     session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
435             if (Build.IS_DEBUGGABLE) {
436                 if (!user.equals(launchIntent.getCreatorUserHandle())) {
437                     Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
438                 }
439             }
440             starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
441                     position, opts, hideTaskToken, splitIndex);
442         }
443     }
444 
445     /**
446      * Launches an intent sender provided by an application.
447      */
launchIntent(DragSession session, Starter starter, @SplitPosition int position, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index)448     private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
449             @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
450         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
451                 position);
452         final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
453         baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
454         baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
455                 MODE_BACKGROUND_ACTIVITY_START_DENIED);
456         // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
457         // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
458         baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
459                 | FLAG_ACTIVITY_MULTIPLE_TASK);
460 
461         final Bundle opts = baseActivityOpts.toBundle();
462         starter.startIntent(session.launchableIntent,
463                 session.launchableIntent.getCreatorUserHandle().getIdentifier(),
464                 null /* fillIntent */, position, opts, hideTaskToken, index);
465     }
466 
467     @Override
onHoveringOver(Target hoverTarget)468     public void onHoveringOver(Target hoverTarget) {
469         final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
470         final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
471         if (!inSplitScreen) {
472             // no need to animate for entering 50/50 split
473             return;
474         }
475 
476         mCurrentHoverTarget = hoverTarget;
477         if (hoverTarget == null) {
478             // Reset to default state
479             BiConsumer<Target, View> biConsumer = new BiConsumer<Target, View>() {
480                 @Override
481                 public void accept(Target target, View view) {
482                     // take into account left/right split
483                     Animator transX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
484                             target.drawRegion.left);
485                     Animator transY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
486                             target.drawRegion.top);
487                     Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, 1);
488                     Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, 1);
489                     AnimatorSet as = new AnimatorSet();
490                     as.play(transX);
491                     as.play(transY);
492                     as.play(scaleX);
493                     as.play(scaleY);
494 
495                     as.start();
496                 }
497             };
498             mDragZoneAnimator.animateDragTargets(List.of(biConsumer));
499             return;
500         }
501 
502         // TODO(b/349828130) get this from split controller
503         @SplitScreenConstants.SnapPosition int snapPosition = SNAP_TO_2_50_50;
504         mCurrentSnapPosition = SNAP_TO_2_50_50;
505         List<BiConsumer<Target, View>> animatingConsumers = new ArrayList<>();
506         final List<List<HoverAnimProps>> hoverAnimProps = mHoverAnimProps.get(snapPosition);
507         List<HoverAnimProps> animProps = hoverAnimProps.get(hoverTarget.index);
508         // Expand start and push out the rest to the end
509         BiConsumer<Target, View> biConsumer = new BiConsumer<>() {
510             @Override
511             public void accept(Target target, View view) {
512                 if (animProps.isEmpty() || animProps.size() < (target.index + 1)) {
513                     return;
514                 }
515                 HoverAnimProps singleAnimProp = animProps.get(target.index);
516                 Animator transX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
517                         singleAnimProp.getTransX());
518                 Animator transY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
519                         singleAnimProp.getTransY());
520                 Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X,
521                         singleAnimProp.getScaleX());
522                 Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y,
523                         singleAnimProp.getScaleY());
524                 AnimatorSet as = new AnimatorSet();
525                 as.play(transX);
526                 as.play(transY);
527                 as.play(scaleX);
528                 as.play(scaleY);
529                 as.start();
530             }
531         };
532         animatingConsumers.add(biConsumer);
533         mDragZoneAnimator.animateDragTargets(animatingConsumers);
534     }
535 
reset()536     private void reset() {
537         mCurrentHoverTarget = null;
538         mCurrentSnapPosition = -1;
539     }
540 
541 
542 
543     /**
544      * Interface for actually committing the task launches.
545      */
546     public interface Starter {
startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken)547         void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
548                 @Nullable WindowContainerToken hideTaskToken);
startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user)549         void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
550                 @Nullable Bundle options, UserHandle user);
startIntent(PendingIntent intent, int userId, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index)551         void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
552                 @SplitPosition int position, @Nullable Bundle options,
553                 @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index);
enterSplitScreen(int taskId, boolean leftOrTop)554         void enterSplitScreen(int taskId, boolean leftOrTop);
555 
556         /**
557          * Exits splitscreen, with an associated exit trigger from the SplitscreenUIChanged proto
558          * for logging.
559          */
exitSplitScreen(int toTopTaskId, int exitTrigger)560         void exitSplitScreen(int toTopTaskId, int exitTrigger);
561     }
562 
563     /**
564      * Default implementation of the starter which calls through the system services to launch the
565      * tasks.
566      */
567     private static class DefaultStarter implements Starter {
568         private final Context mContext;
569 
DefaultStarter(Context context)570         public DefaultStarter(Context context) {
571             mContext = context;
572         }
573 
574         @Override
startTask(int taskId, int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken)575         public void startTask(int taskId, int position, @Nullable Bundle options,
576                 @Nullable WindowContainerToken hideTaskToken) {
577             if (hideTaskToken != null) {
578                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
579                         "Default starter does not support hide task token");
580             }
581             try {
582                 ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
583             } catch (RemoteException e) {
584                 Slog.e(TAG, "Failed to launch task", e);
585             }
586         }
587 
588         @Override
startShortcut(String packageName, String shortcutId, int position, @Nullable Bundle options, UserHandle user)589         public void startShortcut(String packageName, String shortcutId, int position,
590                 @Nullable Bundle options, UserHandle user) {
591             try {
592                 LauncherApps launcherApps =
593                         mContext.getSystemService(LauncherApps.class);
594                 launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
595                         options, user);
596             } catch (ActivityNotFoundException e) {
597                 Slog.e(TAG, "Failed to launch shortcut", e);
598             }
599         }
600 
601         @Override
startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent, int position, @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index)602         public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
603                 int position, @Nullable Bundle options,
604                 @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
605             if (hideTaskToken != null) {
606                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
607                         "Default starter does not support hide task token");
608             }
609             try {
610                 intent.send(mContext, 0, fillInIntent, null, null, null, options);
611             } catch (PendingIntent.CanceledException e) {
612                 Slog.e(TAG, "Failed to launch activity", e);
613             }
614         }
615 
616         @Override
enterSplitScreen(int taskId, boolean leftOrTop)617         public void enterSplitScreen(int taskId, boolean leftOrTop) {
618             throw new UnsupportedOperationException("enterSplitScreen not implemented by starter");
619         }
620 
621         @Override
exitSplitScreen(int toTopTaskId, int exitTrigger)622         public void exitSplitScreen(int toTopTaskId, int exitTrigger) {
623             throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
624         }
625     }
626 
627     /**
628      * Represents a drop target.
629      * TODO(b/349828130): Move this into {@link DropTarget}
630      */
631     public static class Target {
632         static final int TYPE_FULLSCREEN = 0;
633         public static final int TYPE_SPLIT_LEFT = 1;
634         static final int TYPE_SPLIT_TOP = 2;
635         static final int TYPE_SPLIT_RIGHT = 3;
636         static final int TYPE_SPLIT_BOTTOM = 4;
637         @IntDef(value = {
638                 TYPE_FULLSCREEN,
639                 TYPE_SPLIT_LEFT,
640                 TYPE_SPLIT_TOP,
641                 TYPE_SPLIT_RIGHT,
642                 TYPE_SPLIT_BOTTOM
643         })
644         @Retention(RetentionPolicy.SOURCE)
645         @interface Type{}
646 
647         final @Type int type;
648 
649         // The actual hit region for this region
650         final Rect hitRegion;
651         // The approximate visual region for where the task will start
652         final Rect drawRegion;
653         @SplitIndex int index;
654 
655         /**
656          * @param index 0-indexed, represents which position of drop target this object represents,
657          *              0 to N for left to right, top to bottom
658          */
Target(@ype int t, Rect hit, Rect draw, @SplitIndex int index)659         public Target(@Type int t, Rect hit, Rect draw, @SplitIndex int index) {
660             type = t;
661             hitRegion = hit;
662             drawRegion = draw;
663             this.index = index;
664         }
665 
666         @Override
toString()667         public String toString() {
668             return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion
669                     + " index=" + index + "}";
670         }
671     }
672 }
673