• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.server.display;
18 
19 import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
20 import static android.os.Temperature.THROTTLING_CRITICAL;
21 import static android.os.Temperature.THROTTLING_NONE;
22 import static android.view.Display.TYPE_EXTERNAL;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.hardware.display.DisplayManagerGlobal;
27 import android.hardware.display.DisplayManagerGlobal.DisplayEvent;
28 import android.os.Build;
29 import android.os.Handler;
30 import android.os.IThermalEventListener;
31 import android.os.IThermalService;
32 import android.os.RemoteException;
33 import android.os.SystemProperties;
34 import android.os.Temperature;
35 import android.os.Temperature.ThrottlingStatus;
36 import android.util.Slog;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.server.display.DisplayManagerService.SyncRoot;
41 import com.android.server.display.feature.DisplayManagerFlags;
42 import com.android.server.display.notifications.DisplayNotificationManager;
43 import com.android.server.display.utils.DebugUtils;
44 
45 import java.util.HashSet;
46 import java.util.Set;
47 
48 /**
49  * Listens for Skin thermal sensor events, disables external displays if thermal status becomes
50  * equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
51  * status goes below {@link android.os.Temperature#THROTTLING_CRITICAL}.
52  */
53 class ExternalDisplayPolicy {
54     private static final String TAG = "ExternalDisplayPolicy";
55 
56     // To enable these logs, run:
57     // 'adb shell setprop persist.log.tag.ExternalDisplayPolicy DEBUG && adb reboot'
58     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
59 
60     @VisibleForTesting
61     static final String ENABLE_ON_CONNECT = "persist.sys.display.enable_on_connect.external";
62 
isExternalDisplayLocked(@onNull final LogicalDisplay logicalDisplay)63     static boolean isExternalDisplayLocked(@NonNull final LogicalDisplay logicalDisplay) {
64         return logicalDisplay.getDisplayInfoLocked().type == TYPE_EXTERNAL;
65     }
66 
67     /**
68      * Injector interface for {@link ExternalDisplayPolicy}
69      */
70     interface Injector {
sendExternalDisplayEventLocked(@onNull LogicalDisplay display, @DisplayEvent int event)71         void sendExternalDisplayEventLocked(@NonNull LogicalDisplay display,
72                 @DisplayEvent int event);
73 
74         @NonNull
getLogicalDisplayMapper()75         LogicalDisplayMapper getLogicalDisplayMapper();
76 
77         @NonNull
getSyncRoot()78         SyncRoot getSyncRoot();
79 
80         @Nullable
getThermalService()81         IThermalService getThermalService();
82 
83         @NonNull
getFlags()84         DisplayManagerFlags getFlags();
85 
86         @NonNull
getDisplayNotificationManager()87         DisplayNotificationManager getDisplayNotificationManager();
88 
89         @NonNull
getHandler()90         Handler getHandler();
91 
92         @NonNull
getExternalDisplayStatsService()93         ExternalDisplayStatsService getExternalDisplayStatsService();
94     }
95 
96     @NonNull
97     private final Injector mInjector;
98     @NonNull
99     private final LogicalDisplayMapper mLogicalDisplayMapper;
100     @NonNull
101     private final SyncRoot mSyncRoot;
102     @NonNull
103     private final DisplayManagerFlags mFlags;
104     @NonNull
105     private final DisplayNotificationManager mDisplayNotificationManager;
106     @NonNull
107     private final Handler mHandler;
108     @NonNull
109     private final ExternalDisplayStatsService mExternalDisplayStatsService;
110     @ThrottlingStatus
111     private volatile int mStatus = THROTTLING_NONE;
112     //@GuardedBy("mSyncRoot")
113     private boolean mIsBootCompleted;
114     //@GuardedBy("mSyncRoot")
115     private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
116 
ExternalDisplayPolicy(@onNull final Injector injector)117     ExternalDisplayPolicy(@NonNull final Injector injector) {
118         mInjector = injector;
119         mLogicalDisplayMapper = mInjector.getLogicalDisplayMapper();
120         mSyncRoot = mInjector.getSyncRoot();
121         mFlags = mInjector.getFlags();
122         mDisplayNotificationManager = mInjector.getDisplayNotificationManager();
123         mHandler = mInjector.getHandler();
124         mExternalDisplayStatsService = mInjector.getExternalDisplayStatsService();
125     }
126 
127     /**
128      * Starts listening for temperature changes.
129      */
onBootCompleted()130     void onBootCompleted() {
131         synchronized (mSyncRoot) {
132             mIsBootCompleted = true;
133             for (var displayId : mDisplayIdsWaitingForBootCompletion) {
134                 var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
135                 if (logicalDisplay != null) {
136                     handleExternalDisplayConnectedLocked(logicalDisplay);
137                 }
138             }
139             if (!mDisplayIdsWaitingForBootCompletion.isEmpty()) {
140                 mLogicalDisplayMapper.updateLogicalDisplaysLocked();
141             }
142             mDisplayIdsWaitingForBootCompletion.clear();
143         }
144 
145         if (!mFlags.isConnectedDisplayManagementEnabled()) {
146             if (DEBUG) {
147                 Slog.d(TAG, "External display management is not enabled on your device:"
148                                     + " cannot register thermal listener.");
149             }
150             return;
151         }
152 
153         if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
154             if (DEBUG) {
155                 Slog.d(TAG, "ConnectedDisplayErrorHandlingEnabled is not enabled on your device:"
156                                     + " cannot register thermal listener.");
157             }
158             return;
159         }
160 
161         if (!registerThermalServiceListener(new SkinThermalStatusObserver())) {
162             Slog.e(TAG, "Failed to register thermal listener");
163         }
164     }
165 
166     /**
167      * Checks the display type is external, and if it is external then enables/disables it.
168      */
setExternalDisplayEnabledLocked(@onNull final LogicalDisplay logicalDisplay, final boolean enabled)169     void setExternalDisplayEnabledLocked(@NonNull final LogicalDisplay logicalDisplay,
170             final boolean enabled) {
171         if (!isExternalDisplayLocked(logicalDisplay)) {
172             Slog.e(TAG, "setExternalDisplayEnabledLocked called for non external display");
173             return;
174         }
175 
176         if (!mFlags.isConnectedDisplayManagementEnabled()) {
177             if (DEBUG) {
178                 Slog.d(TAG, "setExternalDisplayEnabledLocked: External display management is not"
179                                     + " enabled on your device, cannot enable/disable display.");
180             }
181             return;
182         }
183 
184         if (enabled && !isExternalDisplayAllowed()) {
185             Slog.w(TAG, "setExternalDisplayEnabledLocked: External display can not be enabled"
186                                 + " because it is currently not allowed.");
187             mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
188             return;
189         }
190 
191         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
192     }
193 
194     /**
195      * Upon external display became available check if external displays allowed, this display
196      * is disabled and then sends {@link DisplayManagerGlobal#EVENT_DISPLAY_CONNECTED} to allow
197      * user to decide how to use this display.
198      */
handleExternalDisplayConnectedLocked(@onNull final LogicalDisplay logicalDisplay)199     void handleExternalDisplayConnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
200         if (!isExternalDisplayLocked(logicalDisplay)) {
201             Slog.e(TAG, "handleExternalDisplayConnectedLocked called for non-external display");
202             return;
203         }
204 
205         if (!mFlags.isConnectedDisplayManagementEnabled()) {
206             if (DEBUG) {
207                 Slog.d(TAG, "handleExternalDisplayConnectedLocked connected display management"
208                                     + " flag is off");
209             }
210             return;
211         }
212 
213         if (!mIsBootCompleted) {
214             mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
215             return;
216         }
217 
218         mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
219 
220         if ((Build.IS_ENG || Build.IS_USERDEBUG)
221                 && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
222             Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
223             mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
224             return;
225         } else {
226             // As external display is enabled by default, need to disable it now.
227             // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
228             mLogicalDisplayMapper.setEnabledLocked(logicalDisplay, false);
229         }
230 
231         if (!isExternalDisplayAllowed()) {
232             Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
233                                 + " because it is currently not allowed.");
234             mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
235             return;
236         }
237 
238         mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
239 
240         if (DEBUG) {
241             Slog.d(TAG, "handleExternalDisplayConnectedLocked complete"
242                                 + " displayId=" + logicalDisplay.getDisplayIdLocked());
243         }
244     }
245 
246     /**
247      * Upon external display become unavailable.
248      */
handleLogicalDisplayDisconnectedLocked(@onNull final LogicalDisplay logicalDisplay)249     void handleLogicalDisplayDisconnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
250         // Type of the display here is always UNKNOWN, so we can't verify it is an external display
251 
252         if (!mFlags.isConnectedDisplayManagementEnabled()) {
253             return;
254         }
255 
256         var displayId = logicalDisplay.getDisplayIdLocked();
257         if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
258             return;
259         }
260 
261         mExternalDisplayStatsService.onDisplayDisconnected(displayId);
262     }
263 
264     /**
265      * Upon external display gets added.
266      */
handleLogicalDisplayAddedLocked(@onNull final LogicalDisplay logicalDisplay)267     void handleLogicalDisplayAddedLocked(@NonNull final LogicalDisplay logicalDisplay) {
268         if (!isExternalDisplayLocked(logicalDisplay)) {
269             return;
270         }
271 
272         if (!mFlags.isConnectedDisplayManagementEnabled()) {
273             return;
274         }
275 
276         mExternalDisplayStatsService.onDisplayAdded(logicalDisplay.getDisplayIdLocked());
277     }
278 
279     /**
280      * Upon presentation started.
281      */
onPresentation(int displayId, boolean isShown)282     void onPresentation(int displayId, boolean isShown) {
283         synchronized (mSyncRoot) {
284             var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
285             if (logicalDisplay == null || !isExternalDisplayLocked(logicalDisplay)) {
286                 return;
287             }
288         }
289 
290         if (!mFlags.isConnectedDisplayManagementEnabled()) {
291             return;
292         }
293 
294         if (isShown) {
295             mExternalDisplayStatsService.onPresentationWindowAdded(displayId);
296         } else {
297             mExternalDisplayStatsService.onPresentationWindowRemoved(displayId);
298         }
299     }
300 
301     @GuardedBy("mSyncRoot")
disableExternalDisplayLocked(@onNull final LogicalDisplay logicalDisplay)302     private void disableExternalDisplayLocked(@NonNull final LogicalDisplay logicalDisplay) {
303         if (!isExternalDisplayLocked(logicalDisplay)) {
304             return;
305         }
306 
307         if (!mFlags.isConnectedDisplayManagementEnabled()) {
308             Slog.e(TAG, "disableExternalDisplayLocked shouldn't be called when the"
309                                 + " connected display management flag is off");
310             return;
311         }
312 
313         if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
314             if (DEBUG) {
315                 Slog.d(TAG, "disableExternalDisplayLocked shouldn't be called when the"
316                                     + " error handling flag is off");
317             }
318             return;
319         }
320 
321         if (!logicalDisplay.isEnabledLocked()) {
322             if (DEBUG) {
323                 Slog.d(TAG, "disableExternalDisplayLocked is not allowed:"
324                                     + " displayId=" + logicalDisplay.getDisplayIdLocked()
325                                     + " isEnabledLocked=false");
326             }
327             return;
328         }
329 
330         if (!isExternalDisplayAllowed()) {
331             Slog.w(TAG, "External display is currently not allowed and is getting disabled.");
332             mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
333         }
334 
335         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
336 
337         mExternalDisplayStatsService.onDisplayDisabled(logicalDisplay.getDisplayIdLocked());
338 
339         if (DEBUG) {
340             Slog.d(TAG, "disableExternalDisplayLocked complete"
341                                 + " displayId=" + logicalDisplay.getDisplayIdLocked());
342         }
343     }
344 
345     /**
346      * @return whether external displays use is currently allowed.
347      */
348     @VisibleForTesting
isExternalDisplayAllowed()349     boolean isExternalDisplayAllowed() {
350         return mStatus < THROTTLING_CRITICAL;
351     }
352 
registerThermalServiceListener( @onNull final IThermalEventListener.Stub listener)353     private boolean registerThermalServiceListener(
354             @NonNull final IThermalEventListener.Stub listener) {
355         final var thermalService = mInjector.getThermalService();
356         if (thermalService == null) {
357             Slog.w(TAG, "Could not observe thermal status. Service not available");
358             return false;
359         }
360         try {
361             thermalService.registerThermalEventListenerWithType(listener, Temperature.TYPE_SKIN);
362         } catch (RemoteException e) {
363             Slog.e(TAG, "Failed to register thermal status listener", e);
364             return false;
365         }
366         if (DEBUG) {
367             Slog.d(TAG, "registerThermalServiceListener complete.");
368         }
369         return true;
370     }
371 
disableExternalDisplays()372     private void disableExternalDisplays() {
373         synchronized (mSyncRoot) {
374             mLogicalDisplayMapper.forEachLocked(this::disableExternalDisplayLocked);
375         }
376     }
377 
378     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
379         @Override
notifyThrottling(@onNull final Temperature temp)380         public void notifyThrottling(@NonNull final Temperature temp) {
381             @ThrottlingStatus final int newStatus = temp.getStatus();
382             final var previousStatus = mStatus;
383             mStatus = newStatus;
384             if (THROTTLING_CRITICAL > previousStatus && THROTTLING_CRITICAL <= newStatus) {
385                 disableExternalDisplays();
386             }
387         }
388     }
389 }
390