diff options
| author | 2023-10-24 13:56:39 +0000 | |
|---|---|---|
| committer | 2023-11-10 10:17:30 +0000 | |
| commit | e846dc00c9455126a8d97fb1a7908b43ab15a2e1 (patch) | |
| tree | aa8849891b1835ca68a7c14fe07179d1fff466a7 | |
| parent | 187ed3307e7af9360aaf2b9b60f10989be0ed88c (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
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)); } |