• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.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