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