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.common.split; 18 19 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; 20 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; 21 import static android.view.WindowManager.DOCKED_BOTTOM; 22 import static android.view.WindowManager.DOCKED_INVALID; 23 import static android.view.WindowManager.DOCKED_LEFT; 24 import static android.view.WindowManager.DOCKED_RIGHT; 25 import static android.view.WindowManager.DOCKED_TOP; 26 27 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; 28 import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END; 29 import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START; 30 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; 31 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR; 32 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; 33 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; 34 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; 35 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; 36 37 import android.animation.Animator; 38 import android.animation.AnimatorListenerAdapter; 39 import android.animation.AnimatorSet; 40 import android.animation.ValueAnimator; 41 import android.annotation.NonNull; 42 import android.app.ActivityManager; 43 import android.content.Context; 44 import android.content.res.Configuration; 45 import android.content.res.Resources; 46 import android.graphics.Point; 47 import android.graphics.Rect; 48 import android.view.Display; 49 import android.view.InsetsSourceControl; 50 import android.view.InsetsState; 51 import android.view.RoundedCorner; 52 import android.view.SurfaceControl; 53 import android.view.WindowInsets; 54 import android.view.WindowManager; 55 import android.window.WindowContainerToken; 56 import android.window.WindowContainerTransaction; 57 58 import androidx.annotation.Nullable; 59 60 import com.android.internal.annotations.VisibleForTesting; 61 import com.android.internal.policy.DividerSnapAlgorithm; 62 import com.android.internal.policy.DockedDividerUtils; 63 import com.android.wm.shell.R; 64 import com.android.wm.shell.ShellTaskOrganizer; 65 import com.android.wm.shell.animation.Interpolators; 66 import com.android.wm.shell.common.DisplayImeController; 67 import com.android.wm.shell.common.DisplayInsetsController; 68 import com.android.wm.shell.common.InteractionJankMonitorUtils; 69 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; 70 71 import java.io.PrintWriter; 72 import java.util.function.Consumer; 73 74 /** 75 * Records and handles layout of splits. Helps to calculate proper bounds when configuration or 76 * divide position changes. 77 */ 78 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener { 79 80 public static final int PARALLAX_NONE = 0; 81 public static final int PARALLAX_DISMISSING = 1; 82 public static final int PARALLAX_ALIGN_CENTER = 2; 83 84 public static final int FLING_RESIZE_DURATION = 250; 85 private static final int FLING_SWITCH_DURATION = 350; 86 private static final int FLING_ENTER_DURATION = 450; 87 private static final int FLING_EXIT_DURATION = 450; 88 89 private int mDividerWindowWidth; 90 private int mDividerInsets; 91 private int mDividerSize; 92 93 private final Rect mTempRect = new Rect(); 94 private final Rect mRootBounds = new Rect(); 95 private final Rect mDividerBounds = new Rect(); 96 // Bounds1 final position should be always at top or left 97 private final Rect mBounds1 = new Rect(); 98 // Bounds2 final position should be always at bottom or right 99 private final Rect mBounds2 = new Rect(); 100 // The temp bounds outside of display bounds for side stage when split screen inactive to avoid 101 // flicker next time active split screen. 102 private final Rect mInvisibleBounds = new Rect(); 103 private final Rect mWinBounds1 = new Rect(); 104 private final Rect mWinBounds2 = new Rect(); 105 private final SplitLayoutHandler mSplitLayoutHandler; 106 private final SplitWindowManager mSplitWindowManager; 107 private final DisplayImeController mDisplayImeController; 108 private final ImePositionProcessor mImePositionProcessor; 109 private final ResizingEffectPolicy mSurfaceEffectPolicy; 110 private final ShellTaskOrganizer mTaskOrganizer; 111 private final InsetsState mInsetsState = new InsetsState(); 112 113 private Context mContext; 114 @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm; 115 private WindowContainerToken mWinToken1; 116 private WindowContainerToken mWinToken2; 117 private int mDividePosition; 118 private boolean mInitialized = false; 119 private boolean mFreezeDividerWindow = false; 120 private int mOrientation; 121 private int mRotation; 122 private int mDensity; 123 private int mUiMode; 124 125 private final boolean mDimNonImeSide; 126 private ValueAnimator mDividerFlingAnimator; 127 SplitLayout(String windowName, Context context, Configuration configuration, SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer, int parallaxType)128 public SplitLayout(String windowName, Context context, Configuration configuration, 129 SplitLayoutHandler splitLayoutHandler, 130 SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, 131 DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer, 132 int parallaxType) { 133 mContext = context.createConfigurationContext(configuration); 134 mOrientation = configuration.orientation; 135 mRotation = configuration.windowConfiguration.getRotation(); 136 mDensity = configuration.densityDpi; 137 mSplitLayoutHandler = splitLayoutHandler; 138 mDisplayImeController = displayImeController; 139 mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration, 140 parentContainerCallbacks); 141 mTaskOrganizer = taskOrganizer; 142 mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); 143 mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType); 144 145 updateDividerConfig(mContext); 146 147 mRootBounds.set(configuration.windowConfiguration.getBounds()); 148 mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); 149 resetDividerPosition(); 150 151 mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide); 152 153 updateInvisibleRect(); 154 } 155 updateDividerConfig(Context context)156 private void updateDividerConfig(Context context) { 157 final Resources resources = context.getResources(); 158 final Display display = context.getDisplay(); 159 final int dividerInset = resources.getDimensionPixelSize( 160 com.android.internal.R.dimen.docked_stack_divider_insets); 161 int radius = 0; 162 RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); 163 radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; 164 corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT); 165 radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; 166 corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT); 167 radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; 168 corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); 169 radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; 170 171 mDividerInsets = Math.max(dividerInset, radius); 172 mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width); 173 mDividerWindowWidth = mDividerSize + 2 * mDividerInsets; 174 } 175 176 /** Gets bounds of the primary split with screen based coordinate. */ getBounds1()177 public Rect getBounds1() { 178 return new Rect(mBounds1); 179 } 180 181 /** Gets bounds of the primary split with parent based coordinate. */ getRefBounds1()182 public Rect getRefBounds1() { 183 Rect outBounds = getBounds1(); 184 outBounds.offset(-mRootBounds.left, -mRootBounds.top); 185 return outBounds; 186 } 187 188 /** Gets bounds of the secondary split with screen based coordinate. */ getBounds2()189 public Rect getBounds2() { 190 return new Rect(mBounds2); 191 } 192 193 /** Gets bounds of the secondary split with parent based coordinate. */ getRefBounds2()194 public Rect getRefBounds2() { 195 final Rect outBounds = getBounds2(); 196 outBounds.offset(-mRootBounds.left, -mRootBounds.top); 197 return outBounds; 198 } 199 200 /** Gets root bounds of the whole split layout */ getRootBounds()201 public Rect getRootBounds() { 202 return new Rect(mRootBounds); 203 } 204 205 /** Gets bounds of divider window with screen based coordinate. */ getDividerBounds()206 public Rect getDividerBounds() { 207 return new Rect(mDividerBounds); 208 } 209 210 /** Gets bounds of divider window with parent based coordinate. */ getRefDividerBounds()211 public Rect getRefDividerBounds() { 212 final Rect outBounds = getDividerBounds(); 213 outBounds.offset(-mRootBounds.left, -mRootBounds.top); 214 return outBounds; 215 } 216 217 /** Gets bounds of the primary split with screen based coordinate on the param Rect. */ getBounds1(Rect rect)218 public void getBounds1(Rect rect) { 219 rect.set(mBounds1); 220 } 221 222 /** Gets bounds of the primary split with parent based coordinate on the param Rect. */ getRefBounds1(Rect rect)223 public void getRefBounds1(Rect rect) { 224 getBounds1(rect); 225 rect.offset(-mRootBounds.left, -mRootBounds.top); 226 } 227 228 /** Gets bounds of the secondary split with screen based coordinate on the param Rect. */ getBounds2(Rect rect)229 public void getBounds2(Rect rect) { 230 rect.set(mBounds2); 231 } 232 233 /** Gets bounds of the secondary split with parent based coordinate on the param Rect. */ getRefBounds2(Rect rect)234 public void getRefBounds2(Rect rect) { 235 getBounds2(rect); 236 rect.offset(-mRootBounds.left, -mRootBounds.top); 237 } 238 239 /** Gets root bounds of the whole split layout on the param Rect. */ getRootBounds(Rect rect)240 public void getRootBounds(Rect rect) { 241 rect.set(mRootBounds); 242 } 243 244 /** Gets bounds of divider window with screen based coordinate on the param Rect. */ getDividerBounds(Rect rect)245 public void getDividerBounds(Rect rect) { 246 rect.set(mDividerBounds); 247 } 248 249 /** Gets bounds of divider window with parent based coordinate on the param Rect. */ getRefDividerBounds(Rect rect)250 public void getRefDividerBounds(Rect rect) { 251 getDividerBounds(rect); 252 rect.offset(-mRootBounds.left, -mRootBounds.top); 253 } 254 255 /** Gets bounds size equal to root bounds but outside of screen, used for position side stage 256 * when split inactive to avoid flicker when next time active. */ getInvisibleBounds(Rect rect)257 public void getInvisibleBounds(Rect rect) { 258 rect.set(mInvisibleBounds); 259 } 260 261 /** Returns leash of the current divider bar. */ 262 @Nullable getDividerLeash()263 public SurfaceControl getDividerLeash() { 264 return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl(); 265 } 266 getDividePosition()267 int getDividePosition() { 268 return mDividePosition; 269 } 270 271 /** 272 * Returns the divider position as a fraction from 0 to 1. 273 */ getDividerPositionAsFraction()274 public float getDividerPositionAsFraction() { 275 return Math.min(1f, Math.max(0f, isLandscape() 276 ? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right 277 : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom)); 278 } 279 updateInvisibleRect()280 private void updateInvisibleRect() { 281 mInvisibleBounds.set(mRootBounds.left, mRootBounds.top, 282 isLandscape() ? mRootBounds.right / 2 : mRootBounds.right, 283 isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2); 284 mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0, 285 isLandscape() ? 0 : mRootBounds.bottom); 286 } 287 288 /** Applies new configuration, returns {@code false} if there's no effect to the layout. */ updateConfiguration(Configuration configuration)289 public boolean updateConfiguration(Configuration configuration) { 290 // Update the split bounds when necessary. Besides root bounds changed, split bounds need to 291 // be updated when the rotation changed to cover the case that users rotated the screen 180 292 // degrees. 293 // Make sure to render the divider bar with proper resources that matching the screen 294 // orientation. 295 final int rotation = configuration.windowConfiguration.getRotation(); 296 final Rect rootBounds = configuration.windowConfiguration.getBounds(); 297 final int orientation = configuration.orientation; 298 final int density = configuration.densityDpi; 299 final int uiMode = configuration.uiMode; 300 301 if (mOrientation == orientation 302 && mRotation == rotation 303 && mDensity == density 304 && mUiMode == uiMode 305 && mRootBounds.equals(rootBounds)) { 306 return false; 307 } 308 309 mContext = mContext.createConfigurationContext(configuration); 310 mSplitWindowManager.setConfiguration(configuration); 311 mOrientation = orientation; 312 mTempRect.set(mRootBounds); 313 mRootBounds.set(rootBounds); 314 mRotation = rotation; 315 mDensity = density; 316 mUiMode = uiMode; 317 mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); 318 updateDividerConfig(mContext); 319 initDividerPosition(mTempRect); 320 updateInvisibleRect(); 321 322 return true; 323 } 324 325 /** Rotate the layout to specific rotation and calculate new bounds. The stable insets value 326 * should be calculated by display layout. */ rotateTo(int newRotation, Rect stableInsets)327 public void rotateTo(int newRotation, Rect stableInsets) { 328 final int rotationDelta = (newRotation - mRotation + 4) % 4; 329 final boolean changeOrient = (rotationDelta % 2) != 0; 330 331 mRotation = newRotation; 332 Rect tmpRect = new Rect(mRootBounds); 333 if (changeOrient) { 334 tmpRect.set(mRootBounds.top, mRootBounds.left, mRootBounds.bottom, mRootBounds.right); 335 } 336 337 // We only need new bounds here, other configuration should be update later. 338 mTempRect.set(mRootBounds); 339 mRootBounds.set(tmpRect); 340 mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, stableInsets); 341 initDividerPosition(mTempRect); 342 } 343 initDividerPosition(Rect oldBounds)344 private void initDividerPosition(Rect oldBounds) { 345 final float snapRatio = (float) mDividePosition 346 / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height()); 347 // Estimate position by previous ratio. 348 final float length = 349 (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height()); 350 final int estimatePosition = (int) (length * snapRatio); 351 // Init divider position by estimated position using current bounds snap algorithm. 352 mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget( 353 estimatePosition).position; 354 updateBounds(mDividePosition); 355 } 356 updateBounds(int position)357 private void updateBounds(int position) { 358 updateBounds(position, mBounds1, mBounds2, mDividerBounds, true /* setEffectBounds */); 359 } 360 361 /** Updates recording bounds of divider window and both of the splits. */ updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds, boolean setEffectBounds)362 private void updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds, 363 boolean setEffectBounds) { 364 dividerBounds.set(mRootBounds); 365 bounds1.set(mRootBounds); 366 bounds2.set(mRootBounds); 367 final boolean isLandscape = isLandscape(mRootBounds); 368 if (isLandscape) { 369 position += mRootBounds.left; 370 dividerBounds.left = position - mDividerInsets; 371 dividerBounds.right = dividerBounds.left + mDividerWindowWidth; 372 bounds1.right = position; 373 bounds2.left = bounds1.right + mDividerSize; 374 } else { 375 position += mRootBounds.top; 376 dividerBounds.top = position - mDividerInsets; 377 dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth; 378 bounds1.bottom = position; 379 bounds2.top = bounds1.bottom + mDividerSize; 380 } 381 DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */); 382 DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */); 383 if (setEffectBounds) { 384 mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape); 385 } 386 } 387 388 /** Inflates {@link DividerView} on the root surface. */ init()389 public void init() { 390 if (mInitialized) return; 391 mInitialized = true; 392 mSplitWindowManager.init(this, mInsetsState); 393 mDisplayImeController.addPositionProcessor(mImePositionProcessor); 394 } 395 396 /** Releases the surface holding the current {@link DividerView}. */ release(SurfaceControl.Transaction t)397 public void release(SurfaceControl.Transaction t) { 398 if (!mInitialized) return; 399 mInitialized = false; 400 mSplitWindowManager.release(t); 401 mDisplayImeController.removePositionProcessor(mImePositionProcessor); 402 mImePositionProcessor.reset(); 403 if (mDividerFlingAnimator != null) { 404 mDividerFlingAnimator.cancel(); 405 } 406 resetDividerPosition(); 407 } 408 release()409 public void release() { 410 release(null /* t */); 411 } 412 413 /** Releases and re-inflates {@link DividerView} on the root surface. */ update(SurfaceControl.Transaction t)414 public void update(SurfaceControl.Transaction t) { 415 if (!mInitialized) return; 416 mSplitWindowManager.release(t); 417 mImePositionProcessor.reset(); 418 mSplitWindowManager.init(this, mInsetsState); 419 } 420 421 @Override insetsChanged(InsetsState insetsState)422 public void insetsChanged(InsetsState insetsState) { 423 mInsetsState.set(insetsState); 424 if (!mInitialized) { 425 return; 426 } 427 if (mFreezeDividerWindow) { 428 // DO NOT change its layout before transition actually run because it might cause 429 // flicker. 430 return; 431 } 432 mSplitWindowManager.onInsetsChanged(insetsState); 433 } 434 435 @Override insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)436 public void insetsControlChanged(InsetsState insetsState, 437 InsetsSourceControl[] activeControls) { 438 if (!mInsetsState.equals(insetsState)) { 439 insetsChanged(insetsState); 440 } 441 } 442 setFreezeDividerWindow(boolean freezeDividerWindow)443 public void setFreezeDividerWindow(boolean freezeDividerWindow) { 444 mFreezeDividerWindow = freezeDividerWindow; 445 } 446 447 /** Update current layout as divider put on start or end position. */ setDividerAtBorder(boolean start)448 public void setDividerAtBorder(boolean start) { 449 final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position 450 : mDividerSnapAlgorithm.getDismissEndTarget().position; 451 setDividePosition(pos, false /* applyLayoutChange */); 452 } 453 454 /** 455 * Updates bounds with the passing position. Usually used to update recording bounds while 456 * performing animation or dragging divider bar to resize the splits. 457 */ updateDivideBounds(int position)458 void updateDivideBounds(int position) { 459 updateBounds(position); 460 mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x, 461 mSurfaceEffectPolicy.mParallaxOffset.y); 462 } 463 setDividePosition(int position, boolean applyLayoutChange)464 void setDividePosition(int position, boolean applyLayoutChange) { 465 mDividePosition = position; 466 updateBounds(mDividePosition); 467 if (applyLayoutChange) { 468 mSplitLayoutHandler.onLayoutSizeChanged(this); 469 } 470 } 471 472 /** Updates divide position and split bounds base on the ratio within root bounds. */ setDivideRatio(float ratio)473 public void setDivideRatio(float ratio) { 474 final int position = isLandscape() 475 ? mRootBounds.left + (int) (mRootBounds.width() * ratio) 476 : mRootBounds.top + (int) (mRootBounds.height() * ratio); 477 final DividerSnapAlgorithm.SnapTarget snapTarget = 478 mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); 479 setDividePosition(snapTarget.position, false /* applyLayoutChange */); 480 } 481 482 /** Resets divider position. */ resetDividerPosition()483 public void resetDividerPosition() { 484 mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; 485 updateBounds(mDividePosition); 486 mWinToken1 = null; 487 mWinToken2 = null; 488 mWinBounds1.setEmpty(); 489 mWinBounds2.setEmpty(); 490 } 491 492 /** 493 * Sets new divide position and updates bounds correspondingly. Notifies listener if the new 494 * target indicates dismissing split. 495 */ snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget)496 public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { 497 switch (snapTarget.flag) { 498 case FLAG_DISMISS_START: 499 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, 500 () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, 501 EXIT_REASON_DRAG_DIVIDER)); 502 break; 503 case FLAG_DISMISS_END: 504 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, 505 () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, 506 EXIT_REASON_DRAG_DIVIDER)); 507 break; 508 default: 509 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, 510 () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); 511 break; 512 } 513 } 514 onStartDragging()515 void onStartDragging() { 516 InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext, 517 getDividerLeash(), null /* tag */); 518 } 519 onDraggingCancelled()520 void onDraggingCancelled() { 521 InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE); 522 } 523 onDoubleTappedDivider()524 void onDoubleTappedDivider() { 525 mSplitLayoutHandler.onDoubleTappedDivider(); 526 } 527 528 /** 529 * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity. 530 * If hardDismiss is set to {@code true}, it will be harder to reach dismiss target. 531 */ findSnapTarget(int position, float velocity, boolean hardDismiss)532 public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity, 533 boolean hardDismiss) { 534 return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); 535 } 536 getSnapAlgorithm(Context context, Rect rootBounds, @Nullable Rect stableInsets)537 private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds, 538 @Nullable Rect stableInsets) { 539 final boolean isLandscape = isLandscape(rootBounds); 540 final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context); 541 542 // Make split axis insets value same as the larger one to avoid bounds1 and bounds2 543 // have difference for avoiding size-compat mode when switching unresizable apps in 544 // landscape while they are letterboxed. 545 if (!isLandscape) { 546 final int largerInsets = Math.max(insets.top, insets.bottom); 547 insets.set(insets.left, largerInsets, insets.right, largerInsets); 548 } 549 550 return new DividerSnapAlgorithm( 551 context.getResources(), 552 rootBounds.width(), 553 rootBounds.height(), 554 mDividerSize, 555 !isLandscape, 556 insets, 557 isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); 558 } 559 560 /** Fling divider from current position to end or start position then exit */ flingDividerToDismiss(boolean toEnd, int reason)561 public void flingDividerToDismiss(boolean toEnd, int reason) { 562 final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position 563 : mDividerSnapAlgorithm.getDismissStartTarget().position; 564 flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION, 565 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); 566 } 567 568 /** Fling divider from current position to center position. */ flingDividerToCenter()569 public void flingDividerToCenter() { 570 final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; 571 flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION, 572 () -> setDividePosition(pos, true /* applyLayoutChange */)); 573 } 574 575 @VisibleForTesting flingDividePosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback)576 void flingDividePosition(int from, int to, int duration, 577 @Nullable Runnable flingFinishedCallback) { 578 if (from == to) { 579 // No animation run, still callback to stop resizing. 580 mSplitLayoutHandler.onLayoutSizeChanged(this); 581 582 if (flingFinishedCallback != null) { 583 flingFinishedCallback.run(); 584 } 585 InteractionJankMonitorUtils.endTracing( 586 CUJ_SPLIT_SCREEN_RESIZE); 587 return; 588 } 589 590 if (mDividerFlingAnimator != null) { 591 mDividerFlingAnimator.cancel(); 592 } 593 594 mDividerFlingAnimator = ValueAnimator 595 .ofInt(from, to) 596 .setDuration(duration); 597 mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 598 mDividerFlingAnimator.addUpdateListener( 599 animation -> updateDivideBounds((int) animation.getAnimatedValue())); 600 mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() { 601 @Override 602 public void onAnimationEnd(Animator animation) { 603 if (flingFinishedCallback != null) { 604 flingFinishedCallback.run(); 605 } 606 InteractionJankMonitorUtils.endTracing( 607 CUJ_SPLIT_SCREEN_RESIZE); 608 mDividerFlingAnimator = null; 609 } 610 611 @Override 612 public void onAnimationCancel(Animator animation) { 613 mDividerFlingAnimator = null; 614 } 615 }); 616 mDividerFlingAnimator.start(); 617 } 618 619 /** Switch both surface position with animation. */ splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback)620 public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, 621 SurfaceControl leash2, Consumer<Rect> finishCallback) { 622 final boolean isLandscape = isLandscape(); 623 final Rect insets = getDisplayInsets(mContext); 624 insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top, 625 isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom); 626 627 final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget( 628 isLandscape ? mBounds2.width() : mBounds2.height()).position; 629 final Rect distBounds1 = new Rect(); 630 final Rect distBounds2 = new Rect(); 631 final Rect distDividerBounds = new Rect(); 632 // Compute dist bounds. 633 updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds, 634 false /* setEffectBounds */); 635 // Offset to real position under root container. 636 distBounds1.offset(-mRootBounds.left, -mRootBounds.top); 637 distBounds2.offset(-mRootBounds.left, -mRootBounds.top); 638 distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top); 639 640 ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1, 641 -insets.left, -insets.top); 642 ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2, 643 insets.left, insets.top); 644 ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(), 645 distDividerBounds, 0 /* offsetX */, 0 /* offsetY */); 646 647 AnimatorSet set = new AnimatorSet(); 648 set.playTogether(animator1, animator2, animator3); 649 set.setDuration(FLING_SWITCH_DURATION); 650 set.addListener(new AnimatorListenerAdapter() { 651 @Override 652 public void onAnimationEnd(Animator animation) { 653 mDividePosition = dividerPos; 654 updateBounds(mDividePosition); 655 finishCallback.accept(insets); 656 } 657 }); 658 set.start(); 659 } 660 moveSurface(SurfaceControl.Transaction t, SurfaceControl leash, Rect start, Rect end, float offsetX, float offsetY)661 private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash, 662 Rect start, Rect end, float offsetX, float offsetY) { 663 Rect tempStart = new Rect(start); 664 Rect tempEnd = new Rect(end); 665 final float diffX = tempEnd.left - tempStart.left; 666 final float diffY = tempEnd.top - tempStart.top; 667 final float diffWidth = tempEnd.width() - tempStart.width(); 668 final float diffHeight = tempEnd.height() - tempStart.height(); 669 ValueAnimator animator = ValueAnimator.ofFloat(0, 1); 670 animator.addUpdateListener(animation -> { 671 if (leash == null) return; 672 673 final float scale = (float) animation.getAnimatedValue(); 674 final float distX = tempStart.left + scale * diffX; 675 final float distY = tempStart.top + scale * diffY; 676 final int width = (int) (tempStart.width() + scale * diffWidth); 677 final int height = (int) (tempStart.height() + scale * diffHeight); 678 if (offsetX == 0 && offsetY == 0) { 679 t.setPosition(leash, distX, distY); 680 t.setWindowCrop(leash, width, height); 681 } else { 682 final int diffOffsetX = (int) (scale * offsetX); 683 final int diffOffsetY = (int) (scale * offsetY); 684 t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY); 685 mTempRect.set(0, 0, width, height); 686 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY); 687 t.setCrop(leash, mTempRect); 688 } 689 t.apply(); 690 }); 691 return animator; 692 } 693 getDisplayInsets(Context context)694 private static Rect getDisplayInsets(Context context) { 695 return context.getSystemService(WindowManager.class) 696 .getMaximumWindowMetrics() 697 .getWindowInsets() 698 .getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()) 699 .toRect(); 700 } 701 isLandscape(Rect bounds)702 private static boolean isLandscape(Rect bounds) { 703 return bounds.width() > bounds.height(); 704 } 705 706 /** 707 * Return if this layout is landscape. 708 */ isLandscape()709 public boolean isLandscape() { 710 return isLandscape(mRootBounds); 711 } 712 713 /** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */ applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2, boolean applyResizingOffset)714 public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1, 715 SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2, 716 boolean applyResizingOffset) { 717 final SurfaceControl dividerLeash = getDividerLeash(); 718 if (dividerLeash != null) { 719 getRefDividerBounds(mTempRect); 720 t.setPosition(dividerLeash, mTempRect.left, mTempRect.top); 721 // Resets layer of divider bar to make sure it is always on top. 722 t.setLayer(dividerLeash, Integer.MAX_VALUE); 723 } 724 getRefBounds1(mTempRect); 725 t.setPosition(leash1, mTempRect.left, mTempRect.top) 726 .setWindowCrop(leash1, mTempRect.width(), mTempRect.height()); 727 getRefBounds2(mTempRect); 728 t.setPosition(leash2, mTempRect.left, mTempRect.top) 729 .setWindowCrop(leash2, mTempRect.width(), mTempRect.height()); 730 // Make right or bottom side surface always higher than left or top side to avoid weird 731 // animation when dismiss split. e.g. App surface fling above on decor surface. 732 t.setLayer(leash1, 1); 733 t.setLayer(leash2, 2); 734 735 if (mImePositionProcessor.adjustSurfaceLayoutForIme( 736 t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) { 737 return; 738 } 739 740 mSurfaceEffectPolicy.adjustDimSurface(t, dimLayer1, dimLayer2); 741 if (applyResizingOffset) { 742 mSurfaceEffectPolicy.adjustRootSurface(t, leash1, leash2); 743 } 744 } 745 746 /** Apply recorded task layout to the {@link WindowContainerTransaction}. */ applyTaskChanges(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2)747 public void applyTaskChanges(WindowContainerTransaction wct, 748 ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { 749 if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) { 750 wct.setBounds(task1.token, mBounds1); 751 wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1)); 752 mWinBounds1.set(mBounds1); 753 mWinToken1 = task1.token; 754 } 755 if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) { 756 wct.setBounds(task2.token, mBounds2); 757 wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2)); 758 mWinBounds2.set(mBounds2); 759 mWinToken2 = task2.token; 760 } 761 } 762 getSmallestWidthDp(Rect bounds)763 private int getSmallestWidthDp(Rect bounds) { 764 mTempRect.set(bounds); 765 mTempRect.inset(getDisplayInsets(mContext)); 766 final int minWidth = Math.min(mTempRect.width(), mTempRect.height()); 767 final float density = mContext.getResources().getDisplayMetrics().density; 768 return (int) (minWidth / density); 769 } 770 771 /** 772 * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And 773 * restore shifted configuration bounds if it's no longer shifted. 774 */ applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY, ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2)775 public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY, 776 ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) { 777 if (offsetX == 0 && offsetY == 0) { 778 wct.setBounds(taskInfo1.token, mBounds1); 779 wct.setScreenSizeDp(taskInfo1.token, 780 SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); 781 782 wct.setBounds(taskInfo2.token, mBounds2); 783 wct.setScreenSizeDp(taskInfo2.token, 784 SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); 785 } else { 786 getBounds1(mTempRect); 787 mTempRect.offset(offsetX, offsetY); 788 wct.setBounds(taskInfo1.token, mTempRect); 789 wct.setScreenSizeDp(taskInfo1.token, 790 taskInfo1.configuration.screenWidthDp, 791 taskInfo1.configuration.screenHeightDp); 792 793 getBounds2(mTempRect); 794 mTempRect.offset(offsetX, offsetY); 795 wct.setBounds(taskInfo2.token, mTempRect); 796 wct.setScreenSizeDp(taskInfo2.token, 797 taskInfo2.configuration.screenWidthDp, 798 taskInfo2.configuration.screenHeightDp); 799 } 800 } 801 802 /** Dumps the current split bounds recorded in this layout. */ dump(@onNull PrintWriter pw, String prefix)803 public void dump(@NonNull PrintWriter pw, String prefix) { 804 pw.println(prefix + "bounds1=" + mBounds1.toShortString()); 805 pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString()); 806 pw.println(prefix + "bounds2=" + mBounds2.toShortString()); 807 } 808 809 /** Handles layout change event. */ 810 public interface SplitLayoutHandler { 811 812 /** Calls when dismissing split. */ onSnappedToDismiss(boolean snappedToEnd, int reason)813 void onSnappedToDismiss(boolean snappedToEnd, int reason); 814 815 /** 816 * Calls when resizing the split bounds. 817 * 818 * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, 819 * SurfaceControl, SurfaceControl, boolean) 820 */ onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY)821 void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY); 822 823 /** 824 * Calls when finish resizing the split bounds. 825 * 826 * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo, 827 * ActivityManager.RunningTaskInfo) 828 * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, 829 * SurfaceControl, SurfaceControl, boolean) 830 */ onLayoutSizeChanged(SplitLayout layout)831 void onLayoutSizeChanged(SplitLayout layout); 832 833 /** 834 * Calls when re-positioning the split bounds. Like moving split bounds while showing IME 835 * panel. 836 * 837 * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, 838 * SurfaceControl, SurfaceControl, boolean) 839 */ onLayoutPositionChanging(SplitLayout layout)840 void onLayoutPositionChanging(SplitLayout layout); 841 842 /** 843 * Notifies the target offset for shifting layout. So layout handler can shift configuration 844 * bounds correspondingly to make sure client apps won't get configuration changed or 845 * relaunched. If the layout is no longer shifted, layout handler should restore shifted 846 * configuration bounds. 847 * 848 * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int, 849 * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo) 850 */ setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout)851 void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout); 852 853 /** Calls when user double tapped on the divider bar. */ onDoubleTappedDivider()854 default void onDoubleTappedDivider() { 855 } 856 857 /** Returns split position of the token. */ 858 @SplitPosition getSplitItemPosition(WindowContainerToken token)859 int getSplitItemPosition(WindowContainerToken token); 860 } 861 862 /** 863 * Calculates and applies proper dismissing parallax offset and dimming value to hint users 864 * dismissing gesture. 865 */ 866 private class ResizingEffectPolicy { 867 /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */ 868 private final int mParallaxType; 869 870 int mShrinkSide = DOCKED_INVALID; 871 872 // The current dismissing side. 873 int mDismissingSide = DOCKED_INVALID; 874 875 // The parallax offset to hint the dismissing side and progress. 876 final Point mParallaxOffset = new Point(); 877 878 // The dimming value to hint the dismissing side and progress. 879 float mDismissingDimValue = 0.0f; 880 final Rect mContentBounds = new Rect(); 881 final Rect mSurfaceBounds = new Rect(); 882 ResizingEffectPolicy(int parallaxType)883 ResizingEffectPolicy(int parallaxType) { 884 mParallaxType = parallaxType; 885 } 886 887 /** 888 * Applies a parallax to the task to hint dismissing progress. 889 * 890 * @param position the split position to apply dismissing parallax effect 891 * @param isLandscape indicates whether it's splitting horizontally or vertically 892 */ applyDividerPosition(int position, boolean isLandscape)893 void applyDividerPosition(int position, boolean isLandscape) { 894 mDismissingSide = DOCKED_INVALID; 895 mParallaxOffset.set(0, 0); 896 mDismissingDimValue = 0; 897 898 int totalDismissingDistance = 0; 899 if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) { 900 mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP; 901 totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position 902 - mDividerSnapAlgorithm.getFirstSplitTarget().position; 903 } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) { 904 mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM; 905 totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position 906 - mDividerSnapAlgorithm.getDismissEndTarget().position; 907 } 908 909 final boolean topLeftShrink = isLandscape 910 ? position < mWinBounds1.right : position < mWinBounds1.bottom; 911 if (topLeftShrink) { 912 mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP; 913 mContentBounds.set(mWinBounds1); 914 mSurfaceBounds.set(mBounds1); 915 } else { 916 mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM; 917 mContentBounds.set(mWinBounds2); 918 mSurfaceBounds.set(mBounds2); 919 } 920 921 if (mDismissingSide != DOCKED_INVALID) { 922 float fraction = Math.max(0, 923 Math.min(mDividerSnapAlgorithm.calculateDismissingFraction(position), 1f)); 924 mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction); 925 if (mParallaxType == PARALLAX_DISMISSING) { 926 fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide); 927 if (isLandscape) { 928 mParallaxOffset.x = (int) (fraction * totalDismissingDistance); 929 } else { 930 mParallaxOffset.y = (int) (fraction * totalDismissingDistance); 931 } 932 } 933 } 934 935 if (mParallaxType == PARALLAX_ALIGN_CENTER) { 936 if (isLandscape) { 937 mParallaxOffset.x = 938 (mSurfaceBounds.width() - mContentBounds.width()) / 2; 939 } else { 940 mParallaxOffset.y = 941 (mSurfaceBounds.height() - mContentBounds.height()) / 2; 942 } 943 } 944 } 945 946 /** 947 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a 948 * slowing down parallax effect 949 */ 950 private float calculateParallaxDismissingFraction(float fraction, int dockSide) { 951 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; 952 953 // Less parallax at the top, just because. 954 if (dockSide == WindowManager.DOCKED_TOP) { 955 result /= 2f; 956 } 957 return result; 958 } 959 960 /** Applies parallax offset and dimming value to the root surface at the dismissing side. */ 961 void adjustRootSurface(SurfaceControl.Transaction t, 962 SurfaceControl leash1, SurfaceControl leash2) { 963 SurfaceControl targetLeash = null; 964 965 if (mParallaxType == PARALLAX_DISMISSING) { 966 switch (mDismissingSide) { 967 case DOCKED_TOP: 968 case DOCKED_LEFT: 969 targetLeash = leash1; 970 mTempRect.set(mBounds1); 971 break; 972 case DOCKED_BOTTOM: 973 case DOCKED_RIGHT: 974 targetLeash = leash2; 975 mTempRect.set(mBounds2); 976 break; 977 } 978 } else if (mParallaxType == PARALLAX_ALIGN_CENTER) { 979 switch (mShrinkSide) { 980 case DOCKED_TOP: 981 case DOCKED_LEFT: 982 targetLeash = leash1; 983 mTempRect.set(mBounds1); 984 break; 985 case DOCKED_BOTTOM: 986 case DOCKED_RIGHT: 987 targetLeash = leash2; 988 mTempRect.set(mBounds2); 989 break; 990 } 991 } 992 if (mParallaxType != PARALLAX_NONE && targetLeash != null) { 993 t.setPosition(targetLeash, 994 mTempRect.left + mParallaxOffset.x, mTempRect.top + mParallaxOffset.y); 995 // Transform the screen-based split bounds to surface-based crop bounds. 996 mTempRect.offsetTo(-mParallaxOffset.x, -mParallaxOffset.y); 997 t.setWindowCrop(targetLeash, mTempRect); 998 } 999 } 1000 1001 void adjustDimSurface(SurfaceControl.Transaction t, 1002 SurfaceControl dimLayer1, SurfaceControl dimLayer2) { 1003 SurfaceControl targetDimLayer; 1004 switch (mDismissingSide) { 1005 case DOCKED_TOP: 1006 case DOCKED_LEFT: 1007 targetDimLayer = dimLayer1; 1008 break; 1009 case DOCKED_BOTTOM: 1010 case DOCKED_RIGHT: 1011 targetDimLayer = dimLayer2; 1012 break; 1013 case DOCKED_INVALID: 1014 default: 1015 t.setAlpha(dimLayer1, 0).hide(dimLayer1); 1016 t.setAlpha(dimLayer2, 0).hide(dimLayer2); 1017 return; 1018 } 1019 t.setAlpha(targetDimLayer, mDismissingDimValue) 1020 .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f); 1021 } 1022 } 1023 1024 /** Records IME top offset changes and updates SplitLayout correspondingly. */ 1025 private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor { 1026 /** 1027 * Maximum size of an adjusted split bounds relative to original stack bounds. Used to 1028 * restrict IME adjustment so that a min portion of top split remains visible. 1029 */ 1030 private static final float ADJUSTED_SPLIT_FRACTION_MAX = 0.7f; 1031 private static final float ADJUSTED_NONFOCUS_DIM = 0.3f; 1032 1033 private final int mDisplayId; 1034 1035 private boolean mHasImeFocus; 1036 private boolean mImeShown; 1037 private int mYOffsetForIme; 1038 private float mDimValue1; 1039 private float mDimValue2; 1040 1041 private int mStartImeTop; 1042 private int mEndImeTop; 1043 1044 private int mTargetYOffset; 1045 private int mLastYOffset; 1046 private float mTargetDim1; 1047 private float mTargetDim2; 1048 private float mLastDim1; 1049 private float mLastDim2; 1050 1051 private ImePositionProcessor(int displayId) { 1052 mDisplayId = displayId; 1053 } 1054 1055 @Override 1056 public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, 1057 boolean showing, boolean isFloating, SurfaceControl.Transaction t) { 1058 if (displayId != mDisplayId || !mInitialized) { 1059 return 0; 1060 } 1061 1062 final int imeTargetPosition = getImeTargetPosition(); 1063 mHasImeFocus = imeTargetPosition != SPLIT_POSITION_UNDEFINED; 1064 if (!mHasImeFocus) { 1065 return 0; 1066 } 1067 1068 mStartImeTop = showing ? hiddenTop : shownTop; 1069 mEndImeTop = showing ? shownTop : hiddenTop; 1070 mImeShown = showing; 1071 1072 // Update target dim values 1073 mLastDim1 = mDimValue1; 1074 mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && mImeShown 1075 && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f; 1076 mLastDim2 = mDimValue2; 1077 mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && mImeShown 1078 && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f; 1079 1080 // Calculate target bounds offset for IME 1081 mLastYOffset = mYOffsetForIme; 1082 final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT 1083 && !isFloating && !isLandscape(mRootBounds) && mImeShown; 1084 mTargetYOffset = needOffset ? getTargetYOffset() : 0; 1085 1086 if (mTargetYOffset != mLastYOffset) { 1087 // Freeze the configuration size with offset to prevent app get a configuration 1088 // changed or relaunch. This is required to make sure client apps will calculate 1089 // insets properly after layout shifted. 1090 if (mTargetYOffset == 0) { 1091 mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); 1092 } else { 1093 mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset, 1094 SplitLayout.this); 1095 } 1096 } 1097 1098 // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to 1099 // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough 1100 // because DividerView won't receive onImeVisibilityChanged callback after it being 1101 // re-inflated. 1102 mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus, 1103 "onImeStartPositioning"); 1104 1105 return needOffset ? IME_ANIMATION_NO_ALPHA : 0; 1106 } 1107 1108 @Override 1109 public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { 1110 if (displayId != mDisplayId || !mHasImeFocus) return; 1111 onProgress(getProgress(imeTop)); 1112 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); 1113 } 1114 1115 @Override 1116 public void onImeEndPositioning(int displayId, boolean cancel, 1117 SurfaceControl.Transaction t) { 1118 if (displayId != mDisplayId || !mHasImeFocus || cancel) return; 1119 onProgress(1.0f); 1120 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); 1121 } 1122 1123 @Override 1124 public void onImeControlTargetChanged(int displayId, boolean controlling) { 1125 if (displayId != mDisplayId) return; 1126 // Restore the split layout when wm-shell is not controlling IME insets anymore. 1127 if (!controlling && mImeShown) { 1128 reset(); 1129 mSplitWindowManager.setInteractive(true, "onImeControlTargetChanged"); 1130 mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); 1131 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); 1132 } 1133 } 1134 1135 private int getTargetYOffset() { 1136 final int desireOffset = Math.abs(mEndImeTop - mStartImeTop); 1137 // Make sure to keep at least 30% visible for the top split. 1138 final int maxOffset = (int) (mBounds1.height() * ADJUSTED_SPLIT_FRACTION_MAX); 1139 return -Math.min(desireOffset, maxOffset); 1140 } 1141 1142 @SplitPosition 1143 private int getImeTargetPosition() { 1144 final WindowContainerToken token = mTaskOrganizer.getImeTarget(mDisplayId); 1145 return mSplitLayoutHandler.getSplitItemPosition(token); 1146 } 1147 1148 private float getProgress(int currImeTop) { 1149 return ((float) currImeTop - mStartImeTop) / (mEndImeTop - mStartImeTop); 1150 } 1151 1152 private void onProgress(float progress) { 1153 mDimValue1 = getProgressValue(mLastDim1, mTargetDim1, progress); 1154 mDimValue2 = getProgressValue(mLastDim2, mTargetDim2, progress); 1155 mYOffsetForIme = 1156 (int) getProgressValue((float) mLastYOffset, (float) mTargetYOffset, progress); 1157 } 1158 1159 private float getProgressValue(float start, float end, float progress) { 1160 return start + (end - start) * progress; 1161 } 1162 1163 void reset() { 1164 mHasImeFocus = false; 1165 mImeShown = false; 1166 mYOffsetForIme = mLastYOffset = mTargetYOffset = 0; 1167 mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f; 1168 mDimValue2 = mLastDim2 = mTargetDim2 = 0.0f; 1169 } 1170 1171 /** 1172 * Adjusts surface layout while showing IME. 1173 * 1174 * @return {@code false} if there's no need to adjust, otherwise {@code true} 1175 */ 1176 boolean adjustSurfaceLayoutForIme(SurfaceControl.Transaction t, 1177 SurfaceControl dividerLeash, SurfaceControl leash1, SurfaceControl leash2, 1178 SurfaceControl dimLayer1, SurfaceControl dimLayer2) { 1179 final boolean showDim = mDimValue1 > 0.001f || mDimValue2 > 0.001f; 1180 boolean adjusted = false; 1181 if (mYOffsetForIme != 0) { 1182 if (dividerLeash != null) { 1183 getRefDividerBounds(mTempRect); 1184 mTempRect.offset(0, mYOffsetForIme); 1185 t.setPosition(dividerLeash, mTempRect.left, mTempRect.top); 1186 } 1187 1188 getRefBounds1(mTempRect); 1189 mTempRect.offset(0, mYOffsetForIme); 1190 t.setPosition(leash1, mTempRect.left, mTempRect.top); 1191 1192 getRefBounds2(mTempRect); 1193 mTempRect.offset(0, mYOffsetForIme); 1194 t.setPosition(leash2, mTempRect.left, mTempRect.top); 1195 adjusted = true; 1196 } 1197 1198 if (showDim) { 1199 t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f); 1200 t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f); 1201 adjusted = true; 1202 } 1203 return adjusted; 1204 } 1205 } 1206 } 1207