blob: 80ab166d064990d2fe78f12cbc821c809031c609 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.legacysplitscreen;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.AnimationHandler;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Controls split screen feature.
*/
public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener {
static final boolean DEBUG = false;
private static final String TAG = "SplitScreenCtrl";
private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
private final Context mContext;
private final DisplayChangeController.OnDisplayChangingListener mRotationController;
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
private final DividerImeController mImePositionProcessor;
private final DividerState mDividerState = new DividerState();
private final ForcedResizableInfoActivityController mForcedResizableController;
private final ShellExecutor mMainExecutor;
private final AnimationHandler mSfVsyncAnimationHandler;
private final LegacySplitScreenTaskListener mSplits;
private final SystemWindows mSystemWindows;
final TransactionPool mTransactionPool;
private final WindowManagerProxy mWindowManagerProxy;
private final TaskOrganizer mTaskOrganizer;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners
= new CopyOnWriteArrayList<>();
private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
new ArrayList<>();
private DividerWindowManager mWindowManager;
private DividerView mView;
// Keeps track of real-time split geometry including snap positions and ime adjustments
private LegacySplitDisplayLayout mSplitLayout;
// Transient: this contains the layout calculated for a new rotation requested by WM. This is
// kept around so that we can wait for a matching configuration change and then use the exact
// layout that we sent back to WM.
private LegacySplitDisplayLayout mRotateSplitLayout;
private boolean mIsKeyguardShowing;
private boolean mVisible = false;
private volatile boolean mMinimized = false;
private volatile boolean mAdjustedForIme = false;
private boolean mHomeStackResizable = false;
public LegacySplitScreenController(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
mContext = context;
mDisplayController = displayController;
mSystemWindows = systemWindows;
mImeController = imeController;
mMainExecutor = mainExecutor;
mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
mainExecutor);
mTransactionPool = transactionPool;
mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
mTaskOrganizer = shellTaskOrganizer;
mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions,
syncQueue);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor,
shellTaskOrganizer);
mRotationController =
(display, fromRotation, toRotation, wct) -> {
if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) {
return;
}
WindowContainerTransaction t = new WindowContainerTransaction();
DisplayLayout displayLayout =
new DisplayLayout(mDisplayController.getDisplayLayout(display));
LegacySplitDisplayLayout sdl =
new LegacySplitDisplayLayout(mContext, displayLayout, mSplits);
sdl.rotateTo(toRotation);
mRotateSplitLayout = sdl;
// snap resets to middle target when not minimized and rotation changed.
final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
: sdl.getSnapAlgorithm().getMiddleTarget().position;
DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
final DividerSnapAlgorithm.SnapTarget target =
snap.calculateNonDismissingSnapTarget(position);
sdl.resizeSplits(target.position, t);
if (isSplitActive() && mHomeStackResizable) {
mWindowManagerProxy
.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
}
if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
// Because sync transactions are serialized, its possible for an "older"
// bounds-change to get applied after a screen rotation. In that case, we
// want to actually defer on that rather than apply immediately. Of course,
// this means that the bounds may not change until after the rotation so
// the user might see some artifacts. This should be rare.
Slog.w(TAG, "Screen rotated while other operations were pending, this may"
+ " result in some graphical artifacts.");
} else {
wct.merge(t, true /* transfer */);
}
};
mWindowManager = new DividerWindowManager(mSystemWindows);
mDisplayController.addDisplayWindowListener(this);
// Don't initialize the divider or anything until we get the default display.
taskStackListener.addListener(
new TaskStackListenerCallback() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (!wasVisible || task.getWindowingMode()
!= WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| !mSplits.isSplitScreenSupported()) {
return;
}
if (isMinimized()) {
onUndockingTask();
}
}
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
mForcedResizableController.activityForcedResizable(packageName, taskId,
reason);
}
@Override
public void onActivityDismissingDockedStack() {
mForcedResizableController.activityDismissingSplitScreen();
}
@Override
public void onActivityLaunchOnSecondaryDisplayFailed() {
mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
}
});
}
public LegacySplitScreen asLegacySplitScreen() {
return mImpl;
}
public void onSplitScreenSupported() {
// Set starting tile bounds based on middle target
final WindowContainerTransaction tct = new WindowContainerTransaction();
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
mSplitLayout.resizeSplits(midPos, tct);
mTaskOrganizer.applyTransaction(tct);
}
public void onKeyguardVisibilityChanged(boolean showing) {
if (!isSplitActive() || mView == null) {
return;
}
mView.setHidden(showing);
mIsKeyguardShowing = showing;
}
@Override
public void onDisplayAdded(int displayId) {
if (displayId != DEFAULT_DISPLAY) {
return;
}
mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
mDisplayController.getDisplayLayout(displayId), mSplits);
mImeController.addPositionProcessor(mImePositionProcessor);
mDisplayController.addDisplayChangingController(mRotationController);
if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) {
removeDivider();
return;
}
try {
mSplits.init();
} catch (Exception e) {
Slog.e(TAG, "Failed to register docked stack listener", e);
removeDivider();
return;
}
}
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) {
return;
}
mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
mDisplayController.getDisplayLayout(displayId), mSplits);
if (mRotateSplitLayout == null) {
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
final WindowContainerTransaction tct = new WindowContainerTransaction();
mSplitLayout.resizeSplits(midPos, tct);
mTaskOrganizer.applyTransaction(tct);
} else if (mSplitLayout.mDisplayLayout.rotation()
== mRotateSplitLayout.mDisplayLayout.rotation()) {
mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
mRotateSplitLayout = null;
}
if (isSplitActive()) {
update(newConfig);
}
}
public boolean isMinimized() {
return mMinimized;
}
public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
public DividerView getDividerView() {
return mView;
}
public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
/**
* This indicates that at-least one of the splits has content. This differs from
* isDividerVisible because the divider is only visible once *everything* is in split mode
* while this only cares if some things are (eg. while entering/exiting as well).
*/
public boolean isSplitActive() {
return mSplits.mPrimary != null && mSplits.mSecondary != null
&& (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
|| mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
}
public void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
mView.setAnimationHandler(mSfVsyncAnimationHandler);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController,
mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
final int size = dctx.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? size : displayLayout.width();
final int height = landscape ? displayLayout.height() : size;
mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
public void removeDivider() {
if (mView != null) {
mView.onDividerRemoved();
}
mWindowManager.remove();
}
public void update(Configuration configuration) {
final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
removeDivider();
addDivider(configuration);
if (mMinimized) {
mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);
updateTouchable();
}
mView.setHidden(isDividerHidden);
}
public void onTaskVanished() {
removeDivider();
}
public void updateVisibility(final boolean visible) {
if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
mVisible = visible;
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
if (visible) {
mView.enterSplitMode(mHomeStackResizable);
// Update state because animations won't finish.
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t));
} else {
mView.exitSplitMode();
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t));
}
// Notify existence listeners
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.removeIf(wf -> {
Consumer<Boolean> l = wf.get();
if (l != null) l.accept(visible);
return l == null;
});
}
}
}
public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
if (!mVisible) {
return;
}
setHomeMinimized(minimized);
});
}
public void setHomeMinimized(final boolean minimized) {
if (DEBUG) {
Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
+ mHomeStackResizable + " split:" + isDividerVisible());
}
WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean minimizedChanged = mMinimized != minimized;
// Update minimized state
if (minimizedChanged) {
mMinimized = minimized;
}
// Always set this because we could be entering split when mMinimized is already true
wct.setFocusable(mSplits.mPrimary.token, !mMinimized);
// Sync state to DividerView if it exists.
if (mView != null) {
final int displayId = mView.getDisplay() != null
? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
// pause ime here (before updateMinimizedDockedStack)
if (mMinimized) {
mImePositionProcessor.pause(displayId);
}
if (minimizedChanged) {
// This conflicts with IME adjustment, so only call it when things change.
mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
}
if (!mMinimized) {
// afterwards so it can end any animations started in view
mImePositionProcessor.resume(displayId);
}
}
updateTouchable();
// If we are only setting focusability, a sync transaction isn't necessary (in fact it
// can interrupt other animations), so see if it can be submitted on pending instead.
if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) {
mTaskOrganizer.applyTransaction(wct);
}
}
public void setAdjustedForIme(boolean adjustedForIme) {
if (mAdjustedForIme == adjustedForIme) {
return;
}
mAdjustedForIme = adjustedForIme;
updateTouchable();
}
public void updateTouchable() {
mWindowManager.setTouchable(!mAdjustedForIme);
}
public void onUndockingTask() {
if (mView != null) {
mView.onUndockingTask();
}
}
public void onAppTransitionFinished() {
if (mView == null) {
return;
}
mForcedResizableController.onAppTransitionFinished();
}
public void dump(PrintWriter pw) {
pw.print(" mVisible="); pw.println(mVisible);
pw.print(" mMinimized="); pw.println(mMinimized);
pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
}
public long getAnimDuration() {
float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE,
mContext.getResources().getFloat(
com.android.internal.R.dimen
.config_appTransitionAnimationDurationScaleDefault));
final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
return (long) (transitionDuration * transitionScale);
}
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
}
public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
synchronized (mDockedStackExistsListeners) {
for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
if (mDockedStackExistsListeners.get(i) == listener) {
mDockedStackExistsListeners.remove(i);
}
}
}
}
public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.add(new WeakReference<>(listener));
}
}
public boolean splitPrimaryTask() {
try {
if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) {
return false;
}
} catch (RemoteException e) {
return false;
}
if (isSplitActive() || mSplits.mPrimary == null) {
return false;
}
// Try fetching the top running task.
final List<RunningTaskInfo> runningTasks =
ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
if (runningTasks == null || runningTasks.isEmpty()) {
return false;
}
// Note: The set of running tasks from the system is ordered by recency.
final RunningTaskInfo topRunningTask = runningTasks.get(0);
final int activityType = topRunningTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
return false;
}
if (!topRunningTask.supportsSplitScreenMultiWindow) {
Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
Toast.LENGTH_SHORT).show();
return false;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Clear out current windowing mode before reparenting to split task.
wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED);
wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */);
mWindowManagerProxy.applySyncTransaction(wct);
return true;
}
public void dismissSplitToPrimaryTask() {
startDismissSplit(true /* toPrimaryTask */);
}
/** Notifies the bounds of split screen changed. */
public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.removeIf(wf -> {
BiConsumer<Rect, Rect> l = wf.get();
if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets);
return l == null;
});
}
}
public void startEnterSplit() {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
// Set resizable directly here because applyEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits,
mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
}
public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
// Set resizable directly here because buildEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits,
mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
}
public void finishEnterSplitTransition(boolean minimized) {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
if (minimized) {
ensureMinimizedSplit();
} else {
ensureNormalSplit();
}
}
public void startDismissSplit(boolean toPrimaryTask) {
startDismissSplit(toPrimaryTask, false /* snapped */);
}
public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mSplits.getSplitTransitions().dismissSplit(
mSplits, mSplitLayout, !toPrimaryTask, snapped);
} else {
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
onDismissSplit();
}
}
public void onDismissSplit() {
updateVisibility(false /* visible */);
mMinimized = false;
// Resets divider bar position to undefined, so new divider bar will apply default position
// next time entering split mode.
mDividerState.mRatioPositionBeforeMinimized = 0;
removeDivider();
mImePositionProcessor.reset();
}
public void ensureMinimizedSplit() {
setHomeMinimized(true /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode yet, so enter now.
if (DEBUG) {
Slog.d(TAG, " entering split mode with minimized=true");
}
updateVisibility(true /* visible */);
}
}
public void ensureNormalSplit() {
setHomeMinimized(false /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode, so enter now.
if (DEBUG) {
Slog.d(TAG, " enter split mode unminimized ");
}
updateVisibility(true /* visible */);
}
}
public LegacySplitDisplayLayout getSplitLayout() {
return mSplitLayout;
}
public WindowManagerProxy getWmProxy() {
return mWindowManagerProxy;
}
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
return null;
}
return mSplits.mSecondary.token;
}
private class SplitScreenImpl implements LegacySplitScreen {
@Override
public boolean isMinimized() {
return mMinimized;
}
@Override
public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
/**
* TODO: Remove usage from outside the shell.
*/
@Override
public DividerView getDividerView() {
return LegacySplitScreenController.this.getDividerView();
}
@Override
public boolean isDividerVisible() {
boolean[] result = new boolean[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.isDividerVisible();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to get divider visible");
}
return result[0];
}
@Override
public void onKeyguardVisibilityChanged(boolean isShowing) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing);
});
}
@Override
public void setMinimized(boolean minimized) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.setMinimized(minimized);
});
}
@Override
public void onUndockingTask() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onUndockingTask();
});
}
@Override
public void onAppTransitionFinished() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onAppTransitionFinished();
});
}
@Override
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.registerInSplitScreenListener(listener);
});
}
@Override
public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.unregisterInSplitScreenListener(listener);
});
}
@Override
public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.registerBoundsChangeListener(listener);
});
}
@Override
public WindowContainerToken getSecondaryRoot() {
WindowContainerToken[] result = new WindowContainerToken[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.getSecondaryRoot();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to get secondary root");
}
return result[0];
}
@Override
public boolean splitPrimaryTask() {
boolean[] result = new boolean[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.splitPrimaryTask();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to split primary task");
}
return result[0];
}
@Override
public void dismissSplitToPrimaryTask() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.dismissSplitToPrimaryTask();
});
}
@Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
LegacySplitScreenController.this.dump(pw);
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s");
}
}
}
}