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:
@@ -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>
|
||||
@@ -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>
|
||||
125
libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
Normal file
125
libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user