diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml
new file mode 100644
index 0000000000000..a348b148afb4e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml
new file mode 100644
index 0000000000000..c5d54c5fa4f28
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
new file mode 100644
index 0000000000000..e0fa59c9f157a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 15bfeb297b41e..e9957fd4f4f1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -16,16 +16,32 @@
package com.android.wm.shell.dagger;
-import android.view.IWindowManager;
+import android.content.Context;
+import android.os.Handler;
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
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.TransactionPool;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.tv.TvSplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.tv.TvStartingWindowTypeAlgorithm;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
import dagger.Module;
import dagger.Provides;
@@ -50,5 +66,33 @@ public class TvWMShellModule {
@DynamicOverride
static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
return new TvStartingWindowTypeAlgorithm();
- };
+ }
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
+ static SplitScreenController provideSplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional recentTasks,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Handler mainHandler,
+ SystemWindows systemWindows) {
+ return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
+ shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
+ displayImeController, displayInsetsController, dragAndDropController, transitions,
+ transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
+ systemWindows);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 169e17b770f33..872916197854e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -213,14 +213,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
- mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mTaskOrganizer, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mIconProvider, mMainExecutor, mRecentTasksOptional);
+ mStageCoordinator = createStageCoordinator();
}
mDragAndDropController.setSplitScreenController(this);
}
+ protected StageCoordinator createStageCoordinator() {
+ return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mTaskOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mIconProvider, mMainExecutor, mRecentTasksOptional);
+ }
+
@Override
public Context getContext() {
return mContext;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8d405f4f6fcf3..f6478b8db39fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -69,6 +69,7 @@ import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -81,6 +82,7 @@ import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import android.util.Slog;
import android.view.Choreographer;
@@ -244,7 +246,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
@@ -879,6 +881,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
WindowContainerTransaction wct, @ExitReason int exitReason) {
if (!mMainStage.isActive() || mIsExiting) return;
+ onSplitScreenExit();
+
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
@@ -947,6 +951,47 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ /**
+ * Overridden by child classes.
+ */
+ protected void onSplitScreenEnter() {
+ }
+
+ /**
+ * Overridden by child classes.
+ */
+ protected void onSplitScreenExit() {
+ }
+
+ /**
+ * Exits the split screen by finishing one of the tasks.
+ */
+ protected void exitStage(@SplitPosition int stageToClose) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ StageTaskListener stageToTop = mSideStagePosition == stageToClose
+ ? mMainStage
+ : mSideStage;
+ exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
+ } else {
+ boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
+ }
+ }
+
+ /**
+ * Grants focus to the main or the side stages.
+ */
+ protected void grantFocusToStage(@SplitPosition int stageToFocus) {
+ IActivityTaskManager activityTaskManagerService = IActivityTaskManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE));
+ try {
+ activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
+ } catch (RemoteException | NullPointerException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Unable to update focus on the chosen stage, %s", TAG, e);
+ }
+ }
+
/**
* Returns whether the split pair in the recent tasks list should be broken.
*/
@@ -994,6 +1039,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
if (mMainStage.isActive()) return;
+ onSplitScreenEnter();
if (taskInfo != null) {
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
@@ -1387,6 +1433,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+ onSplitScreenEnter();
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
new file mode 100644
index 0000000000000..1d8a8d506c5c6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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.splitscreen.tv;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.view.LayoutInflater;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitScreenConstants;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * Handles the interaction logic with the {@link TvSplitMenuView}.
+ * A bridge between {@link TvStageCoordinator} and {@link TvSplitMenuView}.
+ */
+public class TvSplitMenuController implements TvSplitMenuView.Listener {
+
+ private static final String TAG = TvSplitMenuController.class.getSimpleName();
+ private static final String ACTION_SHOW_MENU = "com.android.wm.shell.splitscreen.SHOW_MENU";
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+ private final Context mContext;
+ private final StageController mStageController;
+ private final SystemWindows mSystemWindows;
+ private final Handler mMainHandler;
+
+ private final TvSplitMenuView mSplitMenuView;
+
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
+ private final int mTvButtonFadeAnimationDuration;
+
+ public TvSplitMenuController(Context context, StageController stageController,
+ SystemWindows systemWindows, Handler mainHandler) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mStageController = stageController;
+ mSystemWindows = systemWindows;
+
+ mTvButtonFadeAnimationDuration = context.getResources()
+ .getInteger(R.integer.tv_window_menu_fade_animation_duration);
+
+ mSplitMenuView = (TvSplitMenuView) LayoutInflater.from(context)
+ .inflate(R.layout.tv_split_menu_view, null);
+ mSplitMenuView.setListener(this);
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
+ }
+
+ /**
+ * Adds the menu view for the splitscreen to SystemWindows.
+ */
+ void addSplitMenuViewToSystemWindows() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ mContext.getResources().getDisplayMetrics().widthPixels,
+ mContext.getResources().getDisplayMetrics().heightPixels,
+ TYPE_DOCK_DIVIDER,
+ FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ mSplitMenuView.setAlpha(0);
+ mSystemWindows.addView(mSplitMenuView, lp, DEFAULT_DISPLAY, SHELL_ROOT_LAYER_DIVIDER);
+ }
+
+ /**
+ * Removes the menu view for the splitscreen from SystemWindows.
+ */
+ void removeSplitMenuViewFromSystemWindows() {
+ mSystemWindows.removeView(mSplitMenuView);
+ }
+
+ /**
+ * Registers BroadcastReceiver when split screen mode is entered.
+ */
+ void registerBroadcastReceiver() {
+ mActionBroadcastReceiver.register();
+ }
+
+ /**
+ * Unregisters BroadcastReceiver when split screen mode is entered.
+ */
+ void unregisterBroadcastReceiver() {
+ mActionBroadcastReceiver.unregister();
+ }
+
+ @Override
+ public void onBackPress() {
+ setMenuVisibility(false);
+ }
+
+ @Override
+ public void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus) {
+ setMenuVisibility(false);
+ mStageController.grantFocusToStage(stageToFocus);
+ }
+
+ @Override
+ public void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose) {
+ setMenuVisibility(false);
+ mStageController.exitStage(stageToClose);
+ }
+
+ @Override
+ public void onSwapPress() {
+ mStageController.swapStages();
+ }
+
+ private void setMenuVisibility(boolean visible) {
+ applyMenuVisibility(visible);
+ setMenuFocus(visible);
+ }
+
+ private void applyMenuVisibility(boolean visible) {
+ float alphaTarget = visible ? 1F : 0F;
+
+ if (mSplitMenuView.getAlpha() == alphaTarget) {
+ return;
+ }
+
+ mSplitMenuView.animate()
+ .alpha(alphaTarget)
+ .setDuration(mTvButtonFadeAnimationDuration)
+ .withStartAction(() -> {
+ if (alphaTarget != 0) {
+ mSplitMenuView.setVisibility(VISIBLE);
+ }
+ })
+ .withEndAction(() -> {
+ if (alphaTarget == 0) {
+ mSplitMenuView.setVisibility(INVISIBLE);
+ }
+ });
+
+ }
+
+ private void setMenuFocus(boolean focused) {
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null,
+ mSystemWindows.getFocusGrantToken(mSplitMenuView), focused);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Unable to update focus, %s", TAG, e);
+ }
+ }
+
+ interface StageController {
+ void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus);
+ void exitStage(@SplitScreenConstants.SplitPosition int stageToClose);
+ void swapStages();
+ }
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+
+ final IntentFilter mIntentFilter;
+ {
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_SHOW_MENU);
+ }
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+ mMainHandler);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (ACTION_SHOW_MENU.equals(action)) {
+ setMenuVisibility(true);
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
new file mode 100644
index 0000000000000..88e9757a9b315
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.splitscreen.tv;
+
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.KEYCODE_BACK;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.split.SplitScreenConstants;
+
+/**
+ * A View for the Menu Window.
+ */
+public class TvSplitMenuView extends LinearLayout implements View.OnClickListener {
+
+ private Listener mListener;
+
+ public TvSplitMenuView(Context context) {
+ super(context);
+ }
+
+ public TvSplitMenuView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TvSplitMenuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ initButtons();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener == null) return;
+
+ final int id = v.getId();
+ if (id == R.id.tv_split_main_menu_focus_button) {
+ mListener.onFocusStage(SPLIT_POSITION_TOP_OR_LEFT);
+ } else if (id == R.id.tv_split_main_menu_close_button) {
+ mListener.onCloseStage(SPLIT_POSITION_TOP_OR_LEFT);
+ } else if (id == R.id.tv_split_side_menu_focus_button) {
+ mListener.onFocusStage(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ } else if (id == R.id.tv_split_side_menu_close_button) {
+ mListener.onCloseStage(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ } else if (id == R.id.tv_split_menu_swap_stages) {
+ mListener.onSwapPress();
+ }
+ }
+
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == ACTION_DOWN) {
+ if (event.getKeyCode() == KEYCODE_BACK) {
+ if (mListener != null) {
+ mListener.onBackPress();
+ return true;
+ }
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ private void initButtons() {
+ findViewById(R.id.tv_split_main_menu_focus_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_main_menu_close_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_side_menu_focus_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_side_menu_close_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_menu_swap_stages).setOnClickListener(this);
+ }
+
+ void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ interface Listener {
+ /** "Back" button from the remote control */
+ void onBackPress();
+
+ /** Menu Action Buttons */
+
+ void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus);
+
+ void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose);
+
+ void onSwapPress();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
new file mode 100644
index 0000000000000..9285e845a1581
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.splitscreen.tv;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+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.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.SplitscreenEventLogger;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * Class inherits from {@link SplitScreenController} and provides {@link TvStageCoordinator}
+ * for Split Screen on TV.
+ */
+public class TvSplitScreenController extends SplitScreenController {
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final ShellExecutor mMainExecutor;
+ private final DisplayController mDisplayController;
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final IconProvider mIconProvider;
+ private final Optional mRecentTasksOptional;
+
+ private final Handler mMainHandler;
+ private final SystemWindows mSystemWindows;
+
+ public TvSplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional recentTasks,
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ SystemWindows systemWindows) {
+ super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
+ syncQueue, rootTDAOrganizer, displayController, displayImeController,
+ displayInsetsController, dragAndDropController, transitions, transactionPool,
+ iconProvider, recentTasks, mainExecutor);
+
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = recentTasks;
+
+ mMainHandler = mainHandler;
+ mSystemWindows = systemWindows;
+ }
+
+ /**
+ * Provides Tv-specific StageCoordinator.
+ * @return {@link TvStageCoordinator}
+ */
+ @Override
+ protected StageCoordinator createStageCoordinator() {
+ return new TvStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mTaskOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool,
+ new SplitscreenEventLogger(), mIconProvider, mMainExecutor, mMainHandler,
+ mRecentTasksOptional, mSystemWindows);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
new file mode 100644
index 0000000000000..6595beb59adbe
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.splitscreen.tv;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+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.TransactionPool;
+import com.android.wm.shell.common.split.SplitScreenConstants;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitscreenEventLogger;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * Expands {@link StageCoordinator} functionality with Tv-specific methods.
+ */
+public class TvStageCoordinator extends StageCoordinator
+ implements TvSplitMenuController.StageController {
+
+ private final TvSplitMenuController mTvSplitMenuController;
+
+ public TvStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ ShellTaskOrganizer taskOrganizer, DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ IconProvider iconProvider, ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Optional recentTasks,
+ SystemWindows systemWindows) {
+ super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
+ displayInsetsController, transitions, transactionPool, logger, iconProvider,
+ mainExecutor, recentTasks);
+
+ mTvSplitMenuController = new TvSplitMenuController(context, this,
+ systemWindows, mainHandler);
+
+ }
+
+ @Override
+ protected void onSplitScreenEnter() {
+ mTvSplitMenuController.addSplitMenuViewToSystemWindows();
+ mTvSplitMenuController.registerBroadcastReceiver();
+ }
+
+ @Override
+ protected void onSplitScreenExit() {
+ mTvSplitMenuController.unregisterBroadcastReceiver();
+ mTvSplitMenuController.removeSplitMenuViewFromSystemWindows();
+ }
+
+ @Override
+ public void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus) {
+ super.grantFocusToStage(stageToFocus);
+ }
+
+ @Override
+ public void exitStage(@SplitScreenConstants.SplitPosition int stageToClose) {
+ super.exitStage(stageToClose);
+ }
+
+ /**
+ * Swaps the stages inside the SplitLayout.
+ */
+ @Override
+ public void swapStages() {
+ onDoubleTappedDivider();
+ }
+
+}