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.legacysplitscreen; 18 19 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 25 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 26 import static android.view.Display.DEFAULT_DISPLAY; 27 28 import android.animation.AnimationHandler; 29 import android.app.ActivityManager; 30 import android.app.ActivityManager.RunningTaskInfo; 31 import android.app.ActivityTaskManager; 32 import android.content.Context; 33 import android.content.res.Configuration; 34 import android.graphics.Rect; 35 import android.os.RemoteException; 36 import android.provider.Settings; 37 import android.util.Slog; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.widget.Toast; 41 import android.window.TaskOrganizer; 42 import android.window.WindowContainerToken; 43 import android.window.WindowContainerTransaction; 44 45 import com.android.internal.policy.DividerSnapAlgorithm; 46 import com.android.wm.shell.R; 47 import com.android.wm.shell.ShellTaskOrganizer; 48 import com.android.wm.shell.common.DisplayChangeController; 49 import com.android.wm.shell.common.DisplayController; 50 import com.android.wm.shell.common.DisplayImeController; 51 import com.android.wm.shell.common.DisplayLayout; 52 import com.android.wm.shell.common.ShellExecutor; 53 import com.android.wm.shell.common.SyncTransactionQueue; 54 import com.android.wm.shell.common.SystemWindows; 55 import com.android.wm.shell.common.TaskStackListenerCallback; 56 import com.android.wm.shell.common.TaskStackListenerImpl; 57 import com.android.wm.shell.common.TransactionPool; 58 import com.android.wm.shell.transition.Transitions; 59 60 import java.io.PrintWriter; 61 import java.lang.ref.WeakReference; 62 import java.util.ArrayList; 63 import java.util.List; 64 import java.util.concurrent.CopyOnWriteArrayList; 65 import java.util.function.BiConsumer; 66 import java.util.function.Consumer; 67 68 /** 69 * Controls split screen feature. 70 */ 71 public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener { 72 static final boolean DEBUG = false; 73 74 private static final String TAG = "SplitScreenCtrl"; 75 private static final int DEFAULT_APP_TRANSITION_DURATION = 336; 76 77 private final Context mContext; 78 private final DisplayChangeController.OnDisplayChangingListener mRotationController; 79 private final DisplayController mDisplayController; 80 private final DisplayImeController mImeController; 81 private final DividerImeController mImePositionProcessor; 82 private final DividerState mDividerState = new DividerState(); 83 private final ForcedResizableInfoActivityController mForcedResizableController; 84 private final ShellExecutor mMainExecutor; 85 private final AnimationHandler mSfVsyncAnimationHandler; 86 private final LegacySplitScreenTaskListener mSplits; 87 private final SystemWindows mSystemWindows; 88 final TransactionPool mTransactionPool; 89 private final WindowManagerProxy mWindowManagerProxy; 90 private final TaskOrganizer mTaskOrganizer; 91 private final SplitScreenImpl mImpl = new SplitScreenImpl(); 92 93 private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners 94 = new CopyOnWriteArrayList<>(); 95 private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners = 96 new ArrayList<>(); 97 98 99 private DividerWindowManager mWindowManager; 100 private DividerView mView; 101 102 // Keeps track of real-time split geometry including snap positions and ime adjustments 103 private LegacySplitDisplayLayout mSplitLayout; 104 105 // Transient: this contains the layout calculated for a new rotation requested by WM. This is 106 // kept around so that we can wait for a matching configuration change and then use the exact 107 // layout that we sent back to WM. 108 private LegacySplitDisplayLayout mRotateSplitLayout; 109 110 private boolean mIsKeyguardShowing; 111 private boolean mVisible = false; 112 private volatile boolean mMinimized = false; 113 private volatile boolean mAdjustedForIme = false; 114 private boolean mHomeStackResizable = false; 115 LegacySplitScreenController(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener, Transitions transitions, ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler)116 public LegacySplitScreenController(Context context, 117 DisplayController displayController, SystemWindows systemWindows, 118 DisplayImeController imeController, TransactionPool transactionPool, 119 ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, 120 TaskStackListenerImpl taskStackListener, Transitions transitions, 121 ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) { 122 mContext = context; 123 mDisplayController = displayController; 124 mSystemWindows = systemWindows; 125 mImeController = imeController; 126 mMainExecutor = mainExecutor; 127 mSfVsyncAnimationHandler = sfVsyncAnimationHandler; 128 mForcedResizableController = new ForcedResizableInfoActivityController(context, this, 129 mainExecutor); 130 mTransactionPool = transactionPool; 131 mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); 132 mTaskOrganizer = shellTaskOrganizer; 133 mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions, 134 syncQueue); 135 mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor, 136 shellTaskOrganizer); 137 mRotationController = 138 (display, fromRotation, toRotation, wct) -> { 139 if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) { 140 return; 141 } 142 WindowContainerTransaction t = new WindowContainerTransaction(); 143 DisplayLayout displayLayout = 144 new DisplayLayout(mDisplayController.getDisplayLayout(display)); 145 LegacySplitDisplayLayout sdl = 146 new LegacySplitDisplayLayout(mContext, displayLayout, mSplits); 147 sdl.rotateTo(toRotation); 148 mRotateSplitLayout = sdl; 149 // snap resets to middle target when not minimized and rotation changed. 150 final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position 151 : sdl.getSnapAlgorithm().getMiddleTarget().position; 152 DividerSnapAlgorithm snap = sdl.getSnapAlgorithm(); 153 final DividerSnapAlgorithm.SnapTarget target = 154 snap.calculateNonDismissingSnapTarget(position); 155 sdl.resizeSplits(target.position, t); 156 157 if (isSplitActive() && mHomeStackResizable) { 158 mWindowManagerProxy 159 .applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t); 160 } 161 if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) { 162 // Because sync transactions are serialized, its possible for an "older" 163 // bounds-change to get applied after a screen rotation. In that case, we 164 // want to actually defer on that rather than apply immediately. Of course, 165 // this means that the bounds may not change until after the rotation so 166 // the user might see some artifacts. This should be rare. 167 Slog.w(TAG, "Screen rotated while other operations were pending, this may" 168 + " result in some graphical artifacts."); 169 } else { 170 wct.merge(t, true /* transfer */); 171 } 172 }; 173 174 mWindowManager = new DividerWindowManager(mSystemWindows); 175 176 // No need to listen to display window container or create root tasks if the device is not 177 // using legacy split screen. 178 if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) { 179 return; 180 } 181 182 183 mDisplayController.addDisplayWindowListener(this); 184 // Don't initialize the divider or anything until we get the default display. 185 186 taskStackListener.addListener( 187 new TaskStackListenerCallback() { 188 @Override 189 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 190 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 191 if (!wasVisible || task.getWindowingMode() 192 != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY 193 || !mSplits.isSplitScreenSupported()) { 194 return; 195 } 196 197 if (isMinimized()) { 198 onUndockingTask(); 199 } 200 } 201 202 @Override 203 public void onActivityForcedResizable(String packageName, int taskId, 204 int reason) { 205 mForcedResizableController.activityForcedResizable(packageName, taskId, 206 reason); 207 } 208 209 @Override 210 public void onActivityDismissingDockedStack() { 211 mForcedResizableController.activityDismissingSplitScreen(); 212 } 213 214 @Override 215 public void onActivityLaunchOnSecondaryDisplayFailed() { 216 mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); 217 } 218 }); 219 } 220 asLegacySplitScreen()221 public LegacySplitScreen asLegacySplitScreen() { 222 return mImpl; 223 } 224 onSplitScreenSupported()225 public void onSplitScreenSupported() { 226 // Set starting tile bounds based on middle target 227 final WindowContainerTransaction tct = new WindowContainerTransaction(); 228 int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; 229 mSplitLayout.resizeSplits(midPos, tct); 230 mTaskOrganizer.applyTransaction(tct); 231 } 232 onKeyguardVisibilityChanged(boolean showing)233 public void onKeyguardVisibilityChanged(boolean showing) { 234 if (!isSplitActive() || mView == null) { 235 return; 236 } 237 mView.setHidden(showing); 238 mIsKeyguardShowing = showing; 239 } 240 241 @Override onDisplayAdded(int displayId)242 public void onDisplayAdded(int displayId) { 243 if (displayId != DEFAULT_DISPLAY) { 244 return; 245 } 246 mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), 247 mDisplayController.getDisplayLayout(displayId), mSplits); 248 mImeController.addPositionProcessor(mImePositionProcessor); 249 mDisplayController.addDisplayChangingController(mRotationController); 250 if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) { 251 removeDivider(); 252 return; 253 } 254 try { 255 mSplits.init(); 256 } catch (Exception e) { 257 Slog.e(TAG, "Failed to register docked stack listener", e); 258 removeDivider(); 259 return; 260 } 261 } 262 263 @Override onDisplayConfigurationChanged(int displayId, Configuration newConfig)264 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 265 if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) { 266 return; 267 } 268 mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), 269 mDisplayController.getDisplayLayout(displayId), mSplits); 270 if (mRotateSplitLayout == null) { 271 int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; 272 final WindowContainerTransaction tct = new WindowContainerTransaction(); 273 mSplitLayout.resizeSplits(midPos, tct); 274 mTaskOrganizer.applyTransaction(tct); 275 } else if (mSplitLayout.mDisplayLayout.rotation() 276 == mRotateSplitLayout.mDisplayLayout.rotation()) { 277 mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary); 278 mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); 279 mRotateSplitLayout = null; 280 } 281 if (isSplitActive()) { 282 update(newConfig); 283 } 284 } 285 isMinimized()286 public boolean isMinimized() { 287 return mMinimized; 288 } 289 isHomeStackResizable()290 public boolean isHomeStackResizable() { 291 return mHomeStackResizable; 292 } 293 getDividerView()294 public DividerView getDividerView() { 295 return mView; 296 } 297 isDividerVisible()298 public boolean isDividerVisible() { 299 return mView != null && mView.getVisibility() == View.VISIBLE; 300 } 301 302 /** 303 * This indicates that at-least one of the splits has content. This differs from 304 * isDividerVisible because the divider is only visible once *everything* is in split mode 305 * while this only cares if some things are (eg. while entering/exiting as well). 306 */ isSplitActive()307 public boolean isSplitActive() { 308 return mSplits.mPrimary != null && mSplits.mSecondary != null 309 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED 310 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED); 311 } 312 addDivider(Configuration configuration)313 public void addDivider(Configuration configuration) { 314 Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId()); 315 mView = (DividerView) 316 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null); 317 mView.setAnimationHandler(mSfVsyncAnimationHandler); 318 DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId()); 319 mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController, 320 mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy); 321 mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE); 322 mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */); 323 final int size = dctx.getResources().getDimensionPixelSize( 324 com.android.internal.R.dimen.docked_stack_divider_thickness); 325 final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; 326 final int width = landscape ? size : displayLayout.width(); 327 final int height = landscape ? displayLayout.height() : size; 328 mWindowManager.add(mView, width, height, mContext.getDisplayId()); 329 } 330 removeDivider()331 public void removeDivider() { 332 if (mView != null) { 333 mView.onDividerRemoved(); 334 } 335 mWindowManager.remove(); 336 } 337 update(Configuration configuration)338 public void update(Configuration configuration) { 339 final boolean isDividerHidden = mView != null && mIsKeyguardShowing; 340 341 removeDivider(); 342 addDivider(configuration); 343 344 if (mMinimized) { 345 mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */); 346 updateTouchable(); 347 } 348 mView.setHidden(isDividerHidden); 349 } 350 onTaskVanished()351 public void onTaskVanished() { 352 removeDivider(); 353 } 354 updateVisibility(final boolean visible)355 public void updateVisibility(final boolean visible) { 356 if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible); 357 if (mVisible != visible) { 358 mVisible = visible; 359 mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 360 361 if (visible) { 362 mView.enterSplitMode(mHomeStackResizable); 363 // Update state because animations won't finish. 364 mWindowManagerProxy.runInSync( 365 t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t)); 366 367 } else { 368 mView.exitSplitMode(); 369 mWindowManagerProxy.runInSync( 370 t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t)); 371 } 372 // Notify existence listeners 373 synchronized (mDockedStackExistsListeners) { 374 mDockedStackExistsListeners.removeIf(wf -> { 375 Consumer<Boolean> l = wf.get(); 376 if (l != null) l.accept(visible); 377 return l == null; 378 }); 379 } 380 } 381 } 382 setMinimized(final boolean minimized)383 public void setMinimized(final boolean minimized) { 384 if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); 385 mMainExecutor.execute(() -> { 386 if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible); 387 if (!mVisible) { 388 return; 389 } 390 setHomeMinimized(minimized); 391 }); 392 } 393 setHomeMinimized(final boolean minimized)394 public void setHomeMinimized(final boolean minimized) { 395 if (DEBUG) { 396 Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:" 397 + mHomeStackResizable + " split:" + isDividerVisible()); 398 } 399 WindowContainerTransaction wct = new WindowContainerTransaction(); 400 final boolean minimizedChanged = mMinimized != minimized; 401 // Update minimized state 402 if (minimizedChanged) { 403 mMinimized = minimized; 404 } 405 // Always set this because we could be entering split when mMinimized is already true 406 wct.setFocusable(mSplits.mPrimary.token, !mMinimized); 407 408 // Sync state to DividerView if it exists. 409 if (mView != null) { 410 final int displayId = mView.getDisplay() != null 411 ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY; 412 // pause ime here (before updateMinimizedDockedStack) 413 if (mMinimized) { 414 mImePositionProcessor.pause(displayId); 415 } 416 if (minimizedChanged) { 417 // This conflicts with IME adjustment, so only call it when things change. 418 mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable); 419 } 420 if (!mMinimized) { 421 // afterwards so it can end any animations started in view 422 mImePositionProcessor.resume(displayId); 423 } 424 } 425 updateTouchable(); 426 427 // If we are only setting focusability, a sync transaction isn't necessary (in fact it 428 // can interrupt other animations), so see if it can be submitted on pending instead. 429 if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) { 430 mTaskOrganizer.applyTransaction(wct); 431 } 432 } 433 setAdjustedForIme(boolean adjustedForIme)434 public void setAdjustedForIme(boolean adjustedForIme) { 435 if (mAdjustedForIme == adjustedForIme) { 436 return; 437 } 438 mAdjustedForIme = adjustedForIme; 439 updateTouchable(); 440 } 441 updateTouchable()442 public void updateTouchable() { 443 mWindowManager.setTouchable(!mAdjustedForIme); 444 } 445 onUndockingTask()446 public void onUndockingTask() { 447 if (mView != null) { 448 mView.onUndockingTask(); 449 } 450 } 451 onAppTransitionFinished()452 public void onAppTransitionFinished() { 453 if (mView == null) { 454 return; 455 } 456 mForcedResizableController.onAppTransitionFinished(); 457 } 458 dump(PrintWriter pw)459 public void dump(PrintWriter pw) { 460 pw.print(" mVisible="); pw.println(mVisible); 461 pw.print(" mMinimized="); pw.println(mMinimized); 462 pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme); 463 } 464 getAnimDuration()465 public long getAnimDuration() { 466 float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(), 467 Settings.Global.TRANSITION_ANIMATION_SCALE, 468 mContext.getResources().getFloat( 469 com.android.internal.R.dimen 470 .config_appTransitionAnimationDurationScaleDefault)); 471 final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION; 472 return (long) (transitionDuration * transitionScale); 473 } 474 registerInSplitScreenListener(Consumer<Boolean> listener)475 public void registerInSplitScreenListener(Consumer<Boolean> listener) { 476 listener.accept(isDividerVisible()); 477 synchronized (mDockedStackExistsListeners) { 478 mDockedStackExistsListeners.add(new WeakReference<>(listener)); 479 } 480 } 481 unregisterInSplitScreenListener(Consumer<Boolean> listener)482 public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { 483 synchronized (mDockedStackExistsListeners) { 484 for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) { 485 if (mDockedStackExistsListeners.get(i) == listener) { 486 mDockedStackExistsListeners.remove(i); 487 } 488 } 489 } 490 } 491 registerBoundsChangeListener(BiConsumer<Rect, Rect> listener)492 public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { 493 synchronized (mBoundsChangedListeners) { 494 mBoundsChangedListeners.add(new WeakReference<>(listener)); 495 } 496 } 497 splitPrimaryTask()498 public boolean splitPrimaryTask() { 499 try { 500 if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) { 501 return false; 502 } 503 } catch (RemoteException e) { 504 return false; 505 } 506 if (isSplitActive() || mSplits.mPrimary == null) { 507 return false; 508 } 509 510 // Try fetching the top running task. 511 final List<RunningTaskInfo> runningTasks = 512 ActivityTaskManager.getInstance().getTasks(1 /* maxNum */); 513 if (runningTasks == null || runningTasks.isEmpty()) { 514 return false; 515 } 516 // Note: The set of running tasks from the system is ordered by recency. 517 final RunningTaskInfo topRunningTask = runningTasks.get(0); 518 final int activityType = topRunningTask.getActivityType(); 519 if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { 520 return false; 521 } 522 523 if (!topRunningTask.supportsSplitScreenMultiWindow) { 524 Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, 525 Toast.LENGTH_SHORT).show(); 526 return false; 527 } 528 529 final WindowContainerTransaction wct = new WindowContainerTransaction(); 530 // Clear out current windowing mode before reparenting to split task. 531 wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED); 532 wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */); 533 mWindowManagerProxy.applySyncTransaction(wct); 534 return true; 535 } 536 dismissSplitToPrimaryTask()537 public void dismissSplitToPrimaryTask() { 538 startDismissSplit(true /* toPrimaryTask */); 539 } 540 541 /** Notifies the bounds of split screen changed. */ notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets)542 public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { 543 synchronized (mBoundsChangedListeners) { 544 mBoundsChangedListeners.removeIf(wf -> { 545 BiConsumer<Rect, Rect> l = wf.get(); 546 if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets); 547 return l == null; 548 }); 549 } 550 } 551 startEnterSplit()552 public void startEnterSplit() { 553 update(mDisplayController.getDisplayContext( 554 mContext.getDisplayId()).getResources().getConfiguration()); 555 // Set resizable directly here because applyEnterSplit already resizes home stack. 556 mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, 557 mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); 558 } 559 prepareEnterSplitTransition(WindowContainerTransaction outWct)560 public void prepareEnterSplitTransition(WindowContainerTransaction outWct) { 561 // Set resizable directly here because buildEnterSplit already resizes home stack. 562 mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, 563 mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); 564 } 565 finishEnterSplitTransition(boolean minimized)566 public void finishEnterSplitTransition(boolean minimized) { 567 update(mDisplayController.getDisplayContext( 568 mContext.getDisplayId()).getResources().getConfiguration()); 569 if (minimized) { 570 ensureMinimizedSplit(); 571 } else { 572 ensureNormalSplit(); 573 } 574 } 575 startDismissSplit(boolean toPrimaryTask)576 public void startDismissSplit(boolean toPrimaryTask) { 577 startDismissSplit(toPrimaryTask, false /* snapped */); 578 } 579 startDismissSplit(boolean toPrimaryTask, boolean snapped)580 public void startDismissSplit(boolean toPrimaryTask, boolean snapped) { 581 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 582 mSplits.getSplitTransitions().dismissSplit( 583 mSplits, mSplitLayout, !toPrimaryTask, snapped); 584 } else { 585 mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask); 586 onDismissSplit(); 587 } 588 } 589 onDismissSplit()590 public void onDismissSplit() { 591 updateVisibility(false /* visible */); 592 mMinimized = false; 593 // Resets divider bar position to undefined, so new divider bar will apply default position 594 // next time entering split mode. 595 mDividerState.mRatioPositionBeforeMinimized = 0; 596 removeDivider(); 597 mImePositionProcessor.reset(); 598 } 599 ensureMinimizedSplit()600 public void ensureMinimizedSplit() { 601 setHomeMinimized(true /* minimized */); 602 if (mView != null && !isDividerVisible()) { 603 // Wasn't in split-mode yet, so enter now. 604 if (DEBUG) { 605 Slog.d(TAG, " entering split mode with minimized=true"); 606 } 607 updateVisibility(true /* visible */); 608 } 609 } 610 ensureNormalSplit()611 public void ensureNormalSplit() { 612 setHomeMinimized(false /* minimized */); 613 if (mView != null && !isDividerVisible()) { 614 // Wasn't in split-mode, so enter now. 615 if (DEBUG) { 616 Slog.d(TAG, " enter split mode unminimized "); 617 } 618 updateVisibility(true /* visible */); 619 } 620 } 621 getSplitLayout()622 public LegacySplitDisplayLayout getSplitLayout() { 623 return mSplitLayout; 624 } 625 getWmProxy()626 public WindowManagerProxy getWmProxy() { 627 return mWindowManagerProxy; 628 } 629 getSecondaryRoot()630 public WindowContainerToken getSecondaryRoot() { 631 if (mSplits == null || mSplits.mSecondary == null) { 632 return null; 633 } 634 return mSplits.mSecondary.token; 635 } 636 637 private class SplitScreenImpl implements LegacySplitScreen { 638 @Override isMinimized()639 public boolean isMinimized() { 640 return mMinimized; 641 } 642 643 @Override isHomeStackResizable()644 public boolean isHomeStackResizable() { 645 return mHomeStackResizable; 646 } 647 648 /** 649 * TODO: Remove usage from outside the shell. 650 */ 651 @Override getDividerView()652 public DividerView getDividerView() { 653 return LegacySplitScreenController.this.getDividerView(); 654 } 655 656 @Override isDividerVisible()657 public boolean isDividerVisible() { 658 boolean[] result = new boolean[1]; 659 try { 660 mMainExecutor.executeBlocking(() -> { 661 result[0] = LegacySplitScreenController.this.isDividerVisible(); 662 }); 663 } catch (InterruptedException e) { 664 Slog.e(TAG, "Failed to get divider visible"); 665 } 666 return result[0]; 667 } 668 669 @Override onKeyguardVisibilityChanged(boolean isShowing)670 public void onKeyguardVisibilityChanged(boolean isShowing) { 671 mMainExecutor.execute(() -> { 672 LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing); 673 }); 674 } 675 676 @Override setMinimized(boolean minimized)677 public void setMinimized(boolean minimized) { 678 mMainExecutor.execute(() -> { 679 LegacySplitScreenController.this.setMinimized(minimized); 680 }); 681 } 682 683 @Override onUndockingTask()684 public void onUndockingTask() { 685 mMainExecutor.execute(() -> { 686 LegacySplitScreenController.this.onUndockingTask(); 687 }); 688 } 689 690 @Override onAppTransitionFinished()691 public void onAppTransitionFinished() { 692 mMainExecutor.execute(() -> { 693 LegacySplitScreenController.this.onAppTransitionFinished(); 694 }); 695 } 696 697 @Override registerInSplitScreenListener(Consumer<Boolean> listener)698 public void registerInSplitScreenListener(Consumer<Boolean> listener) { 699 mMainExecutor.execute(() -> { 700 LegacySplitScreenController.this.registerInSplitScreenListener(listener); 701 }); 702 } 703 704 @Override unregisterInSplitScreenListener(Consumer<Boolean> listener)705 public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { 706 mMainExecutor.execute(() -> { 707 LegacySplitScreenController.this.unregisterInSplitScreenListener(listener); 708 }); 709 } 710 711 @Override registerBoundsChangeListener(BiConsumer<Rect, Rect> listener)712 public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { 713 mMainExecutor.execute(() -> { 714 LegacySplitScreenController.this.registerBoundsChangeListener(listener); 715 }); 716 } 717 718 @Override getSecondaryRoot()719 public WindowContainerToken getSecondaryRoot() { 720 WindowContainerToken[] result = new WindowContainerToken[1]; 721 try { 722 mMainExecutor.executeBlocking(() -> { 723 result[0] = LegacySplitScreenController.this.getSecondaryRoot(); 724 }); 725 } catch (InterruptedException e) { 726 Slog.e(TAG, "Failed to get secondary root"); 727 } 728 return result[0]; 729 } 730 731 @Override splitPrimaryTask()732 public boolean splitPrimaryTask() { 733 boolean[] result = new boolean[1]; 734 try { 735 mMainExecutor.executeBlocking(() -> { 736 result[0] = LegacySplitScreenController.this.splitPrimaryTask(); 737 }); 738 } catch (InterruptedException e) { 739 Slog.e(TAG, "Failed to split primary task"); 740 } 741 return result[0]; 742 } 743 744 @Override dismissSplitToPrimaryTask()745 public void dismissSplitToPrimaryTask() { 746 mMainExecutor.execute(() -> { 747 LegacySplitScreenController.this.dismissSplitToPrimaryTask(); 748 }); 749 } 750 751 @Override dump(PrintWriter pw)752 public void dump(PrintWriter pw) { 753 try { 754 mMainExecutor.executeBlocking(() -> { 755 LegacySplitScreenController.this.dump(pw); 756 }); 757 } catch (InterruptedException e) { 758 Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s"); 759 } 760 } 761 } 762 } 763