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