summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Oleg Blinnikov <olb@google.com> 2023-10-24 13:56:39 +0000
committer Oleg Blinnikov <olb@google.com> 2023-11-10 10:17:30 +0000
commite846dc00c9455126a8d97fb1a7908b43ab15a2e1 (patch)
treeaa8849891b1835ca68a7c14fe07179d1fff466a7
parent187ed3307e7af9360aaf2b9b60f10989be0ed88c (diff)
External display management on temperature changes
Creates external display policy class. Moves the logic of enable/disable display into this class. Adds skin thermal status listener, which disables external display if the device is too hot, and disallows to enable the display. Change-Id: Iea5090e4246fbc8036b93a56a04b7d46d1264f7f Test: atest ExternalDisplayPolicyTest DisplayManagerServiceTest LogicalDisplayMapperTest Bug: 283461472 Bug: 294014929
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java93
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java277
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java262
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java4
6 files changed, 612 insertions, 35 deletions
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 36fc4aa0524d..8e01762b5e20 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -102,11 +102,11 @@ import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -241,9 +241,6 @@ public final class DisplayManagerService extends SystemService {
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
- @VisibleForTesting
- static final String ENABLE_ON_CONNECT =
- "persist.sys.display.enable_on_connect.external";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
// This value needs to be in sync with the threshold
// in RefreshRateConfigs::getFrameRateDivisor.
@@ -266,6 +263,7 @@ public final class DisplayManagerService extends SystemService {
private final DisplayManagerHandler mHandler;
private final Handler mUiHandler;
private final DisplayModeDirector mDisplayModeDirector;
+ private final ExternalDisplayPolicy mExternalDisplayPolicy;
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
@@ -598,6 +596,7 @@ public final class DisplayManagerService extends SystemService {
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
+ mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
}
public void setupSchedulerPolicies() {
@@ -667,6 +666,7 @@ public final class DisplayManagerService extends SystemService {
mDisplayModeDirector.onBootCompleted();
mLogicalDisplayMapper.onBootCompleted();
mDisplayNotificationManager.onBootCompleted();
+ mExternalDisplayPolicy.onBootCompleted();
}
}
@@ -1947,17 +1947,12 @@ public final class DisplayManagerService extends SystemService {
}
setupLogicalDisplay(display);
- // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
- if (display.getDisplayInfoLocked().type == Display.TYPE_EXTERNAL) {
- if ((Build.IS_ENG || Build.IS_USERDEBUG)
- && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
- Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
- } else {
- display.setEnabledLocked(false);
- }
- }
- sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED);
+ if (ExternalDisplayPolicy.isExternalDisplay(display)) {
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(display);
+ } else {
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED);
+ }
updateLogicalDisplayState(display);
}
@@ -3248,7 +3243,14 @@ public final class DisplayManagerService extends SystemService {
void enableConnectedDisplay(int displayId, boolean enabled) {
synchronized (mSyncRoot) {
- mLogicalDisplayMapper.setDisplayEnabledLocked(displayId, enabled);
+ final var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "enableConnectedDisplay: Can not find displayId=" + displayId);
+ } else if (ExternalDisplayPolicy.isExternalDisplay(logicalDisplay)) {
+ mExternalDisplayPolicy.setExternalDisplayEnabledLocked(logicalDisplay, enabled);
+ } else {
+ mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
+ }
}
}
@@ -4470,22 +4472,12 @@ public final class DisplayManagerService extends SystemService {
@EnforcePermission(MANAGE_DISPLAYS)
public void enableConnectedDisplay(int displayId) {
enableConnectedDisplay_enforcePermission();
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- Slog.w(TAG, "External display management is not enabled on your device: "
- + "cannot enable display.");
- return;
- }
DisplayManagerService.this.enableConnectedDisplay(displayId, true);
}
@EnforcePermission(MANAGE_DISPLAYS)
public void disableConnectedDisplay(int displayId) {
disableConnectedDisplay_enforcePermission();
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- Slog.w(TAG, "External display management is not enabled on your device: "
- + "cannot disable display.");
- return;
- }
DisplayManagerService.this.enableConnectedDisplay(displayId, false);
}
}
@@ -5043,4 +5035,55 @@ public final class DisplayManagerService extends SystemService {
*/
long uptimeMillis();
}
+
+ /**
+ * Implements necessary functionality for {@link ExternalDisplayPolicy}
+ */
+ private class ExternalDisplayPolicyInjector implements ExternalDisplayPolicy.Injector {
+ /**
+ * Sends event for the display.
+ */
+ @Override
+ public void sendExternalDisplayEventLocked(@NonNull final LogicalDisplay display,
+ @DisplayEvent int event) {
+ sendDisplayEventLocked(display, event);
+ }
+
+ /**
+ * Gets thermal service
+ */
+ @Override
+ @Nullable
+ public IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(ServiceManager.getService(
+ Context.THERMAL_SERVICE));
+ }
+
+ /**
+ * @return display manager flags.
+ */
+ @Override
+ @NonNull
+ public DisplayManagerFlags getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * @return Logical display mapper.
+ */
+ @Override
+ @NonNull
+ public LogicalDisplayMapper getLogicalDisplayMapper() {
+ return mLogicalDisplayMapper;
+ }
+
+ /**
+ * @return Sync root, for synchronization on this object across display manager.
+ */
+ @Override
+ @NonNull
+ public SyncRoot getSyncRoot() {
+ return mSyncRoot;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
new file mode 100644
index 000000000000..23ce311250d7
--- /dev/null
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
+import static android.os.Temperature.THROTTLING_CRITICAL;
+import static android.os.Temperature.THROTTLING_NONE;
+import static android.view.Display.TYPE_EXTERNAL;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerGlobal.DisplayEvent;
+import android.os.Build;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.Temperature.ThrottlingStatus;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.utils.DebugUtils;
+
+/**
+ * Listens for Skin thermal sensor events, disables external displays if thermal status becomes
+ * equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
+ * status goes below {@link android.os.Temperature#THROTTLING_CRITICAL}.
+ */
+class ExternalDisplayPolicy {
+ private static final String TAG = "ExternalDisplayPolicy";
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.ExternalDisplayPolicy DEBUG && adb reboot'
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+ @VisibleForTesting
+ static final String ENABLE_ON_CONNECT = "persist.sys.display.enable_on_connect.external";
+
+ static boolean isExternalDisplay(@NonNull final LogicalDisplay logicalDisplay) {
+ return logicalDisplay.getDisplayInfoLocked().type == TYPE_EXTERNAL;
+ }
+
+ /**
+ * Injector interface for {@link ExternalDisplayPolicy}
+ */
+ interface Injector {
+ void sendExternalDisplayEventLocked(@NonNull LogicalDisplay display,
+ @DisplayEvent int event);
+
+ @NonNull
+ LogicalDisplayMapper getLogicalDisplayMapper();
+
+ @NonNull
+ SyncRoot getSyncRoot();
+
+ @Nullable
+ IThermalService getThermalService();
+
+ @NonNull
+ DisplayManagerFlags getFlags();
+ }
+
+ @NonNull
+ private final Injector mInjector;
+ @NonNull
+ private final LogicalDisplayMapper mLogicalDisplayMapper;
+ @NonNull
+ private final SyncRoot mSyncRoot;
+ @NonNull
+ private final DisplayManagerFlags mFlags;
+ @ThrottlingStatus
+ private volatile int mStatus = THROTTLING_NONE;
+
+ ExternalDisplayPolicy(@NonNull final Injector injector) {
+ mInjector = injector;
+ mLogicalDisplayMapper = mInjector.getLogicalDisplayMapper();
+ mSyncRoot = mInjector.getSyncRoot();
+ mFlags = mInjector.getFlags();
+ }
+
+ /**
+ * Starts listening for temperature changes.
+ */
+ void onBootCompleted() {
+ if (!mFlags.isConnectedDisplayManagementEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "External display management is not enabled on your device:"
+ + " cannot register thermal listener.");
+ }
+ return;
+ }
+
+ if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "ConnectedDisplayErrorHandlingEnabled is not enabled on your device:"
+ + " cannot register thermal listener.");
+ }
+ return;
+ }
+
+ if (!registerThermalServiceListener(new SkinThermalStatusObserver())) {
+ Slog.e(TAG, "Failed to register thermal listener");
+ }
+ }
+
+ /**
+ * Checks the display type is external, and if it is external then enables/disables it.
+ */
+ void setExternalDisplayEnabledLocked(@NonNull final LogicalDisplay logicalDisplay,
+ final boolean enabled) {
+ if (!isExternalDisplay(logicalDisplay)) {
+ Slog.e(TAG, "setExternalDisplayEnabledLocked called for non external display");
+ return;
+ }
+
+ if (!mFlags.isConnectedDisplayManagementEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "setExternalDisplayEnabledLocked: External display management is not"
+ + " enabled on your device, cannot enable/disable display.");
+ }
+ return;
+ }
+
+ if (enabled && !isExternalDisplayAllowed()) {
+ Slog.w(TAG, "setExternalDisplayEnabledLocked: External display can not be enabled"
+ + " because it is currently not allowed.");
+ return;
+ }
+
+ mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
+ }
+
+ /**
+ * Upon external display became available check if external displays allowed, this display
+ * is disabled and then sends {@link DisplayManagerGlobal#EVENT_DISPLAY_CONNECTED} to allow
+ * user to decide how to use this display.
+ */
+ void handleExternalDisplayConnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
+ if (!isExternalDisplay(logicalDisplay)) {
+ Slog.e(TAG, "handleExternalDisplayConnectedLocked called for non-external display");
+ return;
+ }
+
+ if (!mFlags.isConnectedDisplayManagementEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "handleExternalDisplayConnectedLocked connected display management"
+ + " flag is off");
+ }
+ return;
+ }
+
+ if ((Build.IS_ENG || Build.IS_USERDEBUG)
+ && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
+ Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
+ mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
+ return;
+ } else {
+ // As external display is enabled by default, need to disable it now.
+ // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
+ logicalDisplay.setEnabledLocked(false);
+ }
+
+ if (!isExternalDisplayAllowed()) {
+ Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
+ + " because it is currently not allowed.");
+ return;
+ }
+
+ mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
+
+ if (DEBUG) {
+ Slog.d(TAG, "handleExternalDisplayConnectedLocked complete"
+ + " displayId=" + logicalDisplay.getDisplayIdLocked());
+ }
+ }
+
+ @GuardedBy("mSyncRoot")
+ private void disableExternalDisplayLocked(@NonNull final LogicalDisplay logicalDisplay) {
+ if (!isExternalDisplay(logicalDisplay)) {
+ return;
+ }
+
+ if (!mFlags.isConnectedDisplayManagementEnabled()) {
+ Slog.e(TAG, "disableExternalDisplayLocked shouldn't be called when the"
+ + " connected display management flag is off");
+ return;
+ }
+
+ if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "disableExternalDisplayLocked shouldn't be called when the"
+ + " error handling flag is off");
+ }
+ return;
+ }
+
+ if (!logicalDisplay.isEnabledLocked()) {
+ if (DEBUG) {
+ Slog.d(TAG, "disableExternalDisplayLocked is not allowed:"
+ + " displayId=" + logicalDisplay.getDisplayIdLocked()
+ + " isEnabledLocked=false");
+ }
+ return;
+ }
+
+ mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
+
+ if (DEBUG) {
+ Slog.d(TAG, "disableExternalDisplayLocked complete"
+ + " displayId=" + logicalDisplay.getDisplayIdLocked());
+ }
+ }
+
+ /**
+ * @return whether external displays use is currently allowed.
+ */
+ @VisibleForTesting
+ boolean isExternalDisplayAllowed() {
+ return mStatus < THROTTLING_CRITICAL;
+ }
+
+ private boolean registerThermalServiceListener(
+ @NonNull final IThermalEventListener.Stub listener) {
+ final var thermalService = mInjector.getThermalService();
+ if (thermalService == null) {
+ Slog.w(TAG, "Could not observe thermal status. Service not available");
+ return false;
+ }
+ try {
+ thermalService.registerThermalEventListenerWithType(listener, Temperature.TYPE_SKIN);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ return false;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "registerThermalServiceListener complete.");
+ }
+ return true;
+ }
+
+ private void disableExternalDisplays() {
+ synchronized (mSyncRoot) {
+ mLogicalDisplayMapper.forEachLocked(this::disableExternalDisplayLocked);
+ }
+ }
+
+ private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+ @Override
+ public void notifyThrottling(@NonNull final Temperature temp) {
+ @ThrottlingStatus final int newStatus = temp.getStatus();
+ final var previousStatus = mStatus;
+ mStatus = newStatus;
+ if (THROTTLING_CRITICAL > previousStatus && THROTTLING_CRITICAL <= newStatus) {
+ disableExternalDisplays();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index f3425d2e2136..115111a4afa3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1255,16 +1255,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return null;
}
- void setDisplayEnabledLocked(int displayId, boolean enabled) {
- LogicalDisplay display = getDisplayLocked(displayId);
- if (display == null) {
- Slog.w(TAG, "Cannot find display " + displayId);
- return;
- }
+ void setDisplayEnabledLocked(@NonNull LogicalDisplay display, boolean enabled) {
boolean isEnabled = display.isEnabledLocked();
if (isEnabled == enabled) {
Slog.w(TAG, "Display is already " + (isEnabled ? "enabled" : "disabled") + ": "
- + displayId);
+ + display.getDisplayIdLocked());
return;
}
setEnabledLocked(display, enabled);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 303525814435..9684f427adb3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -29,7 +29,7 @@ import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.server.display.DisplayManagerService.ENABLE_ON_CONNECT;
+import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
new file mode 100644
index 000000000000..7300db22cc27
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
+import static android.view.Display.TYPE_EXTERNAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * Tests for {@link ExternalDisplayPolicy}
+ * Run: atest ExternalDisplayPolicyTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ExternalDisplayPolicyTest {
+ private static final int EXTERNAL_DISPLAY_ID = 1;
+ private static final Temperature MODERATE_TEMPERATURE = new Temperature(/*value=*/ 40.5f,
+ /*type=*/ Temperature.TYPE_SKIN,
+ /*name=*/ "Test",
+ /*status=*/ Temperature.THROTTLING_MODERATE);
+ private static final Temperature SEVERE_TEMPERATURE = new Temperature(/*value=*/ 50.5f,
+ /*type=*/ Temperature.TYPE_SKIN,
+ /*name=*/ "Test",
+ /*status=*/ Temperature.THROTTLING_SEVERE);
+ private static final Temperature CRITICAL_TEMPERATURE = new Temperature(/*value=*/ 70.5f,
+ /*type=*/ Temperature.TYPE_SKIN,
+ /*name=*/ "Test",
+ /*status=*/ Temperature.THROTTLING_CRITICAL);
+ private static final Temperature EMERGENCY_TEMPERATURE = new Temperature(/*value=*/ 80.5f,
+ /*type=*/ Temperature.TYPE_SKIN,
+ /*name=*/ "Test",
+ /*status=*/ Temperature.THROTTLING_EMERGENCY);
+ @Mock
+ private ExternalDisplayPolicy.Injector mMockedInjector;
+ @Mock
+ private DisplayManagerFlags mMockedFlags;
+ @Mock
+ private LogicalDisplayMapper mMockedLogicalDisplayMapper;
+ @Mock
+ private IThermalService mMockedThermalService;
+ @Mock
+ private SyncRoot mMockedSyncRoot;
+ @Mock
+ private LogicalDisplay mMockedLogicalDisplay;
+ @Captor
+ private ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mThermalEventTypeCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<LogicalDisplay>> mLogicalDisplayConsumerCaptor;
+ @Captor
+ private ArgumentCaptor<Boolean> mIsEnabledCaptor;
+ @Captor
+ private ArgumentCaptor<LogicalDisplay> mLogicalDisplayCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mDisplayEventCaptor;
+ private ExternalDisplayPolicy mExternalDisplayPolicy;
+
+ /** Setup tests. */
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(true);
+ when(mMockedInjector.getFlags()).thenReturn(mMockedFlags);
+ when(mMockedInjector.getLogicalDisplayMapper()).thenReturn(mMockedLogicalDisplayMapper);
+ when(mMockedInjector.getThermalService()).thenReturn(mMockedThermalService);
+ when(mMockedInjector.getSyncRoot()).thenReturn(mMockedSyncRoot);
+ mExternalDisplayPolicy = new ExternalDisplayPolicy(mMockedInjector);
+
+ // Initialize mocked logical display
+ when(mMockedLogicalDisplay.getDisplayIdLocked()).thenReturn(EXTERNAL_DISPLAY_ID);
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ final var mockedLogicalDisplayInfo = new DisplayInfo();
+ mockedLogicalDisplayInfo.type = TYPE_EXTERNAL;
+ when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo);
+ when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn(
+ mMockedLogicalDisplay);
+ }
+
+ @Test
+ public void testTryEnableExternalDisplay_criticalThermalCondition() throws RemoteException {
+ // Disallow external displays due to thermals.
+ setTemperature(registerThermalListener(), List.of(CRITICAL_TEMPERATURE));
+ assertIsExternalDisplayAllowed(/*enabled=*/ false);
+ assertDisplaySetEnabled(/*enabled=*/ false);
+
+ // Check that display can not be enabled with tryEnableExternalDisplay.
+ mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay,
+ /*enabled=*/ true);
+ verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
+ }
+
+ @Test
+ public void testTryEnableExternalDisplay_featureDisabled(@TestParameter final boolean enable) {
+ when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
+ mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, enable);
+ verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
+ }
+
+ @Test
+ public void testTryDisableExternalDisplay_criticalThermalCondition() throws RemoteException {
+ // Disallow external displays due to thermals.
+ setTemperature(registerThermalListener(), List.of(CRITICAL_TEMPERATURE));
+ assertIsExternalDisplayAllowed(/*enabled=*/ false);
+ assertDisplaySetEnabled(/*enabled=*/ false);
+
+ // Check that display can be disabled with tryEnableExternalDisplay.
+ mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay,
+ /*enabled=*/ false);
+ assertDisplaySetEnabled(/*enabled=*/ false);
+ }
+
+ @Test
+ public void testSetEnabledExternalDisplay(@TestParameter final boolean enable) {
+ mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, enable);
+ assertDisplaySetEnabled(enable);
+ }
+
+ @Test
+ public void testOnExternalDisplayAvailable() {
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ assertAskedToEnableDisplay();
+ }
+
+ @Test
+ public void testOnExternalDisplayAvailable_criticalThermalCondition()
+ throws RemoteException {
+ // Disallow external displays due to thermals.
+ setTemperature(registerThermalListener(), List.of(CRITICAL_TEMPERATURE));
+ assertIsExternalDisplayAllowed(/*enabled=*/ false);
+ assertDisplaySetEnabled(/*enabled=*/ false);
+
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+ }
+
+ @Test
+ public void testNoThermalListenerRegistered_featureDisabled(
+ @TestParameter final boolean isConnectedDisplayManagementEnabled,
+ @TestParameter final boolean isErrorHandlingEnabled) throws RemoteException {
+ assumeFalse(isConnectedDisplayManagementEnabled && isErrorHandlingEnabled);
+ when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(
+ isConnectedDisplayManagementEnabled);
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
+ isErrorHandlingEnabled);
+
+ mExternalDisplayPolicy.onBootCompleted();
+ verify(mMockedThermalService, never()).registerThermalEventListenerWithType(
+ any(), anyInt());
+ }
+
+ @Test
+ public void testOnCriticalTemperature_disallowAndAllowExternalDisplay() throws RemoteException {
+ final var thermalListener = registerThermalListener();
+
+ setTemperature(thermalListener, List.of(CRITICAL_TEMPERATURE, EMERGENCY_TEMPERATURE));
+ assertIsExternalDisplayAllowed(/*enabled=*/ false);
+ assertDisplaySetEnabled(false);
+
+ thermalListener.notifyThrottling(SEVERE_TEMPERATURE);
+ thermalListener.notifyThrottling(MODERATE_TEMPERATURE);
+ assertIsExternalDisplayAllowed(/*enabled=*/ true);
+ verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any());
+ }
+
+ private void setTemperature(final IThermalEventListener thermalEventListener,
+ final List<Temperature> temperature) throws RemoteException {
+ for (var t : temperature) {
+ thermalEventListener.notifyThrottling(t);
+ }
+ verify(mMockedLogicalDisplayMapper).forEachLocked(mLogicalDisplayConsumerCaptor.capture());
+ mLogicalDisplayConsumerCaptor.getValue().accept(mMockedLogicalDisplay);
+ }
+
+ private void assertDisplaySetEnabled(final boolean enabled) {
+ // Check setDisplayEnabledLocked is triggered to disable display.
+ verify(mMockedLogicalDisplayMapper).setDisplayEnabledLocked(
+ mLogicalDisplayCaptor.capture(), mIsEnabledCaptor.capture());
+ assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
+ assertThat(mIsEnabledCaptor.getValue()).isEqualTo(enabled);
+ clearInvocations(mMockedLogicalDisplayMapper);
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(enabled);
+ }
+
+ private void assertAskedToEnableDisplay() {
+ // Check sendExternalDisplayEventLocked is triggered when display can be enabled.
+ verify(mMockedInjector).sendExternalDisplayEventLocked(mLogicalDisplayCaptor.capture(),
+ mDisplayEventCaptor.capture());
+ assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
+ assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+ clearInvocations(mMockedLogicalDisplayMapper);
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ }
+
+ private void assertIsExternalDisplayAllowed(final boolean enabled) {
+ assertThat(mExternalDisplayPolicy.isExternalDisplayAllowed()).isEqualTo(enabled);
+ }
+
+ private IThermalEventListener registerThermalListener() throws RemoteException {
+ // Initialize and register thermal listener
+ mExternalDisplayPolicy.onBootCompleted();
+ verify(mMockedThermalService).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), mThermalEventTypeCaptor.capture());
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+ assertThat(listener).isNotNull();
+ assertThat(mThermalEventTypeCaptor.getValue()).isEqualTo(Temperature.TYPE_SKIN);
+ return listener;
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 8b13018fc14b..43d2b8f741ae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -346,14 +346,14 @@ public class LogicalDisplayMapperTest {
// Disable device
mLogicalDisplayMapper.setDisplayEnabledLocked(
- displayAdded.getDisplayIdLocked(), /* isEnabled= */ false);
+ displayAdded, /* isEnabled= */ false);
verify(mListenerMock).onLogicalDisplayEventLocked(mDisplayCaptor.capture(),
eq(LOGICAL_DISPLAY_EVENT_REMOVED));
clearInvocations(mListenerMock);
// Enable device
mLogicalDisplayMapper.setDisplayEnabledLocked(
- displayAdded.getDisplayIdLocked(), /* isEnabled= */ true);
+ displayAdded, /* isEnabled= */ true);
verify(mListenerMock).onLogicalDisplayEventLocked(mDisplayCaptor.capture(),
eq(LOGICAL_DISPLAY_EVENT_ADDED));
}