1 /*
2  * Copyright (C) 2021 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.carlauncher.displayarea;
18 
19 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
20 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
21 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
22 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
23 
24 import static com.android.car.carlauncher.AppGridActivity.CAR_LAUNCHER_STATE.CONTROL_BAR;
25 import static com.android.car.carlauncher.AppGridActivity.CAR_LAUNCHER_STATE.DEFAULT;
26 import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.BACKGROUND_TASK_CONTAINER;
27 import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.CONTROL_BAR_DISPLAY_AREA;
28 import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FEATURE_TITLE_BAR;
29 import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE;
30 import static com.android.car.carlauncher.displayarea.CarDisplayAreaOrganizer.FOREGROUND_DISPLAY_AREA_ROOT;
31 import static com.android.car.carlauncher.displayarea.CarFullscreenTaskListener.MAPS;
32 
33 import android.app.ActivityManager;
34 import android.app.ActivityTaskManager;
35 import android.app.TaskStackListener;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.res.Resources;
40 import android.graphics.PixelFormat;
41 import android.graphics.Rect;
42 import android.os.Binder;
43 import android.os.Bundle;
44 import android.os.IBinder;
45 import android.os.RemoteException;
46 import android.os.UserHandle;
47 import android.util.ArraySet;
48 import android.util.DisplayMetrics;
49 import android.view.Gravity;
50 import android.view.IWindowManager;
51 import android.view.LayoutInflater;
52 import android.view.SurfaceControl;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.view.WindowInsets;
56 import android.view.WindowManager;
57 import android.view.WindowManagerGlobal;
58 import android.window.DisplayAreaAppearedInfo;
59 import android.window.WindowContainerToken;
60 import android.window.WindowContainerTransaction;
61 
62 import androidx.annotation.Nullable;
63 
64 import com.android.car.carlauncher.AppGridActivity;
65 import com.android.car.carlauncher.ControlBarActivity;
66 import com.android.car.carlauncher.R;
67 import com.android.internal.app.AssistUtils;
68 import com.android.wm.shell.common.SyncTransactionQueue;
69 
70 import java.util.HashMap;
71 import java.util.List;
72 
73 /**
74  * Controls the bounds of the home background and application displays. This is a singleton class as
75  * there should be one controller used to register and control the DA's
76  */
77 public class CarDisplayAreaController {
78     private static final String TAG = "CarDisplayAreaController";
79     // TODO(b/198783542): Make this configurable.
80     private static final String LOCATION_SETTINGS_ACTIVITY = "LocationSettingsCheckerAutoActivity";
81     private static final String GRANT_PERMISSION_ACTIVITY = "GrantPermissionsActivity";
82     // TODO(b/194334719): Remove when display area logic is moved into systemui
83     private static final String DISPLAY_AREA_VISIBILITY_CHANGED =
84             "com.android.car.carlauncher.displayarea.DISPLAY_AREA_VISIBILITY_CHANGED";
85     private static final String INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE =
86             "EXTRA_IS_DISPLAY_AREA_VISIBLE";
87 
88     // Layer index of how display areas should be placed. Keeping a gap of 100 if we want to
89     // add some other display area layers in between in future.
90     static final int BACKGROUND_LAYER_INDEX = 200;
91     static final int FOREGROUND_LAYER_INDEX = 100;
92     static final int VOICE_PLATE_LAYER_SHOWN_INDEX = 500;
93     static final int CONTROL_BAR_LAYER_INDEX = 0;
94     static final CarDisplayAreaController INSTANCE = new CarDisplayAreaController();
95     private static final int TITLE_BAR_WINDOW_TYPE =
96             WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
97 
98     private final Rect mControlBarDisplayBounds = new Rect();
99     private final Rect mForegroundApplicationDisplayBounds = new Rect();
100     private final Rect mTitleBarDisplayBounds = new Rect();
101     private final Rect mVoicePlateDisplayBounds = new Rect();
102     private final Rect mBackgroundApplicationDisplayBounds = new Rect();
103     private final Rect mNavBarBounds = new Rect();
104     private final IBinder mWindowToken = new Binder();
105 
106     private SyncTransactionQueue mSyncQueue;
107     private CarDisplayAreaOrganizer mOrganizer;
108     private DisplayAreaAppearedInfo mForegroundApplicationsDisplay;
109     private DisplayAreaAppearedInfo mTitleBarDisplay;
110     private DisplayAreaAppearedInfo mVoicePlateDisplay;
111     private DisplayAreaAppearedInfo mBackgroundApplicationDisplay;
112     private DisplayAreaAppearedInfo mControlBarDisplay;
113     private DisplayAreaAppearedInfo mImeContainerDisplayArea;
114     private String mControlBarActivityComponent;
115     private HashMap<String, Boolean> mForegroundDAComponentsVisibilityMap;
116     private ArraySet<ComponentName> mIgnoreOpeningForegroundDAComponentsSet;
117     private int mTitleBarDragThreshold;
118     private int mEnterExitAnimationDurationMs;
119     private int mDpiDensity;
120     private int mTotalScreenWidth = -1;
121     // height of DA hosting the control bar.
122     private int mControlBarDisplayHeight;
123     // height of DA hosting default apps and covering the maps fully.
124     private int mFullDisplayHeight;
125     // height of DA hosting default apps and covering the maps to default height.
126     private int mDefaultDisplayHeight;
127     private int mTitleBarHeight;
128     private int mScreenHeightWithoutNavBar;
129     private int mTotalScreenHeight;
130     private boolean mIsHostingDefaultApplicationDisplayAreaVisible;
131     private CarDisplayAreaTouchHandler mCarDisplayAreaTouchHandler;
132 
133     private WindowManager mTitleBarWindowManager;
134     private View mTitleBarView;
135     private Context mApplicationContext;
136     private int mForegroundDisplayTop;
137     private AssistUtils mAssistUtils;
138     private boolean mIsForegroundDaVisible = false;
139 
140     /**
141      * The WindowContext that is registered with {@link #mTitleBarWindowManager} with options to
142      * specify the {@link RootDisplayArea} to attach the confirmation window.
143      */
144     @Nullable
145     private Context mTitleBarWindowContext;
146     private boolean mIsGridViewVisibleInForegroundDisplayArea;
147     private ComponentName mAssistantVoicePlateActivityName;
148 
149     private final TaskStackListener mOnActivityRestartAttemptListener = new TaskStackListener() {
150         @Override
151         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
152                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
153             updateForegroundDaVisibility(task.topActivity);
154         }
155 
156         @Override
157         public void onTaskCreated(int taskId, ComponentName componentName) {
158             updateForegroundDaVisibility(componentName);
159         }
160     };
161 
162     /**
163      * Gets the instance of {@link CarDisplayAreaController}
164      */
getInstance()165     public static CarDisplayAreaController getInstance() {
166         return INSTANCE;
167     }
168 
169     /**
170      * Initializes the controller
171      */
init(Context applicationContext, SyncTransactionQueue syncQueue, CarDisplayAreaOrganizer organizer, CarDisplayAreaTouchHandler carDisplayAreaTouchHandler)172     public void init(Context applicationContext, SyncTransactionQueue syncQueue,
173             CarDisplayAreaOrganizer organizer,
174             CarDisplayAreaTouchHandler carDisplayAreaTouchHandler) {
175         mApplicationContext = applicationContext;
176         mSyncQueue = syncQueue;
177         mOrganizer = organizer;
178         mTotalScreenHeight = applicationContext.getResources().getDimensionPixelSize(
179                 R.dimen.total_screen_height);
180         mTotalScreenWidth = applicationContext.getResources().getDimensionPixelSize(
181                 R.dimen.total_screen_width);
182         mControlBarDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
183                 R.dimen.control_bar_height);
184         mFullDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
185                 R.dimen.full_app_display_area_height);
186         mDefaultDisplayHeight = applicationContext.getResources().getDimensionPixelSize(
187                 R.dimen.default_app_display_area_height);
188         mCarDisplayAreaTouchHandler = carDisplayAreaTouchHandler;
189         mControlBarActivityComponent = new ComponentName(applicationContext,
190                 ControlBarActivity.class).flattenToShortString();
191         mAssistantVoicePlateActivityName = ComponentName.unflattenFromString(
192                 applicationContext.getResources().getString(
193                         R.string.config_assistantVoicePlateActivity));
194         mAssistUtils = new AssistUtils(applicationContext);
195 
196         // Get bottom nav bar height.
197         Resources resources = applicationContext.getResources();
198         int navBarHeight = resources.getDimensionPixelSize(
199                 com.android.internal.R.dimen.navigation_bar_height);
200         if (navBarHeight > 0) {
201             mNavBarBounds.set(0, mTotalScreenHeight - navBarHeight, mTotalScreenWidth,
202                     mTotalScreenHeight);
203         }
204 
205         // Get left nav bar width.
206         int leftNavBarWidthResId = resources
207                 .getIdentifier("car_left_system_bar_width", "dimen", "android");
208         int leftNavBarWidth = 0;
209         if (leftNavBarWidthResId > 0) {
210             leftNavBarWidth = resources.getDimensionPixelSize(leftNavBarWidthResId);
211             mNavBarBounds.set(0, 0, leftNavBarWidth, mTotalScreenHeight);
212         }
213 
214         // Get right nav bar width.
215         int rightNavBarWidthResId = resources
216                 .getIdentifier("car_right_system_bar_width", "dimen", "android");
217         int rightNavBarWidth = 0;
218         if (rightNavBarWidthResId > 0) {
219             rightNavBarWidth = resources.getDimensionPixelSize(rightNavBarWidthResId);
220             mNavBarBounds.set(mTotalScreenWidth - rightNavBarWidth, 0, mTotalScreenWidth,
221                     mTotalScreenHeight);
222         }
223 
224         mScreenHeightWithoutNavBar = mTotalScreenHeight - mNavBarBounds.height();
225         mTitleBarHeight = resources.getDimensionPixelSize(R.dimen.title_bar_display_area_height);
226         mEnterExitAnimationDurationMs = applicationContext.getResources().getInteger(
227                 R.integer.enter_exit_animation_foreground_display_area_duration_ms);
228         mTitleBarDragThreshold = applicationContext.getResources().getDimensionPixelSize(
229                 R.dimen.title_bar_display_area_touch_drag_threshold);
230         mForegroundDisplayTop = mScreenHeightWithoutNavBar - mDefaultDisplayHeight;
231 
232         mForegroundDAComponentsVisibilityMap = new HashMap<>();
233         for (String component : mApplicationContext.getResources().getStringArray(
234                 R.array.config_foregroundDAComponents)) {
235             mForegroundDAComponentsVisibilityMap.put(component, false);
236         }
237 
238         String[] ignoreOpeningForegroundDACmp = mApplicationContext.getResources().getStringArray(
239                 R.array.config_ignoreOpeningForegroundDA);
240         mIgnoreOpeningForegroundDAComponentsSet = new ArraySet<>();
241         for (String component : ignoreOpeningForegroundDACmp) {
242             ComponentName componentName = ComponentName.unflattenFromString(component);
243             mIgnoreOpeningForegroundDAComponentsSet.add(componentName);
244         }
245     }
246 
CarDisplayAreaController()247     private CarDisplayAreaController() {
248     }
249 
shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo)250     boolean shouldIgnoreOpeningForegroundDA(ActivityManager.RunningTaskInfo taskInfo) {
251         return taskInfo.baseIntent != null && mIgnoreOpeningForegroundDAComponentsSet.contains(
252                 taskInfo.baseIntent.getComponent());
253     }
254 
255     /**
256      * Show the title bar within a targeted display area using the rootDisplayAreaId.
257      */
showTitleBar(int rootDisplayAreaId, Context context)258     public void showTitleBar(int rootDisplayAreaId, Context context) {
259         LayoutInflater inflater = LayoutInflater.from(context);
260         mTitleBarView = inflater
261                 .inflate(R.layout.title_bar_display_area_view, null, true);
262         mTitleBarView.setVisibility(View.VISIBLE);
263 
264         // Show the confirmation.
265         WindowManager.LayoutParams lp = getTitleBarWindowLayoutParams();
266         getWindowManager(rootDisplayAreaId, context).addView(mTitleBarView, lp);
267     }
268 
getWindowManager(int rootDisplayAreaId, Context context)269     private WindowManager getWindowManager(int rootDisplayAreaId, Context context) {
270         Bundle options = getOptionWithRootDisplayArea(rootDisplayAreaId);
271         if (mTitleBarWindowManager == null || mTitleBarWindowContext == null) {
272             // Create window context to specify the RootDisplayArea
273             mTitleBarWindowContext = context.createWindowContext(
274                     TITLE_BAR_WINDOW_TYPE, options);
275             mTitleBarWindowManager = mTitleBarWindowContext.getSystemService(WindowManager.class);
276             return mTitleBarWindowManager;
277         }
278 
279         // Update the window context and window manager to specify the RootDisplayArea
280         IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
281         try {
282             wms.attachWindowContextToDisplayArea(mTitleBarWindowContext.getWindowContextToken(),
283                     TITLE_BAR_WINDOW_TYPE, context.getDisplayId(), options);
284         } catch (RemoteException e) {
285             throw e.rethrowAsRuntimeException();
286         }
287 
288         return mTitleBarWindowManager;
289     }
290 
291     /**
292      * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window.
293      * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}.
294      */
295     @Nullable
getOptionWithRootDisplayArea(int rootDisplayAreaId)296     private static Bundle getOptionWithRootDisplayArea(int rootDisplayAreaId) {
297         // In case we don't care which root display area the window manager is specifying.
298         if (rootDisplayAreaId == FEATURE_UNDEFINED) {
299             return null;
300         }
301 
302         Bundle options = new Bundle();
303         options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
304         return options;
305     }
306 
getTitleBarWindowLayoutParams()307     private WindowManager.LayoutParams getTitleBarWindowLayoutParams() {
308         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
309                 ViewGroup.LayoutParams.MATCH_PARENT,
310                 mTitleBarHeight,
311                 TITLE_BAR_WINDOW_TYPE,
312                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
313                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
314                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
315                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
316                 PixelFormat.TRANSLUCENT);
317         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.statusBars());
318         // Trusted overlay so touches outside the touchable area are allowed to pass through
319         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
320                 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
321         lp.setTitle("TitleBar");
322         lp.gravity = Gravity.TOP;
323         lp.token = mWindowToken;
324         return lp;
325     }
326 
327     /**
328      * Returns if display area hosting default application is visible to user or not.
329      */
isHostingDefaultApplicationDisplayAreaVisible()330     public boolean isHostingDefaultApplicationDisplayAreaVisible() {
331         return mIsHostingDefaultApplicationDisplayAreaVisible;
332     }
333 
isDisplayAreaAnimating()334     boolean isDisplayAreaAnimating() {
335         return mOrganizer != null && mOrganizer.isDisplayAreaAnimating();
336     }
337 
338     /** Registers the DA organizer. */
register()339     public void register() {
340         mDpiDensity = mOrganizer.getDpiDensity();
341 
342         // Register DA organizer.
343         registerOrganizer();
344 
345         // Pre-calculate the foreground and background display bounds for different configs.
346         populateBounds();
347 
348         // Set the initial bounds for first and second displays.
349         WindowContainerTransaction wct = new WindowContainerTransaction();
350         updateBounds(wct);
351         mOrganizer.applyTransaction(wct);
352 
353         mCarDisplayAreaTouchHandler.registerOnClickListener((x, y) -> {
354             // Check if the click is outside the bounds of default display. If so, close the
355             // display area.
356             if (mIsHostingDefaultApplicationDisplayAreaVisible
357                     && y < (mForegroundDisplayTop)) {
358                 // TODO: closing logic goes here, something like: startAnimation(CONTROL_BAR);
359             }
360         });
361 
362         mCarDisplayAreaTouchHandler.registerTouchEventListener(
363                 new CarDisplayAreaTouchHandler.OnDragDisplayAreaListener() {
364 
365                     float mCurrentPos = -1;
366 
367                     @Override
368                     public void onStart(float x, float y) {
369                         mCurrentPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
370                                 - mControlBarDisplayHeight;
371                     }
372 
373                     @Override
374                     public void onMove(float x, float y) {
375                         if (y <= mScreenHeightWithoutNavBar - mDefaultDisplayHeight
376                                 - mControlBarDisplayHeight) {
377                             return;
378                         }
379                         animateToControlBarState((int) mCurrentPos, (int) y, 0);
380                         mCurrentPos = y;
381                     }
382 
383                     @Override
384                     public void onFinish(float x, float y) {
385                         if (y >= mTitleBarDragThreshold) {
386                             animateToControlBarState((int) y,
387                                     mScreenHeightWithoutNavBar + mTitleBarHeight, 0);
388                             mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
389                             // Notify the system bar button in sysui that the display area has
390                             // been swiped closed
391                             Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED);
392                             intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, false);
393                             mApplicationContext.sendBroadcastAsUser(intent, UserHandle.ALL);
394                         } else {
395                             animateToDefaultState((int) y,
396                                     mScreenHeightWithoutNavBar - mDefaultDisplayHeight
397                                             - mControlBarDisplayHeight, 0);
398                         }
399                     }
400                 });
401         mCarDisplayAreaTouchHandler.enable(true);
402 
403         ActivityTaskManager.getInstance().registerTaskStackListener(
404                 mOnActivityRestartAttemptListener);
405     }
406 
407     /**
408      * Return's the associated organizer.
409      */
getOrganizer()410     public CarDisplayAreaOrganizer getOrganizer() {
411         return mOrganizer;
412     }
413 
updateForegroundDaVisibility(ComponentName componentName)414     private void updateForegroundDaVisibility(ComponentName componentName) {
415         if (componentName == null) {
416             return;
417         }
418 
419         String packageName = componentName.getPackageName();
420         boolean isMaps = packageName.contains(MAPS);
421         boolean ignoreOpeningForegroundDA = mIgnoreOpeningForegroundDAComponentsSet.contains(
422                 componentName);
423         // Voice plate will be shown as the top most layer. Also, we don't want to change the
424         // state of the DA's when voice plate is shown.
425         boolean isVoicePlate = componentName.equals(mAssistantVoicePlateActivityName);
426         if (isVoicePlate || isMaps) {
427             return;
428         }
429 
430         // Check is there is an existing session running for assist, cancel it.
431         if (mAssistUtils.isSessionRunning()) {
432             mAssistUtils.hideCurrentSession();
433         }
434 
435         if (isHostingDefaultApplicationDisplayAreaVisible()) {
436             if (mForegroundDAComponentsVisibilityMap.containsKey(
437                     componentName.flattenToShortString())
438                     && mForegroundDAComponentsVisibilityMap.get(
439                     componentName.flattenToShortString())) {
440                 startAnimation(AppGridActivity.CAR_LAUNCHER_STATE.CONTROL_BAR);
441             }
442         } else if (!(ignoreOpeningForegroundDA || componentName.getClassName().contains(
443                 LOCATION_SETTINGS_ACTIVITY)
444                 || componentName.getClassName().contains(GRANT_PERMISSION_ACTIVITY))) {
445             startAnimation(AppGridActivity.CAR_LAUNCHER_STATE.DEFAULT);
446         }
447 
448         if (ignoreOpeningForegroundDA || componentName.flattenToShortString().equals(
449                 mControlBarActivityComponent)) {
450             return;
451         }
452         mForegroundDAComponentsVisibilityMap.replaceAll(
453                 (n, v) -> componentName.flattenToShortString().equals(n));
454     }
455 
showVoicePlateDisplayArea()456     void showVoicePlateDisplayArea() {
457         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
458         tx.show(mVoicePlateDisplay.getLeash());
459     }
460 
resetVoicePlateDisplayArea()461     void resetVoicePlateDisplayArea() {
462         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
463         tx.hide(mVoicePlateDisplay.getLeash());
464     }
465 
466     /** Registers DA organizer. */
registerOrganizer()467     private void registerOrganizer() {
468         List<DisplayAreaAppearedInfo> foregroundDisplayAreaInfos =
469                 mOrganizer.registerOrganizer(FOREGROUND_DISPLAY_AREA_ROOT);
470         if (foregroundDisplayAreaInfos.size() != 1) {
471             throw new IllegalStateException("Can't find display to launch default applications");
472         }
473 
474         List<DisplayAreaAppearedInfo> titleBarDisplayAreaInfo =
475                 mOrganizer.registerOrganizer(FEATURE_TITLE_BAR);
476         if (titleBarDisplayAreaInfo.size() != 1) {
477             throw new IllegalStateException("Can't find display to launch title bar");
478         }
479 
480         List<DisplayAreaAppearedInfo> voicePlateDisplayAreaInfo =
481                 mOrganizer.registerOrganizer(FEATURE_VOICE_PLATE);
482         if (voicePlateDisplayAreaInfo.size() != 1) {
483             throw new IllegalStateException("Can't find display to launch voice plate");
484         }
485 
486         List<DisplayAreaAppearedInfo> backgroundDisplayAreaInfos =
487                 mOrganizer.registerOrganizer(BACKGROUND_TASK_CONTAINER);
488         if (backgroundDisplayAreaInfos.size() != 1) {
489             throw new IllegalStateException("Can't find display to launch activity in background");
490         }
491 
492         List<DisplayAreaAppearedInfo> controlBarDisplayAreaInfos =
493                 mOrganizer.registerOrganizer(CONTROL_BAR_DISPLAY_AREA);
494         if (controlBarDisplayAreaInfos.size() != 1) {
495             throw new IllegalStateException("Can't find display to launch audio control");
496         }
497 
498         // Get the IME display area attached to the root hierarchy.
499         List<DisplayAreaAppearedInfo> imeDisplayAreaInfos =
500                 mOrganizer.registerOrganizer(FEATURE_IME_PLACEHOLDER);
501         for (DisplayAreaAppearedInfo info : imeDisplayAreaInfos) {
502             if (info.getDisplayAreaInfo().rootDisplayAreaId == FEATURE_ROOT) {
503                 mImeContainerDisplayArea = info;
504             }
505         }
506         // As we have only 1 display defined for each display area feature get the 0th index.
507         mForegroundApplicationsDisplay = foregroundDisplayAreaInfos.get(0);
508         mTitleBarDisplay = titleBarDisplayAreaInfo.get(0);
509         mVoicePlateDisplay = voicePlateDisplayAreaInfo.get(0);
510         mBackgroundApplicationDisplay = backgroundDisplayAreaInfos.get(0);
511         mControlBarDisplay = controlBarDisplayAreaInfos.get(0);
512 
513         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
514         // TODO(b/188102153): replace to set mForegroundApplicationsDisplay to top.
515         tx.setLayer(mBackgroundApplicationDisplay.getLeash(), BACKGROUND_LAYER_INDEX);
516         tx.setLayer(mForegroundApplicationsDisplay.getLeash(), FOREGROUND_LAYER_INDEX);
517         tx.setLayer(mTitleBarDisplay.getLeash(), FOREGROUND_LAYER_INDEX);
518         tx.setLayer(mVoicePlateDisplay.getLeash(), VOICE_PLATE_LAYER_SHOWN_INDEX);
519         tx.setLayer(mControlBarDisplay.getLeash(), CONTROL_BAR_LAYER_INDEX);
520 
521         tx.hide(mVoicePlateDisplay.getLeash());
522         tx.hide(mForegroundApplicationsDisplay.getLeash());
523         tx.apply();
524     }
525 
526     /** Un-Registers DA organizer. */
unregister()527     public void unregister() {
528         mOrganizer.resetWindowsOffset();
529         mOrganizer.unregisterOrganizer();
530         mForegroundApplicationsDisplay = null;
531         mTitleBarDisplay = null;
532         mBackgroundApplicationDisplay = null;
533         mControlBarDisplay = null;
534         mVoicePlateDisplay = null;
535         mImeContainerDisplayArea = null;
536         mCarDisplayAreaTouchHandler.enable(false);
537         ActivityTaskManager.getInstance()
538                 .unregisterTaskStackListener(mOnActivityRestartAttemptListener);
539         mTitleBarView.setVisibility(View.GONE);
540     }
541 
542     /**
543      * This method should be called after the registration of DA's are done. The method expects a
544      * target state as an argument, according to which the animations will take place. For example,
545      * if the target state is {@link AppGridActivity.CAR_LAUNCHER_STATE#DEFAULT} then the foreground
546      * DA hosting default applications will animate to the default set height.
547      */
startAnimation(AppGridActivity.CAR_LAUNCHER_STATE toState)548     public void startAnimation(AppGridActivity.CAR_LAUNCHER_STATE toState) {
549         // TODO: currently the animations are only bottom/up. Make it more generic animations here.
550         int fromPos = 0;
551         int toPos = 0;
552 
553         Intent intent = new Intent(DISPLAY_AREA_VISIBILITY_CHANGED);
554         switch (toState) {
555             case CONTROL_BAR:
556                 // Foreground DA closes.
557                 fromPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
558                         - mControlBarDisplayHeight;
559                 toPos = mScreenHeightWithoutNavBar + mTitleBarHeight;
560                 animateToControlBarState(fromPos, toPos, mEnterExitAnimationDurationMs);
561                 mCarDisplayAreaTouchHandler.updateTitleBarVisibility(false);
562                 intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, false);
563                 break;
564             case FULL:
565                 // TODO: Implement this.
566                 break;
567             default:
568                 // Foreground DA opens to default height.
569                 // update the bounds to expand the foreground display area before starting
570                 // animations.
571                 fromPos = mScreenHeightWithoutNavBar + mTitleBarHeight;
572                 toPos = mScreenHeightWithoutNavBar - mDefaultDisplayHeight
573                         - mControlBarDisplayHeight;
574                 intent.putExtra(INTENT_EXTRA_IS_DISPLAY_AREA_VISIBLE, true);
575                 animateToDefaultState(fromPos, toPos, mEnterExitAnimationDurationMs);
576         }
577         mApplicationContext.sendBroadcastAsUser(intent, UserHandle.ALL);
578     }
579 
animateToControlBarState(int fromPos, int toPos, int durationMs)580     private void animateToControlBarState(int fromPos, int toPos, int durationMs) {
581         mBackgroundApplicationDisplayBounds.bottom =
582                 mScreenHeightWithoutNavBar - mControlBarDisplayHeight;
583         animate(fromPos, toPos, CONTROL_BAR, durationMs);
584         mIsHostingDefaultApplicationDisplayAreaVisible = false;
585     }
586 
animateToDefaultState(int fromPos, int toPos, int durationMs)587     private void animateToDefaultState(int fromPos, int toPos, int durationMs) {
588         if (!mIsForegroundDaVisible) {
589             SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
590             tx.show(mForegroundApplicationsDisplay.getLeash());
591             tx.apply(true);
592             mIsForegroundDaVisible = true;
593         }
594         mBackgroundApplicationDisplayBounds.bottom = toPos - mTitleBarHeight;
595         animate(fromPos, toPos, DEFAULT, durationMs);
596         mIsHostingDefaultApplicationDisplayAreaVisible = true;
597         if (mCarDisplayAreaTouchHandler != null) {
598             mCarDisplayAreaTouchHandler.updateTitleBarVisibility(true);
599         }
600     }
601 
animate(int fromPos, int toPos, AppGridActivity.CAR_LAUNCHER_STATE toState, int durationMs)602     private void animate(int fromPos, int toPos, AppGridActivity.CAR_LAUNCHER_STATE toState,
603             int durationMs) {
604         if (mOrganizer != null) {
605             mOrganizer.scheduleOffset(fromPos, toPos, mBackgroundApplicationDisplayBounds,
606                     mBackgroundApplicationDisplay, mForegroundApplicationsDisplay,
607                     mTitleBarDisplay,
608                     mControlBarDisplay, toState, durationMs);
609         }
610     }
611 
612     /** Pre-calculates the default and background display bounds for different configs. */
populateBounds()613     private void populateBounds() {
614         int controlBarTop = mScreenHeightWithoutNavBar - mControlBarDisplayHeight;
615         int foregroundTop =
616                 mScreenHeightWithoutNavBar - mDefaultDisplayHeight - mControlBarDisplayHeight;
617 
618         // Bottom nav bar. Bottom nav bar height will be 0 if the nav bar is present on the sides.
619         Rect backgroundBounds = new Rect(0, 0, mTotalScreenWidth, controlBarTop);
620         Rect controlBarBounds = new Rect(0, controlBarTop, mTotalScreenWidth,
621                 mScreenHeightWithoutNavBar);
622         Rect foregroundBounds = new Rect(0,
623                 foregroundTop, mTotalScreenWidth,
624                 mScreenHeightWithoutNavBar - mControlBarDisplayHeight);
625         Rect voicePlateBounds = new Rect(0, 0, mTotalScreenWidth,
626                 mScreenHeightWithoutNavBar - mControlBarDisplayHeight);
627         Rect titleBarBounds = new Rect(0,
628                 foregroundTop - mTitleBarHeight, mTotalScreenWidth, foregroundTop);
629 
630         // Adjust the bounds based on the nav bar.
631         // TODO: account for the case where nav bar is at the top.
632 
633         // Populate the bounds depending on where the nav bar is.
634         if (mNavBarBounds.left == 0 && mNavBarBounds.top == 0) {
635             // Left nav bar.
636             backgroundBounds.left = mNavBarBounds.right;
637             controlBarBounds.left = mNavBarBounds.right;
638             foregroundBounds.left = mNavBarBounds.right;
639             titleBarBounds.left = mNavBarBounds.right;
640         } else if (mNavBarBounds.top == 0) {
641             // Right nav bar.
642             backgroundBounds.right = mNavBarBounds.left;
643             controlBarBounds.right = mNavBarBounds.left;
644             foregroundBounds.right = mNavBarBounds.left;
645             titleBarBounds.right = mNavBarBounds.left;
646         }
647 
648         mBackgroundApplicationDisplayBounds.set(backgroundBounds);
649         mControlBarDisplayBounds.set(controlBarBounds);
650         mForegroundApplicationDisplayBounds.set(foregroundBounds);
651         mTitleBarDisplayBounds.set(titleBarBounds);
652         mVoicePlateDisplayBounds.set(voicePlateBounds);
653         mCarDisplayAreaTouchHandler.setTitleBarBounds(titleBarBounds);
654     }
655 
656     /** Updates the default and background display bounds for the given config. */
updateBounds(WindowContainerTransaction wct)657     private void updateBounds(WindowContainerTransaction wct) {
658         Rect foregroundApplicationDisplayBound = mForegroundApplicationDisplayBounds;
659         Rect titleBarDisplayBounds = mTitleBarDisplayBounds;
660         Rect voicePlateDisplayBounds = mVoicePlateDisplayBounds;
661         Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds;
662         Rect controlBarDisplayBound = mControlBarDisplayBounds;
663 
664         WindowContainerToken foregroundDisplayToken =
665                 mForegroundApplicationsDisplay.getDisplayAreaInfo().token;
666         WindowContainerToken imeRootDisplayToken =
667                 mImeContainerDisplayArea.getDisplayAreaInfo().token;
668         WindowContainerToken titleBarDisplayToken =
669                 mTitleBarDisplay.getDisplayAreaInfo().token;
670         WindowContainerToken voicePlateDisplayToken =
671                 mVoicePlateDisplay.getDisplayAreaInfo().token;
672         WindowContainerToken backgroundDisplayToken =
673                 mBackgroundApplicationDisplay.getDisplayAreaInfo().token;
674         WindowContainerToken controlBarDisplayToken =
675                 mControlBarDisplay.getDisplayAreaInfo().token;
676 
677         // Default TDA
678         int foregroundDisplayWidthDp =
679                 foregroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
680                         / mDpiDensity;
681         int foregroundDisplayHeightDp =
682                 foregroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
683                         / mDpiDensity;
684         wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBound);
685         wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp,
686                 foregroundDisplayHeightDp);
687         wct.setSmallestScreenWidthDp(foregroundDisplayToken, foregroundDisplayWidthDp);
688 
689         // Title bar
690         int titleBarDisplayWidthDp =
691                 titleBarDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
692                         / mDpiDensity;
693         int titleBarDisplayHeightDp =
694                 titleBarDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
695                         / mDpiDensity;
696         wct.setBounds(titleBarDisplayToken, titleBarDisplayBounds);
697         wct.setScreenSizeDp(titleBarDisplayToken, titleBarDisplayWidthDp,
698                 titleBarDisplayHeightDp);
699         wct.setSmallestScreenWidthDp(titleBarDisplayToken, titleBarDisplayWidthDp);
700 
701         // voice plate
702         int voicePlateDisplayWidthDp =
703                 voicePlateDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
704                         / mDpiDensity;
705         int voicePlateDisplayHeightDp =
706                 voicePlateDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
707                         / mDpiDensity;
708         wct.setBounds(voicePlateDisplayToken, voicePlateDisplayBounds);
709         wct.setScreenSizeDp(voicePlateDisplayToken, voicePlateDisplayWidthDp,
710                 voicePlateDisplayHeightDp);
711         wct.setSmallestScreenWidthDp(voicePlateDisplayToken, voicePlateDisplayWidthDp);
712 
713         // background TDA
714         int backgroundDisplayWidthDp =
715                 backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
716                         / mDpiDensity;
717         int backgroundDisplayHeightDp =
718                 backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
719                         / mDpiDensity;
720         wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound);
721         wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp,
722                 backgroundDisplayHeightDp);
723         wct.setSmallestScreenWidthDp(backgroundDisplayToken, backgroundDisplayWidthDp);
724 
725         // Change the bounds of the IME attached to the root display to be same as the background DA
726         wct.setBounds(imeRootDisplayToken, backgroundApplicationDisplayBound);
727         wct.setScreenSizeDp(imeRootDisplayToken, backgroundDisplayWidthDp,
728                 backgroundDisplayHeightDp);
729         wct.setSmallestScreenWidthDp(imeRootDisplayToken, backgroundDisplayWidthDp);
730 
731         // control bar
732         int controlBarDisplayWidthDp =
733                 controlBarDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
734                         / mDpiDensity;
735         int controlBarDisplayHeightDp =
736                 controlBarDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
737                         / mDpiDensity;
738         wct.setBounds(controlBarDisplayToken, controlBarDisplayBound);
739         wct.setScreenSizeDp(controlBarDisplayToken, controlBarDisplayWidthDp,
740                 controlBarDisplayHeightDp);
741         wct.setSmallestScreenWidthDp(controlBarDisplayToken, controlBarDisplayWidthDp);
742 
743         mSyncQueue.runInSync(t -> {
744             t.setPosition(mForegroundApplicationsDisplay.getLeash(),
745                     foregroundApplicationDisplayBound.left,
746                     foregroundApplicationDisplayBound.top);
747             t.setPosition(mVoicePlateDisplay.getLeash(),
748                     voicePlateDisplayBounds.left,
749                     voicePlateDisplayBounds.top);
750             t.setPosition(mTitleBarDisplay.getLeash(),
751                     titleBarDisplayBounds.left, -mTitleBarHeight);
752             t.setPosition(mBackgroundApplicationDisplay.getLeash(),
753                     backgroundApplicationDisplayBound.left,
754                     backgroundApplicationDisplayBound.top);
755             t.setPosition(mImeContainerDisplayArea.getLeash(),
756                     backgroundApplicationDisplayBound.left,
757                     backgroundApplicationDisplayBound.top);
758             t.setPosition(mControlBarDisplay.getLeash(),
759                     controlBarDisplayBound.left,
760                     controlBarDisplayBound.top);
761         });
762     }
763 
764     /**
765      * Update the bounds of foreground DA to cover full screen.
766      */
makeForegroundDAFullscreen()767     public void makeForegroundDAFullscreen() {
768         WindowContainerTransaction wct = new WindowContainerTransaction();
769         Rect foregroundApplicationDisplayBounds = new Rect(0, 0, mTotalScreenWidth,
770                 mTotalScreenHeight);
771         WindowContainerToken foregroundDisplayToken =
772                 mForegroundApplicationsDisplay.getDisplayAreaInfo().token;
773 
774         int foregroundDisplayWidthDp =
775                 foregroundApplicationDisplayBounds.width() * DisplayMetrics.DENSITY_DEFAULT
776                         / mDpiDensity;
777         int foregroundDisplayHeightDp =
778                 foregroundApplicationDisplayBounds.height() * DisplayMetrics.DENSITY_DEFAULT
779                         / mDpiDensity;
780         wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBounds);
781         wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp,
782                 foregroundDisplayHeightDp);
783         wct.setSmallestScreenWidthDp(foregroundDisplayToken,
784                 Math.min(foregroundDisplayWidthDp, foregroundDisplayHeightDp));
785 
786         mSyncQueue.runInSync(t -> {
787             t.setPosition(mBackgroundApplicationDisplay.getLeash(), 0, 0);
788         });
789 
790         mOrganizer.applyTransaction(wct);
791     }
792 }
793 
794