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