Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e846dc00 authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

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
parent 187ed330
Loading
Loading
Loading
Loading
+68 −25
Original line number Diff line number Diff line
@@ -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);
            }
        }

        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;
        }
    }
}
+277 −0
Original line number Diff line number Diff line
/*
 * 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();
            }
        }
    }
}
+2 −7
Original line number Diff line number Diff line
@@ -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);
+1 −1
Original line number Diff line number Diff line
@@ -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;
+262 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading