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 mDisplayController.addDisplayWindowListener(this); 176 // Don't initialize the divider or anything until we get the default display. 177 178 taskStackListener.addListener( 179 new TaskStackListenerCallback() { 180 @Override 181 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 182 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 183 if (!wasVisible || task.getWindowingMode() 184 != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY 185 || !mSplits.isSplitScreenSupported()) { 186 return; 187 } 188 189 if (isMinimized()) { 190 onUndockingTask(); 191 } 192 } 193 194 @Override 195 public void onActivityForcedResizable(String packageName, int taskId, 196 int reason) { 197 mForcedResizableController.activityForcedResizable(packageName, taskId, 198 reason); 199 } 200 201 @Override 202 public void onActivityDismissingDockedStack() { 203 mForcedResizableController.activityDismissingSplitScreen(); 204 } 205 206 @Override 207 public void onActivityLaunchOnSecondaryDisplayFailed() { 208 mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); 209 } 210 }); 211 } 212 asLegacySplitScreen()213 public LegacySplitScreen asLegacySplitScreen() { 214 return mImpl; 215 } 216 onSplitScreenSupported()217 public void onSplitScreenSupported() { 218 // Set starting tile bounds based on middle target 219 final WindowContainerTransaction tct = new WindowContainerTransaction(); 220 int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; 221 mSplitLayout.resizeSplits(midPos, tct); 222 mTaskOrganizer.applyTransaction(tct); 223 } 224 onKeyguardVisibilityChanged(boolean showing)225 public void onKeyguardVisibilityChanged(boolean showing) { 226 if (!isSplitActive() || mView == null) { 227 return; 228 } 229 mView.setHidden(showing); 230 mIsKeyguardShowing = showing; 231 } 232 233 @Override onDisplayAdded(int displayId)234 public void onDisplayAdded(int displayId) { 235 if (displayId != DEFAULT_DISPLAY) { 236 return; 237 } 238 mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), 239 mDisplayController.getDisplayLayout(displayId), mSplits); 240 mImeController.addPositionProcessor(mImePositionProcessor); 241 mDisplayController.addDisplayChangingController(mRotationController); 242 if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) { 243 removeDivider(); 244 return; 245 } 246 try { 247 mSplits.init(); 248 } catch (Exception e) { 249 Slog.e(TAG, "Failed to register docked stack listener", e); 250 removeDivider(); 251 return; 252 } 253 } 254 255 @Override onDisplayConfigurationChanged(int displayId, Configuration newConfig)256 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 257 if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) { 258 return; 259 } 260 mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), 261 mDisplayController.getDisplayLayout(displayId), mSplits); 262 if (mRotateSplitLayout == null) { 263 int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; 264 final WindowContainerTransaction tct = new WindowContainerTransaction(); 265 mSplitLayout.resizeSplits(midPos, tct); 266 mTaskOrganizer.applyTransaction(tct); 267 } else if (mSplitLayout.mDisplayLayout.rotation() 268 == mRotateSplitLayout.mDisplayLayout.rotation()) { 269 mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary); 270 mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); 271 mRotateSplitLayout = null; 272 } 273 if (isSplitActive()) { 274 update(newConfig); 275 } 276 } 277 isMinimized()278 public boolean isMinimized() { 279 return mMinimized; 280 } 281 isHomeStackResizable()282 public boolean isHomeStackResizable() { 283 return mHomeStackResizable; 284 } 285 getDividerView()286 public DividerView getDividerView() { 287 return mView; 288 } 289 isDividerVisible()290 public boolean isDividerVisible() { 291 return mView != null && mView.getVisibility() == View.VISIBLE; 292 } 293 294 /** 295 * This indicates that at-least one of the splits has content. This differs from 296 * isDividerVisible because the divider is only visible once *everything* is in split mode 297 * while this only cares if some things are (eg. while entering/exiting as well). 298 */ isSplitActive()299 public boolean isSplitActive() { 300 return mSplits.mPrimary != null && mSplits.mSecondary != null 301 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED 302 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED); 303 } 304 addDivider(Configuration configuration)305 public void addDivider(Configuration configuration) { 306 Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId()); 307 mView = (DividerView) 308 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null); 309 mView.setAnimationHandler(mSfVsyncAnimationHandler); 310 DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId()); 311 mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController, 312 mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy); 313 mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE); 314 mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */); 315 final int size = dctx.getResources().getDimensionPixelSize( 316 com.android.internal.R.dimen.docked_stack_divider_thickness); 317 final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; 318 final int width = landscape ? size : displayLayout.width(); 319 final int height = landscape ? displayLayout.height() : size; 320 mWindowManager.add(mView, width, height, mContext.getDisplayId()); 321 } 322 removeDivider()323 public void removeDivider() { 324 if (mView != null) { 325 mView.onDividerRemoved(); 326 } 327 mWindowManager.remove(); 328 } 329 update(Configuration configuration)330 public void update(Configuration configuration) { 331 final boolean isDividerHidden = mView != null && mIsKeyguardShowing; 332 333 removeDivider(); 334 addDivider(configuration); 335 336 if (mMinimized) { 337 mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */); 338 updateTouchable(); 339 } 340 mView.setHidden(isDividerHidden); 341 } 342 onTaskVanished()343 public void onTaskVanished() { 344 removeDivider(); 345 } 346 updateVisibility(final boolean visible)347 public void updateVisibility(final boolean visible) { 348 if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible); 349 if (mVisible != visible) { 350 mVisible = visible; 351 mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 352 353 if (visible) { 354 mView.enterSplitMode(mHomeStackResizable); 355 // Update state because animations won't finish. 356 mWindowManagerProxy.runInSync( 357 t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t)); 358 359 } else { 360 mView.exitSplitMode(); 361 mWindowManagerProxy.runInSync( 362 t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t)); 363 } 364 // Notify existence listeners 365 synchronized (mDockedStackExistsListeners) { 366 mDockedStackExistsListeners.removeIf(wf -> { 367 Consumer<Boolean> l = wf.get(); 368 if (l != null) l.accept(visible); 369 return l == null; 370 }); 371 } 372 } 373 } 374 setMinimized(final boolean minimized)375 public void setMinimized(final boolean minimized) { 376 if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); 377 mMainExecutor.execute(() -> { 378 if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible); 379 if (!mVisible) { 380 return; 381 } 382 setHomeMinimized(minimized); 383 }); 384 } 385 setHomeMinimized(final boolean minimized)386 public void setHomeMinimized(final boolean minimized) { 387 if (DEBUG) { 388 Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:" 389 + mHomeStackResizable + " split:" + isDividerVisible()); 390 } 391 WindowContainerTransaction wct = new WindowContainerTransaction(); 392 final boolean minimizedChanged = mMinimized != minimized; 393 // Update minimized state 394 if (minimizedChanged) { 395 mMinimized = minimized; 396 } 397 // Always set this because we could be entering split when mMinimized is already true 398 wct.setFocusable(mSplits.mPrimary.token, !mMinimized); 399 400 // Sync state to DividerView if it exists. 401 if (mView != null) { 402 final int displayId = mView.getDisplay() != null 403 ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY; 404 // pause ime here (before updateMinimizedDockedStack) 405 if (mMinimized) { 406 mImePositionProcessor.pause(displayId); 407 } 408 if (minimizedChanged) { 409 // This conflicts with IME adjustment, so only call it when things change. 410 mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable); 411 } 412 if (!mMinimized) { 413 // afterwards so it can end any animations started in view 414 mImePositionProcessor.resume(displayId); 415 } 416 } 417 updateTouchable(); 418 419 // If we are only setting focusability, a sync transaction isn't necessary (in fact it 420 // can interrupt other animations), so see if it can be submitted on pending instead. 421 if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) { 422 mTaskOrganizer.applyTransaction(wct); 423 } 424 } 425 setAdjustedForIme(boolean adjustedForIme)426 public void setAdjustedForIme(boolean adjustedForIme) { 427 if (mAdjustedForIme == adjustedForIme) { 428 return; 429 } 430 mAdjustedForIme = adjustedForIme; 431 updateTouchable(); 432 } 433 updateTouchable()434 public void updateTouchable() { 435 mWindowManager.setTouchable(!mAdjustedForIme); 436 } 437 onUndockingTask()438 public void onUndockingTask() { 439 if (mView != null) { 440 mView.onUndockingTask(); 441 } 442 } 443 onAppTransitionFinished()444 public void onAppTransitionFinished() { 445 if (mView == null) { 446 return; 447 } 448 mForcedResizableController.onAppTransitionFinished(); 449 } 450 dump(PrintWriter pw)451 public void dump(PrintWriter pw) { 452 pw.print(" mVisible="); pw.println(mVisible); 453 pw.print(" mMinimized="); pw.println(mMinimized); 454 pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme); 455 } 456 getAnimDuration()457 public long getAnimDuration() { 458 float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(), 459 Settings.Global.TRANSITION_ANIMATION_SCALE, 460 mContext.getResources().getFloat( 461 com.android.internal.R.dimen 462 .config_appTransitionAnimationDurationScaleDefault)); 463 final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION; 464 return (long) (transitionDuration * transitionScale); 465 } 466 registerInSplitScreenListener(Consumer<Boolean> listener)467 public void registerInSplitScreenListener(Consumer<Boolean> listener) { 468 listener.accept(isDividerVisible()); 469 synchronized (mDockedStackExistsListeners) { 470 mDockedStackExistsListeners.add(new WeakReference<>(listener)); 471 } 472 } 473 unregisterInSplitScreenListener(Consumer<Boolean> listener)474 public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { 475 synchronized (mDockedStackExistsListeners) { 476 for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) { 477 if (mDockedStackExistsListeners.get(i) == listener) { 478 mDockedStackExistsListeners.remove(i); 479 } 480 } 481 } 482 } 483 registerBoundsChangeListener(BiConsumer<Rect, Rect> listener)484 public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { 485 synchronized (mBoundsChangedListeners) { 486 mBoundsChangedListeners.add(new WeakReference<>(listener)); 487 } 488 } 489 splitPrimaryTask()490 public boolean splitPrimaryTask() { 491 try { 492 if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) { 493 return false; 494 } 495 } catch (RemoteException e) { 496 return false; 497 } 498 if (isSplitActive() || mSplits.mPrimary == null) { 499 return false; 500 } 501 502 // Try fetching the top running task. 503 final List<RunningTaskInfo> runningTasks = 504 ActivityTaskManager.getInstance().getTasks(1 /* maxNum */); 505 if (runningTasks == null || runningTasks.isEmpty()) { 506 return false; 507 } 508 // Note: The set of running tasks from the system is ordered by recency. 509 final RunningTaskInfo topRunningTask = runningTasks.get(0); 510 final int activityType = topRunningTask.getActivityType(); 511 if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { 512 return false; 513 } 514 515 if (!topRunningTask.supportsSplitScreenMultiWindow) { 516 Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, 517 Toast.LENGTH_SHORT).show(); 518 return false; 519 } 520 521 final WindowContainerTransaction wct = new WindowContainerTransaction(); 522 // Clear out current windowing mode before reparenting to split task. 523 wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED); 524 wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */); 525 mWindowManagerProxy.applySyncTransaction(wct); 526 return true; 527 } 528 dismissSplitToPrimaryTask()529 public void dismissSplitToPrimaryTask() { 530 startDismissSplit(true /* toPrimaryTask */); 531 } 532 533 /** Notifies the bounds of split screen changed. */ notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets)534 public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { 535 synchronized (mBoundsChangedListeners) { 536 mBoundsChangedListeners.removeIf(wf -> { 537 BiConsumer<Rect, Rect> l = wf.get(); 538 if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets); 539 return l == null; 540 }); 541 } 542 } 543 startEnterSplit()544 public void startEnterSplit() { 545 update(mDisplayController.getDisplayContext( 546 mContext.getDisplayId()).getResources().getConfiguration()); 547 // Set resizable directly here because applyEnterSplit already resizes home stack. 548 mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, 549 mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); 550 } 551 prepareEnterSplitTransition(WindowContainerTransaction outWct)552 public void prepareEnterSplitTransition(WindowContainerTransaction outWct) { 553 // Set resizable directly here because buildEnterSplit already resizes home stack. 554 mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, 555 mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); 556 } 557 finishEnterSplitTransition(boolean minimized)558 public void finishEnterSplitTransition(boolean minimized) { 559 update(mDisplayController.getDisplayContext( 560 mContext.getDisplayId()).getResources().getConfiguration()); 561 if (minimized) { 562 ensureMinimizedSplit(); 563 } else { 564 ensureNormalSplit(); 565 } 566 } 567 startDismissSplit(boolean toPrimaryTask)568 public void startDismissSplit(boolean toPrimaryTask) { 569 startDismissSplit(toPrimaryTask, false /* snapped */); 570 } 571 startDismissSplit(boolean toPrimaryTask, boolean snapped)572 public void startDismissSplit(boolean toPrimaryTask, boolean snapped) { 573 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 574 mSplits.getSplitTransitions().dismissSplit( 575 mSplits, mSplitLayout, !toPrimaryTask, snapped); 576 } else { 577 mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask); 578 onDismissSplit(); 579 } 580 } 581 onDismissSplit()582 public void onDismissSplit() { 583 updateVisibility(false /* visible */); 584 mMinimized = false; 585 // Resets divider bar position to undefined, so new divider bar will apply default position 586 // next time entering split mode. 587 mDividerState.mRatioPositionBeforeMinimized = 0; 588 removeDivider(); 589 mImePositionProcessor.reset(); 590 } 591 ensureMinimizedSplit()592 public void ensureMinimizedSplit() { 593 setHomeMinimized(true /* minimized */); 594 if (mView != null && !isDividerVisible()) { 595 // Wasn't in split-mode yet, so enter now. 596 if (DEBUG) { 597 Slog.d(TAG, " entering split mode with minimized=true"); 598 } 599 updateVisibility(true /* visible */); 600 } 601 } 602 ensureNormalSplit()603 public void ensureNormalSplit() { 604 setHomeMinimized(false /* minimized */); 605 if (mView != null && !isDividerVisible()) { 606 // Wasn't in split-mode, so enter now. 607 if (DEBUG) { 608 Slog.d(TAG, " enter split mode unminimized "); 609 } 610 updateVisibility(true /* visible */); 611 } 612 } 613 getSplitLayout()614 public LegacySplitDisplayLayout getSplitLayout() { 615 return mSplitLayout; 616 } 617 getWmProxy()618 public WindowManagerProxy getWmProxy() { 619 return mWindowManagerProxy; 620 } 621 getSecondaryRoot()622 public WindowContainerToken getSecondaryRoot() { 623 if (mSplits == null || mSplits.mSecondary == null) { 624 return null; 625 } 626 return mSplits.mSecondary.token; 627 } 628 629 private class SplitScreenImpl implements LegacySplitScreen { 630 @Override isMinimized()631 public boolean isMinimized() { 632 return mMinimized; 633 } 634 635 @Override isHomeStackResizable()636 public boolean isHomeStackResizable() { 637 return mHomeStackResizable; 638 } 639 640 /** 641 * TODO: Remove usage from outside the shell. 642 */ 643 @Override getDividerView()644 public DividerView getDividerView() { 645 return LegacySplitScreenController.this.getDividerView(); 646 } 647 648 @Override isDividerVisible()649 public boolean isDividerVisible() { 650 boolean[] result = new boolean[1]; 651 try { 652 mMainExecutor.executeBlocking(() -> { 653 result[0] = LegacySplitScreenController.this.isDividerVisible(); 654 }); 655 } catch (InterruptedException e) { 656 Slog.e(TAG, "Failed to get divider visible"); 657 } 658 return result[0]; 659 } 660 661 @Override onKeyguardVisibilityChanged(boolean isShowing)662 public void onKeyguardVisibilityChanged(boolean isShowing) { 663 mMainExecutor.execute(() -> { 664 LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing); 665 }); 666 } 667 668 @Override setMinimized(boolean minimized)669 public void setMinimized(boolean minimized) { 670 mMainExecutor.execute(() -> { 671 LegacySplitScreenController.this.setMinimized(minimized); 672 }); 673 } 674 675 @Override onUndockingTask()676 public void onUndockingTask() { 677 mMainExecutor.execute(() -> { 678 LegacySplitScreenController.this.onUndockingTask(); 679 }); 680 } 681 682 @Override onAppTransitionFinished()683 public void onAppTransitionFinished() { 684 mMainExecutor.execute(() -> { 685 LegacySplitScreenController.this.onAppTransitionFinished(); 686 }); 687 } 688 689 @Override registerInSplitScreenListener(Consumer<Boolean> listener)690 public void registerInSplitScreenListener(Consumer<Boolean> listener) { 691 mMainExecutor.execute(() -> { 692 LegacySplitScreenController.this.registerInSplitScreenListener(listener); 693 }); 694 } 695 696 @Override unregisterInSplitScreenListener(Consumer<Boolean> listener)697 public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { 698 mMainExecutor.execute(() -> { 699 LegacySplitScreenController.this.unregisterInSplitScreenListener(listener); 700 }); 701 } 702 703 @Override registerBoundsChangeListener(BiConsumer<Rect, Rect> listener)704 public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { 705 mMainExecutor.execute(() -> { 706 LegacySplitScreenController.this.registerBoundsChangeListener(listener); 707 }); 708 } 709 710 @Override getSecondaryRoot()711 public WindowContainerToken getSecondaryRoot() { 712 WindowContainerToken[] result = new WindowContainerToken[1]; 713 try { 714 mMainExecutor.executeBlocking(() -> { 715 result[0] = LegacySplitScreenController.this.getSecondaryRoot(); 716 }); 717 } catch (InterruptedException e) { 718 Slog.e(TAG, "Failed to get secondary root"); 719 } 720 return result[0]; 721 } 722 723 @Override splitPrimaryTask()724 public boolean splitPrimaryTask() { 725 boolean[] result = new boolean[1]; 726 try { 727 mMainExecutor.executeBlocking(() -> { 728 result[0] = LegacySplitScreenController.this.splitPrimaryTask(); 729 }); 730 } catch (InterruptedException e) { 731 Slog.e(TAG, "Failed to split primary task"); 732 } 733 return result[0]; 734 } 735 736 @Override dismissSplitToPrimaryTask()737 public void dismissSplitToPrimaryTask() { 738 mMainExecutor.execute(() -> { 739 LegacySplitScreenController.this.dismissSplitToPrimaryTask(); 740 }); 741 } 742 743 @Override dump(PrintWriter pw)744 public void dump(PrintWriter pw) { 745 try { 746 mMainExecutor.executeBlocking(() -> { 747 LegacySplitScreenController.this.dump(pw); 748 }); 749 } catch (InterruptedException e) { 750 Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s"); 751 } 752 } 753 } 754 } 755