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.car.portraitlauncher.homeactivities; 18 19 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; 20 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 21 22 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE; 23 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_APP_GRID_VISIBILITY_CHANGE; 24 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_COLLAPSE_APPLICATION; 25 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_FG_TASK_VIEW_READY; 26 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE; 27 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_IMMERSIVE_MODE_CHANGE; 28 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_IMMERSIVE_MODE_REQUESTED; 29 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_NOTIFICATIONS_VISIBILITY_CHANGE; 30 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_RECENTS_VISIBILITY_CHANGE; 31 import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_SUW_IN_PROGRESS; 32 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_ACTIVITY_RESTART_ATTEMPT; 33 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_CALM_MODE_STARTED; 34 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_COLLAPSE_MSG; 35 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_DRIVE_STATE_CHANGED; 36 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_HOME_SCREEN_LAYOUT_CHANGED; 37 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_IMMERSIVE_REQUEST; 38 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_MEDIA_INTENT; 39 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_PANEL_READY; 40 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_SUW_STATE_CHANGED; 41 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_TASK_MOVED_TO_FRONT; 42 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.createReason; 43 44 import android.annotation.Nullable; 45 import android.annotation.SuppressLint; 46 import android.app.ActivityManager; 47 import android.app.ActivityOptions; 48 import android.app.ActivityTaskManager; 49 import android.app.IActivityTaskManager; 50 import android.app.TaskInfo; 51 import android.app.TaskStackListener; 52 import android.content.ActivityNotFoundException; 53 import android.content.ComponentName; 54 import android.content.Intent; 55 import android.content.res.Configuration; 56 import android.graphics.Insets; 57 import android.graphics.Rect; 58 import android.graphics.Region; 59 import android.graphics.drawable.Drawable; 60 import android.hardware.input.InputManager; 61 import android.hardware.input.InputManagerGlobal; 62 import android.os.Build; 63 import android.os.Bundle; 64 import android.os.Handler; 65 import android.os.Message; 66 import android.os.RemoteException; 67 import android.os.SystemClock; 68 import android.util.Log; 69 import android.view.KeyCharacterMap; 70 import android.view.KeyEvent; 71 import android.view.SurfaceView; 72 import android.view.View; 73 import android.view.ViewGroup; 74 import android.view.WindowInsets; 75 import android.view.WindowManager; 76 import android.widget.FrameLayout; 77 import android.widget.LinearLayout; 78 79 import androidx.annotation.NonNull; 80 import androidx.core.view.WindowCompat; 81 import androidx.fragment.app.FragmentActivity; 82 import androidx.fragment.app.FragmentTransaction; 83 import androidx.lifecycle.ViewModelProvider; 84 85 import com.android.car.carlauncher.CarLauncher; 86 import com.android.car.carlauncher.CarLauncherUtils; 87 import com.android.car.carlauncher.homescreen.HomeCardModule; 88 import com.android.car.carlauncher.homescreen.audio.IntentHandler; 89 import com.android.car.carlauncher.homescreen.audio.media.MediaIntentRouter; 90 import com.android.car.carlauncher.taskstack.TaskStackChangeListeners; 91 import com.android.car.portraitlauncher.R; 92 import com.android.car.portraitlauncher.calmmode.PortraitCalmModeActivity; 93 import com.android.car.portraitlauncher.common.CarUiPortraitServiceManager; 94 import com.android.car.portraitlauncher.common.UserEventReceiver; 95 import com.android.car.portraitlauncher.panel.TaskViewPanel; 96 import com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason; 97 98 import java.lang.reflect.InvocationTargetException; 99 import java.util.List; 100 import java.util.Set; 101 102 /** 103 * This home screen has maps running in the background hosted in a TaskView. At the bottom, there 104 * is a control bar view that will display information regarding media/dialer. Any other 105 * application other than assistant voice activity will be launched in the rootTaskView that is 106 * displayed in the lower half of the screen. 107 * 108 * —-------------------------------------------------------------------- 109 * | Status Bar | 110 * —-------------------------------------------------------------------- 111 * | | 112 * | | 113 * | | 114 * | Navigation app | 115 * | (BackgroundTask View) | 116 * | | 117 * | | 118 * | | 119 * | | 120 * —-------------------------------------------------------------------- 121 * | | 122 * | | 123 * | | 124 * | App Space (Root Task View Panel) | 125 * | (This layer is above maps) | 126 * | | 127 * | | 128 * | | 129 * | | 130 * —-------------------------------------------------------------------- 131 * | Control Bar | 132 * | | 133 * —-------------------------------------------------------------------- 134 * | Nav Bar | 135 * —-------------------------------------------------------------------- 136 * 137 * In total this Activity has 3 TaskViews. 138 * Background Task view: 139 * - It only contains maps app. 140 * - Maps app is manually started in this taskview. 141 * 142 * Root Task View (Part of Root Task View Panel): 143 * - It acts as the default container. Which means all the apps will run inside it by default. 144 * 145 * Note: Root Task View Panel always overlap over the Background TaskView. 146 * 147 * Fullscreen Task View 148 * - Used for voice assist applications 149 */ 150 public final class CarUiPortraitHomeScreen extends FragmentActivity { 151 public static final String TAG = CarUiPortraitHomeScreen.class.getSimpleName(); 152 153 private static final boolean DBG = Build.IS_DEBUGGABLE; 154 /** Identifiers for panels. */ 155 private static final int APPLICATION = 1; 156 private static final int BACKGROUND = 2; 157 private static final int FULLSCREEN = 3; 158 private static final long IMMERSIVE_MODE_REQUEST_TIMEOUT = 500; 159 private static final String SAVED_BACKGROUND_APP_COMPONENT_NAME = 160 "SAVED_BACKGROUND_APP_COMPONENT_NAME"; 161 private static final IActivityTaskManager sActivityTaskManager = 162 ActivityTaskManager.getService(); 163 private static final int INVALID_TASK_ID = -1; 164 private final UserEventReceiver mUserEventReceiver = new UserEventReceiver(); 165 private final Configuration mConfiguration = new Configuration(); 166 167 private int mStatusBarHeight; 168 private FrameLayout mContainer; 169 private LinearLayout mControlBarView; 170 // All the TaskViews & corresponding helper instance variables. 171 private TaskViewControllerWrapper mTaskViewControllerWrapper; 172 private ViewGroup mBackgroundAppArea; 173 private ViewGroup mFullScreenAppArea; 174 private boolean mIsBackgroundTaskViewReady; 175 private boolean mIsFullScreenTaskViewReady; 176 private int mNavBarHeight; 177 private boolean mIsSUWInProgress; 178 private TaskCategoryManager mTaskCategoryManager; 179 private ActivityManager.RunningTaskInfo mCurrentTaskInRootTaskView; 180 private boolean mIsNotificationCenterOnTop; 181 private boolean mIsRecentsOnTop; 182 private boolean mIsAppGridOnTop; 183 private boolean mIsCalmMode; 184 private TaskInfoCache mTaskInfoCache; 185 private TaskViewPanel mRootTaskViewPanel; 186 private boolean mSkipAppGridOnRestartAttempt; 187 private int mAppGridTaskId = INVALID_TASK_ID; 188 private final IntentHandler mMediaIntentHandler = new IntentHandler() { 189 @Override 190 public void handleIntent(Intent intent) { 191 logIfDebuggable("handleIntent mCurrentTaskInRootTaskView: " + mCurrentTaskInRootTaskView 192 + ", incoming intent =" + intent); 193 if (TaskCategoryManager.isMediaApp(mCurrentTaskInRootTaskView) 194 && mRootTaskViewPanel.isOpen()) { 195 mRootTaskViewPanel.closePanel(createReason(ON_MEDIA_INTENT, intent.getComponent())); 196 return; 197 } 198 199 ActivityOptions options = ActivityOptions.makeBasic(); 200 options.setLaunchDisplayId(getDisplay().getDisplayId()); 201 202 startActivity(intent, options.toBundle()); 203 } 204 }; 205 /** 206 * Only resize the size of rootTaskView when SUW is in progress. This is to resize the height of 207 * rootTaskView after status bar hide on SUW start. 208 */ 209 private final View.OnLayoutChangeListener mHomeScreenLayoutChangeListener = 210 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 211 int newHeight = bottom - top; 212 int oldHeight = oldBottom - oldTop; 213 if (oldHeight == newHeight) { 214 return; 215 } 216 logIfDebuggable("container height change from " + oldHeight + " to " + newHeight); 217 if (mIsSUWInProgress) { 218 mRootTaskViewPanel.openFullScreenPanel(/* animated = */ false, 219 /* showToolBar = */ false, /* bottomAdjustment= */ 0, 220 createReason(ON_HOME_SCREEN_LAYOUT_CHANGED)); 221 } 222 }; 223 private final View.OnLayoutChangeListener mControlBarOnLayoutChangeListener = 224 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 225 if (bottom == oldBottom && top == oldTop) { 226 return; 227 } 228 229 logIfDebuggable("Control Bar layout changed!"); 230 updateTaskViewInsets(); 231 updateBackgroundTaskViewInsets(); 232 updateObscuredTouchRegion(); 233 }; 234 235 private ComponentName mUnhandledImmersiveModeRequestComponent; 236 private long mUnhandledImmersiveModeRequestTimestamp; 237 private boolean mUnhandledImmersiveModeRequest; 238 // This listener lets us know when actives are added and removed from any of the display regions 239 // we care about, so we can trigger the opening and closing of the app containers as needed. 240 private final TaskStackListener mTaskStackListener = new TaskStackListener() { 241 @Override 242 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { 243 logIfDebuggable("On task created, task = " + taskId); 244 if (componentName != null) { 245 logIfDebuggable( 246 "On task created, task = " + taskId + " componentName " + componentName); 247 } 248 if (mTaskCategoryManager.isAppGridActivity(componentName)) { 249 mAppGridTaskId = taskId; 250 } 251 } 252 253 @Override 254 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) 255 throws RemoteException { 256 logIfDebuggable("On task moved to front, task = " + taskInfo.taskId + ", cmp = " 257 + taskInfo.baseActivity + ", isVisible=" + taskInfo.isVisible); 258 if (!mRootTaskViewPanel.isReady()) { 259 logIfDebuggable("Root Task View is not ready yet."); 260 if (!TaskCategoryManager.isHomeIntent(taskInfo) 261 && !mTaskCategoryManager.isBackgroundApp(taskInfo)) { 262 263 cacheTask(taskInfo); 264 } 265 return; 266 } 267 268 TaskViewPanelStateChangeReason reason = createReason(ON_TASK_MOVED_TO_FRONT, 269 taskInfo.taskId, getVisibleActivity(taskInfo)); 270 handleTaskStackChange(taskInfo, reason); 271 } 272 273 /** 274 * Called when a task is removed. 275 * @param taskId id of the task. 276 * @throws RemoteException 277 */ 278 @Override 279 public void onTaskRemoved(int taskId) throws RemoteException { 280 super.onTaskRemoved(taskId); 281 logIfDebuggable("onTaskRemoved taskId=" + taskId); 282 if (mAppGridTaskId == taskId) { 283 Log.e(TAG, "onTaskRemoved, App Grid task is removed."); 284 mAppGridTaskId = INVALID_TASK_ID; 285 } 286 } 287 288 /** 289 * Called whenever IActivityManager.startActivity is called on an activity that is already 290 * running, but the task is either brought to the front or a new Intent is delivered to it. 291 * 292 * @param taskInfo information about the task the activity was relaunched into 293 * @param homeTaskVisible whether or not the home task is visible 294 * @param clearedTask whether or not the launch activity also cleared the task as a part of 295 * starting 296 * @param wasVisible whether the activity was visible before the restart attempt 297 */ 298 @Override 299 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo taskInfo, 300 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) 301 throws RemoteException { 302 super.onActivityRestartAttempt(taskInfo, homeTaskVisible, clearedTask, wasVisible); 303 304 logIfDebuggable("On Activity restart attempt, task = " + taskInfo.taskId + ", cmp =" 305 + taskInfo.baseActivity + " wasVisible = " + wasVisible); 306 307 TaskViewPanelStateChangeReason reason = createReason(ON_ACTIVITY_RESTART_ATTEMPT, 308 taskInfo.taskId, getVisibleActivity(taskInfo)); 309 handleTaskStackChange(taskInfo, reason); 310 } 311 }; 312 handleCalmMode(ActivityManager.RunningTaskInfo taskInfo, @NonNull TaskViewPanelStateChangeReason reason)313 private void handleCalmMode(ActivityManager.RunningTaskInfo taskInfo, 314 @NonNull TaskViewPanelStateChangeReason reason) { 315 if (!ON_TASK_MOVED_TO_FRONT.equals(reason.getReason())) { 316 logIfDebuggable( 317 "Skip handling calm mode since the reason is not " + ON_TASK_MOVED_TO_FRONT); 318 return; 319 } 320 if (mTaskCategoryManager.isFullScreenActivity(taskInfo)) { 321 logIfDebuggable( 322 "Skip handling calm mode if new activity is full screen activity"); 323 return; 324 } 325 326 boolean wasCalmMode = mIsCalmMode; 327 mIsCalmMode = mTaskCategoryManager.isCalmModeActivity(taskInfo); 328 329 if (wasCalmMode && !mIsCalmMode) { 330 exitCalmMode(); 331 } else if (!wasCalmMode && mIsCalmMode) { 332 enterCalmMode(taskInfo); 333 } 334 } 335 exitCalmMode()336 private void exitCalmMode() { 337 logIfDebuggable("Exiting calm mode"); 338 PortraitCalmModeActivity.dismissCalmMode(getApplicationContext()); 339 setControlBarVisibility(/* isVisible = */ true, /* animate = */ true); 340 notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, WindowInsets.Type.systemBars()); 341 } 342 enterCalmMode(ActivityManager.RunningTaskInfo taskInfo)343 private void enterCalmMode(ActivityManager.RunningTaskInfo taskInfo) { 344 logIfDebuggable("Entering calm mode"); 345 if (mRootTaskViewPanel.isVisible()) { 346 mRootTaskViewPanel.closePanel( 347 createReason(ON_CALM_MODE_STARTED, taskInfo.taskId, 348 getVisibleActivity(taskInfo))); 349 } 350 setControlBarVisibility(/* isVisible = */ false, /* animate = */ true); 351 int windowInsetsType = WindowInsets.Type.navigationBars(); 352 notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, windowInsetsType); 353 } 354 handleSystemBarButton(boolean isPanelVisible)355 private void handleSystemBarButton(boolean isPanelVisible) { 356 notifySystemUI(MSG_APP_GRID_VISIBILITY_CHANGE, boolToInt( 357 isPanelVisible && mTaskCategoryManager.isAppGridActivity( 358 mCurrentTaskInRootTaskView))); 359 notifySystemUI(MSG_NOTIFICATIONS_VISIBILITY_CHANGE, boolToInt( 360 isPanelVisible && mTaskCategoryManager.isNotificationActivity( 361 mCurrentTaskInRootTaskView))); 362 notifySystemUI(MSG_RECENTS_VISIBILITY_CHANGE, boolToInt( 363 isPanelVisible && mTaskCategoryManager.isRecentsActivity( 364 mCurrentTaskInRootTaskView))); 365 } 366 handleFullScreenPanel(ActivityManager.RunningTaskInfo taskInfo)367 private void handleFullScreenPanel(ActivityManager.RunningTaskInfo taskInfo) { 368 adjustFullscreenSpacing(mTaskCategoryManager.isFullScreenActivity(taskInfo)); 369 } 370 handleTaskStackChange(ActivityManager.RunningTaskInfo taskInfo, TaskViewPanelStateChangeReason reason)371 private void handleTaskStackChange(ActivityManager.RunningTaskInfo taskInfo, 372 TaskViewPanelStateChangeReason reason) { 373 374 mIsNotificationCenterOnTop = mTaskCategoryManager.isNotificationActivity(taskInfo); 375 mIsRecentsOnTop = mTaskCategoryManager.isRecentsActivity(taskInfo); 376 mIsAppGridOnTop = mTaskCategoryManager.isAppGridActivity(taskInfo); 377 378 if (mTaskCategoryManager.isBackgroundApp(taskInfo)) { 379 mTaskCategoryManager.setCurrentBackgroundApp(taskInfo.baseActivity); 380 } 381 382 handleFullScreenPanel(taskInfo); 383 handleCalmMode(taskInfo, reason); 384 385 if (!shouldUpdateApplicationPanelState(taskInfo)) { 386 return; 387 } 388 389 mCurrentTaskInRootTaskView = taskInfo; 390 391 if (mIsAppGridOnTop && !shouldOpenPanelForAppGrid(reason)) { 392 logIfDebuggable("Panel should not open for app grid, check previous log for details"); 393 return; 394 } 395 396 if (shouldOpenFullScreenPanel(taskInfo)) { 397 mRootTaskViewPanel.openFullScreenPanel(/* animated= */ true, /* showToolBar= */ true, 398 mNavBarHeight, reason); 399 } else { 400 mRootTaskViewPanel.openPanel(reason); 401 } 402 } 403 404 /** 405 * Determine if the Application panel should open for the AppGrid. 406 * 407 * <p> AppGrid is used as the application panel's 408 * 1. background when panel is open, preventing the user from seeing an empty panel. 409 * 2. foreground when panel is closed, putting any ongoing activities within the panel to 410 * onStop state. 411 * 412 * <p> If the reason of panel state change is ON_TASK_MOVED_TO_FRONT, always returns false. 413 * <p> If the reason of panel state change is ON_ACTIVITY_RESTART_ATTEMPT, check 414 * {@link mSkipAppGridOnRestartAttempt}. 415 */ shouldOpenPanelForAppGrid(TaskViewPanelStateChangeReason reason)416 private boolean shouldOpenPanelForAppGrid(TaskViewPanelStateChangeReason reason) { 417 if (ON_TASK_MOVED_TO_FRONT.equals(reason.getReason())) { 418 logIfDebuggable("Skip panel action for app grid in onTaskMovedToFront"); 419 return false; 420 } else if (ON_ACTIVITY_RESTART_ATTEMPT.equals(reason.getReason()) 421 && mSkipAppGridOnRestartAttempt) { 422 logIfDebuggable( 423 "Skip panel action for app grid in onActivityRestartAttempt after manually " 424 + "close the panel"); 425 mSkipAppGridOnRestartAttempt = false; 426 return false; 427 } 428 429 return true; 430 } 431 432 private CarUiPortraitServiceManager mCarUiPortraitServiceManager; 433 private CarUiPortraitDriveStateController mCarUiPortraitDriveStateController; 434 private boolean mReceivedNewIntent; 435 logIfDebuggable(String message)436 private static void logIfDebuggable(String message) { 437 if (DBG) { 438 Log.d(TAG, message); 439 } 440 } 441 442 /** 443 * Send both action down and up to be qualified as a back press. Set time for key events, so 444 * they are not staled. 445 */ sendVirtualBackPress()446 public static void sendVirtualBackPress() { 447 long downEventTime = SystemClock.uptimeMillis(); 448 long upEventTime = downEventTime + 1; 449 450 final KeyEvent keydown = new KeyEvent(downEventTime, downEventTime, KeyEvent.ACTION_DOWN, 451 KeyEvent.KEYCODE_BACK, /* repeat= */ 0, /* metaState= */ 0, 452 KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, KeyEvent.FLAG_FROM_SYSTEM); 453 final KeyEvent keyup = new KeyEvent(upEventTime, upEventTime, KeyEvent.ACTION_UP, 454 KeyEvent.KEYCODE_BACK, /* repeat= */ 0, /* metaState= */ 0, 455 KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, KeyEvent.FLAG_FROM_SYSTEM); 456 457 InputManagerGlobal inputManagerGlobal = InputManagerGlobal.getInstance(); 458 inputManagerGlobal.injectInputEvent(keydown, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 459 inputManagerGlobal.injectInputEvent(keyup, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 460 } 461 boolToInt(Boolean b)462 private static int boolToInt(Boolean b) { 463 return b ? 1 : 0; 464 } 465 intToBool(int val)466 private static boolean intToBool(int val) { 467 return val == 1; 468 } 469 setFocusToBackgroundApp()470 private void setFocusToBackgroundApp() { 471 if (mCurrentTaskInRootTaskView == null) { 472 return; 473 } 474 try { 475 sActivityTaskManager.setFocusedTask(mCurrentTaskInRootTaskView.taskId); 476 } catch (RemoteException e) { 477 Log.w(TAG, "Unable to set focus on background app: ", e); 478 } 479 } 480 shouldOpenFullScreenPanel(ActivityManager.RunningTaskInfo taskInfo)481 private boolean shouldOpenFullScreenPanel(ActivityManager.RunningTaskInfo taskInfo) { 482 return taskInfo.baseActivity != null 483 && taskInfo.baseActivity.equals(mUnhandledImmersiveModeRequestComponent) 484 && mUnhandledImmersiveModeRequest 485 && System.currentTimeMillis() - mUnhandledImmersiveModeRequestTimestamp 486 < IMMERSIVE_MODE_REQUEST_TIMEOUT; 487 } 488 489 @Override onCreate(@ullable Bundle savedInstanceState)490 protected void onCreate(@Nullable Bundle savedInstanceState) { 491 super.onCreate(savedInstanceState); 492 493 if (getApplicationContext().getResources().getConfiguration().orientation 494 == Configuration.ORIENTATION_LANDSCAPE) { 495 Intent launcherIntent = new Intent(this, CarLauncher.class); 496 launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 497 startActivity(launcherIntent); 498 finish(); 499 return; 500 } 501 502 setContentView(R.layout.car_ui_portrait_launcher); 503 504 registerUserEventReceiver(); 505 mTaskCategoryManager = new TaskCategoryManager(getApplicationContext()); 506 if (savedInstanceState != null) { 507 String savedBackgroundAppName = savedInstanceState.getString( 508 SAVED_BACKGROUND_APP_COMPONENT_NAME); 509 if (savedBackgroundAppName != null) { 510 mTaskCategoryManager.setCurrentBackgroundApp( 511 ComponentName.unflattenFromString(savedBackgroundAppName)); 512 } 513 } 514 515 mTaskInfoCache = new TaskInfoCache(getApplicationContext()); 516 517 // Make the window fullscreen as GENERIC_OVERLAYS are supplied to the background task view 518 WindowCompat.setDecorFitsSystemWindows(getWindow(), false); 519 520 // Activity is running fullscreen to allow background task to bleed behind status bar 521 mNavBarHeight = getResources().getDimensionPixelSize( 522 com.android.internal.R.dimen.navigation_bar_height); 523 logIfDebuggable("Navbar height: " + mNavBarHeight); 524 mContainer = findViewById(R.id.container); 525 setHomeScreenBottomPadding(mNavBarHeight); 526 mContainer.addOnLayoutChangeListener(mHomeScreenLayoutChangeListener); 527 528 mRootTaskViewPanel = findViewById(R.id.application_panel); 529 530 mStatusBarHeight = getResources().getDimensionPixelSize( 531 com.android.internal.R.dimen.status_bar_height); 532 533 mControlBarView = findViewById(R.id.control_bar_area); 534 mControlBarView.addOnLayoutChangeListener(mControlBarOnLayoutChangeListener); 535 536 // Setting as trusted overlay to let touches pass through. 537 getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY); 538 // To pass touches to the underneath task. 539 getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); 540 541 mContainer.addOnLayoutChangeListener( 542 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 543 boolean widthChanged = (right - left) != (oldRight - oldLeft); 544 boolean heightChanged = (bottom - top) != (oldBottom - oldTop); 545 546 if (widthChanged || heightChanged) { 547 onContainerDimensionsChanged(); 548 } 549 }); 550 551 // If we happen to be resurfaced into a multi display mode we skip launching content 552 // in the activity view as we will get recreated anyway. 553 if (isInMultiWindowMode() || isInPictureInPictureMode()) { 554 return; 555 } 556 557 mCarUiPortraitServiceManager = new CarUiPortraitServiceManager(/* activity= */ this, 558 new IncomingHandler()); 559 initializeCards(); 560 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 561 mCarUiPortraitDriveStateController = new CarUiPortraitDriveStateController( 562 getApplicationContext()); 563 MediaIntentRouter.getInstance().registerMediaIntentHandler(mMediaIntentHandler); 564 565 if (mTaskViewControllerWrapper == null) { 566 mTaskViewControllerWrapper = new RemoteCarTaskViewControllerWrapperImpl( 567 /* activity= */ this, this::onTaskViewControllerReady); 568 } 569 } 570 onTaskViewControllerReady()571 private void onTaskViewControllerReady() { 572 setUpRootTaskView(); 573 } 574 575 @Override onNewIntent(@onNull Intent intent)576 protected void onNewIntent(@NonNull Intent intent) { 577 super.onNewIntent(intent); 578 mReceivedNewIntent = true; 579 // No state change on application panel. If there is no task in application panel, user will 580 // see a surface view. 581 582 mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND, 583 mTaskCategoryManager.getBackgroundActivitiesList()); 584 mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN, 585 mTaskCategoryManager.getFullScreenActivitiesList()); 586 mTaskViewControllerWrapper.showEmbeddedTasks(new int[]{BACKGROUND, FULLSCREEN}); 587 } 588 registerUserEventReceiver()589 private void registerUserEventReceiver() { 590 UserEventReceiver.Callback callback = new UserEventReceiver.Callback() { 591 @Override 592 public void onUserSwitching() { 593 logIfDebuggable("On user switching"); 594 if (!isFinishing()) { 595 finish(); 596 } 597 } 598 599 @Override 600 public void onUserUnlock() { 601 logIfDebuggable("On user unlock"); 602 initTaskViews(); 603 } 604 }; 605 mUserEventReceiver.register(this, callback); 606 } 607 initializeCards()608 private void initializeCards() { 609 Set<HomeCardModule> homeCardModules = new androidx.collection.ArraySet<>(); 610 for (String providerClassName : getResources().getStringArray( 611 R.array.config_homeCardModuleClasses)) { 612 try { 613 HomeCardModule cardModule = Class.forName(providerClassName).asSubclass( 614 HomeCardModule.class).getDeclaredConstructor().newInstance(); 615 616 cardModule.setViewModelProvider(new ViewModelProvider(/* owner= */ this)); 617 homeCardModules.add(cardModule); 618 } catch (IllegalAccessException | InstantiationException | ClassNotFoundException 619 | InvocationTargetException | NoSuchMethodException e) { 620 Log.w(TAG, "Unable to create HomeCardProvider class " + providerClassName, e); 621 } 622 } 623 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 624 for (HomeCardModule cardModule : homeCardModules) { 625 transaction.replace(cardModule.getCardResId(), cardModule.getCardView().getFragment()); 626 } 627 transaction.commitNow(); 628 } 629 collapseAppPanel()630 private void collapseAppPanel() { 631 logIfDebuggable("On collapse app panel"); 632 mRootTaskViewPanel.closePanel(createReason(ON_COLLAPSE_MSG)); 633 } 634 635 @Override onConfigurationChanged(@onNull Configuration newConfig)636 public void onConfigurationChanged(@NonNull Configuration newConfig) { 637 super.onConfigurationChanged(newConfig); 638 int diff = mConfiguration.updateFrom(newConfig); 639 logIfDebuggable("onConfigurationChanged with diff =" + diff); 640 if ((diff & CONFIG_UI_MODE) == 0) { 641 return; 642 } 643 initializeCards(); 644 Drawable background = getResources().getDrawable(R.drawable.control_bar_background, 645 getTheme()); 646 mControlBarView.setBackground(background); 647 mRootTaskViewPanel.post(() -> mRootTaskViewPanel.refresh(getTheme())); 648 updateBackgroundTaskViewInsets(); 649 } 650 651 @Override onSaveInstanceState(@onNull Bundle outState)652 protected void onSaveInstanceState(@NonNull Bundle outState) { 653 super.onSaveInstanceState(outState); 654 if (mTaskCategoryManager.getCurrentBackgroundApp() == null) { 655 return; 656 } 657 outState.putString(SAVED_BACKGROUND_APP_COMPONENT_NAME, 658 mTaskCategoryManager.getCurrentBackgroundApp().flattenToString()); 659 } 660 661 @Override onDestroy()662 protected void onDestroy() { 663 mTaskViewControllerWrapper.onDestroy(); 664 mRootTaskViewPanel.onDestroy(); 665 mTaskCategoryManager.onDestroy(); 666 mUserEventReceiver.unregister(); 667 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 668 mCarUiPortraitServiceManager.onDestroy(); 669 super.onDestroy(); 670 } 671 672 @Override onResume()673 protected void onResume() { 674 super.onResume(); 675 // the showEmbeddedTasks will make the task visible which will lead to opening of the panel 676 // and that should be skipped for application panel when the home intent is sent. Because 677 // that leads to CTS failures. 678 if (mReceivedNewIntent) { 679 mReceivedNewIntent = false; 680 } else { 681 mTaskViewControllerWrapper.showEmbeddedTasks(new int[]{APPLICATION}); 682 } 683 } 684 685 @Override onPause()686 protected void onPause() { 687 super.onPause(); 688 // This usually happens during user switch, SUW might starts before 689 // SemiControlledCarTaskView get released. So manually clean the 690 // SemiControlledCarTaskView allowlist to avoid null pointers. 691 mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND, List.of()); 692 mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN, List.of()); 693 } 694 695 @Override 696 @SuppressLint("MissingSuperCall") onBackPressed()697 public void onBackPressed() { 698 // ignore back presses 699 } 700 shouldUpdateApplicationPanelState(TaskInfo taskInfo)701 private boolean shouldUpdateApplicationPanelState(TaskInfo taskInfo) { 702 if (mIsSUWInProgress) { 703 logIfDebuggable("Skip application panel state change during suw"); 704 return false; 705 } 706 if (taskInfo.baseIntent.getComponent() == null) { 707 logIfDebuggable("Should not show on application panel since base intent is null"); 708 return false; 709 } 710 711 // Fullscreen activities will open in a separate task view which will show on the top most 712 // z-layer, that should not change the state of the application panel. 713 if (mTaskCategoryManager.isFullScreenActivity(taskInfo)) { 714 logIfDebuggable( 715 "Should not show on application panel since task is full screen activity"); 716 return false; 717 } 718 719 // Background activities will open in a separate task view which will show on the bottom 720 // most z-layer, that should not change the state of the application panel. 721 if (mTaskCategoryManager.isBackgroundApp(taskInfo)) { 722 logIfDebuggable( 723 "Should not show on application panel since task is background activity"); 724 return false; 725 } 726 727 // Should not trigger application panel state change for the task that is not on current 728 // user. 729 // TODO(b/336850810): update logic for ABA if necessary. 730 if (taskInfo.userId != ActivityManager.getCurrentUser()) { 731 logIfDebuggable( 732 "Should not show on application panel since task is not on current user"); 733 return false; 734 } 735 736 // Should not trigger application panel state change for the task that is not allowed. 737 if (mTaskCategoryManager.shouldIgnoreForApplicationPanel(taskInfo)) { 738 logIfDebuggable("Should not show on application panel since task is not on allowed"); 739 return false; 740 } 741 return true; 742 } 743 cacheTask(ActivityManager.RunningTaskInfo taskInfo)744 private void cacheTask(ActivityManager.RunningTaskInfo taskInfo) { 745 logIfDebuggable("Caching the task: " + taskInfo.taskId); 746 if (mTaskInfoCache.cancelTask(taskInfo)) { 747 boolean cached = mTaskInfoCache.cacheTask(taskInfo); 748 logIfDebuggable("Task " + taskInfo.taskId + " is cached = " + cached); 749 } 750 } 751 setControlBarSpacerVisibility(boolean isVisible)752 private void setControlBarSpacerVisibility(boolean isVisible) { 753 if (mControlBarView == null) { 754 return; 755 } 756 FrameLayout.LayoutParams params = 757 (FrameLayout.LayoutParams) mControlBarView.getLayoutParams(); 758 params.setMargins(/* left= */ 0, /* top= */ 0, /* right= */ 0, 759 isVisible ? mNavBarHeight : 0); 760 mControlBarView.requestLayout(); 761 } 762 setHomeScreenBottomPadding(int bottomPadding)763 private void setHomeScreenBottomPadding(int bottomPadding) { 764 // Set padding instead of margin so the bottom area shows background of 765 // car_ui_portrait_launcher during immersive mode without nav bar, and panel states are 766 // calculated correctly. 767 mContainer.setPadding(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottomPadding); 768 } 769 adjustFullscreenSpacing(boolean isFullscreen)770 private void adjustFullscreenSpacing(boolean isFullscreen) { 771 if (mIsSUWInProgress) { 772 logIfDebuggable("don't change spaceing during suw"); 773 return; 774 } 775 logIfDebuggable(isFullscreen 776 ? "Adjusting screen spacing for fullscreen task view" 777 : "Adjusting screen spacing for non-fullscreen task views"); 778 setControlBarSpacerVisibility(isFullscreen); 779 setHomeScreenBottomPadding(isFullscreen ? 0 : mNavBarHeight); 780 // Add offset to background app area to avoid background apps shift for full screen app. 781 mBackgroundAppArea.setPadding(/* left= */ 0, /* top= */ 0, /* right= */ 0, 782 isFullscreen ? mNavBarHeight : 0); 783 } 784 785 // TODO(b/275633095): Add test to verify the region is set correctly in each mode updateObscuredTouchRegion()786 private void updateObscuredTouchRegion() { 787 logIfDebuggable("updateObscuredTouchRegion, mIsSUWInProgress=" + mIsSUWInProgress); 788 Rect controlBarRect = new Rect(); 789 mControlBarView.getBoundsOnScreen(controlBarRect); 790 791 Rect rootPanelGripBarRect = new Rect(); 792 mRootTaskViewPanel.getGripBarBounds(rootPanelGripBarRect); 793 794 // Make the system bar bounds outside of display, so eventually the background and 795 // fullscreen taskviews don't block the SUW 796 int homescreenHeight = getWindow().getDecorView().getHeight(); 797 Rect navigationBarRect = new Rect(controlBarRect.left, /* top= */ 798 mIsSUWInProgress ? homescreenHeight : homescreenHeight - mNavBarHeight, 799 controlBarRect.right, 800 mIsSUWInProgress ? homescreenHeight + mNavBarHeight : homescreenHeight); 801 802 Rect statusBarRect = new Rect(controlBarRect.left, /* top= */ 803 mIsSUWInProgress ? -mStatusBarHeight : 0, 804 controlBarRect.right, 805 mIsSUWInProgress ? 0 : mStatusBarHeight); 806 807 Region obscuredTouchRegion = new Region(); 808 if (!mRootTaskViewPanel.isFullScreen()) { 809 obscuredTouchRegion.union(controlBarRect); 810 } 811 obscuredTouchRegion.union(navigationBarRect); 812 mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, APPLICATION); 813 814 if (!mIsSUWInProgress) { 815 obscuredTouchRegion.union(rootPanelGripBarRect); 816 obscuredTouchRegion.union(statusBarRect); 817 } else if (mRootTaskViewPanel.isVisible()) { 818 obscuredTouchRegion.union(rootPanelGripBarRect); 819 } 820 logIfDebuggable("ObscuredTouchRegion, rootPanelGripBarRect = " 821 + rootPanelGripBarRect + ", navigationBarRect = " 822 + navigationBarRect + ", statusBarRect = " 823 + statusBarRect + ", controlBarRect = " 824 + controlBarRect); 825 826 mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, BACKGROUND); 827 mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, FULLSCREEN); 828 } 829 updateBackgroundTaskViewInsets()830 private void updateBackgroundTaskViewInsets() { 831 if (mBackgroundAppArea == null) { 832 return; 833 } 834 835 int bottomOverlap = mControlBarView.getTop(); 836 if (mRootTaskViewPanel.isVisible()) { 837 bottomOverlap = mRootTaskViewPanel.getTop(); 838 } 839 840 Rect appAreaBounds = new Rect(); 841 mBackgroundAppArea.getBoundsOnScreen(appAreaBounds); 842 843 Rect bottomInsets = new Rect(appAreaBounds.left, bottomOverlap, appAreaBounds.right, 844 appAreaBounds.bottom); 845 Rect topInsets = new Rect(appAreaBounds.left, appAreaBounds.top, appAreaBounds.right, 846 mStatusBarHeight); 847 848 logIfDebuggable("Applying inset to backgroundTaskView bottom: " + bottomInsets + " top: " 849 + topInsets); 850 mTaskViewControllerWrapper.setSystemOverlayInsets(bottomInsets, topInsets, BACKGROUND); 851 } 852 updateFullScreenTaskViewInsets()853 private void updateFullScreenTaskViewInsets() { 854 if (mFullScreenAppArea == null) { 855 return; 856 } 857 858 Rect appAreaBounds = new Rect(); 859 mFullScreenAppArea.getBoundsOnScreen(appAreaBounds); 860 861 Rect bottomInsets = new Rect(appAreaBounds.left, appAreaBounds.height() - mNavBarHeight, 862 appAreaBounds.right, appAreaBounds.bottom); 863 864 Rect topInsets = new Rect(appAreaBounds.left, appAreaBounds.top, appAreaBounds.right, 865 mStatusBarHeight); 866 867 logIfDebuggable("Applying inset to fullScreenTaskView bottom: " + bottomInsets + " top: " 868 + topInsets); 869 mTaskViewControllerWrapper.setSystemOverlayInsets(bottomInsets, topInsets, FULLSCREEN); 870 } 871 updateTaskViewInsets()872 private void updateTaskViewInsets() { 873 int bottom = mIsSUWInProgress ? 0 : mControlBarView.getHeight() + mNavBarHeight; 874 Insets insets = Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom); 875 876 if (mRootTaskViewPanel != null) { 877 mRootTaskViewPanel.setInsets(insets); 878 } 879 mTaskViewControllerWrapper.updateWindowBounds(); 880 } 881 onContainerDimensionsChanged()882 private void onContainerDimensionsChanged() { 883 if (mRootTaskViewPanel != null) { 884 mRootTaskViewPanel.onParentDimensionChanged(); 885 } 886 887 updateTaskViewInsets(); 888 mTaskViewControllerWrapper.updateWindowBounds(); 889 } 890 setUpBackgroundTaskView()891 private void setUpBackgroundTaskView() { 892 mBackgroundAppArea = findViewById(R.id.background_app_area); 893 894 TaskViewControllerWrapper.TaskViewCallback callback = 895 new TaskViewControllerWrapper.TaskViewCallback() { 896 @Override 897 public void onTaskViewCreated(@NonNull SurfaceView taskView) { 898 logIfDebuggable("Background Task View is created"); 899 taskView.setZOrderOnTop(false); 900 mBackgroundAppArea.addView(taskView); 901 mTaskViewControllerWrapper.setTaskView(taskView, BACKGROUND); 902 } 903 904 @Override 905 public void onTaskViewInitialized() { 906 logIfDebuggable("Background Task View is ready"); 907 mIsBackgroundTaskViewReady = true; 908 startBackgroundActivities(); 909 onTaskViewReadinessUpdated(); 910 updateBackgroundTaskViewInsets(); 911 registerOnBackgroundApplicationInstallUninstallListener(); 912 } 913 914 @Override 915 public void onTaskViewReleased() { 916 logIfDebuggable("Background Task View is released"); 917 mTaskViewControllerWrapper.setTaskView(/* taskView= */ null, BACKGROUND); 918 mIsBackgroundTaskViewReady = false; 919 } 920 }; 921 922 mTaskViewControllerWrapper.createCarRootTaskView(callback, getDisplayId(), 923 getMainExecutor(), mTaskCategoryManager.getBackgroundActivitiesList()); 924 } 925 startBackgroundActivities()926 private void startBackgroundActivities() { 927 logIfDebuggable("start background activities"); 928 Intent backgroundIntent = mTaskCategoryManager.getCurrentBackgroundApp() == null 929 ? CarLauncherUtils.getMapsIntent(getApplicationContext()) 930 : (new Intent()).setComponent(mTaskCategoryManager.getCurrentBackgroundApp()); 931 932 Intent failureRecoveryIntent = BackgroundPanelBaseActivity.createIntent( 933 getApplicationContext()); 934 935 Intent[] intents = {failureRecoveryIntent, backgroundIntent}; 936 937 startActivitiesInternal(intents); 938 939 // Set the background app here to avoid recreating 940 // CarUiPortraitHomeScreen in onTaskCreated 941 mTaskCategoryManager.setCurrentBackgroundApp(backgroundIntent.getComponent()); 942 } 943 944 /** Starts given {@code intents} in order. */ startActivitiesInternal(Intent[] intents)945 private void startActivitiesInternal(Intent[] intents) { 946 for (Intent intent : intents) { 947 startActivityInternal(intent); 948 } 949 } 950 951 /** Starts given {@code intent}. */ startActivityInternal(Intent intent)952 private void startActivityInternal(Intent intent) { 953 try { 954 startActivity(intent); 955 } catch (ActivityNotFoundException e) { 956 Log.e(TAG, "Failed to launch", e); 957 } 958 } 959 registerOnBackgroundApplicationInstallUninstallListener()960 private void registerOnBackgroundApplicationInstallUninstallListener() { 961 mTaskCategoryManager.registerOnApplicationInstallUninstallListener( 962 new TaskCategoryManager.OnApplicationInstallUninstallListener() { 963 @Override 964 public void onAppInstalled(String packageName) { 965 mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND, 966 mTaskCategoryManager.getBackgroundActivitiesList()); 967 mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN, 968 mTaskCategoryManager.getFullScreenActivitiesList()); 969 } 970 971 @Override 972 public void onAppUninstall(String packageName) { 973 mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND, 974 mTaskCategoryManager.getBackgroundActivitiesList()); 975 mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN, 976 mTaskCategoryManager.getFullScreenActivitiesList()); 977 } 978 }); 979 } 980 setControlBarVisibility(boolean isVisible, boolean animate)981 private void setControlBarVisibility(boolean isVisible, boolean animate) { 982 float translationY = isVisible ? 0 : mContainer.getHeight() - mControlBarView.getTop(); 983 if (animate) { 984 mControlBarView.animate().translationY(translationY).withEndAction(() -> { 985 // TODO (b/316344351): Investigate why control bar does not re-appear 986 // without requestLayout() 987 mControlBarView.requestLayout(); 988 updateObscuredTouchRegion(); 989 }); 990 } else { 991 mControlBarView.setTranslationY(translationY); 992 updateObscuredTouchRegion(); 993 } 994 } 995 996 isAllTaskViewsReady()997 private boolean isAllTaskViewsReady() { 998 return mRootTaskViewPanel.isReady() 999 && mIsBackgroundTaskViewReady 1000 && mIsFullScreenTaskViewReady; 1001 } 1002 1003 /** 1004 * Initialize non-default taskviews. Proceed after both {@link TaskViewControllerWrapper} and 1005 * {@code 1006 * mRootTaskViewPanel} are ready. 1007 * 1008 * <p>Note: 1. After flashing device and FRX, {@link UserEventReceiver} doesn't receive 1009 * {@link Intent.ACTION_USER_UNLOCKED}, but PackageManager already starts 1010 * resolving intent right after {@code mRootTaskViewPanel} is ready. So initialize 1011 * {@link RemoteCarTaskView}s directly. 2. For device boot later, PackageManager starts to 1012 * resolving intent after {@link Intent.ACTION_USER_UNLOCKED}, so wait 1013 * until {@link UserEventReceiver} notify {@link CarUiPortraitHomeScreen}. 1014 */ initTaskViews()1015 private void initTaskViews() { 1016 if (!mTaskCategoryManager.isReady() || !mRootTaskViewPanel.isReady() 1017 || isAllTaskViewsReady()) { 1018 return; 1019 } 1020 1021 // BackgroundTaskView and FullScreenTaskView are init with activities lists provided by 1022 // mTaskCategoryManager. mTaskCategoryManager needs refresh to get up-to-date activities 1023 // lists. 1024 mTaskCategoryManager.refresh(); 1025 setUpBackgroundTaskView(); 1026 setUpFullScreenTaskView(); 1027 } 1028 onTaskViewReadinessUpdated()1029 private void onTaskViewReadinessUpdated() { 1030 if (!isAllTaskViewsReady()) { 1031 return; 1032 } 1033 logIfDebuggable("All task views are ready"); 1034 updateObscuredTouchRegion(); 1035 updateBackgroundTaskViewInsets(); 1036 notifySystemUI(MSG_FG_TASK_VIEW_READY, boolToInt(true)); 1037 Rect controlBarBounds = new Rect(); 1038 mControlBarView.getBoundsOnScreen(controlBarBounds); 1039 boolean isControlBarVisible = mControlBarView.getVisibility() == View.VISIBLE; 1040 logIfDebuggable("Control bar:" + "( visible: " + isControlBarVisible + ", bounds:" 1041 + controlBarBounds + ")"); 1042 mTaskInfoCache.startCachedTasks(); 1043 } 1044 setUpRootTaskView()1045 private void setUpRootTaskView() { 1046 mRootTaskViewPanel.setTag("RootPanel"); 1047 mRootTaskViewPanel.setOnStateChangeListener(new TaskViewPanel.OnStateChangeListener() { 1048 @Override 1049 public void onStateChangeStart(TaskViewPanel.State oldState, 1050 TaskViewPanel.State newState, boolean animated, 1051 TaskViewPanelStateChangeReason reason) { 1052 boolean isFullScreen = newState.isFullScreen(); 1053 if (!mIsSUWInProgress) { 1054 setControlBarVisibility(!isFullScreen, animated); 1055 notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, 1056 isFullScreen ? WindowInsets.Type.navigationBars() 1057 : WindowInsets.Type.systemBars()); 1058 } 1059 1060 if (newState.isVisible() && newState != oldState) { 1061 mTaskViewControllerWrapper.setWindowBounds( 1062 mRootTaskViewPanel.getTaskViewBounds(newState), APPLICATION); 1063 } 1064 handleSystemBarButton(newState.isVisible()); 1065 } 1066 1067 @Override 1068 public void onStateChangeEnd(TaskViewPanel.State oldState, TaskViewPanel.State newState, 1069 boolean animated, TaskViewPanelStateChangeReason reason) { 1070 updateObscuredTouchRegion(); 1071 // Hide the control bar after the animation if in full screen. 1072 if (newState.isFullScreen()) { 1073 setControlBarVisibility(/* isVisible= */ false, animated); 1074 updateObscuredTouchRegion(); 1075 } else { 1076 // Update the background task view insets to make sure their content is not 1077 // covered with our panels. We only need to do this when we are not in 1078 // fullscreen. 1079 updateBackgroundTaskViewInsets(); 1080 } 1081 1082 mTaskViewControllerWrapper.setWindowBounds( 1083 mRootTaskViewPanel.getTaskViewBounds(newState), APPLICATION); 1084 1085 if (!newState.isVisible()) { 1086 startActivityInternal(CarLauncherUtils.getAppsGridIntent()); 1087 // Ensure the first click on AppGrid button can open the panel. 1088 // When panel is closed for ON_PANEL_READY, the AppGrid get launched for the 1089 // first time, it won't triggers OnRestartAttempt and reset 1090 // mSkipAppGridOnRestartAttempt. 1091 if (!ON_PANEL_READY.equals(reason.getReason())) { 1092 mSkipAppGridOnRestartAttempt = true; 1093 } 1094 } 1095 } 1096 }); 1097 1098 TaskViewControllerWrapper.TaskViewCallback callback = 1099 new TaskViewControllerWrapper.TaskViewCallback() { 1100 @Override 1101 public void onTaskViewCreated(@NonNull SurfaceView taskView) { 1102 logIfDebuggable("Root Task View is created"); 1103 taskView.setZOrderMediaOverlay(true); 1104 mRootTaskViewPanel.setTaskView(taskView); 1105 mRootTaskViewPanel.setToolBarCallback(() -> sendVirtualBackPress()); 1106 mTaskViewControllerWrapper.setTaskView(taskView, APPLICATION); 1107 mTaskViewControllerWrapper.updateWindowBounds(); 1108 } 1109 1110 @Override 1111 public void onTaskViewInitialized() { 1112 logIfDebuggable("Root Task View is ready"); 1113 mRootTaskViewPanel.setReady(true); 1114 onTaskViewReadinessUpdated(); 1115 initTaskViews(); 1116 } 1117 1118 @Override 1119 public void onTaskViewReleased() { 1120 logIfDebuggable("Root Task View is released"); 1121 mTaskViewControllerWrapper.setTaskView(/* taskView= */ null, APPLICATION); 1122 mRootTaskViewPanel.setReady(/* isReady= */ false); 1123 } 1124 }; 1125 1126 mTaskViewControllerWrapper.createCarDefaultRootTaskView(callback, getDisplayId(), 1127 getMainExecutor()); 1128 } 1129 setUpFullScreenTaskView()1130 private void setUpFullScreenTaskView() { 1131 mFullScreenAppArea = findViewById(R.id.fullscreen_container); 1132 TaskViewControllerWrapper.TaskViewCallback callback = 1133 new TaskViewControllerWrapper.TaskViewCallback() { 1134 @Override 1135 public void onTaskViewCreated(@NonNull SurfaceView taskView) { 1136 logIfDebuggable("FullScreen Task View is created"); 1137 taskView.setZOrderOnTop(true); 1138 mFullScreenAppArea.addView(taskView); 1139 mTaskViewControllerWrapper.setTaskView(taskView, FULLSCREEN); 1140 } 1141 1142 @Override 1143 public void onTaskViewInitialized() { 1144 logIfDebuggable("FullScreen Task View is ready"); 1145 mIsFullScreenTaskViewReady = true; 1146 onTaskViewReadinessUpdated(); 1147 updateFullScreenTaskViewInsets(); 1148 } 1149 1150 @Override 1151 public void onTaskViewReleased() { 1152 logIfDebuggable("FullScreen Task View is released"); 1153 mTaskViewControllerWrapper.setTaskView(/* taskView= */ null, FULLSCREEN); 1154 mIsFullScreenTaskViewReady = false; 1155 } 1156 }; 1157 mTaskViewControllerWrapper.createCarRootTaskView(callback, getDisplayId(), 1158 getMainExecutor(), 1159 mTaskCategoryManager.getFullScreenActivitiesList().stream().toList()); 1160 } 1161 onImmersiveModeRequested(boolean requested, ComponentName componentName)1162 private void onImmersiveModeRequested(boolean requested, ComponentName componentName) { 1163 logIfDebuggable("onImmersiveModeRequested = " + requested + " cmp=" + componentName); 1164 if (mIsSUWInProgress) { 1165 logIfDebuggable("skip immersive change during suw"); 1166 return; 1167 } 1168 1169 if (mCarUiPortraitDriveStateController.isDrivingStateMoving() 1170 && mRootTaskViewPanel.isFullScreen()) { 1171 mRootTaskViewPanel.openPanel(createReason(ON_DRIVE_STATE_CHANGED, componentName)); 1172 logIfDebuggable("Quit immersive mode due to drive mode"); 1173 return; 1174 } 1175 1176 // Only handles the immersive mode request here if requesting component has the same package 1177 // name as the current top task. 1178 if (!isPackageVisibleOnApplicationPanel(componentName)) { 1179 // Save the component and timestamp of the latest immersive mode request, in case any 1180 // race condition with TaskStackListener. 1181 setUnhandledImmersiveModeRequest(componentName, System.currentTimeMillis(), requested); 1182 logIfDebuggable("Set unhandle since the task is not at front"); 1183 return; 1184 } 1185 1186 if (requested) { 1187 mRootTaskViewPanel.openFullScreenPanel(/* animated= */ true, /* showToolBar= */ true, 1188 mNavBarHeight, createReason(ON_IMMERSIVE_REQUEST, componentName)); 1189 } else { 1190 mRootTaskViewPanel.openPanelWithIcon(createReason(ON_IMMERSIVE_REQUEST, componentName)); 1191 } 1192 } 1193 isPackageVisibleOnApplicationPanel(ComponentName componentName)1194 private boolean isPackageVisibleOnApplicationPanel(ComponentName componentName) { 1195 TaskInfo taskInfo = mCurrentTaskInRootTaskView; 1196 logIfDebuggable("Top task in launch root task is" + taskInfo); 1197 if (taskInfo == null) { 1198 return false; 1199 } 1200 1201 ComponentName visibleComponentName = getVisibleActivity(taskInfo); 1202 1203 return visibleComponentName != null && componentName.getPackageName().equals( 1204 visibleComponentName.getPackageName()); 1205 } 1206 getVisibleActivity(TaskInfo taskInfo)1207 private ComponentName getVisibleActivity(TaskInfo taskInfo) { 1208 if (taskInfo.topActivity != null) { 1209 return taskInfo.topActivity; 1210 } else if (taskInfo.baseActivity != null) { 1211 return taskInfo.baseActivity; 1212 } else { 1213 return taskInfo.baseIntent.getComponent(); 1214 } 1215 } 1216 setUnhandledImmersiveModeRequest(ComponentName componentName, long timestamp, boolean requested)1217 private void setUnhandledImmersiveModeRequest(ComponentName componentName, long timestamp, 1218 boolean requested) { 1219 mUnhandledImmersiveModeRequestComponent = componentName; 1220 mUnhandledImmersiveModeRequestTimestamp = timestamp; 1221 mUnhandledImmersiveModeRequest = requested; 1222 } 1223 getComponentNameFromBundle(Bundle bundle)1224 private ComponentName getComponentNameFromBundle(Bundle bundle) { 1225 String cmpString = bundle.getString(INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE); 1226 return (cmpString == null) ? null : ComponentName.unflattenFromString(cmpString); 1227 } 1228 notifySystemUI(int key, int value)1229 void notifySystemUI(int key, int value) { 1230 if (mCarUiPortraitServiceManager != null) { 1231 logIfDebuggable("NotifySystemUI, key=" + key + ", value=" + value); 1232 mCarUiPortraitServiceManager.notifySystemUI(key, value); 1233 } 1234 } 1235 1236 /** 1237 * Handler of incoming messages from service. 1238 */ 1239 class IncomingHandler extends Handler { 1240 @Override handleMessage(Message msg)1241 public void handleMessage(Message msg) { 1242 switch (msg.what) { 1243 case MSG_IMMERSIVE_MODE_REQUESTED: 1244 onImmersiveModeRequested(intToBool(msg.arg1), 1245 getComponentNameFromBundle(msg.getData())); 1246 break; 1247 case MSG_SUW_IN_PROGRESS: 1248 mIsSUWInProgress = intToBool(msg.arg1); 1249 logIfDebuggable("Get intent about the SUW is " + mIsSUWInProgress); 1250 if (mIsSUWInProgress) { 1251 mRootTaskViewPanel.openFullScreenPanel(/* animated= */false, 1252 /* showToolBar= */ false, /* bottomAdjustment= */ 0, 1253 createReason(ON_SUW_STATE_CHANGED)); 1254 } else { 1255 mRootTaskViewPanel.closePanel(createReason(ON_SUW_STATE_CHANGED)); 1256 } 1257 break; 1258 case MSG_IMMERSIVE_MODE_CHANGE: 1259 boolean hideNavBar = intToBool(msg.arg1); 1260 mRootTaskViewPanel.setToolBarViewVisibility(hideNavBar); 1261 break; 1262 case MSG_COLLAPSE_APPLICATION: 1263 collapseAppPanel(); 1264 break; 1265 default: 1266 super.handleMessage(msg); 1267 } 1268 } 1269 } 1270 } 1271