Add TV splitscreen menu

Add a splitscreen menu for TV allowing to change the focused task, swap
the stages and exit the splitscreen mode by closing one of the stages.

Bug: 242179031
Test: Enter the splitscreen mode. Send the corresponding broadcast to open the menu (adb shell am
   broadcast -a com.android.wm.shell.splitscreen.SHOW_MENU). Choose the needed action button and press it, open the menu again, etc. Press the back button on the
menu control to hide the menu window.

Change-Id: Iac17445414a190c91ccd407d67f0f8c29d6bd4bf
This commit is contained in:
Ivan Makarov
2022-08-02 11:25:44 +00:00
parent 16985df5b8
commit 7ab2a40d0a
10 changed files with 826 additions and 7 deletions

View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/tv_window_menu_icon_size"
android:height="@dimen/tv_window_menu_icon_size"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M17,4h3c1.1,0 2,0.9 2,2v2h-2L20,6h-3L17,4zM4,8L4,6h3L7,4L4,4c-1.1,0 -2,0.9 -2,2v2h2zM20,16v2h-3v2h3c1.1,0 2,-0.9 2,-2v-2h-2zM7,18L4,18v-2L2,16v2c0,1.1 0.9,2 2,2h3v-2zM18,8L6,8v8h12L18,8z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/tv_window_menu_icon_size"
android:height="@dimen/tv_window_menu_icon_size"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
</vector>

View File

@@ -0,0 +1,125 @@
<!--
~ 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.
-->
<!-- Layout for TvSplitMenuView -->
<com.android.wm.shell.splitscreen.tv.TvSplitMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<LinearLayout
android:id="@+id/tv_split_main_menu"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_split_main_menu_focus_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tv_split_menu_ic_focus" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_split_main_menu_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_close_white" />
</LinearLayout>
</LinearLayout>
<com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_split_menu_swap_stages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tv_split_menu_ic_swap" />
<LinearLayout
android:id="@+id/tv_split_side_menu"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_split_side_menu_focus_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tv_split_menu_ic_focus" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_split_side_menu_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_close_white" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.android.wm.shell.splitscreen.tv.TvSplitMenuView>

View File

@@ -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<RecentTasksController> 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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<RecentTasksController> 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<RecentTasksController> 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);
}
}

View File

@@ -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<RecentTasksController> 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();
}
}