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.systemui.car.cluster;
18 
19 import static android.car.cluster.ClusterHomeManager.CONFIG_DISPLAY_BOUNDS;
20 import static android.car.cluster.ClusterHomeManager.CONFIG_DISPLAY_ID;
21 
22 import android.car.Car;
23 import android.car.cluster.ClusterHomeManager;
24 import android.car.cluster.ClusterState;
25 import android.content.Context;
26 import android.graphics.Rect;
27 import android.util.Slog;
28 import android.view.Display;
29 import android.view.SurfaceControl;
30 import android.window.DisplayAreaInfo;
31 import android.window.WindowContainerToken;
32 import android.window.WindowContainerTransaction;
33 
34 import com.android.systemui.SystemUI;
35 import com.android.systemui.car.CarServiceProvider;
36 import com.android.systemui.dagger.SysUISingleton;
37 import com.android.systemui.dagger.qualifiers.Main;
38 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
39 
40 import java.util.Optional;
41 import java.util.concurrent.Executor;
42 
43 import javax.inject.Inject;
44 
45 /***
46  * Controls the RootTDA of cluster display per CLUSTER_DISPLAY_STATE message.
47  */
48 @SysUISingleton
49 public class ClusterDisplayController extends SystemUI {
50     private static final String TAG = ClusterDisplayController.class.getSimpleName();
51     private static final boolean DBG = false;
52 
53     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
54     private final CarServiceProvider mCarServiceProvider;
55     private final Executor mMainExecutor;
56 
57     private ClusterHomeManager mClusterHomeManager;
58     private WindowContainerToken mRootTDAToken;
59     private ClusterState mClusterState;
60 
61     @Inject
ClusterDisplayController(Context context, Optional<RootTaskDisplayAreaOrganizer> rootTDAOrganizer, CarServiceProvider carServiceProvider, @Main Executor mainExecutor)62     public ClusterDisplayController(Context context,
63             Optional<RootTaskDisplayAreaOrganizer> rootTDAOrganizer,
64             CarServiceProvider carServiceProvider, @Main Executor mainExecutor) {
65         super(context);
66         mRootTDAOrganizer = rootTDAOrganizer.orElse(null);
67         mCarServiceProvider = carServiceProvider;
68         mMainExecutor = mainExecutor;
69     }
70 
71     @Override
start()72     public void start() {
73         if (mRootTDAOrganizer == null) {
74             Slog.w(TAG, "ClusterDisplayController is disabled because of no "
75                     + "RootTaskDisplayAreaOrganizer");
76             return;
77         }
78         mCarServiceProvider.addListener(mCarServiceOnConnectedListener);
79     }
80 
81     private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener =
82             new CarServiceProvider.CarServiceOnConnectedListener() {
83         @Override
84         public void onConnected(Car car) {
85             mClusterHomeManager = (ClusterHomeManager) car.getCarManager(Car.CLUSTER_HOME_SERVICE);
86             if (mClusterHomeManager == null) {
87                 Slog.w(TAG, "ClusterHomeManager is disabled");
88                 return;
89             }
90             mClusterHomeManager.registerClusterStateListener(mMainExecutor, mClusterStateListener);
91 
92             mClusterState = mClusterHomeManager.getClusterState();
93             if (mClusterState.displayId != Display.INVALID_DISPLAY) {
94                 mRootTDAOrganizer.registerListener(mClusterState.displayId, mRootTDAListener);
95             }
96         }
97     };
98 
99     private final ClusterHomeManager.ClusterStateListener mClusterStateListener =
100             new ClusterHomeManager.ClusterStateListener() {
101         @Override
102         public void onClusterStateChanged(ClusterState state, int changes) {
103             // Need to update mClusterState first, since mClusterState will be referenced during
104             // registerListener() -> onDisplayAreaAppeared() -> resizeTDA().
105             mClusterState = state;
106             if (DBG) {
107                 Slog.d(TAG, "onClusterStateChanged: changes=" + changes
108                         + ", displayId=" + state.displayId);
109             }
110             if ((changes & CONFIG_DISPLAY_ID) != 0) {
111                 if (state.displayId != Display.INVALID_DISPLAY) {
112                     mRootTDAOrganizer.registerListener(state.displayId, mRootTDAListener);
113                 } else {
114                     mRootTDAOrganizer.unregisterListener(mRootTDAListener);
115                 }
116             }
117             if ((changes & CONFIG_DISPLAY_BOUNDS) != 0 && mRootTDAToken != null) {
118                 resizeTDA(mRootTDAToken, state.bounds, state.displayId);
119             }
120         }
121     };
122 
123     private final RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener mRootTDAListener =
124             new RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener() {
125         @Override
126         public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
127             if (DBG) Slog.d(TAG, "onDisplayAreaAppeared: " + displayAreaInfo);
128             if (mClusterState != null && mClusterState.displayId != Display.INVALID_DISPLAY) {
129                 resizeTDA(displayAreaInfo.token, mClusterState.bounds, mClusterState.displayId);
130             }
131             mRootTDAToken = displayAreaInfo.token;
132         }
133 
134         @Override
135         public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
136             if (DBG) Slog.d(TAG, "onDisplayAreaVanished: " + displayAreaInfo);
137             mRootTDAToken = null;
138         }
139 
140         @Override
141         public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
142             if (DBG) Slog.d(TAG, "onDisplayAreaInfoChanged: " + displayAreaInfo);
143             mRootTDAToken = displayAreaInfo.token;
144         }
145     };
146 
resizeTDA(WindowContainerToken token, Rect bounds, int displayId)147     private void resizeTDA(WindowContainerToken token, Rect bounds, int displayId) {
148         if (DBG) {
149             Slog.d(TAG, "resizeTDA: token=" + token + ", bounds=" + bounds
150                     + ", displayId=" + displayId);
151         }
152         WindowContainerTransaction wct = new WindowContainerTransaction();
153         wct.setBounds(token, bounds);
154         wct.setAppBounds(token, bounds);
155         mRootTDAOrganizer.applyTransaction(wct);
156 
157         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
158         mRootTDAOrganizer.setPosition(tx, displayId, bounds.left, bounds.top);
159         tx.apply();
160     }
161 }
162