blob: 44471e871be729c5ddc6f3c5c3c95395687b25bf [file] [log] [blame]
/*
* 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.systemui.car.displayarea;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.BACKGROUND_TASK_CONTAINER;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.CONTROL_BAR_DISPLAY_AREA;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_TITLE_BAR;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FOREGROUND_DISPLAY_AREA_ROOT;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.DISPLAY_AREA_VISIBILITY_CHANGED;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.DEFAULT;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.FULL;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.FULL_TO_DEFAULT;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.app.UiModeManager;
import android.car.Car;
import android.car.app.CarActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.ImageView;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.android.internal.app.AssistUtils;
import com.android.systemui.R;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.qs.QSHost;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.wm.CarUiPortraitDisplaySystemBarsController;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
/**
* Controls the bounds of the home background, audio bar and application displays. This is a
* singleton class as there should be one controller used to register and control the DA's
*/
public class CarDisplayAreaController implements ConfigurationController.ConfigurationListener {
// Layer index of how display areas should be placed. Keeping a gap of 100 if we want to
// add some other display area layers in between in the future.
static final int FOREGROUND_LAYER_INDEX = 0;
static final int BACKGROUND_LAYER_INDEX = 100;
static final int CONTROL_BAR_LAYER_INDEX = 200;
static final int VOICE_PLATE_LAYER_SHOWN_INDEX = 300;
private static final String TAG = "CarDisplayAreaController";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int TITLE_BAR_WINDOW_TYPE =
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
private final Rect mControlBarDisplayBounds = new Rect();
private final Rect mForegroundApplicationDisplayBounds = new Rect();
private final Rect mTitleBarDisplayBounds = new Rect();
private final Rect mVoicePlateDisplayBounds = new Rect();
private final Rect mBackgroundApplicationDisplayBounds = new Rect();
private final Rect mNavBarBounds = new Rect();
private final IBinder mWindowToken = new Binder();
private final SyncTransactionQueue mSyncQueue;
private final CarDisplayAreaOrganizer mOrganizer;
private final CarFullscreenTaskListener mCarFullscreenTaskListener;
private final ComponentName mControlBarActivityComponent;
private final CarUiPortraitDisplaySystemBarsController mCarUiDisplaySystemBarsController;
private final List<ComponentName> mBackgroundActivityComponent;
private final HashMap<String, Boolean> mForegroundDAComponentsVisibilityMap;
private final ArraySet<ComponentName> mIgnoreOpeningForegroundDAComponentsSet;
private final int mTitleBarDragThreshold;
private final ShellExecutor mShellExecutor;
private final int mEnterExitAnimationDurationMs;
// height of DA hosting the control bar.
private final int mControlBarDisplayHeight;
private final int mDpiDensity;
private final int mTotalScreenWidth;
// height of DA hosting default apps and covering the maps fully.
private final int mFullDisplayHeight;
// height of DA hosting default apps and covering the maps to default height.
private final int mDefaultDisplayHeight;
private final int mTitleBarHeight;
private final int mScreenHeightWithoutNavBar;
private final int mTotalScreenHeight;
private final CarDisplayAreaTouchHandler mCarDisplayAreaTouchHandler;
private final Context mApplicationContext;
private final int mForegroundDisplayTop;
private final AssistUtils mAssistUtils;
private HashSet<Integer> mActiveTasksOnForegroundDA;
private HashSet<Integer> mActiveTasksOnBackgroundDA;
private final ConfigurationController mConfigurationController;
private final UiModeManager mUiModeManager;
private DisplayAreaAppearedInfo mForegroundApplicationsDisplay;
private DisplayAreaAppearedInfo mTitleBarDisplay;
private DisplayAreaAppearedInfo mVoicePlateDisplay;
private DisplayAreaAppearedInfo mBackgroundApplicationDisplay;
private DisplayAreaAppearedInfo mControlBarDisplay;
private DisplayAreaAppearedInfo mImeContainerDisplayArea;
private boolean mIsHostingDefaultApplicationDisplayAreaVisible;
private WindowManager mTitleBarWindowManager;
private View mTitleBarView;
private View mTitleHandleBarView;
private ImageView mImmersiveButtonView;
private Drawable mChevronUpDrawable;
private Drawable mChevronDownDrawable;
private boolean mIsForegroundDaVisible = false;
private boolean mIsForegroundDaFullScreen = false;
private boolean mIsUiModeNight = false;
// contains the list of activities that will be displayed on feature {@link
// CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE)
private final Set<ComponentName> mVoicePlateActivitySet;
// true if there are activities still pending to be mapped to the voice plate DA as
// Car object was not created.
private boolean mIsPendingVoicePlateActivityMappingToDA;
private boolean mIsControlBarDisplayAreaEmpty = true;
private int mControlBarTaskId = -1;
private final CarServiceProvider mCarServiceProvider;
private Car mCar;
/**
* The WindowContext that is registered with {@link #mTitleBarWindowManager} with options to
* specify the {@link RootDisplayArea} to attach the confirmation window.
*/
@Nullable
private Context mTitleBarWindowContext;
private final TaskStackListener mOnActivityRestartAttemptListener = new TaskStackListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible)
throws RemoteException {
super.onActivityRestartAttempt(task, homeTaskVisible, clearedTask, wasVisible);
logIfDebuggable("onActivityRestartAttempt: " + task);
updateForegroundDaVisibility(task);
}
@Override
public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
throws RemoteException {
super.onTaskMovedToFront(taskInfo);
logIfDebuggable("onTaskMovedToFront: " + taskInfo);
updateForegroundDaVisibility(taskInfo);
}
@Override
public void onTaskRemoved(int taskId) throws RemoteException {
super.onTaskRemoved(taskId);
Log.e(TAG, " onTaskRemoved: " + taskId);
// maybe recover
if (mActiveTasksOnBackgroundDA != null
&& mActiveTasksOnBackgroundDA.isEmpty()) {
// re launch background app
relaunchBackgroundApp();
}
if (mIsControlBarDisplayAreaEmpty && taskId == mControlBarTaskId) {
relaunchControlBarApp();
}
}
};
private final CarFullscreenTaskListener.OnTaskChangeListener mOnTaskChangeListener =
new CarFullscreenTaskListener.OnTaskChangeListener() {
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
logIfDebuggable("onTaskAppeared: " + taskInfo);
updateForegroundDaVisibility(taskInfo);
ComponentName componentName = null;
if (taskInfo.baseIntent != null) {
componentName = taskInfo.baseIntent.getComponent();
}
boolean isBackgroundApp = mBackgroundActivityComponent.contains(componentName);
if (isBackgroundApp) {
addActiveTaskToBackgroundDAMap(taskInfo.taskId);
}
boolean isControlBarApp = mControlBarActivityComponent.equals(componentName);
if (isControlBarApp) {
mIsControlBarDisplayAreaEmpty = false;
}
}
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
Log.e(TAG, " onTaskVanished: " + taskInfo);
boolean isBackgroundApp = false;
boolean isControlBarApp = false;
ComponentName cmp = null;
if (taskInfo.baseIntent != null) {
cmp = taskInfo.baseIntent.getComponent();
if (cmp != null) {
isBackgroundApp = mBackgroundActivityComponent.contains(cmp);
isControlBarApp = cmp.equals(mControlBarActivityComponent);
}
}
if (mActiveTasksOnBackgroundDA != null
&& mActiveTasksOnBackgroundDA.remove(taskInfo.taskId)) {
logIfDebuggable("removed task " + taskInfo.taskId
+ " from background DA, total tasks: "
+ mActiveTasksOnBackgroundDA.size());
}
if (isBackgroundApp && mActiveTasksOnBackgroundDA != null
&& mActiveTasksOnBackgroundDA.isEmpty()) {
// re launch background app
relaunchBackgroundApp();
}
if (isControlBarApp) {
// re launch controlbar app
mIsControlBarDisplayAreaEmpty = true;
relaunchControlBarApp();
}
if (taskInfo.displayAreaFeatureId == FEATURE_VOICE_PLATE) {
resetVoicePlateDisplayArea();
}
if (mActiveTasksOnForegroundDA == null) {
return;
}
if (mActiveTasksOnForegroundDA.remove(taskInfo.taskId)) {
logIfDebuggable("removed task " + taskInfo.taskId
+ " from foreground DA, total tasks: "
+ mActiveTasksOnForegroundDA.size());
}
if (mActiveTasksOnForegroundDA.isEmpty()
&& isHostingDefaultApplicationDisplayAreaVisible()) {
logIfDebuggable("no more tasks left in foreground DA, closing... ");
startAnimation(CONTROL_BAR);
}
}
};
private final CarUiPortraitDisplaySystemBarsController.Callback
mCarUiPortraitDisplaySystemBarsControllerCallback =
new CarUiPortraitDisplaySystemBarsController.Callback() {
@Override
public void onImmersiveRequestedChanged(String pkg, boolean requested) {
// If the requesting application is a voice plate, background, or ignored
// package, ignore immersive requests.
if (mVoicePlateActivitySet != null && mVoicePlateActivitySet.stream().anyMatch(
component -> component.getPackageName().equals(pkg))) {
return;
}
if (mBackgroundActivityComponent != null
&& mBackgroundActivityComponent.stream().anyMatch(
component -> component.getPackageName().equals(pkg))) {
return;
}
if (mIgnoreOpeningForegroundDAComponentsSet != null
&& mIgnoreOpeningForegroundDAComponentsSet.stream().anyMatch(
component -> component.getPackageName().equals(pkg))) {
return;
}
if (mTitleHandleBarView != null) {
mTitleHandleBarView.setVisibility(requested ? View.GONE : View.VISIBLE);
}
if (mImmersiveButtonView != null) {
mImmersiveButtonView.setVisibility(requested ? View.VISIBLE : View.GONE);
}
}
@Override
public void onImmersiveStateChanged(boolean immersive) {
setImmersive(immersive);
}
};
private void relaunchBackgroundApp() {
logIfDebuggable("relaunching background app...");
Intent mapsIntent = new Intent();
mapsIntent.setComponent(mBackgroundActivityComponent.get(0));
mApplicationContext.startActivityAsUser(mapsIntent, UserHandle.CURRENT);
}
private void relaunchControlBarApp() {
logIfDebuggable("relaunching controlbar app...");
Intent controlBarIntent = new Intent();
controlBarIntent.setComponent(mControlBarActivityComponent);
mApplicationContext.startActivityAsUser(controlBarIntent,
UserHandle.CURRENT);
}
/**
* Initializes the controller
*/
@Inject
public CarDisplayAreaController(Context applicationContext, SyncTransactionQueue syncQueue,
CarFullscreenTaskListener carFullscreenTaskListener,
ShellExecutor shellExecutor,
ConfigurationController configurationController,
QSHost host,
CarServiceProvider carServiceProvider,
CarDisplayAreaOrganizer organizer,
CarUiPortraitDisplaySystemBarsController carUiPortraitDisplaySystemBarsController) {
mApplicationContext = applicationContext;
mSyncQueue = syncQueue;
mOrganizer = organizer;
mShellExecutor = shellExecutor;
mCarFullscreenTaskListener = carFullscreenTaskListener;
mConfigurationController = configurationController;
mCarServiceProvider = carServiceProvider;
mCarUiDisplaySystemBarsController = carUiPortraitDisplaySystemBarsController;
mCarUiDisplaySystemBarsController.registerCallback(mApplicationContext.getDisplayId(),
mCarUiPortraitDisplaySystemBarsControllerCallback);
mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
mConfigurationController.addCallback(this);
mDpiDensity = mOrganizer.getDpiDensity();
Resources resources = applicationContext.getResources();
mTotalScreenHeight = resources.getDimensionPixelSize(
R.dimen.total_screen_height);
mTotalScreenWidth = resources.getDimensionPixelSize(
R.dimen.total_screen_width);
mControlBarDisplayHeight = resources.getDimensionPixelSize(
R.dimen.control_bar_height);
mFullDisplayHeight = resources.getDimensionPixelSize(
R.dimen.full_app_display_area_height);
mDefaultDisplayHeight = resources.getDimensionPixelSize(
R.dimen.default_app_display_area_height);
mChevronUpDrawable = resources.getDrawable(R.drawable.ic_chevron_up);
mChevronDownDrawable = resources.getDrawable(R.drawable.ic_chevron_down);
mCarDisplayAreaTouchHandler = new CarDisplayAreaTouchHandler(
new HandlerExecutor(applicationContext.getMainThreadHandler()));
mControlBarActivityComponent = ComponentName.unflattenFromString(
resources.getString(
R.string.config_controlBarActivity));
mBackgroundActivityComponent = new ArrayList<>();
mVoicePlateActivitySet = new ArraySet<>();
String[] backgroundActivities = mApplicationContext.getResources().getStringArray(
R.array.config_backgroundActivities);
for (String backgroundActivity : backgroundActivities) {
mBackgroundActivityComponent
.add(ComponentName.unflattenFromString(backgroundActivity));
}
mAssistUtils = new AssistUtils(applicationContext);
// Get bottom nav bar height.
int navBarHeight = resources.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
if (navBarHeight > 0) {
mNavBarBounds.set(0, mTotalScreenHeight - navBarHeight, mTotalScreenWidth,
mTotalScreenHeight);
}
// Get left nav bar width.
int leftNavBarWidthResId = resources
.getIdentifier("car_left_system_bar_width", "dimen", "android");
int leftNavBarWidth = 0;
if (leftNavBarWidthResId > 0) {
leftNavBarWidth = resources.getDimensionPixelSize(leftNavBarWidthResId);
mNavBarBounds.set(0, 0, leftNavBarWidth, mTotalScreenHeight);
}
// Get right nav bar width.
int rightNavBarWidthResId = resources
.getIdentifier("car_right_system_bar_width", "dimen", "android");
int rightNavBarWidth = 0;
if (rightNavBarWidthResId > 0) {
rightNavBarWidth = resources.getDimensionPixelSize(rightNavBarWidthResId);
mNavBarBounds.set(mTotalScreenWidth - rightNavBarWidth, 0, mTotalScreenWidth,
mTotalScreenHeight);
}
mScreenHeightWithoutNavBar = mTotalScreenHeight - mNavBarBounds.height();
mTitleBarHeight = resources.getDimensionPixelSize(R.dimen.title_bar_display_area_height);
mEnterExitAnimationDurationMs = resources.getInteger(
R.integer.enter_exit_animation_foreground_display_area_duration_ms);
mTitleBarDragThreshold = resources.getDimensionPixelSize(
R.dimen.title_bar_display_area_touch_drag_threshold);
mForegroundDisplayTop = mScreenHeightWithoutNavBar - mDefaultDisplayHeight;
mForegroundDAComponentsVisibilityMap = new HashMap<>();
for (String component : mApplicationContext.getResources().getStringArray(
R.array.config_foregroundDAComponents)) {
mForegroundDAComponentsVisibilityMap.put(component, false);
}
String[] ignoreOpeningForegroundDACmp = mApplicationContext.getResources().getStringArray(
R.array.config_ignoreOpeningForegroundDA);
mIgnoreOpeningForegroundDAComponentsSet = new ArraySet<>();
for (String component : ignoreOpeningForegroundDACmp) {
ComponentName componentName = ComponentName.unflattenFromString(component);
mIgnoreOpeningForegroundDAComponentsSet.add(componentName);
}
}
/**
* Returns options that specify the {@link RootDisplayArea} to attach the confirmation window.
* {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}.
*/
@Nullable
private static Bundle getOptionWithRootDisplayArea(int rootDisplayAreaId) {
// In case we don't care which root display area the window manager is specifying.
if (rootDisplayAreaId == FEATURE_UNDEFINED) {
return null;
}
Bundle options = new Bundle();
options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
return options;
}
private static void logIfDebuggable(String message) {
if (DEBUG) {
Log.d(TAG, message);
}
}
boolean shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo) {
return taskInfo.baseIntent != null && mIgnoreOpeningForegroundDAComponentsSet.contains(
taskInfo.baseIntent.getComponent());
}
void setControlBarVisibility(boolean show) {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
// Reset the layer for voice plate. This is needed as when the tasks are launched on
// other DA's those are brought to the top.
tx.setLayer(mControlBarDisplay.getLeash(), CONTROL_BAR_LAYER_INDEX);
if (show) {
tx.show(mControlBarDisplay.getLeash());
} else {
tx.hide(mControlBarDisplay.getLeash());
}
tx.apply(true);
}
/**
* Show the title bar within a targeted display area using the rootDisplayAreaId.
*/
void showTitleBar() {
if (mTitleBarView != null) {
mTitleBarView.setVisibility(View.VISIBLE);
return;
}
hideTitleBar();
createTitleBar();
}
private void createTitleBar() {
LayoutInflater inflater = LayoutInflater.from(mApplicationContext);
mTitleBarView = inflater.inflate(R.layout.title_bar_display_area_view, null, true);
mTitleBarView.setVisibility(View.VISIBLE);
mTitleHandleBarView = mTitleBarView.findViewById(R.id.title_handle_bar);
mImmersiveButtonView = mTitleBarView.findViewById(R.id.immersive_button);
if (mImmersiveButtonView != null) {
mImmersiveButtonView.setImageDrawable(
mIsForegroundDaFullScreen ? mChevronDownDrawable
: mChevronUpDrawable);
mImmersiveButtonView.setOnClickListener(v -> {
mCarUiDisplaySystemBarsController.requestImmersiveMode(
mApplicationContext.getDisplayId(), !mIsForegroundDaFullScreen);
});
}
// Show the confirmation.
WindowManager.LayoutParams lp = getTitleBarWindowLayoutParams();
getWindowManager().addView(mTitleBarView, lp);
}
private void setImmersive(boolean immersive) {
if (mIsForegroundDaFullScreen == immersive) {
return;
}
mIsForegroundDaFullScreen = immersive;
if (mIsForegroundDaFullScreen) {
if (!isForegroundDaVisible()) {
makeForegroundDaVisible(true);
}
startAnimation(FULL);
} else {
startAnimation(FULL_TO_DEFAULT);
}
if (mImmersiveButtonView != null) {
mImmersiveButtonView.setImageDrawable(
mIsForegroundDaFullScreen ? mChevronDownDrawable : mChevronUpDrawable);
}
}
private WindowManager getWindowManager() {
Bundle options = getOptionWithRootDisplayArea(FOREGROUND_DISPLAY_AREA_ROOT);
if (mTitleBarWindowManager == null || mTitleBarWindowContext == null) {
// Create window context to specify the RootDisplayArea
mTitleBarWindowContext = mApplicationContext.createWindowContext(
TITLE_BAR_WINDOW_TYPE, options);
mTitleBarWindowManager = mTitleBarWindowContext.getSystemService(WindowManager.class);
return mTitleBarWindowManager;
}
// Update the window context and window manager to specify the RootDisplayArea
IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
try {
wms.attachWindowContextToDisplayArea(mTitleBarWindowContext.getWindowContextToken(),
TITLE_BAR_WINDOW_TYPE, mApplicationContext.getDisplayId(), options);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
return mTitleBarWindowManager;
}
/**
* Hide the title bar view
*/
public void hideTitleBar() {
if (mTitleBarView != null) {
mTitleBarView.setVisibility(View.INVISIBLE);
}
}
/**
* Remove the title bar view
*/
public void removeTitleBar() {
if (mTitleBarView != null) {
mTitleBarWindowManager.removeView(mTitleBarView);
}
}
private WindowManager.LayoutParams getTitleBarWindowLayoutParams() {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
mTitleBarHeight,
TITLE_BAR_WINDOW_TYPE,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.statusBars());
// Trusted overlay so touches outside the touchable area are allowed to pass through
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.setTitle("TitleBar");
lp.gravity = Gravity.TOP;
lp.token = mWindowToken;
return lp;
}
/**
* Returns if display area hosting default application is visible to user or not.
*/
public boolean isHostingDefaultApplicationDisplayAreaVisible() {
return mIsHostingDefaultApplicationDisplayAreaVisible;
}
boolean isDisplayAreaAnimating() {
return mOrganizer != null && mOrganizer.isDisplayAreaAnimating();
}
/** Registers the DA organizer. */
public void register() {
logIfDebuggable("register organizer and set default bounds");
ShellTaskOrganizer taskOrganizer = new ShellTaskOrganizer(mShellExecutor,
mApplicationContext);
taskOrganizer.addListenerForType(mCarFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
taskOrganizer.registerOrganizer();
// Register DA organizer.
registerOrganizer();
// Pre-calculate the foreground and background display bounds for different configs.
setDefaultBounds();
// show the title bar window
showTitleBar();
mCarDisplayAreaTouchHandler.registerOnClickListener((x, y) -> {
// Check if the click is outside the bounds of default display. If so, close the
// display area.
if (mIsHostingDefaultApplicationDisplayAreaVisible
&& y < (mForegroundDisplayTop)) {
// TODO: closing logic goes here, something like: startAnimation(CONTROL_BAR);
}
});
mCarDisplayAreaTouchHandler.registerTouchEventListener(
new CarDisplayAreaTouchHandler.OnDragDisplayAreaListener() {
float mCurrentPos = -1;
@Override
public void onStart(float x, float y) {
mCurrentPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight;
}
@Override
public void onMove(float x, float y) {
if (y <= mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight) {
return;
}
animateToControlBarState((int) mCurrentPos, (int) y, 0);
mCurrentPos = y;
}
@Override
public void onFinish(float x, float y) {
if (y >= mTitleBarDragThreshold) {
animateToControlBarState((int) y,
mScreenHeightWithoutNavBar + mTitleBarHeight, 0);
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
} else {
animateToDefaultState((int) y,
mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight, 0);
}
}
});
mCarDisplayAreaTouchHandler.enable(true);
mCarServiceProvider.addListener(car -> {
mCar = car;
if (mIsPendingVoicePlateActivityMappingToDA) {
mIsPendingVoicePlateActivityMappingToDA = false;
updateVoicePlateActivityMap();
}
});
ActivityTaskManager.getInstance().registerTaskStackListener(
mOnActivityRestartAttemptListener);
// add CarFullscreenTaskListener to control the foreground DA when the task appears.
mCarFullscreenTaskListener.registerOnTaskChangeListener(mOnTaskChangeListener);
}
void updateVoicePlateActivityMap() {
Context currentUserContext = mApplicationContext.createContextAsUser(
UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0);
Intent voiceIntent = new Intent(Intent.ACTION_VOICE_ASSIST, /* uri= */ null);
List<ResolveInfo> result = currentUserContext.getPackageManager().queryIntentActivities(
voiceIntent, PackageManager.MATCH_ALL);
if (!result.isEmpty() && mCar == null) {
mIsPendingVoicePlateActivityMappingToDA = true;
return;
} else if (result.isEmpty()) {
return;
}
CarActivityManager carAm = (CarActivityManager) mCar.getCarManager(
Car.CAR_ACTIVITY_SERVICE);
for (ResolveInfo info : result) {
if (mVoicePlateActivitySet.add(info.activityInfo.getComponentName())) {
logIfDebuggable("adding the following component to voice plate: "
+ info.activityInfo.getComponentName());
CarDisplayAreaUtils.setPersistentActivity(carAm,
info.activityInfo.getComponentName(),
FEATURE_VOICE_PLATE, "VoicePlate");
}
}
}
@Override
public void onConfigChanged(Configuration newConfig) {
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode == Configuration.UI_MODE_NIGHT_YES && !mIsUiModeNight) {
removeTitleBar();
mUiModeManager.setNightModeActivated(true);
createTitleBar();
mIsUiModeNight = true;
} else if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && mIsUiModeNight) {
removeTitleBar();
mUiModeManager.setNightModeActivated(false);
createTitleBar();
mIsUiModeNight = false;
}
}
private void updateForegroundDaVisibility(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.baseIntent == null || taskInfo.baseIntent.getComponent() == null
|| isDisplayAreaAnimating()) {
return;
}
ComponentName componentName = taskInfo.baseIntent.getComponent();
// Voice plate will be shown as the top most layer. Also, we don't want to change the
// state of the DA's when voice plate is shown.
boolean isVoicePlate = mVoicePlateActivitySet.contains(componentName);
if (isVoicePlate) {
showVoicePlateDisplayArea();
return;
}
boolean isControlBar = componentName.equals(mControlBarActivityComponent);
boolean isBackgroundApp = mBackgroundActivityComponent.contains(componentName);
if (isBackgroundApp) {
// we don't want to change the state of the foreground DA when background
// apps are launched.
return;
}
if (isControlBar) {
// we don't want to change the state of the foreground DA when
// controlbar apps are launched.
mControlBarTaskId = taskInfo.taskId;
return;
}
if (mIsForegroundDaFullScreen) {
logIfDebuggable("foregroundDA in fullscreen mode, skip updating its state ");
return;
}
// Check is there is an existing session running for assist, cancel it.
if (mAssistUtils.isSessionRunning()) {
mAssistUtils.hideCurrentSession();
}
// Any task that does NOT meet all the below criteria should be ignored.
// 1. displayAreaFeatureId should be FEATURE_DEFAULT_TASK_CONTAINER
// 2. should be visible
// 3. for the current user ONLY. System user launches some tasks on cluster that should
// not affect the state of the foreground DA
// 4. any task that is manually defined to be ignored
if (!(taskInfo.displayAreaFeatureId == FEATURE_DEFAULT_TASK_CONTAINER
&& taskInfo.isVisible()
&& taskInfo.userId == ActivityManager.getCurrentUser()
&& !shouldIgnoreOpeningForegroundDA(taskInfo))) {
return;
}
String name = componentName.flattenToShortString();
// check if the foreground DA is visible to the user
if (isHostingDefaultApplicationDisplayAreaVisible()) {
if (mForegroundDAComponentsVisibilityMap.containsKey(name)
&& mForegroundDAComponentsVisibilityMap.get(name)) {
// close the foreground DA
startAnimation(CONTROL_BAR);
}
addActiveTaskToForegroundDAMap(taskInfo.taskId);
} else {
logIfDebuggable("opening DA on request for cmp: " + componentName);
startAnimation(DEFAULT);
addActiveTaskToForegroundDAMap(taskInfo.taskId);
}
mForegroundDAComponentsVisibilityMap.replaceAll((n, v) -> name.equals(n));
}
private void addActiveTaskToForegroundDAMap(int taskId) {
if (mActiveTasksOnForegroundDA == null) {
mActiveTasksOnForegroundDA = new HashSet<>();
}
if (taskId != -1) {
mActiveTasksOnForegroundDA.add(taskId);
logIfDebuggable("added task to foreground DA: " + taskId + " total tasks: "
+ mActiveTasksOnForegroundDA.size());
}
}
private void addActiveTaskToBackgroundDAMap(int taskId) {
if (mActiveTasksOnBackgroundDA == null) {
mActiveTasksOnBackgroundDA = new HashSet<>();
}
if (taskId != -1) {
mActiveTasksOnBackgroundDA.add(taskId);
logIfDebuggable("added task to background DA: " + taskId + " total tasks: "
+ mActiveTasksOnBackgroundDA.size());
}
}
void showVoicePlateDisplayArea() {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
// Reset the layer for voice plate. This is needed as when the tasks are launched on
// other DA's those are brought to the top.
tx.setLayer(mVoicePlateDisplay.getLeash(), VOICE_PLATE_LAYER_SHOWN_INDEX);
tx.show(mVoicePlateDisplay.getLeash());
tx.apply(true);
}
void resetVoicePlateDisplayArea() {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.hide(mVoicePlateDisplay.getLeash());
tx.apply(true);
}
/** Registers DA organizer. */
private void registerOrganizer() {
List<DisplayAreaAppearedInfo> foregroundDisplayAreaInfos =
mOrganizer.registerOrganizer(FOREGROUND_DISPLAY_AREA_ROOT);
if (foregroundDisplayAreaInfos.size() != 1) {
throw new IllegalStateException("Can't find display to launch default applications");
}
List<DisplayAreaAppearedInfo> titleBarDisplayAreaInfo =
mOrganizer.registerOrganizer(FEATURE_TITLE_BAR);
if (titleBarDisplayAreaInfo.size() != 1) {
throw new IllegalStateException("Can't find display to launch title bar");
}
List<DisplayAreaAppearedInfo> voicePlateDisplayAreaInfo =
mOrganizer.registerOrganizer(FEATURE_VOICE_PLATE);
if (voicePlateDisplayAreaInfo.size() != 1) {
throw new IllegalStateException("Can't find display to launch voice plate");
}
List<DisplayAreaAppearedInfo> backgroundDisplayAreaInfos =
mOrganizer.registerOrganizer(BACKGROUND_TASK_CONTAINER);
if (backgroundDisplayAreaInfos.size() != 1) {
throw new IllegalStateException("Can't find display to launch activity in background");
}
List<DisplayAreaAppearedInfo> controlBarDisplayAreaInfos =
mOrganizer.registerOrganizer(CONTROL_BAR_DISPLAY_AREA);
if (controlBarDisplayAreaInfos.size() != 1) {
throw new IllegalStateException("Can't find display to launch audio control");
}
// Get the IME display area attached to the root hierarchy.
List<DisplayAreaAppearedInfo> imeDisplayAreaInfos =
mOrganizer.registerOrganizer(FEATURE_IME_PLACEHOLDER);
for (DisplayAreaAppearedInfo info : imeDisplayAreaInfos) {
DisplayAreaInfo daInfo = info.getDisplayAreaInfo();
// Need to check the display for the multi displays platform.
if (daInfo.rootDisplayAreaId == FEATURE_ROOT
&& daInfo.displayId == Display.DEFAULT_DISPLAY) {
mImeContainerDisplayArea = info;
}
}
// As we have only 1 display defined for each display area feature get the 0th index.
mForegroundApplicationsDisplay = foregroundDisplayAreaInfos.get(0);
mTitleBarDisplay = titleBarDisplayAreaInfo.get(0);
mVoicePlateDisplay = voicePlateDisplayAreaInfo.get(0);
mBackgroundApplicationDisplay = backgroundDisplayAreaInfos.get(0);
mControlBarDisplay = controlBarDisplayAreaInfos.get(0);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
// TODO(b/188102153): replace to set mForegroundApplicationsDisplay to top.
tx.setLayer(mBackgroundApplicationDisplay.getLeash(), BACKGROUND_LAYER_INDEX);
tx.setLayer(mForegroundApplicationsDisplay.getLeash(), FOREGROUND_LAYER_INDEX);
tx.setLayer(mTitleBarDisplay.getLeash(), FOREGROUND_LAYER_INDEX);
tx.setLayer(mVoicePlateDisplay.getLeash(), VOICE_PLATE_LAYER_SHOWN_INDEX);
tx.setLayer(mControlBarDisplay.getLeash(), CONTROL_BAR_LAYER_INDEX);
tx.hide(mVoicePlateDisplay.getLeash());
tx.hide(mForegroundApplicationsDisplay.getLeash());
tx.apply(true);
}
/** Un-Registers DA organizer. */
public void unregister() {
mOrganizer.resetWindowsOffset();
mOrganizer.unregisterOrganizer();
mForegroundApplicationsDisplay = null;
mTitleBarDisplay = null;
mBackgroundApplicationDisplay = null;
mControlBarDisplay = null;
mVoicePlateDisplay = null;
mImeContainerDisplayArea = null;
mCarDisplayAreaTouchHandler.enable(false);
ActivityTaskManager.getInstance()
.unregisterTaskStackListener(mOnActivityRestartAttemptListener);
mTitleBarView.setVisibility(View.GONE);
}
/**
* This method should be called after the registration of DA's are done. The method expects a
* target state as an argument, according to which the animations will take place. For example,
* if the target state is {@link DisplayAreaComponent.FOREGROUND_DA_STATE#DEFAULT} then the
* foreground DA hosting default applications will animate to the default set height.
*/
public void startAnimation(DisplayAreaComponent.FOREGROUND_DA_STATE toState) {
// TODO: currently the animations are only bottom/up. Make it more generic animations here.
int fromPos = 0;
int toPos = 0;
switch (toState) {
case CONTROL_BAR:
// Foreground DA closes.
fromPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight;
toPos = mScreenHeightWithoutNavBar + mTitleBarHeight;
animateToControlBarState(fromPos, toPos, mEnterExitAnimationDurationMs);
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
break;
case FULL:
fromPos =
isForegroundDaVisible() ? mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight
: mScreenHeightWithoutNavBar + mTitleBarHeight;
toPos = mTitleBarHeight;
animateToFullState(fromPos, toPos, mEnterExitAnimationDurationMs);
break;
case FULL_TO_DEFAULT:
toPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight;
animateFullToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs);
break;
default:
// Foreground DA opens to default height.
// update the bounds to expand the foreground display area before starting
// animations.
fromPos = mScreenHeightWithoutNavBar + mTitleBarHeight;
toPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight;
animateToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs);
}
}
private void animateToControlBarState(int fromPos, int toPos, int durationMs) {
mBackgroundApplicationDisplayBounds.bottom =
mScreenHeightWithoutNavBar - mControlBarDisplayHeight;
animate(fromPos, toPos, CONTROL_BAR, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = false;
broadcastForegroundDAVisibilityChange(false);
}
private void animateToDefaultState(int fromPos, int toPos, int durationMs) {
if (!isForegroundDaVisible()) {
makeForegroundDaVisible(true);
showTitleBar();
}
mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight;
animate(fromPos, toPos, DEFAULT, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = true;
broadcastForegroundDAVisibilityChange(true);
if (mCarDisplayAreaTouchHandler != null) {
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true);
}
}
private void animateFullToDefaultState(int fromPos, int toPos, int durationMs) {
mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight;
mIsForegroundDaFullScreen = false;
animate(fromPos, toPos, FULL_TO_DEFAULT, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = true;
showTitleBar();
setControlBarVisibility(true);
if (mCarDisplayAreaTouchHandler != null) {
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true);
}
}
private void animateToFullState(int fromPos, int toPos, int durationMs) {
if (!isForegroundDaVisible()) {
makeForegroundDaVisible(true);
}
setControlBarVisibility(false);
mBackgroundApplicationDisplayBounds.bottom = mTotalScreenHeight;
makeForegroundDAFullScreen(/* setFullPosition= */ false, /* showTitleBar= */ true);
animate(fromPos, toPos, FULL, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = true;
if (mCarDisplayAreaTouchHandler != null) {
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
}
}
private void animate(int fromPos, int toPos, DisplayAreaComponent.FOREGROUND_DA_STATE toState,
int durationMs) {
if (mOrganizer != null) {
mOrganizer.scheduleOffset(fromPos, toPos, mBackgroundApplicationDisplayBounds,
mForegroundApplicationDisplayBounds, mBackgroundApplicationDisplay,
mForegroundApplicationsDisplay, mControlBarDisplay, toState, durationMs);
}
}
void makeForegroundDaVisible(boolean isVisible) {
logIfDebuggable("make foregroundDA visible? " + isVisible);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
if (isVisible) {
tx.show(mForegroundApplicationsDisplay.getLeash());
mIsForegroundDaVisible = true;
} else {
tx.hide(mForegroundApplicationsDisplay.getLeash());
mIsForegroundDaVisible = false;
}
tx.apply(true);
}
boolean isForegroundDaVisible() {
return mIsForegroundDaVisible;
}
/** Pre-calculates the display bounds for different DA's. */
void setDefaultBounds() {
logIfDebuggable("setting default bounds for all the DA's");
int controlBarTop = mScreenHeightWithoutNavBar - mControlBarDisplayHeight;
int foregroundTop =
mScreenHeightWithoutNavBar - mDefaultDisplayHeight - mControlBarDisplayHeight;
// Bottom nav bar. Bottom nav bar height will be 0 if the nav bar is present on the sides.
Rect backgroundBounds = new Rect(0, 0, mTotalScreenWidth, controlBarTop);
Rect controlBarBounds = new Rect(0, controlBarTop, mTotalScreenWidth,
mScreenHeightWithoutNavBar);
Rect foregroundBounds = new Rect(0,
foregroundTop, mTotalScreenWidth,
mScreenHeightWithoutNavBar - mControlBarDisplayHeight);
Rect voicePlateBounds = new Rect(0, 0, mTotalScreenWidth,
mScreenHeightWithoutNavBar - mControlBarDisplayHeight);
Rect titleBarBounds = new Rect(0,
foregroundTop - mTitleBarHeight, mTotalScreenWidth, foregroundTop);
// Adjust the bounds based on the nav bar.
// TODO: account for the case where nav bar is at the top.
// Populate the bounds depending on where the nav bar is.
if (mNavBarBounds.left == 0 && mNavBarBounds.top == 0) {
// Left nav bar.
backgroundBounds.left = mNavBarBounds.right;
controlBarBounds.left = mNavBarBounds.right;
foregroundBounds.left = mNavBarBounds.right;
titleBarBounds.left = mNavBarBounds.right;
} else if (mNavBarBounds.top == 0) {
// Right nav bar.
backgroundBounds.right = mNavBarBounds.left;
controlBarBounds.right = mNavBarBounds.left;
foregroundBounds.right = mNavBarBounds.left;
titleBarBounds.right = mNavBarBounds.left;
}
mBackgroundApplicationDisplayBounds.set(backgroundBounds);
mControlBarDisplayBounds.set(controlBarBounds);
mForegroundApplicationDisplayBounds.set(foregroundBounds);
mTitleBarDisplayBounds.set(titleBarBounds);
mVoicePlateDisplayBounds.set(voicePlateBounds);
mCarDisplayAreaTouchHandler.setTitleBarBounds(titleBarBounds);
// Set the initial bounds for first and second displays.
updateBounds();
mIsForegroundDaFullScreen = false;
}
/** Updates the default and background display bounds for the given config. */
private void updateBounds() {
WindowContainerTransaction wct = new WindowContainerTransaction();
Rect foregroundApplicationDisplayBound = mForegroundApplicationDisplayBounds;
Rect titleBarDisplayBounds = mTitleBarDisplayBounds;
Rect voicePlateDisplayBounds = mVoicePlateDisplayBounds;
Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds;
Rect controlBarDisplayBound = mControlBarDisplayBounds;
WindowContainerToken foregroundDisplayToken =
mForegroundApplicationsDisplay.getDisplayAreaInfo().token;
WindowContainerToken imeRootDisplayToken =
mImeContainerDisplayArea.getDisplayAreaInfo().token;
WindowContainerToken titleBarDisplayToken =
mTitleBarDisplay.getDisplayAreaInfo().token;
WindowContainerToken voicePlateDisplayToken =
mVoicePlateDisplay.getDisplayAreaInfo().token;
WindowContainerToken backgroundDisplayToken =
mBackgroundApplicationDisplay.getDisplayAreaInfo().token;
WindowContainerToken controlBarDisplayToken =
mControlBarDisplay.getDisplayAreaInfo().token;
// Default TDA
int foregroundDisplayWidthDp =
foregroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int foregroundDisplayHeightDp =
foregroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBound);
wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp,
foregroundDisplayHeightDp);
wct.setSmallestScreenWidthDp(foregroundDisplayToken, foregroundDisplayWidthDp);
// Title bar
int titleBarDisplayWidthDp =
titleBarDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int titleBarDisplayHeightDp =
titleBarDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(titleBarDisplayToken, titleBarDisplayBounds);
wct.setScreenSizeDp(titleBarDisplayToken, titleBarDisplayWidthDp,
titleBarDisplayHeightDp);
wct.setSmallestScreenWidthDp(titleBarDisplayToken, titleBarDisplayWidthDp);
// voice plate
int voicePlateDisplayWidthDp =
voicePlateDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int voicePlateDisplayHeightDp =
voicePlateDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(voicePlateDisplayToken, voicePlateDisplayBounds);
wct.setScreenSizeDp(voicePlateDisplayToken, voicePlateDisplayWidthDp,
voicePlateDisplayHeightDp);
wct.setSmallestScreenWidthDp(voicePlateDisplayToken, voicePlateDisplayWidthDp);
// background TDA
int backgroundDisplayWidthDp =
backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int backgroundDisplayHeightDp =
backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound);
wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp,
backgroundDisplayHeightDp);
wct.setSmallestScreenWidthDp(backgroundDisplayToken, backgroundDisplayWidthDp);
// Change the bounds of the IME attached to the root display to be same as the background DA
wct.setBounds(imeRootDisplayToken, backgroundApplicationDisplayBound);
wct.setScreenSizeDp(imeRootDisplayToken, backgroundDisplayWidthDp,
backgroundDisplayHeightDp);
wct.setSmallestScreenWidthDp(imeRootDisplayToken, backgroundDisplayWidthDp);
// control bar
int controlBarDisplayWidthDp =
controlBarDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int controlBarDisplayHeightDp =
controlBarDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(controlBarDisplayToken, controlBarDisplayBound);
wct.setScreenSizeDp(controlBarDisplayToken, controlBarDisplayWidthDp,
controlBarDisplayHeightDp);
wct.setSmallestScreenWidthDp(controlBarDisplayToken, controlBarDisplayWidthDp);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
Rect foregroundApplicationAndTitleBarDisplayBound = new Rect(0, -mTitleBarHeight,
foregroundApplicationDisplayBound.width(),
foregroundApplicationDisplayBound.height());
t.setCrop(mForegroundApplicationsDisplay.getLeash(),
foregroundApplicationAndTitleBarDisplayBound);
t.setPosition(mForegroundApplicationsDisplay.getLeash(),
foregroundApplicationDisplayBound.left,
foregroundApplicationDisplayBound.top);
t.setWindowCrop(mVoicePlateDisplay.getLeash(),
voicePlateDisplayBounds.width(), voicePlateDisplayBounds.height());
t.setPosition(mVoicePlateDisplay.getLeash(),
voicePlateDisplayBounds.left,
voicePlateDisplayBounds.top);
t.setWindowCrop(mTitleBarDisplay.getLeash(),
titleBarDisplayBounds.width(), titleBarDisplayBounds.height());
t.setPosition(mTitleBarDisplay.getLeash(),
titleBarDisplayBounds.left, -mTitleBarHeight);
t.setWindowCrop(mBackgroundApplicationDisplay.getLeash(),
backgroundApplicationDisplayBound.width(),
backgroundApplicationDisplayBound.height());
t.setPosition(mBackgroundApplicationDisplay.getLeash(),
backgroundApplicationDisplayBound.left,
backgroundApplicationDisplayBound.top);
t.setWindowCrop(mImeContainerDisplayArea.getLeash(),
backgroundApplicationDisplayBound.width(),
backgroundApplicationDisplayBound.height());
t.setPosition(mImeContainerDisplayArea.getLeash(),
backgroundApplicationDisplayBound.left,
backgroundApplicationDisplayBound.top);
t.setWindowCrop(mControlBarDisplay.getLeash(),
controlBarDisplayBound.width(), controlBarDisplayBound.height());
t.setPosition(mControlBarDisplay.getLeash(),
controlBarDisplayBound.left,
controlBarDisplayBound.top);
});
}
/** Bypass the typical fullscreen flow specifically for SUW */
void immersiveForSUW(boolean immersive) {
if (immersive) {
makeForegroundDAFullScreen(/* setFullPosition= */ true, /* showTitleBar= */ false);
} else {
setDefaultBounds();
}
mCarUiDisplaySystemBarsController.requestImmersiveModeForSUW(
mApplicationContext.getDisplayId(), immersive);
}
/**
* Update the bounds of foreground DA to cover full screen.
*
* @param setFullPosition whether or not the surface's position should be set to the full
* position. Setting this to true will set the position to the full
* screen while setting to false will use the default display bounds.
*/
void makeForegroundDAFullScreen(boolean setFullPosition, boolean showTitleBar) {
logIfDebuggable("make foregroundDA fullscreen");
WindowContainerTransaction wct = new WindowContainerTransaction();
int topBound = showTitleBar ? mTitleBarHeight : 0;
Rect foregroundApplicationDisplayBounds = new Rect(0, topBound, mTotalScreenWidth,
mTotalScreenHeight);
WindowContainerToken foregroundDisplayToken =
mForegroundApplicationsDisplay.getDisplayAreaInfo().token;
int foregroundDisplayWidthDp =
foregroundApplicationDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
int foregroundDisplayHeightDp =
foregroundApplicationDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
/ mDpiDensity;
wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBounds);
wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp,
foregroundDisplayHeightDp);
wct.setSmallestScreenWidthDp(foregroundDisplayToken,
Math.min(foregroundDisplayWidthDp, foregroundDisplayHeightDp));
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setWindowCrop(mForegroundApplicationsDisplay.getLeash(),
mTotalScreenWidth, mTotalScreenHeight);
if (setFullPosition) {
t.setPosition(mForegroundApplicationsDisplay.getLeash(), 0, 0);
}
});
mIsForegroundDaFullScreen = true;
}
private void broadcastForegroundDAVisibilityChange(boolean visible) {
Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED);
intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, visible);
LocalBroadcastManager.getInstance(mApplicationContext).sendBroadcast(
intent);
}
}