1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.car.users;
18 
19 import static android.car.CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
20 import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS;
21 
22 import static com.android.systemui.car.users.CarSystemUIUserUtil.isCurrentSystemUIDisplay;
23 import static com.android.systemui.car.users.CarSystemUIUserUtil.isMUMDSystemUI;
24 
25 import android.car.Car;
26 import android.car.CarOccupantZoneManager;
27 import android.content.Context;
28 import android.hardware.display.DisplayManager;
29 import android.os.Handler;
30 import android.view.Display;
31 
32 import androidx.annotation.GuardedBy;
33 import androidx.annotation.WorkerThread;
34 
35 import com.android.systemui.car.CarServiceProvider;
36 import com.android.systemui.settings.DisplayTracker;
37 import com.android.systemui.settings.UserTracker;
38 import com.android.systemui.util.Assert;
39 
40 import java.lang.ref.WeakReference;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * Custom {@link DisplayTracker} for CarSystemUI. This class utilizes the
47  * {@link CarOccupantZoneManager} to provide the relevant displays and callbacks for a particular
48  * SystemUI instance running for a particular user.
49  */
50 public class CarDisplayTrackerImpl implements DisplayTracker {
51     private final Context mContext;
52     private final DisplayManager mDisplayManager;
53     private final UserTracker mUserTracker;
54     private final Handler mHandler;
55     private CarOccupantZoneManager mCarOccupantZoneManager;
56     private CarOccupantZoneManager.OccupantZoneInfo mOccupantZone;
57     @GuardedBy("mDisplayCallbacks")
58     private final List<DisplayTrackerCallbackData> mDisplayCallbacks = new ArrayList<>();
59     @GuardedBy("mBrightnessCallbacks")
60     private final List<DisplayTrackerCallbackData> mBrightnessCallbacks = new ArrayList<>();
61 
62     private final CarOccupantZoneManager.OccupantZoneConfigChangeListener mConfigChangeListener =
63             new CarOccupantZoneManager.OccupantZoneConfigChangeListener() {
64                 @Override
65                 public void onOccupantZoneConfigChanged(int changeFlags) {
66                     mOccupantZone = mCarOccupantZoneManager.getOccupantZoneForUser(
67                             mUserTracker.getUserHandle());
68                 }
69             };
70 
71     private final DisplayManager.DisplayListener mDisplayListener =
72             new DisplayManager.DisplayListener() {
73                 @Override
74                 public void onDisplayAdded(int displayId) {
75                     List<DisplayTrackerCallbackData> callbacks;
76                     synchronized (mDisplayCallbacks) {
77                         callbacks = List.copyOf(mDisplayCallbacks);
78                     }
79                     CarDisplayTrackerImpl.this.onDisplayAdded(displayId, callbacks);
80                 }
81 
82                 @Override
83                 public void onDisplayRemoved(int displayId) {
84                     List<DisplayTrackerCallbackData> callbacks;
85                     synchronized (mDisplayCallbacks) {
86                         callbacks = List.copyOf(mDisplayCallbacks);
87                     }
88                     CarDisplayTrackerImpl.this.onDisplayRemoved(displayId, callbacks);
89                 }
90 
91                 @Override
92                 public void onDisplayChanged(int displayId) {
93                     List<DisplayTrackerCallbackData> callbacks;
94                     synchronized (mDisplayCallbacks) {
95                         callbacks = List.copyOf(mDisplayCallbacks);
96                     }
97                     CarDisplayTrackerImpl.this.onDisplayChanged(displayId, callbacks);
98                 }
99             };
100 
101     private final DisplayManager.DisplayListener mBrightnessChangedListener =
102             new DisplayManager.DisplayListener() {
103                 @Override
104                 public void onDisplayAdded(int displayId) {
105                 }
106 
107                 @Override
108                 public void onDisplayRemoved(int displayId) {
109                 }
110 
111                 @Override
112                 public void onDisplayChanged(int displayId) {
113                     List<DisplayTrackerCallbackData> callbacks;
114                     synchronized (mBrightnessCallbacks) {
115                         callbacks = List.copyOf(mBrightnessCallbacks);
116                     }
117                     CarDisplayTrackerImpl.this.onDisplayChanged(displayId, callbacks);
118                 }
119             };
120 
CarDisplayTrackerImpl(Context context, UserTracker userTracker, CarServiceProvider carServiceProvider, Handler backgroundHandler)121     public CarDisplayTrackerImpl(Context context, UserTracker userTracker,
122             CarServiceProvider carServiceProvider, Handler backgroundHandler) {
123         mContext = context;
124         mDisplayManager = mContext.getSystemService(DisplayManager.class);
125         mUserTracker = userTracker;
126         mHandler = backgroundHandler;
127         carServiceProvider.addListener(mCarServiceOnConnectedListener);
128     }
129 
130     @Override
getDefaultDisplayId()131     public int getDefaultDisplayId() {
132         if (!isMUMDSystemUI()) {
133             return Display.DEFAULT_DISPLAY;
134         }
135         if (mOccupantZone != null) {
136             Display display = mCarOccupantZoneManager.getDisplayForOccupant(mOccupantZone,
137                     DISPLAY_TYPE_MAIN);
138             if (display != null) {
139                 return display.getDisplayId();
140             }
141         }
142         return mContext.getDisplayId();
143     }
144 
145     @Override
getAllDisplays()146     public Display[] getAllDisplays() {
147         if (!isMUMDSystemUI()) {
148             return mDisplayManager.getDisplays();
149         }
150         if (mOccupantZone != null) {
151             return mCarOccupantZoneManager.getAllDisplaysForOccupant(mOccupantZone)
152                     .toArray(Display[]::new);
153         }
154         return new Display[]{mDisplayManager.getDisplay(mContext.getDisplayId())};
155     }
156 
157     @Override
getDisplay(int displayId)158     public Display getDisplay(int displayId) {
159         return mDisplayManager.getDisplay(displayId);
160     }
161 
162     @Override
addDisplayChangeCallback(Callback callback, Executor executor)163     public void addDisplayChangeCallback(Callback callback, Executor executor) {
164         synchronized (mDisplayCallbacks) {
165             if (mDisplayCallbacks.isEmpty()) {
166                 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
167             }
168             mDisplayCallbacks.add(new DisplayTrackerCallbackData(callback, executor));
169         }
170     }
171 
172     @Override
addBrightnessChangeCallback(Callback callback, Executor executor)173     public void addBrightnessChangeCallback(Callback callback, Executor executor) {
174         synchronized (mBrightnessCallbacks) {
175             if (mBrightnessCallbacks.isEmpty()) {
176                 mDisplayManager.registerDisplayListener(mBrightnessChangedListener, mHandler,
177                         EVENT_FLAG_DISPLAY_BRIGHTNESS);
178             }
179             mBrightnessCallbacks.add(new DisplayTrackerCallbackData(callback, executor));
180         }
181     }
182 
183     @Override
removeCallback(Callback callback)184     public void removeCallback(Callback callback) {
185         synchronized (mDisplayCallbacks) {
186             boolean changed = mDisplayCallbacks.removeIf(it -> it.sameOrEmpty(callback));
187             if (changed && mDisplayCallbacks.isEmpty()) {
188                 mDisplayManager.unregisterDisplayListener(mDisplayListener);
189             }
190         }
191 
192         synchronized (mBrightnessCallbacks) {
193             boolean changed = mBrightnessCallbacks.removeIf(it -> it.sameOrEmpty(callback));
194             if (changed && mBrightnessCallbacks.isEmpty()) {
195                 mDisplayManager.unregisterDisplayListener(mBrightnessChangedListener);
196             }
197         }
198     }
199 
200     @WorkerThread
onDisplayAdded(int displayId, List<DisplayTrackerCallbackData> callbacks)201     private void onDisplayAdded(int displayId, List<DisplayTrackerCallbackData> callbacks) {
202         Assert.isNotMainThread();
203         if (!shouldExecuteDisplayCallback(displayId)) {
204             return;
205         }
206 
207         callbacks.forEach(it -> {
208             DisplayTracker.Callback callback = it.mCallback.get();
209             if (callback != null) {
210                 it.mExecutor.execute(() -> callback.onDisplayAdded(displayId));
211             }
212         });
213     }
214 
215     @WorkerThread
onDisplayRemoved(int displayId, List<DisplayTrackerCallbackData> callbacks)216     private void onDisplayRemoved(int displayId, List<DisplayTrackerCallbackData> callbacks) {
217         Assert.isNotMainThread();
218         if (!shouldExecuteDisplayCallback(displayId)) {
219             return;
220         }
221 
222         callbacks.forEach(it -> {
223             DisplayTracker.Callback callback = it.mCallback.get();
224             if (callback != null) {
225                 it.mExecutor.execute(() -> callback.onDisplayRemoved(displayId));
226             }
227         });
228     }
229 
230     @WorkerThread
onDisplayChanged(int displayId, List<DisplayTrackerCallbackData> callbacks)231     private void onDisplayChanged(int displayId, List<DisplayTrackerCallbackData> callbacks) {
232         Assert.isNotMainThread();
233         if (!shouldExecuteDisplayCallback(displayId)) {
234             return;
235         }
236 
237         callbacks.forEach(it -> {
238             DisplayTracker.Callback callback = it.mCallback.get();
239             if (callback != null) {
240                 it.mExecutor.execute(() -> callback.onDisplayChanged(displayId));
241             }
242         });
243     }
244 
shouldExecuteDisplayCallback(int displayId)245     private boolean shouldExecuteDisplayCallback(int displayId) {
246         if (!isMUMDSystemUI()) {
247             return true;
248         }
249         return mOccupantZone != null && isCurrentSystemUIDisplay(mCarOccupantZoneManager,
250                 mOccupantZone, displayId);
251     }
252 
253     private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener =
254             new CarServiceProvider.CarServiceOnConnectedListener() {
255                 @Override
256                 public void onConnected(Car car) {
257                     mCarOccupantZoneManager =
258                             (CarOccupantZoneManager) car.getCarManager(
259                                     Car.CAR_OCCUPANT_ZONE_SERVICE);
260                     if (mCarOccupantZoneManager != null) {
261                         mOccupantZone = mCarOccupantZoneManager.getOccupantZoneForUser(
262                                 mUserTracker.getUserHandle());
263                         mCarOccupantZoneManager.registerOccupantZoneConfigChangeListener(
264                                 mConfigChangeListener);
265                     }
266                 }
267             };
268 
269     private static class DisplayTrackerCallbackData {
270         final WeakReference<Callback> mCallback;
271         final Executor mExecutor;
272 
DisplayTrackerCallbackData(Callback callback, Executor executor)273         DisplayTrackerCallbackData(Callback callback, Executor executor) {
274             mCallback = new WeakReference<>(callback);
275             mExecutor = executor;
276         }
277 
sameOrEmpty(DisplayTracker.Callback other)278         boolean sameOrEmpty(DisplayTracker.Callback other) {
279             DisplayTracker.Callback callback = mCallback.get();
280             if (callback == null) {
281                 return true;
282             }
283             return callback.equals(other);
284         }
285     }
286 }
287