• Home
  • 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.isConnectedDisplayErrorHandlingEnabled()) {
146             if (DEBUG) {
147                 Slog.d(TAG, "ConnectedDisplayErrorHandlingEnabled is not enabled on your device:"
148                                     + " cannot register thermal listener.");
149             }
150             return;
151         }
152 
153         if (!registerThermalServiceListener(new SkinThermalStatusObserver())) {
154             Slog.e(TAG, "Failed to register thermal listener");
155         }
156     }
157 
158     /**
159      * Checks the display type is external, and if it is external then enables/disables it.
160      */
setExternalDisplayEnabledLocked(@onNull final LogicalDisplay logicalDisplay, final boolean enabled)161     void setExternalDisplayEnabledLocked(@NonNull final LogicalDisplay logicalDisplay,
162             final boolean enabled) {
163         if (!isExternalDisplayLocked(logicalDisplay)) {
164             Slog.e(TAG, "setExternalDisplayEnabledLocked called for non external display");
165             return;
166         }
167 
168         if (enabled && !isExternalDisplayAllowed()) {
169             Slog.w(TAG, "setExternalDisplayEnabledLocked: External display can not be enabled"
170                                 + " because it is currently not allowed.");
171             mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
172             return;
173         }
174 
175         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
176     }
177 
178     /**
179      * Upon external display became available check if external displays allowed, this display
180      * is disabled and then sends {@link DisplayManagerGlobal#EVENT_DISPLAY_CONNECTED} to allow
181      * user to decide how to use this display.
182      */
handleExternalDisplayConnectedLocked(@onNull final LogicalDisplay logicalDisplay)183     void handleExternalDisplayConnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
184         if (!isExternalDisplayLocked(logicalDisplay)) {
185             Slog.e(TAG, "handleExternalDisplayConnectedLocked called for non-external display");
186             return;
187         }
188 
189         if (!mIsBootCompleted) {
190             mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
191             return;
192         }
193 
194         mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
195 
196         if (((Build.IS_ENG || Build.IS_USERDEBUG)
197                         && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false))
198                 || (mFlags.isDisplayContentModeManagementEnabled()
199                         && logicalDisplay.canHostTasksLocked())) {
200             Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
201             mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
202             return;
203         } else {
204             // As external display is enabled by default, need to disable it now.
205             // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
206             mLogicalDisplayMapper.setEnabledLocked(logicalDisplay, false);
207         }
208 
209         if (!isExternalDisplayAllowed()) {
210             Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
211                                 + " because it is currently not allowed.");
212             mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
213             return;
214         }
215 
216         mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
217 
218         if (DEBUG) {
219             Slog.d(TAG, "handleExternalDisplayConnectedLocked complete"
220                                 + " displayId=" + logicalDisplay.getDisplayIdLocked());
221         }
222     }
223 
224     /**
225      * Upon external display become unavailable.
226      */
handleLogicalDisplayDisconnectedLocked(@onNull final LogicalDisplay logicalDisplay)227     void handleLogicalDisplayDisconnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
228         // Type of the display here is always UNKNOWN, so we can't verify it is an external display
229 
230         var displayId = logicalDisplay.getDisplayIdLocked();
231         if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
232             return;
233         }
234 
235         mExternalDisplayStatsService.onDisplayDisconnected(displayId);
236     }
237 
238     /**
239      * Upon external display gets added.
240      */
handleLogicalDisplayAddedLocked(@onNull final LogicalDisplay logicalDisplay)241     void handleLogicalDisplayAddedLocked(@NonNull final LogicalDisplay logicalDisplay) {
242         if (!isExternalDisplayLocked(logicalDisplay)) {
243             return;
244         }
245 
246         mExternalDisplayStatsService.onDisplayAdded(logicalDisplay.getDisplayIdLocked());
247     }
248 
249     /**
250      * Upon presentation started.
251      */
onPresentation(int displayId, boolean isShown)252     void onPresentation(int displayId, boolean isShown) {
253         synchronized (mSyncRoot) {
254             var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
255             if (logicalDisplay == null || !isExternalDisplayLocked(logicalDisplay)) {
256                 return;
257             }
258         }
259 
260         if (isShown) {
261             mExternalDisplayStatsService.onPresentationWindowAdded(displayId);
262         } else {
263             mExternalDisplayStatsService.onPresentationWindowRemoved(displayId);
264         }
265     }
266 
267     @GuardedBy("mSyncRoot")
disableExternalDisplayLocked(@onNull final LogicalDisplay logicalDisplay)268     private void disableExternalDisplayLocked(@NonNull final LogicalDisplay logicalDisplay) {
269         if (!isExternalDisplayLocked(logicalDisplay)) {
270             return;
271         }
272 
273         if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
274             if (DEBUG) {
275                 Slog.d(TAG, "disableExternalDisplayLocked shouldn't be called when the"
276                                     + " error handling flag is off");
277             }
278             return;
279         }
280 
281         if (!logicalDisplay.isEnabledLocked()) {
282             if (DEBUG) {
283                 Slog.d(TAG, "disableExternalDisplayLocked is not allowed:"
284                                     + " displayId=" + logicalDisplay.getDisplayIdLocked()
285                                     + " isEnabledLocked=false");
286             }
287             return;
288         }
289 
290         if (!isExternalDisplayAllowed()) {
291             Slog.w(TAG, "External display is currently not allowed and is getting disabled.");
292             mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
293         }
294 
295         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
296 
297         mExternalDisplayStatsService.onDisplayDisabled(logicalDisplay.getDisplayIdLocked());
298 
299         if (DEBUG) {
300             Slog.d(TAG, "disableExternalDisplayLocked complete"
301                                 + " displayId=" + logicalDisplay.getDisplayIdLocked());
302         }
303     }
304 
305     /**
306      * @return whether external displays use is currently allowed.
307      */
308     @VisibleForTesting
isExternalDisplayAllowed()309     boolean isExternalDisplayAllowed() {
310         return mStatus < THROTTLING_CRITICAL;
311     }
312 
registerThermalServiceListener( @onNull final IThermalEventListener.Stub listener)313     private boolean registerThermalServiceListener(
314             @NonNull final IThermalEventListener.Stub listener) {
315         final var thermalService = mInjector.getThermalService();
316         if (thermalService == null) {
317             Slog.w(TAG, "Could not observe thermal status. Service not available");
318             return false;
319         }
320         try {
321             thermalService.registerThermalEventListenerWithType(listener, Temperature.TYPE_SKIN);
322         } catch (RemoteException e) {
323             Slog.e(TAG, "Failed to register thermal status listener", e);
324             return false;
325         }
326         if (DEBUG) {
327             Slog.d(TAG, "registerThermalServiceListener complete.");
328         }
329         return true;
330     }
331 
disableExternalDisplays()332     private void disableExternalDisplays() {
333         synchronized (mSyncRoot) {
334             mLogicalDisplayMapper.forEachLocked(this::disableExternalDisplayLocked);
335         }
336     }
337 
isDisplayReadyForMirroring(int displayId)338     boolean isDisplayReadyForMirroring(int displayId) {
339         if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) {
340             if (DEBUG) {
341                 Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - "
342                         + " flag 'waiting for confirmation before mirroring' is disabled");
343             }
344             return true;
345         }
346 
347         synchronized (mSyncRoot) {
348             if (!mIsBootCompleted) {
349                 if (DEBUG) {
350                     Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
351                             + "boot is in progress");
352                 }
353                 return false;
354             }
355 
356             var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
357             if (logicalDisplay == null) {
358                 if (DEBUG) {
359                     Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
360                             + "logicalDisplay is null");
361                 }
362                 return false;
363             }
364 
365             if (!isExternalDisplayLocked(logicalDisplay)) {
366                 if (DEBUG) {
367                     Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
368                             + "logicalDisplay" + logicalDisplay.getDisplayIdLocked()
369                             + " type is " + logicalDisplay.getDisplayInfoLocked().type);
370                 }
371                 return false;
372             }
373 
374             if (!logicalDisplay.isEnabledLocked()) {
375                 if (DEBUG) {
376                     Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
377                             + "logicalDisplay is disabled");
378                 }
379                 return false;
380             }
381         }
382 
383         return true;
384     }
385 
386     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
387         @Override
notifyThrottling(@onNull final Temperature temp)388         public void notifyThrottling(@NonNull final Temperature temp) {
389             @ThrottlingStatus final int newStatus = temp.getStatus();
390             final var previousStatus = mStatus;
391             mStatus = newStatus;
392             if (THROTTLING_CRITICAL > previousStatus && THROTTLING_CRITICAL <= newStatus) {
393                 disableExternalDisplays();
394             }
395         }
396     }
397 }
398