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