blob: e96bb5c2867e4b10a928d5e34eac6797c5ffe032 [file] [log] [blame]
/*
* Copyright (C) 2021 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.car.carlauncher.displayarea;
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.car.carlauncher.AppGridActivity.CAR_LAUNCHER_STATE.CONTROL_BAR;
import static com.android.car.carlauncher.AppGridActivity.CAR_LAUNCHER_STATE.DEFAULT;
import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.BACKGROUND_TASK_CONTAINER;
import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.CONTROL_BAR_DISPLAY_AREA;
import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FEATURE_TITLE_BAR;
import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE;
import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FOREGROUND_DISPLAY_AREA_ROOT;
import static com.android.car.carlauncher.displayarea.CarFullscreenTaskListener.MAPS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
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.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.window.DisplayAreaAppearedInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.car.carlauncher.AppGridActivity;
import com.android.car.carlauncher.ControlBarActivity;
import com.android.car.carlauncher.R;
import com.android.internal.app.AssistUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.util.HashMap;
import java.util.List;
/**
* Controls the bounds of the home background 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 {
private static final String TAG = "CarDisplayAreaController";
// TODO(b/198783542): Make this configurable.
private static final String LOCATION_SETTINGS_ACTIVITY = "LocationSettingsCheckerAutoActivity";
private static final String GRANT_PERMISSION_ACTIVITY = "GrantPermissionsActivity";
// TODO(b/194334719): Remove when display area logic is moved into systemui
private static final String DISPLAY_AREA_VISIBILITY_CHANGED =
"com.android.car.carlauncher.displayarea.DISPLAY_AREA_VISIBILITY_CHANGED";
private static final String INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE =
"EXTRA_IS_DISPLAY_AREA_VISIBLE";
// 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 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 = 500;
static final CarDisplayAreaController INSTANCE = new CarDisplayAreaController();
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 SyncTransactionQueue mSyncQueue;
private CarDisplayAreaOrganizer mOrganizer;
private DisplayAreaAppearedInfo mForegroundApplicationsDisplay;
private DisplayAreaAppearedInfo mTitleBarDisplay;
private DisplayAreaAppearedInfo mVoicePlateDisplay;
private DisplayAreaAppearedInfo mBackgroundApplicationDisplay;
private DisplayAreaAppearedInfo mControlBarDisplay;
private DisplayAreaAppearedInfo mImeContainerDisplayArea;
private String mControlBarActivityComponent;
private HashMap<String, Boolean> mForegroundDAComponentsVisibilityMap;
private ArraySet<ComponentName> mIgnoreOpeningForegroundDAComponentsSet;
private int mTitleBarDragThreshold;
private int mEnterExitAnimationDurationMs;
private int mDpiDensity;
private int mTotalScreenWidth = -1;
// height of DA hosting the control bar.
private int mControlBarDisplayHeight;
// height of DA hosting default apps and covering the maps fully.
private int mFullDisplayHeight;
// height of DA hosting default apps and covering the maps to default height.
private int mDefaultDisplayHeight;
private int mTitleBarHeight;
private int mScreenHeightWithoutNavBar;
private int mTotalScreenHeight;
private boolean mIsHostingDefaultApplicationDisplayAreaVisible;
private CarDisplayAreaTouchHandler mCarDisplayAreaTouchHandler;
private WindowManager mTitleBarWindowManager;
private View mTitleBarView;
private Context mApplicationContext;
private int mForegroundDisplayTop;
private AssistUtils mAssistUtils;
private boolean mIsForegroundDaVisible = false;
/**
* 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 boolean mIsGridViewVisibleInForegroundDisplayArea;
private ComponentName mAssistantVoicePlateActivityName;
private final TaskStackListener mOnActivityRestartAttemptListener = new TaskStackListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
updateForegroundDaVisibility(task.topActivity);
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
updateForegroundDaVisibility(componentName);
}
};
/**
* Gets the instance of {@link CarDisplayAreaController}
*/
public static CarDisplayAreaController getInstance() {
return INSTANCE;
}
/**
* Initializes the controller
*/
public void init(Context applicationContext, SyncTransactionQueue syncQueue,
CarDisplayAreaOrganizer organizer,
CarDisplayAreaTouchHandler carDisplayAreaTouchHandler) {
mApplicationContext = applicationContext;
mSyncQueue = syncQueue;
mOrganizer = organizer;
mTotalScreenHeight = applicationContext.getResources().getDimensionPixelSize(
R.dimen.total_screen_height);
mTotalScreenWidth = applicationContext.getResources().getDimensionPixelSize(
R.dimen.total_screen_width);
mControlBarDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
R.dimen.control_bar_height);
mFullDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
R.dimen.full_app_display_area_height);
mDefaultDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
R.dimen.default_app_display_area_height);
mCarDisplayAreaTouchHandler = carDisplayAreaTouchHandler;
mControlBarActivityComponent = new ComponentName(applicationContext,
ControlBarActivity.class).flattenToShortString();
mAssistantVoicePlateActivityName = ComponentName.unflattenFromString(
applicationContext.getResources().getString(
R.string.config_assistantVoicePlateActivity));
mAssistUtils = new AssistUtils(applicationContext);
// Get bottom nav bar height.
Resources resources = applicationContext.getResources();
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 = applicationContext.getResources().getInteger(
R.integer.enter_exit_animation_foreground_display_area_duration_ms);
mTitleBarDragThreshold = applicationContext.getResources().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);
}
}
private CarDisplayAreaController() {
}
boolean shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo) {
return taskInfo.baseIntent != null && mIgnoreOpeningForegroundDAComponentsSet.contains(
taskInfo.baseIntent.getComponent());
}
/**
* Show the title bar within a targeted display area using the rootDisplayAreaId.
*/
public void showTitleBar(int rootDisplayAreaId, Context context) {
removeTitleBar();
LayoutInflater inflater = LayoutInflater.from(context);
mTitleBarView = inflater
.inflate(R.layout.title_bar_display_area_view, null, true);
mTitleBarView.setVisibility(View.VISIBLE);
// Show the confirmation.
WindowManager.LayoutParams lp = getTitleBarWindowLayoutParams();
getWindowManager(rootDisplayAreaId, context).addView(mTitleBarView, lp);
}
private WindowManager getWindowManager(int rootDisplayAreaId, Context context) {
Bundle options = getOptionWithRootDisplayArea(rootDisplayAreaId);
if (mTitleBarWindowManager == null || mTitleBarWindowContext == null) {
// Create window context to specify the RootDisplayArea
mTitleBarWindowContext = context.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, context.getDisplayId(), options);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
return mTitleBarWindowManager;
}
/**
* Remove the title bar view
*/
public void removeTitleBar() {
if (mTitleBarView != null) {
mTitleBarWindowManager.removeView(mTitleBarView);
}
}
/**
* 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 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() {
mDpiDensity = mOrganizer.getDpiDensity();
// Register DA organizer.
registerOrganizer();
// Pre-calculate the foreground and background display bounds for different configs.
populateBounds();
// Set the initial bounds for first and second displays.
WindowContainerTransaction wct = new WindowContainerTransaction();
updateBounds(wct);
mOrganizer.applyTransaction(wct);
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);
// Notify the system bar button in sysui that the display area has
// been swiped closed
Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED);
intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, false);
mApplicationContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} else {
animateToDefaultState((int) y,
mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight, 0);
}
}
});
mCarDisplayAreaTouchHandler.enable(true);
ActivityTaskManager.getInstance().registerTaskStackListener(
mOnActivityRestartAttemptListener);
}
/**
* Return's the associated organizer.
*/
public CarDisplayAreaOrganizer getOrganizer() {
return mOrganizer;
}
private void updateForegroundDaVisibility(ComponentName componentName) {
if (componentName == null || isDisplayAreaAnimating()) {
return;
}
String packageName = componentName.getPackageName();
boolean isMaps = packageName.contains(MAPS);
boolean ignoreOpeningForegroundDA = mIgnoreOpeningForegroundDAComponentsSet.contains(
componentName);
// 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 = componentName.equals(mAssistantVoicePlateActivityName);
if (isVoicePlate) {
showVoicePlateDisplayArea();
return;
}
if (isMaps) {
return;
}
// Check is there is an existing session running for assist, cancel it.
if (mAssistUtils.isSessionRunning()) {
mAssistUtils.hideCurrentSession();
}
if (isHostingDefaultApplicationDisplayAreaVisible()) {
if (mForegroundDAComponentsVisibilityMap.containsKey(
componentName.flattenToShortString())
&& mForegroundDAComponentsVisibilityMap.get(
componentName.flattenToShortString())) {
startAnimation(AppGridActivity.CAR_LAUNCHER_STATE.CONTROL_BAR);
}
} else if (!(ignoreOpeningForegroundDA || componentName.getClassName().contains(
LOCATION_SETTINGS_ACTIVITY)
|| componentName.getClassName().contains(GRANT_PERMISSION_ACTIVITY))) {
startAnimation(AppGridActivity.CAR_LAUNCHER_STATE.DEFAULT);
}
if (ignoreOpeningForegroundDA || componentName.flattenToShortString().equals(
mControlBarActivityComponent)) {
return;
}
mForegroundDAComponentsVisibilityMap.replaceAll(
(n, v) -> componentName.flattenToShortString().equals(n));
}
void showVoicePlateDisplayArea() {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
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) {
if (info.getDisplayAreaInfo().rootDisplayAreaId == FEATURE_ROOT) {
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 AppGridActivity.CAR_LAUNCHER_STATE#DEFAULT} then the foreground
* DA hosting default applications will animate to the default set height.
*/
public void startAnimation(AppGridActivity.CAR_LAUNCHER_STATE toState) {
// TODO: currently the animations are only bottom/up. Make it more generic animations here.
int fromPos = 0;
int toPos = 0;
Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED);
switch (toState) {
case CONTROL_BAR:
// Foreground DA closes.
fromPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
- mControlBarDisplayHeight;
toPos = mScreenHeightWithoutNavBar + mTitleBarHeight;
animateToControlBarState(fromPos, toPos, mEnterExitAnimationDurationMs);
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, false);
break;
case FULL:
// TODO: Implement this.
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;
intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, true);
animateToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs);
}
mApplicationContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void animateToControlBarState(int fromPos, int toPos, int durationMs) {
mBackgroundApplicationDisplayBounds.bottom =
mScreenHeightWithoutNavBar - mControlBarDisplayHeight;
animate(fromPos, toPos, CONTROL_BAR, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = false;
}
private void animateToDefaultState(int fromPos, int toPos, int durationMs) {
if (!mIsForegroundDaVisible) {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.show(mForegroundApplicationsDisplay.getLeash());
tx.apply(true);
mIsForegroundDaVisible = true;
}
mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight;
animate(fromPos, toPos, DEFAULT, durationMs);
mIsHostingDefaultApplicationDisplayAreaVisible = true;
if (mCarDisplayAreaTouchHandler != null) {
mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true);
}
}
private void animate(int fromPos, int toPos, AppGridActivity.CAR_LAUNCHER_STATE toState,
int durationMs) {
if (mOrganizer != null) {
mOrganizer.scheduleOffset(fromPos, toPos, mBackgroundApplicationDisplayBounds,
mBackgroundApplicationDisplay, mForegroundApplicationsDisplay,
mTitleBarDisplay,
mControlBarDisplay, toState, durationMs);
}
}
/** Pre-calculates the default and background display bounds for different configs. */
private void populateBounds() {
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);
}
/** Updates the default and background display bounds for the given config. */
private void updateBounds(WindowContainerTransaction wct) {
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.runInSync(t -> {
t.setPosition(mForegroundApplicationsDisplay.getLeash(),
foregroundApplicationDisplayBound.left,
foregroundApplicationDisplayBound.top);
t.setPosition(mVoicePlateDisplay.getLeash(),
voicePlateDisplayBounds.left,
voicePlateDisplayBounds.top);
t.setPosition(mTitleBarDisplay.getLeash(),
titleBarDisplayBounds.left, -mTitleBarHeight);
t.setPosition(mBackgroundApplicationDisplay.getLeash(),
backgroundApplicationDisplayBound.left,
backgroundApplicationDisplayBound.top);
t.setPosition(mImeContainerDisplayArea.getLeash(),
backgroundApplicationDisplayBound.left,
backgroundApplicationDisplayBound.top);
t.setPosition(mControlBarDisplay.getLeash(),
controlBarDisplayBound.left,
controlBarDisplayBound.top);
});
}
/**
* Update the bounds of foreground DA to cover full screen.
*/
public void makeForegroundDAFullscreen() {
WindowContainerTransaction wct = new WindowContainerTransaction();
Rect foregroundApplicationDisplayBounds = new Rect(0, 0, 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.runInSync(t -> {
t.setPosition(mBackgroundApplicationDisplay.getLeash(), 0, 0);
});
mOrganizer.applyTransaction(wct);
}
}