1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.car.displayarea; 18 19 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; 20 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; 21 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; 22 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; 23 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; 24 25 import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.BACKGROUND_TASK_CONTAINER; 26 import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.CONTROL_BAR_DISPLAY_AREA; 27 import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_TITLE_BAR; 28 import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE; 29 import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FOREGROUND_DISPLAY_AREA_ROOT; 30 import static com.android.systemui.car.displayarea.DisplayAreaComponent.DISPLAY_AREA_VISIBILITY_CHANGED; 31 import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR; 32 import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.DEFAULT; 33 import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.FULL; 34 import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.FULL_TO_DEFAULT; 35 import static com.android.systemui.car.displayarea.DisplayAreaComponent.INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE; 36 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; 37 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; 38 39 import android.app.ActivityManager; 40 import android.app.ActivityTaskManager; 41 import android.app.TaskStackListener; 42 import android.app.UiModeManager; 43 import android.car.Car; 44 import android.car.app.CarActivityManager; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.content.pm.PackageManager; 49 import android.content.pm.ResolveInfo; 50 import android.content.res.Configuration; 51 import android.content.res.Resources; 52 import android.graphics.PixelFormat; 53 import android.graphics.Rect; 54 import android.graphics.drawable.Drawable; 55 import android.os.Binder; 56 import android.os.Build; 57 import android.os.Bundle; 58 import android.os.IBinder; 59 import android.os.RemoteException; 60 import android.os.UserHandle; 61 import android.util.ArraySet; 62 import android.util.DisplayMetrics; 63 import android.util.Log; 64 import android.view.Display; 65 import android.view.Gravity; 66 import android.view.IWindowManager; 67 import android.view.LayoutInflater; 68 import android.view.SurfaceControl; 69 import android.view.View; 70 import android.view.ViewGroup; 71 import android.view.WindowInsets; 72 import android.view.WindowManager; 73 import android.view.WindowManagerGlobal; 74 import android.widget.ImageView; 75 import android.window.DisplayAreaAppearedInfo; 76 import android.window.DisplayAreaInfo; 77 import android.window.WindowContainerToken; 78 import android.window.WindowContainerTransaction; 79 80 import androidx.annotation.Nullable; 81 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 82 83 import com.android.internal.app.AssistUtils; 84 import com.android.systemui.R; 85 import com.android.systemui.car.CarDeviceProvisionedController; 86 import com.android.systemui.car.CarDeviceProvisionedListener; 87 import com.android.systemui.car.CarServiceProvider; 88 import com.android.systemui.qs.QSHost; 89 import com.android.systemui.statusbar.CommandQueue; 90 import com.android.systemui.statusbar.policy.ConfigurationController; 91 import com.android.systemui.wm.CarUiPortraitDisplaySystemBarsController; 92 import com.android.wm.shell.ShellTaskOrganizer; 93 import com.android.wm.shell.common.HandlerExecutor; 94 import com.android.wm.shell.common.ShellExecutor; 95 import com.android.wm.shell.common.SyncTransactionQueue; 96 97 import java.util.ArrayList; 98 import java.util.HashMap; 99 import java.util.HashSet; 100 import java.util.List; 101 import java.util.Set; 102 103 import javax.inject.Inject; 104 105 /** 106 * Controls the bounds of the home background, audio bar and application displays. This is a 107 * singleton class as there should be one controller used to register and control the DA's 108 */ 109 public class CarDisplayAreaController implements ConfigurationController.ConfigurationListener, 110 CommandQueue.Callbacks { 111 112 // Layer index of how display areas should be placed. Keeping a gap of 100 if we want to 113 // add some other display area layers in between in the future. 114 static final int FOREGROUND_LAYER_INDEX = 0; 115 static final int TITLE_BAR_LAYER_INDEX = 10; 116 static final int BACKGROUND_LAYER_INDEX = 100; 117 static final int CONTROL_BAR_LAYER_INDEX = 200; 118 static final int VOICE_PLATE_LAYER_SHOWN_INDEX = 300; 119 private static final String TAG = "CarDisplayAreaController"; 120 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 121 private static final int TITLE_BAR_WINDOW_TYPE = 122 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 123 124 private final Rect mControlBarDisplayBounds = new Rect(); 125 private final Rect mForegroundApplicationDisplayBounds = new Rect(); 126 private final Rect mTitleBarDisplayBounds = new Rect(); 127 private final Rect mVoicePlateDisplayBounds = new Rect(); 128 private final Rect mBackgroundApplicationDisplayBounds = new Rect(); 129 private final Rect mNavBarBounds = new Rect(); 130 private final IBinder mWindowToken = new Binder(); 131 132 private final SyncTransactionQueue mSyncQueue; 133 private final CarDisplayAreaOrganizer mOrganizer; 134 private final CarFullscreenTaskListener mCarFullscreenTaskListener; 135 private final ComponentName mControlBarActivityComponent; 136 private final ComponentName mHomeActivityComponent; 137 private final CarUiPortraitDisplaySystemBarsController mCarUiDisplaySystemBarsController; 138 private final CarDeviceProvisionedController mCarDeviceProvisionedController; 139 private final List<ComponentName> mBackgroundActivityComponent; 140 private final HashMap<String, Boolean> mForegroundDAComponentsVisibilityMap; 141 private final ArraySet<ComponentName> mIgnoreOpeningForegroundDAComponentsSet; 142 private final int mTitleBarDragThreshold; 143 private final ShellExecutor mShellExecutor; 144 private final int mEnterExitAnimationDurationMs; 145 // height of DA hosting the control bar. 146 private final int mControlBarDisplayHeight; 147 private final int mDpiDensity; 148 private final int mTotalScreenWidth; 149 // height of DA hosting default apps and covering the maps fully. 150 private final int mFullDisplayHeight; 151 // height of DA hosting default apps and covering the maps to default height. 152 private final int mDefaultDisplayHeight; 153 private final int mTitleBarHeight; 154 private final int mScreenHeightWithoutNavBar; 155 private final int mTotalScreenHeight; 156 private final ComponentName mNotificationCenterComponent; 157 private final CarDisplayAreaTouchHandler mCarDisplayAreaTouchHandler; 158 private final Context mApplicationContext; 159 private final int mForegroundDisplayTop; 160 private final AssistUtils mAssistUtils; 161 private HashSet<Integer> mActiveTasksOnForegroundDA; 162 private HashSet<Integer> mActiveTasksOnBackgroundDA; 163 private final ConfigurationController mConfigurationController; 164 private final UiModeManager mUiModeManager; 165 private DisplayAreaAppearedInfo mForegroundApplicationsDisplay; 166 private DisplayAreaAppearedInfo mTitleBarDisplay; 167 private DisplayAreaAppearedInfo mVoicePlateDisplay; 168 private DisplayAreaAppearedInfo mBackgroundApplicationDisplay; 169 private DisplayAreaAppearedInfo mControlBarDisplay; 170 private DisplayAreaAppearedInfo mImeContainerDisplayArea; 171 private boolean mIsHostingDefaultApplicationDisplayAreaVisible; 172 private WindowManager mTitleBarWindowManager; 173 private View mTitleBarView; 174 private View mTitleHandleBarView; 175 private ImageView mImmersiveButtonView; 176 private Drawable mChevronUpDrawable; 177 private Drawable mChevronDownDrawable; 178 private boolean mIsForegroundDaVisible = false; 179 private boolean mIsForegroundDaFullScreen = false; 180 private boolean mIsForegroundAppRequestingImmersiveMode = false; 181 private boolean mIsUiModeNight = false; 182 private boolean mIsUserSetupInProgress; 183 private DisplayAreaComponent.FOREGROUND_DA_STATE mCurrentForegroundDaState; 184 // contains the list of activities that will be displayed on feature {@link 185 // CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE) 186 private final Set<ComponentName> mVoicePlateActivitySet; 187 // true if there are activities still pending to be mapped to the voice plate DA as 188 // Car object was not created. 189 private boolean mIsPendingVoicePlateActivityMappingToDA; 190 private boolean mIsControlBarDisplayAreaEmpty = true; 191 private int mControlBarTaskId = -1; 192 private final CarServiceProvider mCarServiceProvider; 193 private Car mCar; 194 195 /** 196 * The WindowContext that is registered with {@link #mTitleBarWindowManager} with options to 197 * specify the {@link RootDisplayArea} to attach the confirmation window. 198 */ 199 @Nullable 200 private Context mTitleBarWindowContext; 201 private final TaskStackListener mOnActivityRestartAttemptListener = new TaskStackListener() { 202 @Override 203 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 204 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) 205 throws RemoteException { 206 super.onActivityRestartAttempt(task, homeTaskVisible, clearedTask, wasVisible); 207 logIfDebuggable("onActivityRestartAttempt: " + task); 208 updateForegroundDaVisibility(task); 209 } 210 211 @Override 212 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) 213 throws RemoteException { 214 super.onTaskMovedToFront(taskInfo); 215 logIfDebuggable("onTaskMovedToFront: " + taskInfo); 216 updateForegroundDaVisibility(taskInfo); 217 } 218 219 @Override 220 public void onTaskRemoved(int taskId) throws RemoteException { 221 super.onTaskRemoved(taskId); 222 Log.e(TAG, " onTaskRemoved: " + taskId); 223 // maybe recover 224 if (mActiveTasksOnBackgroundDA != null 225 && mActiveTasksOnBackgroundDA.isEmpty()) { 226 // re launch background app 227 relaunchBackgroundApp(); 228 } 229 230 if (mIsControlBarDisplayAreaEmpty && taskId == mControlBarTaskId) { 231 relaunchControlBarApp(); 232 } 233 } 234 }; 235 236 private final CarFullscreenTaskListener.OnTaskChangeListener mOnTaskChangeListener = 237 new CarFullscreenTaskListener.OnTaskChangeListener() { 238 @Override 239 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { 240 logIfDebuggable("onTaskAppeared: " + taskInfo); 241 updateForegroundDaVisibility(taskInfo); 242 ComponentName componentName = null; 243 if (taskInfo.baseIntent != null) { 244 componentName = taskInfo.baseIntent.getComponent(); 245 } 246 247 boolean isBackgroundApp = mBackgroundActivityComponent.contains(componentName); 248 if (isBackgroundApp) { 249 addActiveTaskToBackgroundDAMap(taskInfo.taskId); 250 } 251 252 boolean isControlBarApp = mControlBarActivityComponent.equals(componentName); 253 if (isControlBarApp) { 254 mIsControlBarDisplayAreaEmpty = false; 255 } 256 } 257 258 @Override 259 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 260 Log.e(TAG, " onTaskVanished: " + taskInfo); 261 boolean isBackgroundApp = false; 262 boolean isControlBarApp = false; 263 ComponentName cmp = null; 264 if (taskInfo.baseIntent != null) { 265 cmp = taskInfo.baseIntent.getComponent(); 266 if (cmp != null) { 267 isBackgroundApp = mBackgroundActivityComponent.contains(cmp); 268 isControlBarApp = cmp.equals(mControlBarActivityComponent); 269 } 270 } 271 272 if (mActiveTasksOnBackgroundDA != null 273 && mActiveTasksOnBackgroundDA.remove(taskInfo.taskId)) { 274 logIfDebuggable("removed task " + taskInfo.taskId 275 + " from background DA, total tasks: " 276 + mActiveTasksOnBackgroundDA.size()); 277 } 278 279 if (isBackgroundApp && mActiveTasksOnBackgroundDA != null 280 && mActiveTasksOnBackgroundDA.isEmpty()) { 281 // re launch background app 282 relaunchBackgroundApp(); 283 } 284 285 if (isControlBarApp) { 286 // re launch controlbar app 287 mIsControlBarDisplayAreaEmpty = true; 288 relaunchControlBarApp(); 289 } 290 291 if (taskInfo.displayAreaFeatureId == FEATURE_VOICE_PLATE) { 292 resetVoicePlateDisplayArea(); 293 } 294 295 if (mActiveTasksOnForegroundDA == null) { 296 return; 297 } 298 299 if (mActiveTasksOnForegroundDA.remove(taskInfo.taskId)) { 300 logIfDebuggable("removed task " + taskInfo.taskId 301 + " from foreground DA, total tasks: " 302 + mActiveTasksOnForegroundDA.size()); 303 } 304 305 if (mActiveTasksOnForegroundDA.isEmpty() 306 && isHostingDefaultApplicationDisplayAreaVisible()) { 307 logIfDebuggable("no more tasks left in foreground DA, closing... "); 308 startAnimation(CONTROL_BAR); 309 } 310 } 311 }; 312 313 private final CarUiPortraitDisplaySystemBarsController.Callback 314 mCarUiPortraitDisplaySystemBarsControllerCallback = 315 new CarUiPortraitDisplaySystemBarsController.Callback() { 316 @Override 317 public void onImmersiveRequestedChanged(ComponentName componentName, 318 boolean requested) { 319 // If the requesting application is a voice plate, background, or ignored 320 // package, ignore immersive requests. 321 if (mVoicePlateActivitySet != null && mVoicePlateActivitySet.contains( 322 componentName)) { 323 return; 324 } 325 if (mBackgroundActivityComponent != null 326 && mBackgroundActivityComponent.contains(componentName)) { 327 return; 328 } 329 if (mIgnoreOpeningForegroundDAComponentsSet != null 330 && mIgnoreOpeningForegroundDAComponentsSet.contains(componentName)) { 331 return; 332 } 333 334 if (mTitleHandleBarView != null) { 335 mTitleHandleBarView.setVisibility(requested ? View.GONE : View.VISIBLE); 336 } 337 if (mImmersiveButtonView != null) { 338 mImmersiveButtonView.setVisibility(requested ? View.VISIBLE : View.GONE); 339 } 340 mIsForegroundAppRequestingImmersiveMode = requested; 341 } 342 343 @Override 344 public void onImmersiveStateChanged(boolean immersive) { 345 setImmersive(immersive); 346 } 347 }; 348 349 private final CarDeviceProvisionedListener mCarDeviceProvisionedListener = 350 new CarDeviceProvisionedListener() { 351 @Override 352 public void onUserSetupInProgressChanged() { 353 updateUserSetupState(); 354 } 355 }; 356 relaunchBackgroundApp()357 private void relaunchBackgroundApp() { 358 logIfDebuggable("relaunching background app..."); 359 Intent mapsIntent = new Intent(); 360 mapsIntent.setComponent(mBackgroundActivityComponent.get(0)); 361 mApplicationContext.startActivityAsUser(mapsIntent, UserHandle.CURRENT); 362 } 363 relaunchControlBarApp()364 private void relaunchControlBarApp() { 365 logIfDebuggable("relaunching controlbar app..."); 366 Intent controlBarIntent = new Intent(); 367 controlBarIntent.setComponent(mControlBarActivityComponent); 368 mApplicationContext.startActivityAsUser(controlBarIntent, 369 UserHandle.CURRENT); 370 } 371 372 /** 373 * Initializes the controller 374 */ 375 @Inject CarDisplayAreaController(Context applicationContext, SyncTransactionQueue syncQueue, CarFullscreenTaskListener carFullscreenTaskListener, ShellExecutor shellExecutor, ConfigurationController configurationController, QSHost host, CarServiceProvider carServiceProvider, CarDisplayAreaOrganizer organizer, CarUiPortraitDisplaySystemBarsController carUiPortraitDisplaySystemBarsController, CommandQueue commandQueue, CarDeviceProvisionedController deviceProvisionedController)376 public CarDisplayAreaController(Context applicationContext, SyncTransactionQueue syncQueue, 377 CarFullscreenTaskListener carFullscreenTaskListener, 378 ShellExecutor shellExecutor, 379 ConfigurationController configurationController, 380 QSHost host, 381 CarServiceProvider carServiceProvider, 382 CarDisplayAreaOrganizer organizer, 383 CarUiPortraitDisplaySystemBarsController carUiPortraitDisplaySystemBarsController, 384 CommandQueue commandQueue, 385 CarDeviceProvisionedController deviceProvisionedController) { 386 mApplicationContext = applicationContext; 387 mSyncQueue = syncQueue; 388 mOrganizer = organizer; 389 mShellExecutor = shellExecutor; 390 mCarFullscreenTaskListener = carFullscreenTaskListener; 391 mConfigurationController = configurationController; 392 mCarServiceProvider = carServiceProvider; 393 mCarUiDisplaySystemBarsController = carUiPortraitDisplaySystemBarsController; 394 mCarDeviceProvisionedController = deviceProvisionedController; 395 mCarUiDisplaySystemBarsController.registerCallback(mApplicationContext.getDisplayId(), 396 mCarUiPortraitDisplaySystemBarsControllerCallback); 397 mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); 398 mConfigurationController.addCallback(this); 399 mDpiDensity = mOrganizer.getDpiDensity(); 400 Resources resources = applicationContext.getResources(); 401 mTotalScreenHeight = resources.getDimensionPixelSize( 402 R.dimen.total_screen_height); 403 mTotalScreenWidth = resources.getDimensionPixelSize( 404 R.dimen.total_screen_width); 405 mControlBarDisplayHeight = resources.getDimensionPixelSize( 406 R.dimen.control_bar_height); 407 mFullDisplayHeight = resources.getDimensionPixelSize( 408 R.dimen.full_app_display_area_height); 409 mDefaultDisplayHeight = resources.getDimensionPixelSize( 410 R.dimen.default_app_display_area_height); 411 mChevronUpDrawable = resources.getDrawable(R.drawable.ic_chevron_up); 412 mChevronDownDrawable = resources.getDrawable(R.drawable.ic_chevron_down); 413 mCarDisplayAreaTouchHandler = new CarDisplayAreaTouchHandler( 414 new HandlerExecutor(applicationContext.getMainThreadHandler())); 415 mControlBarActivityComponent = ComponentName.unflattenFromString( 416 resources.getString( 417 R.string.config_controlBarActivity)); 418 mNotificationCenterComponent = ComponentName.unflattenFromString(resources.getString( 419 R.string.config_notificationCenterActivity)); 420 mHomeActivityComponent = ComponentName.unflattenFromString(resources.getString( 421 R.string.config_homeActivity)); 422 mBackgroundActivityComponent = new ArrayList<>(); 423 mVoicePlateActivitySet = new ArraySet<>(); 424 String[] backgroundActivities = mApplicationContext.getResources().getStringArray( 425 R.array.config_backgroundActivities); 426 for (String backgroundActivity : backgroundActivities) { 427 mBackgroundActivityComponent 428 .add(ComponentName.unflattenFromString(backgroundActivity)); 429 } 430 mAssistUtils = new AssistUtils(applicationContext); 431 commandQueue.addCallback(this); 432 433 // Get bottom nav bar height. 434 int navBarHeight = resources.getDimensionPixelSize( 435 com.android.internal.R.dimen.navigation_bar_height); 436 if (navBarHeight > 0) { 437 mNavBarBounds.set(0, mTotalScreenHeight - navBarHeight, mTotalScreenWidth, 438 mTotalScreenHeight); 439 } 440 441 // Get left nav bar width. 442 int leftNavBarWidthResId = resources 443 .getIdentifier("car_left_system_bar_width", "dimen", "android"); 444 int leftNavBarWidth = 0; 445 if (leftNavBarWidthResId > 0) { 446 leftNavBarWidth = resources.getDimensionPixelSize(leftNavBarWidthResId); 447 mNavBarBounds.set(0, 0, leftNavBarWidth, mTotalScreenHeight); 448 } 449 450 // Get right nav bar width. 451 int rightNavBarWidthResId = resources 452 .getIdentifier("car_right_system_bar_width", "dimen", "android"); 453 int rightNavBarWidth = 0; 454 if (rightNavBarWidthResId > 0) { 455 rightNavBarWidth = resources.getDimensionPixelSize(rightNavBarWidthResId); 456 mNavBarBounds.set(mTotalScreenWidth - rightNavBarWidth, 0, mTotalScreenWidth, 457 mTotalScreenHeight); 458 } 459 460 mScreenHeightWithoutNavBar = mTotalScreenHeight - mNavBarBounds.height(); 461 mTitleBarHeight = resources.getDimensionPixelSize(R.dimen.title_bar_display_area_height); 462 mEnterExitAnimationDurationMs = resources.getInteger( 463 R.integer.enter_exit_animation_foreground_display_area_duration_ms); 464 mTitleBarDragThreshold = resources.getDimensionPixelSize( 465 R.dimen.title_bar_display_area_touch_drag_threshold); 466 mForegroundDisplayTop = mScreenHeightWithoutNavBar - mDefaultDisplayHeight; 467 468 mForegroundDAComponentsVisibilityMap = new HashMap<>(); 469 for (String component : mApplicationContext.getResources().getStringArray( 470 R.array.config_foregroundDAComponents)) { 471 mForegroundDAComponentsVisibilityMap.put(component, false); 472 } 473 474 String[] ignoreOpeningForegroundDACmp = mApplicationContext.getResources().getStringArray( 475 R.array.config_ignoreOpeningForegroundDA); 476 mIgnoreOpeningForegroundDAComponentsSet = new ArraySet<>(); 477 for (String component : ignoreOpeningForegroundDACmp) { 478 ComponentName componentName = ComponentName.unflattenFromString(component); 479 mIgnoreOpeningForegroundDAComponentsSet.add(componentName); 480 } 481 } 482 483 @Override animateExpandNotificationsPanel()484 public void animateExpandNotificationsPanel() { 485 String name = mNotificationCenterComponent.flattenToShortString(); 486 if (isHostingDefaultApplicationDisplayAreaVisible() 487 && mForegroundDAComponentsVisibilityMap.containsKey(name) 488 && mForegroundDAComponentsVisibilityMap.get(name)) { 489 // notifications activity already visible 490 return; 491 } 492 Intent intent = new Intent(); 493 intent.setComponent(mNotificationCenterComponent); 494 mApplicationContext.startActivityAsUser(intent, UserHandle.CURRENT); 495 } 496 497 @Override animateCollapsePanels(int flags, boolean force)498 public void animateCollapsePanels(int flags, boolean force) { 499 if (mIsForegroundDaFullScreen) { 500 return; 501 } 502 Intent homeActivityIntent = new Intent(Intent.ACTION_MAIN); 503 homeActivityIntent.addCategory(Intent.CATEGORY_HOME); 504 homeActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 505 mApplicationContext.startActivityAsUser(homeActivityIntent, UserHandle.CURRENT); 506 } 507 508 /** 509 * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window. 510 * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}. 511 */ 512 @Nullable getOptionWithRootDisplayArea(int rootDisplayAreaId)513 private static Bundle getOptionWithRootDisplayArea(int rootDisplayAreaId) { 514 // In case we don't care which root display area the window manager is specifying. 515 if (rootDisplayAreaId == FEATURE_UNDEFINED) { 516 return null; 517 } 518 519 Bundle options = new Bundle(); 520 options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); 521 return options; 522 } 523 logIfDebuggable(String message)524 private static void logIfDebuggable(String message) { 525 if (DEBUG) { 526 Log.d(TAG, message); 527 } 528 } 529 shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo)530 boolean shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo) { 531 return taskInfo.baseIntent != null && mIgnoreOpeningForegroundDAComponentsSet.contains( 532 taskInfo.baseIntent.getComponent()); 533 } 534 setControlBarVisibility(boolean show)535 void setControlBarVisibility(boolean show) { 536 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 537 // Reset the layer for voice plate. This is needed as when the tasks are launched on 538 // other DA's those are brought to the top. 539 tx.setLayer(mControlBarDisplay.getLeash(), CONTROL_BAR_LAYER_INDEX); 540 if (show) { 541 tx.show(mControlBarDisplay.getLeash()); 542 } else { 543 tx.hide(mControlBarDisplay.getLeash()); 544 } 545 tx.apply(true); 546 } 547 548 /** 549 * Show the title bar within a targeted display area using the rootDisplayAreaId. 550 */ showTitleBar()551 void showTitleBar() { 552 if (mTitleBarView != null) { 553 mTitleBarView.setVisibility(View.VISIBLE); 554 return; 555 } 556 hideTitleBar(); 557 createTitleBar(); 558 } 559 createTitleBar()560 private void createTitleBar() { 561 LayoutInflater inflater = LayoutInflater.from(mApplicationContext); 562 mTitleBarView = inflater.inflate(R.layout.title_bar_display_area_view, null, true); 563 mTitleBarView.setVisibility(View.VISIBLE); 564 mTitleHandleBarView = mTitleBarView.findViewById(R.id.title_handle_bar); 565 mImmersiveButtonView = mTitleBarView.findViewById(R.id.immersive_button); 566 if (mImmersiveButtonView != null) { 567 mImmersiveButtonView.setImageDrawable( 568 mIsForegroundDaFullScreen ? mChevronDownDrawable 569 : mChevronUpDrawable); 570 mImmersiveButtonView.setOnClickListener(v -> { 571 mCarUiDisplaySystemBarsController.requestImmersiveMode( 572 mApplicationContext.getDisplayId(), !mIsForegroundDaFullScreen); 573 }); 574 } 575 576 // Show the confirmation. 577 WindowManager.LayoutParams lp = getTitleBarWindowLayoutParams(); 578 getWindowManager().addView(mTitleBarView, lp); 579 } 580 setImmersive(boolean immersive)581 private void setImmersive(boolean immersive) { 582 if (mIsForegroundDaFullScreen == immersive) { 583 return; 584 } 585 mIsForegroundDaFullScreen = immersive; 586 if (mIsForegroundDaFullScreen) { 587 if (!isForegroundDaVisible()) { 588 makeForegroundDaVisible(true); 589 } 590 startAnimation(FULL); 591 } else { 592 startAnimation(FULL_TO_DEFAULT); 593 } 594 if (mImmersiveButtonView != null) { 595 mImmersiveButtonView.setImageDrawable( 596 mIsForegroundDaFullScreen ? mChevronDownDrawable : mChevronUpDrawable); 597 } 598 } 599 getWindowManager()600 private WindowManager getWindowManager() { 601 Bundle options = getOptionWithRootDisplayArea(FOREGROUND_DISPLAY_AREA_ROOT); 602 if (mTitleBarWindowManager == null || mTitleBarWindowContext == null) { 603 // Create window context to specify the RootDisplayArea 604 mTitleBarWindowContext = mApplicationContext.createWindowContext( 605 TITLE_BAR_WINDOW_TYPE, options); 606 mTitleBarWindowManager = mTitleBarWindowContext.getSystemService(WindowManager.class); 607 return mTitleBarWindowManager; 608 } 609 610 // Update the window context and window manager to specify the RootDisplayArea 611 IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); 612 try { 613 wms.attachWindowContextToDisplayArea(mTitleBarWindowContext.getWindowContextToken(), 614 TITLE_BAR_WINDOW_TYPE, mApplicationContext.getDisplayId(), options); 615 } catch (RemoteException e) { 616 throw e.rethrowAsRuntimeException(); 617 } 618 619 return mTitleBarWindowManager; 620 } 621 622 /** 623 * Hide the title bar view 624 */ hideTitleBar()625 public void hideTitleBar() { 626 if (mTitleBarView != null) { 627 mTitleBarView.setVisibility(View.INVISIBLE); 628 } 629 } 630 631 /** 632 * Remove the title bar view 633 */ removeTitleBar()634 public void removeTitleBar() { 635 if (mTitleBarView != null) { 636 mTitleBarWindowManager.removeView(mTitleBarView); 637 } 638 } 639 getTitleBarWindowLayoutParams()640 private WindowManager.LayoutParams getTitleBarWindowLayoutParams() { 641 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 642 ViewGroup.LayoutParams.MATCH_PARENT, 643 mTitleBarHeight, 644 TITLE_BAR_WINDOW_TYPE, 645 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 646 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 647 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 648 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 649 PixelFormat.TRANSLUCENT); 650 lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.statusBars()); 651 // Trusted overlay so touches outside the touchable area are allowed to pass through 652 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS 653 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 654 lp.setTitle("TitleBar"); 655 lp.gravity = Gravity.TOP; 656 lp.token = mWindowToken; 657 return lp; 658 } 659 660 /** 661 * Returns if display area hosting default application is visible to user or not. 662 */ isHostingDefaultApplicationDisplayAreaVisible()663 public boolean isHostingDefaultApplicationDisplayAreaVisible() { 664 return mIsHostingDefaultApplicationDisplayAreaVisible; 665 } 666 isDisplayAreaAnimating()667 boolean isDisplayAreaAnimating() { 668 return mOrganizer != null && mOrganizer.isDisplayAreaAnimating(); 669 } 670 671 /** Registers the DA organizer. */ register()672 public void register() { 673 logIfDebuggable("register organizer and set default bounds"); 674 675 ShellTaskOrganizer taskOrganizer = new ShellTaskOrganizer(mShellExecutor); 676 taskOrganizer.addListenerForType(mCarFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); 677 // Use the same TaskListener for MULTI_WINDOW windowing mode as there is nothing that has 678 // to be done differently. This is because the tasks are still running in 'fullscreen' 679 // within a DisplayArea. 680 taskOrganizer.addListenerForType(mCarFullscreenTaskListener, 681 TASK_LISTENER_TYPE_MULTI_WINDOW); 682 683 taskOrganizer.registerOrganizer(); 684 // Register DA organizer. 685 registerOrganizer(); 686 687 // Pre-calculate the foreground and background display bounds for different configs. 688 setDefaultBounds(); 689 690 // show the title bar window 691 showTitleBar(); 692 693 mCarDisplayAreaTouchHandler.registerOnClickListener((x, y) -> { 694 // Check if the click is outside the bounds of default display. If so, close the 695 // display area. 696 if (mIsHostingDefaultApplicationDisplayAreaVisible 697 && y < (mForegroundDisplayTop)) { 698 // TODO: closing logic goes here, something like: startAnimation(CONTROL_BAR); 699 } 700 }); 701 702 mCarDisplayAreaTouchHandler.registerTouchEventListener( 703 new CarDisplayAreaTouchHandler.OnDragDisplayAreaListener() { 704 705 float mCurrentPos = -1; 706 707 @Override 708 public void onStart(float x, float y) { 709 mCurrentPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight 710 - mControlBarDisplayHeight; 711 } 712 713 @Override 714 public void onMove(float x, float y) { 715 if (mIsForegroundAppRequestingImmersiveMode) { 716 return; 717 } 718 if (y <= mScreenHeightWithoutNavBar - mDefaultDisplayHeight 719 - mControlBarDisplayHeight) { 720 return; 721 } 722 animateToControlBarState((int) mCurrentPos, (int) y, 0); 723 mCurrentPos = y; 724 } 725 726 @Override 727 public void onFinish(float x, float y) { 728 if (mIsForegroundAppRequestingImmersiveMode) { 729 return; 730 } 731 if (y >= mTitleBarDragThreshold) { 732 animateToControlBarState((int) y, 733 mScreenHeightWithoutNavBar + mTitleBarHeight, 0); 734 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false); 735 } else { 736 animateToDefaultState((int) y, 737 mScreenHeightWithoutNavBar - mDefaultDisplayHeight 738 - mControlBarDisplayHeight, 0); 739 } 740 } 741 }); 742 mCarDisplayAreaTouchHandler.enable(true); 743 744 mCarServiceProvider.addListener(car -> { 745 mCar = car; 746 if (mIsPendingVoicePlateActivityMappingToDA) { 747 mIsPendingVoicePlateActivityMappingToDA = false; 748 updateVoicePlateActivityMap(); 749 } 750 }); 751 752 ActivityTaskManager.getInstance().registerTaskStackListener( 753 mOnActivityRestartAttemptListener); 754 // add CarFullscreenTaskListener to control the foreground DA when the task appears. 755 mCarFullscreenTaskListener.registerOnTaskChangeListener(mOnTaskChangeListener); 756 757 updateUserSetupState(); 758 mCarDeviceProvisionedController.addCallback(mCarDeviceProvisionedListener); 759 } 760 updateVoicePlateActivityMap()761 void updateVoicePlateActivityMap() { 762 Context currentUserContext = mApplicationContext.createContextAsUser( 763 UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0); 764 765 Intent voiceIntent = new Intent(Intent.ACTION_VOICE_ASSIST, /* uri= */ null); 766 List<ResolveInfo> result = currentUserContext.getPackageManager().queryIntentActivities( 767 voiceIntent, PackageManager.MATCH_ALL); 768 if (!result.isEmpty() && mCar == null) { 769 mIsPendingVoicePlateActivityMappingToDA = true; 770 return; 771 } else if (result.isEmpty()) { 772 return; 773 } 774 775 CarActivityManager carAm = (CarActivityManager) mCar.getCarManager( 776 Car.CAR_ACTIVITY_SERVICE); 777 for (ResolveInfo info : result) { 778 if (mVoicePlateActivitySet.add(info.activityInfo.getComponentName())) { 779 logIfDebuggable("adding the following component to voice plate: " 780 + info.activityInfo.getComponentName()); 781 CarDisplayAreaUtils.setPersistentActivity(carAm, 782 info.activityInfo.getComponentName(), 783 FEATURE_VOICE_PLATE, "VoicePlate"); 784 } 785 } 786 } 787 788 @Override onConfigChanged(Configuration newConfig)789 public void onConfigChanged(Configuration newConfig) { 790 int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 791 if (currentNightMode == Configuration.UI_MODE_NIGHT_YES && !mIsUiModeNight) { 792 removeTitleBar(); 793 mUiModeManager.setNightModeActivated(true); 794 createTitleBar(); 795 mIsUiModeNight = true; 796 } else if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && mIsUiModeNight) { 797 removeTitleBar(); 798 mUiModeManager.setNightModeActivated(false); 799 createTitleBar(); 800 mIsUiModeNight = false; 801 } 802 } 803 updateForegroundDaVisibility(ActivityManager.RunningTaskInfo taskInfo)804 private void updateForegroundDaVisibility(ActivityManager.RunningTaskInfo taskInfo) { 805 if (taskInfo.baseIntent == null || taskInfo.baseIntent.getComponent() == null 806 || isDisplayAreaAnimating()) { 807 return; 808 } 809 810 ComponentName componentName = taskInfo.baseIntent.getComponent(); 811 812 // Voice plate will be shown as the top most layer. Also, we don't want to change the 813 // state of the DA's when voice plate is shown. 814 boolean isVoicePlate = mVoicePlateActivitySet.contains(componentName); 815 if (isVoicePlate) { 816 showVoicePlateDisplayArea(); 817 return; 818 } 819 820 boolean isControlBar = componentName.equals(mControlBarActivityComponent); 821 boolean isBackgroundApp = mBackgroundActivityComponent.contains(componentName); 822 boolean isHomeActivity = componentName.equals(mHomeActivityComponent); 823 824 if (isBackgroundApp) { 825 // we don't want to change the state of the foreground DA when background 826 // apps are launched. 827 return; 828 } 829 830 if (isHomeActivity && (mCurrentForegroundDaState != CONTROL_BAR)) { 831 // close the foreground DA 832 startAnimation(CONTROL_BAR); 833 return; 834 } 835 836 if (isControlBar) { 837 // we don't want to change the state of the foreground DA when 838 // controlbar apps are launched. 839 mControlBarTaskId = taskInfo.taskId; 840 return; 841 } 842 843 if (mIsForegroundDaFullScreen) { 844 logIfDebuggable("foregroundDA in fullscreen mode, skip updating its state "); 845 return; 846 } 847 848 // Check is there is an existing session running for assist, cancel it. 849 if (mAssistUtils.isSessionRunning()) { 850 mAssistUtils.hideCurrentSession(); 851 } 852 853 // Any task that does NOT meet all the below criteria should be ignored. 854 // 1. displayAreaFeatureId should be FEATURE_DEFAULT_TASK_CONTAINER 855 // 2. should be visible 856 // 3. for the current user ONLY. System user launches some tasks on cluster that should 857 // not affect the state of the foreground DA 858 // 4. any task that is manually defined to be ignored 859 // 5. home activity. We use this activity as the wallpaper. 860 if (!(taskInfo.displayAreaFeatureId == FEATURE_DEFAULT_TASK_CONTAINER 861 && taskInfo.isVisible() 862 && taskInfo.userId == ActivityManager.getCurrentUser() 863 && !shouldIgnoreOpeningForegroundDA(taskInfo) 864 && !isHomeActivity)) { 865 return; 866 } 867 868 String name = componentName.flattenToShortString(); 869 870 // check if the foreground DA is visible to the user 871 if (isHostingDefaultApplicationDisplayAreaVisible()) { 872 if (mForegroundDAComponentsVisibilityMap.containsKey(name) 873 && mForegroundDAComponentsVisibilityMap.get(name)) { 874 // close the foreground DA 875 startAnimation(CONTROL_BAR); 876 } 877 addActiveTaskToForegroundDAMap(taskInfo.taskId); 878 } else { 879 logIfDebuggable("opening DA on request for cmp: " + componentName); 880 startAnimation(DEFAULT); 881 addActiveTaskToForegroundDAMap(taskInfo.taskId); 882 } 883 884 mForegroundDAComponentsVisibilityMap.replaceAll((n, v) -> name.equals(n)); 885 } 886 addActiveTaskToForegroundDAMap(int taskId)887 private void addActiveTaskToForegroundDAMap(int taskId) { 888 if (mActiveTasksOnForegroundDA == null) { 889 mActiveTasksOnForegroundDA = new HashSet<>(); 890 } 891 if (taskId != -1) { 892 mActiveTasksOnForegroundDA.add(taskId); 893 logIfDebuggable("added task to foreground DA: " + taskId + " total tasks: " 894 + mActiveTasksOnForegroundDA.size()); 895 } 896 } 897 addActiveTaskToBackgroundDAMap(int taskId)898 private void addActiveTaskToBackgroundDAMap(int taskId) { 899 if (mActiveTasksOnBackgroundDA == null) { 900 mActiveTasksOnBackgroundDA = new HashSet<>(); 901 } 902 if (taskId != -1) { 903 mActiveTasksOnBackgroundDA.add(taskId); 904 logIfDebuggable("added task to background DA: " + taskId + " total tasks: " 905 + mActiveTasksOnBackgroundDA.size()); 906 907 } 908 } 909 showVoicePlateDisplayArea()910 void showVoicePlateDisplayArea() { 911 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 912 // Reset the layer for voice plate. This is needed as when the tasks are launched on 913 // other DA's those are brought to the top. 914 tx.setLayer(mVoicePlateDisplay.getLeash(), VOICE_PLATE_LAYER_SHOWN_INDEX); 915 tx.show(mVoicePlateDisplay.getLeash()); 916 tx.apply(true); 917 } 918 resetVoicePlateDisplayArea()919 void resetVoicePlateDisplayArea() { 920 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 921 tx.hide(mVoicePlateDisplay.getLeash()); 922 tx.apply(true); 923 } 924 925 /** Registers DA organizer. */ registerOrganizer()926 private void registerOrganizer() { 927 List<DisplayAreaAppearedInfo> foregroundDisplayAreaInfos = 928 mOrganizer.registerOrganizer(FOREGROUND_DISPLAY_AREA_ROOT); 929 if (foregroundDisplayAreaInfos.size() != 1) { 930 throw new IllegalStateException("Can't find display to launch default applications"); 931 } 932 933 List<DisplayAreaAppearedInfo> titleBarDisplayAreaInfo = 934 mOrganizer.registerOrganizer(FEATURE_TITLE_BAR); 935 if (titleBarDisplayAreaInfo.size() != 1) { 936 throw new IllegalStateException("Can't find display to launch title bar"); 937 } 938 939 List<DisplayAreaAppearedInfo> voicePlateDisplayAreaInfo = 940 mOrganizer.registerOrganizer(FEATURE_VOICE_PLATE); 941 if (voicePlateDisplayAreaInfo.size() != 1) { 942 throw new IllegalStateException("Can't find display to launch voice plate"); 943 } 944 945 List<DisplayAreaAppearedInfo> backgroundDisplayAreaInfos = 946 mOrganizer.registerOrganizer(BACKGROUND_TASK_CONTAINER); 947 if (backgroundDisplayAreaInfos.size() != 1) { 948 throw new IllegalStateException("Can't find display to launch activity in background"); 949 } 950 951 List<DisplayAreaAppearedInfo> controlBarDisplayAreaInfos = 952 mOrganizer.registerOrganizer(CONTROL_BAR_DISPLAY_AREA); 953 if (controlBarDisplayAreaInfos.size() != 1) { 954 throw new IllegalStateException("Can't find display to launch audio control"); 955 } 956 957 // Get the IME display area attached to the root hierarchy. 958 List<DisplayAreaAppearedInfo> imeDisplayAreaInfos = 959 mOrganizer.registerOrganizer(FEATURE_IME_PLACEHOLDER); 960 for (DisplayAreaAppearedInfo info : imeDisplayAreaInfos) { 961 DisplayAreaInfo daInfo = info.getDisplayAreaInfo(); 962 // Need to check the display for the multi displays platform. 963 if (daInfo.rootDisplayAreaId == FEATURE_ROOT 964 && daInfo.displayId == Display.DEFAULT_DISPLAY) { 965 mImeContainerDisplayArea = info; 966 } 967 } 968 // As we have only 1 display defined for each display area feature get the 0th index. 969 mForegroundApplicationsDisplay = foregroundDisplayAreaInfos.get(0); 970 mTitleBarDisplay = titleBarDisplayAreaInfo.get(0); 971 mVoicePlateDisplay = voicePlateDisplayAreaInfo.get(0); 972 mBackgroundApplicationDisplay = backgroundDisplayAreaInfos.get(0); 973 mControlBarDisplay = controlBarDisplayAreaInfos.get(0); 974 975 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 976 // TODO(b/188102153): replace to set mForegroundApplicationsDisplay to top. 977 tx.setLayer(mBackgroundApplicationDisplay.getLeash(), BACKGROUND_LAYER_INDEX); 978 tx.setLayer(mForegroundApplicationsDisplay.getLeash(), FOREGROUND_LAYER_INDEX); 979 tx.setLayer(mTitleBarDisplay.getLeash(), TITLE_BAR_LAYER_INDEX); 980 tx.setLayer(mVoicePlateDisplay.getLeash(), VOICE_PLATE_LAYER_SHOWN_INDEX); 981 tx.setLayer(mControlBarDisplay.getLeash(), CONTROL_BAR_LAYER_INDEX); 982 983 tx.hide(mVoicePlateDisplay.getLeash()); 984 tx.hide(mForegroundApplicationsDisplay.getLeash()); 985 tx.apply(true); 986 } 987 988 /** Un-Registers DA organizer. */ unregister()989 public void unregister() { 990 mOrganizer.resetWindowsOffset(); 991 mOrganizer.unregisterOrganizer(); 992 mForegroundApplicationsDisplay = null; 993 mTitleBarDisplay = null; 994 mBackgroundApplicationDisplay = null; 995 mControlBarDisplay = null; 996 mVoicePlateDisplay = null; 997 mImeContainerDisplayArea = null; 998 mCarDisplayAreaTouchHandler.enable(false); 999 ActivityTaskManager.getInstance() 1000 .unregisterTaskStackListener(mOnActivityRestartAttemptListener); 1001 mCarDeviceProvisionedController.removeCallback(mCarDeviceProvisionedListener); 1002 mTitleBarView.setVisibility(View.GONE); 1003 } 1004 1005 /** 1006 * This method should be called after the registration of DA's are done. The method expects a 1007 * target state as an argument, according to which the animations will take place. For example, 1008 * if the target state is {@link DisplayAreaComponent.FOREGROUND_DA_STATE#DEFAULT} then the 1009 * foreground DA hosting default applications will animate to the default set height. 1010 */ startAnimation(DisplayAreaComponent.FOREGROUND_DA_STATE toState)1011 public void startAnimation(DisplayAreaComponent.FOREGROUND_DA_STATE toState) { 1012 if (mIsUserSetupInProgress) { 1013 // No animations while in setup 1014 return; 1015 } 1016 // TODO: currently the animations are only bottom/up. Make it more generic animations here. 1017 int fromPos = 0; 1018 int toPos = 0; 1019 mCurrentForegroundDaState = toState; 1020 1021 switch (toState) { 1022 case CONTROL_BAR: 1023 // Foreground DA closes. 1024 fromPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight 1025 - mControlBarDisplayHeight; 1026 toPos = mScreenHeightWithoutNavBar + mTitleBarHeight; 1027 animateToControlBarState(fromPos, toPos, mEnterExitAnimationDurationMs); 1028 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false); 1029 break; 1030 case FULL: 1031 fromPos = 1032 isForegroundDaVisible() ? mScreenHeightWithoutNavBar - mDefaultDisplayHeight 1033 - mControlBarDisplayHeight 1034 : mScreenHeightWithoutNavBar + mTitleBarHeight; 1035 toPos = mTitleBarHeight; 1036 animateToFullState(fromPos, toPos, mEnterExitAnimationDurationMs); 1037 break; 1038 case FULL_TO_DEFAULT: 1039 toPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight 1040 - mControlBarDisplayHeight; 1041 animateFullToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs); 1042 break; 1043 default: 1044 // Foreground DA opens to default height. 1045 // update the bounds to expand the foreground display area before starting 1046 // animations. 1047 fromPos = mScreenHeightWithoutNavBar + mTitleBarHeight; 1048 toPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight 1049 - mControlBarDisplayHeight; 1050 animateToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs); 1051 } 1052 } 1053 animateToControlBarState(int fromPos, int toPos, int durationMs)1054 private void animateToControlBarState(int fromPos, int toPos, int durationMs) { 1055 mBackgroundApplicationDisplayBounds.bottom = 1056 mScreenHeightWithoutNavBar - mControlBarDisplayHeight; 1057 animate(fromPos, toPos, CONTROL_BAR, durationMs); 1058 mIsHostingDefaultApplicationDisplayAreaVisible = false; 1059 broadcastForegroundDAVisibilityChange(false); 1060 } 1061 animateToDefaultState(int fromPos, int toPos, int durationMs)1062 private void animateToDefaultState(int fromPos, int toPos, int durationMs) { 1063 if (!isForegroundDaVisible()) { 1064 makeForegroundDaVisible(true); 1065 showTitleBar(); 1066 } 1067 mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight; 1068 animate(fromPos, toPos, DEFAULT, durationMs); 1069 mIsHostingDefaultApplicationDisplayAreaVisible = true; 1070 broadcastForegroundDAVisibilityChange(true); 1071 if (mCarDisplayAreaTouchHandler != null) { 1072 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true); 1073 } 1074 } 1075 animateFullToDefaultState(int fromPos, int toPos, int durationMs)1076 private void animateFullToDefaultState(int fromPos, int toPos, int durationMs) { 1077 mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight; 1078 mIsForegroundDaFullScreen = false; 1079 animate(fromPos, toPos, FULL_TO_DEFAULT, durationMs); 1080 mIsHostingDefaultApplicationDisplayAreaVisible = true; 1081 showTitleBar(); 1082 setControlBarVisibility(true); 1083 if (mCarDisplayAreaTouchHandler != null) { 1084 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true); 1085 } 1086 } 1087 animateToFullState(int fromPos, int toPos, int durationMs)1088 private void animateToFullState(int fromPos, int toPos, int durationMs) { 1089 if (!isForegroundDaVisible()) { 1090 makeForegroundDaVisible(true); 1091 } 1092 setControlBarVisibility(false); 1093 mBackgroundApplicationDisplayBounds.bottom = mTotalScreenHeight; 1094 makeForegroundDAFullScreen(/* setFullPosition= */ false, /* showTitleBar= */ true); 1095 animate(fromPos, toPos, FULL, durationMs); 1096 mIsHostingDefaultApplicationDisplayAreaVisible = true; 1097 if (mCarDisplayAreaTouchHandler != null) { 1098 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false); 1099 } 1100 } 1101 animate(int fromPos, int toPos, DisplayAreaComponent.FOREGROUND_DA_STATE toState, int durationMs)1102 private void animate(int fromPos, int toPos, DisplayAreaComponent.FOREGROUND_DA_STATE toState, 1103 int durationMs) { 1104 if (mOrganizer != null) { 1105 mOrganizer.scheduleOffset(fromPos, toPos, mBackgroundApplicationDisplayBounds, 1106 mForegroundApplicationDisplayBounds, mBackgroundApplicationDisplay, 1107 mForegroundApplicationsDisplay, mControlBarDisplay, toState, durationMs); 1108 } 1109 } 1110 makeForegroundDaVisible(boolean isVisible)1111 void makeForegroundDaVisible(boolean isVisible) { 1112 logIfDebuggable("make foregroundDA visible? " + isVisible); 1113 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 1114 if (isVisible) { 1115 tx.show(mForegroundApplicationsDisplay.getLeash()); 1116 mIsForegroundDaVisible = true; 1117 } else { 1118 tx.hide(mForegroundApplicationsDisplay.getLeash()); 1119 mIsForegroundDaVisible = false; 1120 } 1121 tx.apply(true); 1122 } 1123 isForegroundDaVisible()1124 boolean isForegroundDaVisible() { 1125 return mIsForegroundDaVisible; 1126 } 1127 1128 /** Pre-calculates the display bounds for different DA's. */ setDefaultBounds()1129 void setDefaultBounds() { 1130 logIfDebuggable("setting default bounds for all the DA's"); 1131 int controlBarTop = mScreenHeightWithoutNavBar - mControlBarDisplayHeight; 1132 int foregroundTop = 1133 mScreenHeightWithoutNavBar - mDefaultDisplayHeight - mControlBarDisplayHeight; 1134 1135 // Bottom nav bar. Bottom nav bar height will be 0 if the nav bar is present on the sides. 1136 Rect backgroundBounds = new Rect(0, 0, mTotalScreenWidth, controlBarTop); 1137 Rect controlBarBounds = new Rect(0, controlBarTop, mTotalScreenWidth, 1138 mScreenHeightWithoutNavBar); 1139 Rect foregroundBounds = new Rect(0, 1140 foregroundTop, mTotalScreenWidth, 1141 mScreenHeightWithoutNavBar - mControlBarDisplayHeight); 1142 Rect voicePlateBounds = new Rect(0, 0, mTotalScreenWidth, 1143 mScreenHeightWithoutNavBar - mControlBarDisplayHeight); 1144 Rect titleBarBounds = new Rect(0, 1145 foregroundTop - mTitleBarHeight, mTotalScreenWidth, foregroundTop); 1146 1147 // Adjust the bounds based on the nav bar. 1148 // TODO: account for the case where nav bar is at the top. 1149 1150 // Populate the bounds depending on where the nav bar is. 1151 if (mNavBarBounds.left == 0 && mNavBarBounds.top == 0) { 1152 // Left nav bar. 1153 backgroundBounds.left = mNavBarBounds.right; 1154 controlBarBounds.left = mNavBarBounds.right; 1155 foregroundBounds.left = mNavBarBounds.right; 1156 titleBarBounds.left = mNavBarBounds.right; 1157 } else if (mNavBarBounds.top == 0) { 1158 // Right nav bar. 1159 backgroundBounds.right = mNavBarBounds.left; 1160 controlBarBounds.right = mNavBarBounds.left; 1161 foregroundBounds.right = mNavBarBounds.left; 1162 titleBarBounds.right = mNavBarBounds.left; 1163 } 1164 1165 mBackgroundApplicationDisplayBounds.set(backgroundBounds); 1166 mControlBarDisplayBounds.set(controlBarBounds); 1167 mForegroundApplicationDisplayBounds.set(foregroundBounds); 1168 mTitleBarDisplayBounds.set(titleBarBounds); 1169 mVoicePlateDisplayBounds.set(voicePlateBounds); 1170 mCarDisplayAreaTouchHandler.setTitleBarBounds(titleBarBounds); 1171 1172 // Set the initial bounds for first and second displays. 1173 updateBounds(); 1174 mIsForegroundDaFullScreen = false; 1175 } 1176 1177 /** Updates the default and background display bounds for the given config. */ updateBounds()1178 private void updateBounds() { 1179 WindowContainerTransaction wct = new WindowContainerTransaction(); 1180 1181 Rect foregroundApplicationDisplayBound = mForegroundApplicationDisplayBounds; 1182 Rect titleBarDisplayBounds = mTitleBarDisplayBounds; 1183 Rect voicePlateDisplayBounds = mVoicePlateDisplayBounds; 1184 Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds; 1185 Rect controlBarDisplayBound = mControlBarDisplayBounds; 1186 1187 WindowContainerToken foregroundDisplayToken = 1188 mForegroundApplicationsDisplay.getDisplayAreaInfo().token; 1189 WindowContainerToken imeRootDisplayToken = 1190 mImeContainerDisplayArea.getDisplayAreaInfo().token; 1191 WindowContainerToken titleBarDisplayToken = 1192 mTitleBarDisplay.getDisplayAreaInfo().token; 1193 WindowContainerToken voicePlateDisplayToken = 1194 mVoicePlateDisplay.getDisplayAreaInfo().token; 1195 WindowContainerToken backgroundDisplayToken = 1196 mBackgroundApplicationDisplay.getDisplayAreaInfo().token; 1197 WindowContainerToken controlBarDisplayToken = 1198 mControlBarDisplay.getDisplayAreaInfo().token; 1199 1200 // Default TDA 1201 int foregroundDisplayWidthDp = 1202 foregroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 1203 / mDpiDensity; 1204 int foregroundDisplayHeightDp = 1205 foregroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 1206 / mDpiDensity; 1207 wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBound); 1208 wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp, 1209 foregroundDisplayHeightDp); 1210 wct.setSmallestScreenWidthDp(foregroundDisplayToken, foregroundDisplayWidthDp); 1211 1212 // Title bar 1213 int titleBarDisplayWidthDp = 1214 titleBarDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT 1215 / mDpiDensity; 1216 int titleBarDisplayHeightDp = 1217 titleBarDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT 1218 / mDpiDensity; 1219 wct.setBounds(titleBarDisplayToken, titleBarDisplayBounds); 1220 wct.setScreenSizeDp(titleBarDisplayToken, titleBarDisplayWidthDp, 1221 titleBarDisplayHeightDp); 1222 wct.setSmallestScreenWidthDp(titleBarDisplayToken, titleBarDisplayWidthDp); 1223 1224 // voice plate 1225 int voicePlateDisplayWidthDp = 1226 voicePlateDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT 1227 / mDpiDensity; 1228 int voicePlateDisplayHeightDp = 1229 voicePlateDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT 1230 / mDpiDensity; 1231 wct.setBounds(voicePlateDisplayToken, voicePlateDisplayBounds); 1232 wct.setScreenSizeDp(voicePlateDisplayToken, voicePlateDisplayWidthDp, 1233 voicePlateDisplayHeightDp); 1234 wct.setSmallestScreenWidthDp(voicePlateDisplayToken, voicePlateDisplayWidthDp); 1235 1236 // background TDA 1237 int backgroundDisplayWidthDp = 1238 backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 1239 / mDpiDensity; 1240 int backgroundDisplayHeightDp = 1241 backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 1242 / mDpiDensity; 1243 wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound); 1244 wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp, 1245 backgroundDisplayHeightDp); 1246 wct.setSmallestScreenWidthDp(backgroundDisplayToken, backgroundDisplayWidthDp); 1247 1248 // Change the bounds of the IME attached to the root display to be same as the background DA 1249 wct.setBounds(imeRootDisplayToken, backgroundApplicationDisplayBound); 1250 wct.setScreenSizeDp(imeRootDisplayToken, backgroundDisplayWidthDp, 1251 backgroundDisplayHeightDp); 1252 wct.setSmallestScreenWidthDp(imeRootDisplayToken, backgroundDisplayWidthDp); 1253 1254 // control bar 1255 int controlBarDisplayWidthDp = 1256 controlBarDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 1257 / mDpiDensity; 1258 int controlBarDisplayHeightDp = 1259 controlBarDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 1260 / mDpiDensity; 1261 wct.setBounds(controlBarDisplayToken, controlBarDisplayBound); 1262 wct.setScreenSizeDp(controlBarDisplayToken, controlBarDisplayWidthDp, 1263 controlBarDisplayHeightDp); 1264 wct.setSmallestScreenWidthDp(controlBarDisplayToken, controlBarDisplayWidthDp); 1265 mSyncQueue.queue(wct); 1266 1267 mSyncQueue.runInSync(t -> { 1268 Rect foregroundApplicationAndTitleBarDisplayBound = new Rect(0, -mTitleBarHeight, 1269 foregroundApplicationDisplayBound.width(), 1270 foregroundApplicationDisplayBound.height()); 1271 t.setCrop(mForegroundApplicationsDisplay.getLeash(), 1272 foregroundApplicationAndTitleBarDisplayBound); 1273 t.setPosition(mForegroundApplicationsDisplay.getLeash(), 1274 foregroundApplicationDisplayBound.left, 1275 foregroundApplicationDisplayBound.top); 1276 1277 t.setWindowCrop(mVoicePlateDisplay.getLeash(), 1278 voicePlateDisplayBounds.width(), voicePlateDisplayBounds.height()); 1279 t.setPosition(mVoicePlateDisplay.getLeash(), 1280 voicePlateDisplayBounds.left, 1281 voicePlateDisplayBounds.top); 1282 1283 t.setWindowCrop(mTitleBarDisplay.getLeash(), 1284 titleBarDisplayBounds.width(), titleBarDisplayBounds.height()); 1285 t.setPosition(mTitleBarDisplay.getLeash(), 1286 titleBarDisplayBounds.left, -mTitleBarHeight); 1287 1288 t.setWindowCrop(mBackgroundApplicationDisplay.getLeash(), 1289 backgroundApplicationDisplayBound.width(), 1290 backgroundApplicationDisplayBound.height()); 1291 t.setPosition(mBackgroundApplicationDisplay.getLeash(), 1292 backgroundApplicationDisplayBound.left, 1293 backgroundApplicationDisplayBound.top); 1294 1295 t.setWindowCrop(mImeContainerDisplayArea.getLeash(), 1296 backgroundApplicationDisplayBound.width(), 1297 backgroundApplicationDisplayBound.height()); 1298 t.setPosition(mImeContainerDisplayArea.getLeash(), 1299 backgroundApplicationDisplayBound.left, 1300 backgroundApplicationDisplayBound.top); 1301 1302 t.setWindowCrop(mControlBarDisplay.getLeash(), 1303 controlBarDisplayBound.width(), controlBarDisplayBound.height()); 1304 t.setPosition(mControlBarDisplay.getLeash(), 1305 controlBarDisplayBound.left, 1306 controlBarDisplayBound.top); 1307 }); 1308 } 1309 1310 /** Bypass the typical fullscreen flow specifically for SUW */ immersiveForSUW(boolean immersive)1311 void immersiveForSUW(boolean immersive) { 1312 if (immersive) { 1313 makeForegroundDAFullScreen(/* setFullPosition= */ true, /* showTitleBar= */ false); 1314 } else { 1315 setDefaultBounds(); 1316 } 1317 mCarUiDisplaySystemBarsController.requestImmersiveModeForSUW( 1318 mApplicationContext.getDisplayId(), immersive); 1319 } 1320 1321 /** 1322 * Update the bounds of foreground DA to cover full screen. 1323 * 1324 * @param setFullPosition whether or not the surface's position should be set to the full 1325 * position. Setting this to true will set the position to the full 1326 * screen while setting to false will use the default display bounds. 1327 */ makeForegroundDAFullScreen(boolean setFullPosition, boolean showTitleBar)1328 void makeForegroundDAFullScreen(boolean setFullPosition, boolean showTitleBar) { 1329 logIfDebuggable("make foregroundDA fullscreen"); 1330 WindowContainerTransaction wct = new WindowContainerTransaction(); 1331 int topBound = showTitleBar ? mTitleBarHeight : 0; 1332 Rect foregroundApplicationDisplayBounds = new Rect(0, topBound, mTotalScreenWidth, 1333 mTotalScreenHeight); 1334 WindowContainerToken foregroundDisplayToken = 1335 mForegroundApplicationsDisplay.getDisplayAreaInfo().token; 1336 1337 int foregroundDisplayWidthDp = 1338 foregroundApplicationDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT 1339 / mDpiDensity; 1340 int foregroundDisplayHeightDp = 1341 foregroundApplicationDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT 1342 / mDpiDensity; 1343 wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBounds); 1344 wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp, 1345 foregroundDisplayHeightDp); 1346 wct.setSmallestScreenWidthDp(foregroundDisplayToken, 1347 Math.min(foregroundDisplayWidthDp, foregroundDisplayHeightDp)); 1348 mSyncQueue.queue(wct); 1349 1350 mSyncQueue.runInSync(t -> { 1351 Rect foregroundApplicationAndTitleBarDisplayBound = new Rect(0, -topBound, 1352 foregroundApplicationDisplayBounds.width(), 1353 foregroundApplicationDisplayBounds.height()); 1354 t.setWindowCrop(mForegroundApplicationsDisplay.getLeash(), 1355 foregroundApplicationAndTitleBarDisplayBound); 1356 if (setFullPosition) { 1357 t.setPosition(mForegroundApplicationsDisplay.getLeash(), 0, 0); 1358 } 1359 }); 1360 1361 mIsForegroundDaFullScreen = true; 1362 } 1363 broadcastForegroundDAVisibilityChange(boolean visible)1364 private void broadcastForegroundDAVisibilityChange(boolean visible) { 1365 Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED); 1366 intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, visible); 1367 LocalBroadcastManager.getInstance(mApplicationContext).sendBroadcast( 1368 intent); 1369 } 1370 updateUserSetupState()1371 private void updateUserSetupState() { 1372 boolean userSetupInProgress = mCarDeviceProvisionedController 1373 .isCurrentUserSetupInProgress(); 1374 if (mIsUserSetupInProgress == userSetupInProgress) { 1375 return; 1376 } 1377 mIsUserSetupInProgress = userSetupInProgress; 1378 if (mIsUserSetupInProgress) { 1379 if (!isForegroundDaVisible()) { 1380 hideTitleBar(); 1381 makeForegroundDaVisible(true); 1382 } 1383 setControlBarVisibility(false); 1384 immersiveForSUW(true); 1385 } else { 1386 makeForegroundDaVisible(false); 1387 immersiveForSUW(false); 1388 showTitleBar(); 1389 setControlBarVisibility(true); 1390 } 1391 } 1392 } 1393