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