blob: f953cc8c8a271aee43a438557b0aa75f3f8085c5 [file] [log] [blame]
/*
* Copyright (C) 2012 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 android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.sidekick.SidekickInternal;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.RoundedCorners;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* A display adapter for the local displays managed by SurfaceFlinger.
* <p>
* Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
* </p>
*/
final class LocalDisplayAdapter extends DisplayAdapter {
private static final String TAG = "LocalDisplayAdapter";
private static final boolean DEBUG = false;
private static final String UNIQUE_ID_PREFIX = "local:";
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
private static final int NO_DISPLAY_MODE_ID = 0;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
private final Injector mInjector;
private final SurfaceControlProxy mSurfaceControlProxy;
// Called with SyncRoot lock held.
public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
this(syncRoot, context, handler, listener, new Injector());
}
@VisibleForTesting
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener, Injector injector) {
super(syncRoot, context, handler, listener, TAG);
mInjector = injector;
mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
}
@Override
public void registerLocked() {
super.registerLocked();
mInjector.setDisplayEventListenerLocked(getHandler().getLooper(),
new LocalDisplayEventListener());
for (long physicalDisplayId : mSurfaceControlProxy.getPhysicalDisplayIds()) {
tryConnectDisplayLocked(physicalDisplayId);
}
}
private void tryConnectDisplayLocked(long physicalDisplayId) {
final IBinder displayToken =
mSurfaceControlProxy.getPhysicalDisplayToken(physicalDisplayId);
if (displayToken != null) {
SurfaceControl.StaticDisplayInfo staticInfo =
mSurfaceControlProxy.getStaticDisplayInfo(displayToken);
if (staticInfo == null) {
Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);
return;
}
SurfaceControl.DynamicDisplayInfo dynamicInfo =
mSurfaceControlProxy.getDynamicDisplayInfo(displayToken);
if (dynamicInfo == null) {
Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);
return;
}
if (dynamicInfo.supportedDisplayModes == null) {
// There are no valid modes for this device, so we can't use it
Slog.w(TAG, "No valid modes found for display device " + physicalDisplayId);
return;
}
if (dynamicInfo.activeDisplayModeId < 0) {
// There is no active mode, and for now we don't have the
// policy to set one.
Slog.w(TAG, "No valid active mode found for display device " + physicalDisplayId);
return;
}
if (dynamicInfo.activeColorMode < 0) {
// We failed to get the active color mode. We don't bail out here since on the next
// configuration pass we'll go ahead and set it to whatever it was set to last (or
// COLOR_MODE_NATIVE if this is the first configuration).
Slog.w(TAG, "No valid active color mode for display device " + physicalDisplayId);
dynamicInfo.activeColorMode = Display.COLOR_MODE_INVALID;
}
SurfaceControl.DesiredDisplayModeSpecs modeSpecs =
mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken);
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
// Display was added.
final boolean isDefaultDisplay = mDevices.size() == 0;
device = new LocalDisplayDevice(displayToken, physicalDisplayId, staticInfo,
dynamicInfo, modeSpecs, isDefaultDisplay);
mDevices.put(physicalDisplayId, device);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
} else if (device.updateDisplayPropertiesLocked(staticInfo, dynamicInfo,
modeSpecs)) {
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
}
} else {
// The display is no longer available. Ignore the attempt to add it.
// If it was connected but has already been disconnected, we'll get a
// disconnect event that will remove it from mDevices.
}
}
private void tryDisconnectDisplayLocked(long physicalDisplayId) {
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device != null) {
// Display was removed.
mDevices.remove(physicalDisplayId);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
}
}
static int getPowerModeForState(int state) {
switch (state) {
case Display.STATE_OFF:
return SurfaceControl.POWER_MODE_OFF;
case Display.STATE_DOZE:
return SurfaceControl.POWER_MODE_DOZE;
case Display.STATE_DOZE_SUSPEND:
return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
case Display.STATE_ON_SUSPEND:
return SurfaceControl.POWER_MODE_ON_SUSPEND;
default:
return SurfaceControl.POWER_MODE_NORMAL;
}
}
private final class LocalDisplayDevice extends DisplayDevice {
private final long mPhysicalDisplayId;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
private final boolean mIsDefaultDisplay;
private final BacklightAdapter mBacklightAdapter;
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
private int mState = Display.STATE_UNKNOWN;
// This is only set in the runnable returned from requestDisplayStateLocked.
private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private int mDefaultModeId;
private int mDefaultModeGroup;
private int mActiveModeId;
private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
new DisplayModeDirector.DesiredDisplayModeSpecs();
private boolean mDisplayModeSpecsInvalid;
private int mActiveColorMode;
private Display.HdrCapabilities mHdrCapabilities;
private boolean mAllmSupported;
private boolean mGameContentTypeSupported;
private boolean mAllmRequested;
private boolean mGameContentTypeRequested;
private boolean mSidekickActive;
private SidekickInternal mSidekickInternal;
private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
// The supported display modes according in SurfaceFlinger
private SurfaceControl.DisplayMode[] mSfDisplayModes;
// The active display mode in SurfaceFlinger
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
private DisplayDeviceConfig mDisplayDeviceConfig;
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
SurfaceControl.StaticDisplayInfo staticDisplayInfo,
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isDefaultDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
getContext());
mPhysicalDisplayId = physicalDisplayId;
mIsDefaultDisplay = isDefaultDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
mSurfaceControlProxy);
mDisplayDeviceConfig = null;
}
@Override
public boolean hasStableUniqueId() {
return true;
}
/**
* Returns true if there is a change.
**/
public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo,
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
boolean changed = updateDisplayModesLocked(
dynamicInfo.supportedDisplayModes, dynamicInfo.activeDisplayModeId, modeSpecs);
changed |= updateStaticInfo(staticInfo);
changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
dynamicInfo.activeColorMode);
changed |= updateHdrCapabilitiesLocked(dynamicInfo.hdrCapabilities);
changed |= updateAllmSupport(dynamicInfo.autoLowLatencyModeSupported);
changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported);
if (changed) {
mHavePendingChanges = true;
}
return changed;
}
public boolean updateDisplayModesLocked(
SurfaceControl.DisplayMode[] displayModes, int activeDisplayModeId,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
mActiveSfDisplayMode = getModeById(displayModes, activeDisplayModeId);
// Build an updated list of all existing modes.
ArrayList<DisplayModeRecord> records = new ArrayList<>();
boolean modesAdded = false;
for (int i = 0; i < displayModes.length; i++) {
SurfaceControl.DisplayMode mode = displayModes[i];
List<Float> alternativeRefreshRates = new ArrayList<>();
for (int j = 0; j < displayModes.length; j++) {
SurfaceControl.DisplayMode other = displayModes[j];
boolean isAlternative = j != i && other.width == mode.width
&& other.height == mode.height
&& other.refreshRate != mode.refreshRate
&& other.group == mode.group;
if (isAlternative) {
alternativeRefreshRates.add(displayModes[j].refreshRate);
}
}
Collections.sort(alternativeRefreshRates);
// First, check to see if we've already added a matching mode. Since not all
// configuration options are exposed via Display.Mode, it's possible that we have
// multiple DisplayModes that would generate the same Display.Mode.
boolean existingMode = false;
for (DisplayModeRecord record : records) {
if (record.hasMatchingMode(mode)
&& refreshRatesEquals(alternativeRefreshRates,
record.mMode.getAlternativeRefreshRates())) {
existingMode = true;
break;
}
}
if (existingMode) {
continue;
}
// If we haven't already added a mode for this configuration to the new set of
// supported modes then check to see if we have one in the prior set of supported
// modes to reuse.
DisplayModeRecord record = findDisplayModeRecord(mode, alternativeRefreshRates);
if (record == null) {
float[] alternativeRates = new float[alternativeRefreshRates.size()];
for (int j = 0; j < alternativeRates.length; j++) {
alternativeRates[j] = alternativeRefreshRates.get(j);
}
record = new DisplayModeRecord(mode, alternativeRates);
modesAdded = true;
}
records.add(record);
}
// Get the currently active mode
DisplayModeRecord activeRecord = null;
for (DisplayModeRecord record : records) {
if (record.hasMatchingMode(mActiveSfDisplayMode)) {
activeRecord = record;
break;
}
}
boolean activeModeChanged = false;
// Check whether SurfaceFlinger or the display device changed the active mode out from
// under us.
if (mActiveModeId != NO_DISPLAY_MODE_ID
&& mActiveModeId != activeRecord.mMode.getModeId()) {
Slog.d(TAG, "The active mode was changed from SurfaceFlinger or the display"
+ "device.");
mActiveModeId = activeRecord.mMode.getModeId();
activeModeChanged = true;
sendTraversalRequestLocked();
}
// Check whether surface flinger spontaneously changed display config specs out from
// under us. If so, schedule a traversal to reapply our display config specs.
if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
int activeBaseMode = findMatchingModeIdLocked(modeSpecs.defaultMode);
// If we can't map the defaultMode index to a mode, then the physical display
// modes must have changed, and the code below for handling changes to the
// list of available modes will take care of updating display mode specs.
if (activeBaseMode == NO_DISPLAY_MODE_ID
|| mDisplayModeSpecs.baseModeId != activeBaseMode
|| mDisplayModeSpecs.primaryRefreshRateRange.min
!= modeSpecs.primaryRefreshRateMin
|| mDisplayModeSpecs.primaryRefreshRateRange.max
!= modeSpecs.primaryRefreshRateMax
|| mDisplayModeSpecs.appRequestRefreshRateRange.min
!= modeSpecs.appRequestRefreshRateMin
|| mDisplayModeSpecs.appRequestRefreshRateRange.max
!= modeSpecs.appRequestRefreshRateMax) {
mDisplayModeSpecsInvalid = true;
sendTraversalRequestLocked();
}
}
boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
// If the records haven't changed then we're done here.
if (!recordsChanged) {
return activeModeChanged;
}
mSupportedModes.clear();
for (DisplayModeRecord record : records) {
mSupportedModes.put(record.mMode.getModeId(), record);
}
// For a new display, we need to initialize the default mode ID.
if (mDefaultModeId == NO_DISPLAY_MODE_ID) {
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
} else if (findDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
Slog.w(TAG, "Default display mode no longer available, using currently"
+ " active mode as default.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
}
// Determine whether the display mode specs' base mode is still there.
if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
Slog.w(TAG,
"DisplayModeSpecs base mode no longer available, using currently"
+ " active mode.");
}
mDisplayModeSpecs.baseModeId = activeRecord.mMode.getModeId();
mDisplayModeSpecsInvalid = true;
}
// Determine whether the active mode is still there.
if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
if (mActiveModeId != NO_DISPLAY_MODE_ID) {
Slog.w(TAG, "Active display mode no longer available, reverting to default"
+ " mode.");
}
mActiveModeId = mDefaultModeId;
}
// Schedule traversals so that we apply pending changes.
sendTraversalRequestLocked();
return true;
}
@Override
public DisplayDeviceConfig getDisplayDeviceConfig() {
if (mDisplayDeviceConfig == null) {
loadDisplayDeviceConfig();
}
return mDisplayDeviceConfig;
}
private void loadDisplayDeviceConfig() {
// Load display device config
final Context context = getOverlayContext();
mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
mIsDefaultDisplay);
if (mDisplayDeviceConfig == null) {
return;
}
// Load brightness HWC quirk
mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
DisplayDeviceConfig.QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC));
}
private boolean updateStaticInfo(SurfaceControl.StaticDisplayInfo info) {
if (Objects.equals(mStaticDisplayInfo, info)) {
return false;
}
mStaticDisplayInfo = info;
return true;
}
private boolean updateColorModesLocked(int[] colorModes, int activeColorMode) {
if (colorModes == null) {
return false;
}
List<Integer> pendingColorModes = new ArrayList<>();
// Build an updated list of all existing color modes.
boolean colorModesAdded = false;
for (int colorMode : colorModes) {
if (!mSupportedColorModes.contains(colorMode)) {
colorModesAdded = true;
}
pendingColorModes.add(colorMode);
}
boolean colorModesChanged =
pendingColorModes.size() != mSupportedColorModes.size()
|| colorModesAdded;
// If the supported color modes haven't changed then we're done here.
if (!colorModesChanged) {
return false;
}
mSupportedColorModes.clear();
mSupportedColorModes.addAll(pendingColorModes);
Collections.sort(mSupportedColorModes);
// Determine whether the active color mode is still there.
if (!mSupportedColorModes.contains(mActiveColorMode)) {
if (mActiveColorMode != Display.COLOR_MODE_DEFAULT) {
Slog.w(TAG, "Active color mode no longer available, reverting"
+ " to default mode.");
mActiveColorMode = Display.COLOR_MODE_DEFAULT;
} else {
if (!mSupportedColorModes.isEmpty()) {
// This should never happen.
Slog.e(TAG, "Default and active color mode is no longer available!"
+ " Reverting to first available mode.");
mActiveColorMode = mSupportedColorModes.get(0);
} else {
// This should really never happen.
Slog.e(TAG, "No color modes available!");
}
}
}
return true;
}
private boolean updateHdrCapabilitiesLocked(Display.HdrCapabilities newHdrCapabilities) {
// If the HDR capabilities haven't changed, then we're done here.
if (Objects.equals(mHdrCapabilities, newHdrCapabilities)) {
return false;
}
mHdrCapabilities = newHdrCapabilities;
return true;
}
private boolean updateAllmSupport(boolean supported) {
if (mAllmSupported == supported) {
return false;
}
mAllmSupported = supported;
return true;
}
private boolean updateGameContentTypeSupport(boolean supported) {
if (mGameContentTypeSupported == supported) {
return false;
}
mGameContentTypeSupported = supported;
return true;
}
private SurfaceControl.DisplayMode getModeById(SurfaceControl.DisplayMode[] supportedModes,
int modeId) {
for (SurfaceControl.DisplayMode mode : supportedModes) {
if (mode.id == modeId) {
return mode;
}
}
Slog.e(TAG, "Can't find display mode with id " + modeId);
return null;
}
private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayMode mode,
List<Float> alternativeRefreshRates) {
for (int i = 0; i < mSupportedModes.size(); i++) {
DisplayModeRecord record = mSupportedModes.valueAt(i);
if (record.hasMatchingMode(mode)
&& refreshRatesEquals(alternativeRefreshRates,
record.mMode.getAlternativeRefreshRates())) {
return record;
}
}
return null;
}
private boolean refreshRatesEquals(List<Float> list, float[] array) {
if (list.size() != array.length) {
return false;
}
for (int i = 0; i < list.size(); i++) {
if (Float.floatToIntBits(list.get(i)) != Float.floatToIntBits(array[i])) {
return false;
}
}
return true;
}
@Override
public void applyPendingDisplayDeviceInfoChangesLocked() {
if (mHavePendingChanges) {
mInfo = null;
mHavePendingChanges = false;
}
}
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
mInfo = new DisplayDeviceInfo();
mInfo.width = mActiveSfDisplayMode.width;
mInfo.height = mActiveSfDisplayMode.height;
mInfo.modeId = mActiveModeId;
mInfo.defaultModeId = mDefaultModeId;
mInfo.supportedModes = getDisplayModes(mSupportedModes);
mInfo.colorMode = mActiveColorMode;
mInfo.allmSupported = mAllmSupported;
mInfo.gameContentTypeSupported = mGameContentTypeSupported;
mInfo.supportedColorModes =
new int[mSupportedColorModes.size()];
for (int i = 0; i < mSupportedColorModes.size(); i++) {
mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
}
mInfo.hdrCapabilities = mHdrCapabilities;
mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
mInfo.state = mState;
mInfo.uniqueId = getUniqueId();
final DisplayAddress.Physical physicalAddress =
DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
mInfo.address = physicalAddress;
mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f);
mInfo.xDpi = mActiveSfDisplayMode.xDpi;
mInfo.yDpi = mActiveSfDisplayMode.yDpi;
mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
// Assume that all built-in displays that have secure output (eg. HDCP) also
// support compositing from gralloc protected buffers.
if (mStaticDisplayInfo.secure) {
mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
}
final Resources res = getOverlayContext().getResources();
if (mIsDefaultDisplay) {
mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
if (res.getBoolean(com.android.internal.R.bool.config_mainBuiltInDisplayIsRound)
|| (Build.IS_EMULATOR
&& SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
}
if (res.getBoolean(
com.android.internal.R.bool.config_maskMainBuiltInDisplayCutout)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
}
mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
mInfo.width, mInfo.height);
mInfo.roundedCorners = RoundedCorners.fromResources(
res, mInfo.width, mInfo.height);
} else {
if (!res.getBoolean(
com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
}
if (isDisplayPrivate(physicalAddress)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
}
}
if (mStaticDisplayInfo.isInternal) {
mInfo.type = Display.TYPE_INTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
mInfo.name = res.getString(
com.android.internal.R.string.display_manager_built_in_display_name);
} else {
mInfo.type = Display.TYPE_EXTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_hdmi_display_name);
}
mInfo.frameRateOverrides = mFrameRateOverrides;
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
}
return mInfo;
}
@Override
public Runnable requestDisplayStateLocked(final int state, final float brightnessState,
final float sdrBrightnessState) {
// Assume that the brightness is off if the display is being turned off.
assert state != Display.STATE_OFF
|| brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
final boolean stateChanged = (mState != state);
final boolean brightnessChanged = mBrightnessState != brightnessState
|| mSdrBrightnessState != sdrBrightnessState;
if (stateChanged || brightnessChanged) {
final long physicalDisplayId = mPhysicalDisplayId;
final IBinder token = getDisplayTokenLocked();
final int oldState = mState;
if (stateChanged) {
mState = state;
updateDeviceInfoLocked();
}
// Defer actually setting the display state until after we have exited
// the critical section since it can take hundreds of milliseconds
// to complete.
return new Runnable() {
@Override
public void run() {
// Exit a suspended state before making any changes.
int currentState = oldState;
if (Display.isSuspendedState(oldState)
|| oldState == Display.STATE_UNKNOWN) {
if (!Display.isSuspendedState(state)) {
setDisplayState(state);
currentState = state;
} else if (state == Display.STATE_DOZE_SUSPEND
|| oldState == Display.STATE_DOZE_SUSPEND) {
setDisplayState(Display.STATE_DOZE);
currentState = Display.STATE_DOZE;
} else if (state == Display.STATE_ON_SUSPEND
|| oldState == Display.STATE_ON_SUSPEND) {
setDisplayState(Display.STATE_ON);
currentState = Display.STATE_ON;
// If UNKNOWN, we still want to set the initial display state,
// otherwise, return early.
} else if (oldState != Display.STATE_UNKNOWN) {
return; // old state and new state is off
}
}
// If the state change was from or to VR, then we need to tell the light
// so that it can apply appropriate VR brightness settings. Also, update the
// brightness so the state is propogated to light.
boolean vrModeChange = false;
if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
currentState != state) {
setVrMode(state == Display.STATE_VR);
vrModeChange = true;
}
// Apply brightness changes given that we are in a non-suspended state.
if (brightnessChanged || vrModeChange) {
setDisplayBrightness(brightnessState, sdrBrightnessState);
mBrightnessState = brightnessState;
mSdrBrightnessState = sdrBrightnessState;
}
// Enter the final desired state, possibly suspended.
if (state != currentState) {
setDisplayState(state);
}
}
private void setVrMode(boolean isVrEnabled) {
if (DEBUG) {
Slog.d(TAG, "setVrMode("
+ "id=" + physicalDisplayId
+ ", state=" + Display.stateToString(state) + ")");
}
mBacklightAdapter.setVrMode(isVrEnabled);
}
private void setDisplayState(int state) {
if (DEBUG) {
Slog.d(TAG, "setDisplayState("
+ "id=" + physicalDisplayId
+ ", state=" + Display.stateToString(state) + ")");
}
// We must tell sidekick to stop controlling the display before we
// can change its power mode, so do that first.
if (mSidekickActive) {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"SidekickInternal#endDisplayControl");
try {
mSidekickInternal.endDisplayControl();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
mSidekickActive = false;
}
final int mode = getPowerModeForState(state);
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
+ "id=" + physicalDisplayId
+ ", state=" + Display.stateToString(state) + ")");
try {
mSurfaceControlProxy.setDisplayPowerMode(token, mode);
Trace.traceCounter(Trace.TRACE_TAG_POWER, "DisplayPowerMode", mode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
// If we're entering a suspended (but not OFF) power state and we
// have a sidekick available, tell it now that it can take control.
if (Display.isSuspendedState(state) && state != Display.STATE_OFF
&& mSidekickInternal != null && !mSidekickActive) {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"SidekickInternal#startDisplayControl");
try {
mSidekickActive = mSidekickInternal.startDisplayControl(state);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
}
private void setDisplayBrightness(float brightnessState,
float sdrBrightnessState) {
// brightnessState includes invalid, off and full range.
if (Float.isNaN(brightnessState) || Float.isNaN(sdrBrightnessState)) {
return;
}
if (DEBUG) {
Slog.d(TAG, "setDisplayBrightness("
+ "id=" + physicalDisplayId
+ ", brightnessState=" + brightnessState
+ ", sdrBrightnessState=" + sdrBrightnessState + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
+ "id=" + physicalDisplayId + ", brightnessState="
+ brightnessState + ", sdrBrightnessState=" + sdrBrightnessState
+ ")");
try {
final float backlight = brightnessToBacklight(brightnessState);
final float sdrBacklight = brightnessToBacklight(sdrBrightnessState);
final float nits = backlightToNits(backlight);
final float sdrNits = backlightToNits(sdrBacklight);
mBacklightAdapter.setBacklight(sdrBacklight, sdrNits, backlight, nits);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
"ScreenBrightness",
BrightnessSynchronizer.brightnessFloatToInt(brightnessState));
Trace.traceCounter(Trace.TRACE_TAG_POWER,
"SdrScreenBrightness",
BrightnessSynchronizer.brightnessFloatToInt(
sdrBrightnessState));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
private float brightnessToBacklight(float brightness) {
if (brightness == PowerManager.BRIGHTNESS_OFF_FLOAT) {
return PowerManager.BRIGHTNESS_OFF_FLOAT;
} else {
return getDisplayDeviceConfig().getBacklightFromBrightness(brightness);
}
}
private float backlightToNits(float backlight) {
return getDisplayDeviceConfig().getNitsFromBacklight(backlight);
}
};
}
return null;
}
@Override
public void setRequestedColorModeLocked(int colorMode) {
requestColorModeLocked(colorMode);
}
@Override
public void setDesiredDisplayModeSpecsLocked(
DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
if (displayModeSpecs.baseModeId == 0) {
// Bail if the caller is requesting a null mode. We'll get called again shortly with
// a valid mode.
return;
}
// Find the mode Id based on the desired mode specs. In case there is more than one
// mode matching the mode spec, prefer the one that is in the default mode group.
// For now the default config mode is taken from the active mode when we got the
// hotplug event for the display. In the future we might want to change the default
// mode based on vendor requirements.
// Note: We prefer the default mode group over the current one as this is the mode
// group the vendor prefers.
int baseModeId = findDisplayModeIdLocked(displayModeSpecs.baseModeId,
mDefaultModeGroup);
if (baseModeId < 0) {
// When a display is hotplugged, it's possible for a mode to be removed that was
// previously valid. Because of the way display changes are propagated through the
// framework, and the caching of the display mode specs in LogicalDisplay, it's
// possible we'll get called with a stale mode id that no longer represents a valid
// mode. This should only happen in extremely rare cases. A followup call will
// contain a valid mode id.
Slog.w(TAG,
"Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId);
updateDeviceInfoLocked();
return;
}
if (mDisplayModeSpecsInvalid || !displayModeSpecs.equals(mDisplayModeSpecs)) {
mDisplayModeSpecsInvalid = false;
mDisplayModeSpecs.copyFrom(displayModeSpecs);
getHandler().sendMessage(PooledLambda.obtainMessage(
LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
getDisplayTokenLocked(),
new SurfaceControl.DesiredDisplayModeSpecs(baseModeId,
mDisplayModeSpecs.allowGroupSwitching,
mDisplayModeSpecs.primaryRefreshRateRange.min,
mDisplayModeSpecs.primaryRefreshRateRange.max,
mDisplayModeSpecs.appRequestRefreshRateRange.min,
mDisplayModeSpecs.appRequestRefreshRateRange.max)));
}
}
private void setDesiredDisplayModeSpecsAsync(IBinder displayToken,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
// Do not lock when calling these SurfaceControl methods because they are sync
// operations that may block for a while when setting display power mode.
mSurfaceControlProxy.setDesiredDisplayModeSpecs(displayToken, modeSpecs);
}
@Override
public void onOverlayChangedLocked() {
updateDeviceInfoLocked();
}
public void onActiveDisplayModeChangedLocked(int sfModeId) {
if (updateActiveModeLocked(sfModeId)) {
updateDeviceInfoLocked();
}
}
public void onFrameRateOverridesChanged(
DisplayEventReceiver.FrameRateOverride[] overrides) {
if (updateFrameRateOverridesLocked(overrides)) {
updateDeviceInfoLocked();
}
}
public boolean updateActiveModeLocked(int activeSfModeId) {
if (mActiveSfDisplayMode.id == activeSfModeId) {
return false;
}
mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
mActiveModeId = findMatchingModeIdLocked(activeSfModeId);
if (mActiveModeId == NO_DISPLAY_MODE_ID) {
Slog.w(TAG, "In unknown mode after setting allowed modes"
+ ", activeModeId=" + activeSfModeId);
}
return true;
}
public boolean updateFrameRateOverridesLocked(
DisplayEventReceiver.FrameRateOverride[] overrides) {
if (overrides.equals(mFrameRateOverrides)) {
return false;
}
mFrameRateOverrides = overrides;
return true;
}
public void requestColorModeLocked(int colorMode) {
if (mActiveColorMode == colorMode) {
return;
}
if (!mSupportedColorModes.contains(colorMode)) {
Slog.w(TAG, "Unable to find color mode " + colorMode
+ ", ignoring request.");
return;
}
mActiveColorMode = colorMode;
getHandler().sendMessage(PooledLambda.obtainMessage(
LocalDisplayDevice::requestColorModeAsync, this,
getDisplayTokenLocked(), colorMode));
}
private void requestColorModeAsync(IBinder displayToken, int colorMode) {
// Do not lock when calling this SurfaceControl method because it is a sync operation
// that may block for a while when setting display power mode.
mSurfaceControlProxy.setActiveColorMode(displayToken, colorMode);
synchronized (getSyncRoot()) {
updateDeviceInfoLocked();
}
}
@Override
public void setAutoLowLatencyModeLocked(boolean on) {
if (mAllmRequested == on) {
return;
}
mAllmRequested = on;
if (!mAllmSupported) {
Slog.d(TAG, "Unable to set ALLM because the connected display "
+ "does not support ALLM.");
return;
}
mSurfaceControlProxy.setAutoLowLatencyMode(getDisplayTokenLocked(), on);
}
@Override
public void setGameContentTypeLocked(boolean on) {
if (mGameContentTypeRequested == on) {
return;
}
mGameContentTypeRequested = on;
if (!mGameContentTypeSupported) {
Slog.d(TAG, "Unable to set game content type because the connected "
+ "display does not support game content type.");
return;
}
mSurfaceControlProxy.setGameContentType(getDisplayTokenLocked(), on);
}
@Override
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
pw.println("mDisplayModeSpecs={" + mDisplayModeSpecs + "}");
pw.println("mDisplayModeSpecsInvalid=" + mDisplayModeSpecsInvalid);
pw.println("mActiveModeId=" + mActiveModeId);
pw.println("mActiveColorMode=" + mActiveColorMode);
pw.println("mDefaultModeId=" + mDefaultModeId);
pw.println("mState=" + Display.stateToString(mState));
pw.println("mBrightnessState=" + mBrightnessState);
pw.println("mBacklightAdapter=" + mBacklightAdapter);
pw.println("mAllmSupported=" + mAllmSupported);
pw.println("mAllmRequested=" + mAllmRequested);
pw.println("mGameContentTypeSupported=" + mGameContentTypeSupported);
pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested);
pw.println("mStaticDisplayInfo=" + mStaticDisplayInfo);
pw.println("mSfDisplayModes=");
for (int i = 0; i < mSfDisplayModes.length; i++) {
pw.println(" " + mSfDisplayModes[i]);
}
pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
pw.println("mSupportedModes=");
for (int i = 0; i < mSupportedModes.size(); i++) {
pw.println(" " + mSupportedModes.valueAt(i));
}
pw.println("mSupportedColorModes=" + mSupportedColorModes.toString());
pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig);
}
private int findDisplayModeIdLocked(int modeId, int modeGroup) {
int matchingModeId = SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID;
DisplayModeRecord record = mSupportedModes.get(modeId);
if (record != null) {
for (SurfaceControl.DisplayMode mode : mSfDisplayModes) {
if (record.hasMatchingMode(mode)) {
if (matchingModeId
== SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID) {
matchingModeId = mode.id;
}
// Prefer to return a mode that matches the modeGroup
if (mode.group == modeGroup) {
return mode.id;
}
}
}
}
return matchingModeId;
}
private int findMatchingModeIdLocked(int sfModeId) {
SurfaceControl.DisplayMode mode = getModeById(mSfDisplayModes, sfModeId);
if (mode == null) {
Slog.e(TAG, "Invalid display mode ID " + sfModeId);
return NO_DISPLAY_MODE_ID;
}
for (int i = 0; i < mSupportedModes.size(); i++) {
DisplayModeRecord record = mSupportedModes.valueAt(i);
if (record.hasMatchingMode(mode)) {
return record.mMode.getModeId();
}
}
return NO_DISPLAY_MODE_ID;
}
private void updateDeviceInfoLocked() {
mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
private Display.Mode[] getDisplayModes(SparseArray<DisplayModeRecord> records) {
final int size = records.size();
Display.Mode[] modes = new Display.Mode[size];
for (int i = 0; i < size; i++) {
DisplayModeRecord record = records.valueAt(i);
modes[i] = record.mMode;
}
return modes;
}
private boolean isDisplayPrivate(DisplayAddress.Physical physicalAddress) {
if (physicalAddress == null) {
return false;
}
final Resources res = getOverlayContext().getResources();
int[] ports = res.getIntArray(
com.android.internal.R.array.config_localPrivateDisplayPorts);
if (ports != null) {
int port = physicalAddress.getPort();
for (int p : ports) {
if (p == port) {
return true;
}
}
}
return false;
}
}
/** Supplies a context whose Resources apply runtime-overlays */
Context getOverlayContext() {
return ActivityThread.currentActivityThread().getSystemUiContext();
}
/**
* Keeps track of a display mode.
*/
private static final class DisplayModeRecord {
public final Display.Mode mMode;
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
mMode = createMode(mode.width, mode.height, mode.refreshRate,
alternativeRefreshRates);
}
/**
* Returns whether the mode generated by the given DisplayModes matches the mode
* contained by the record modulo mode ID.
*
* Note that this doesn't necessarily mean that the DisplayModes are identical, just
* that they generate identical modes.
*/
public boolean hasMatchingMode(SurfaceControl.DisplayMode mode) {
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
== Float.floatToIntBits(mode.refreshRate);
}
public String toString() {
return "DisplayModeRecord{mMode=" + mMode + "}";
}
}
public static class Injector {
private ProxyDisplayEventReceiver mReceiver;
public void setDisplayEventListenerLocked(Looper looper, DisplayEventListener listener) {
mReceiver = new ProxyDisplayEventReceiver(looper, listener);
}
public SurfaceControlProxy getSurfaceControlProxy() {
return new SurfaceControlProxy();
}
}
public interface DisplayEventListener {
void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
}
public static final class ProxyDisplayEventReceiver extends DisplayEventReceiver {
private final DisplayEventListener mListener;
ProxyDisplayEventReceiver(Looper looper, DisplayEventListener listener) {
super(looper, VSYNC_SOURCE_APP,
EVENT_REGISTRATION_MODE_CHANGED_FLAG
| EVENT_REGISTRATION_FRAME_RATE_OVERRIDE_FLAG);
mListener = listener;
}
@Override
public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
mListener.onHotplug(timestampNanos, physicalDisplayId, connected);
}
@Override
public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
}
@Override
public void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides) {
mListener.onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
}
private final class LocalDisplayEventListener implements DisplayEventListener {
@Override
public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
synchronized (getSyncRoot()) {
if (connected) {
tryConnectDisplayLocked(physicalDisplayId);
} else {
tryDisconnectDisplayLocked(physicalDisplayId);
}
}
}
@Override
public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
if (DEBUG) {
Slog.d(TAG, "onModeChanged("
+ "timestampNanos=" + timestampNanos
+ ", physicalDisplayId=" + physicalDisplayId
+ ", modeId=" + modeId + ")");
}
synchronized (getSyncRoot()) {
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
if (DEBUG) {
Slog.d(TAG, "Received mode change for unhandled physical display: "
+ "physicalDisplayId=" + physicalDisplayId);
}
return;
}
device.onActiveDisplayModeChangedLocked(modeId);
}
}
@Override
public void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides) {
if (DEBUG) {
Slog.d(TAG, "onFrameRateOverrideChanged(timestampNanos=" + timestampNanos
+ ", physicalDisplayId=" + physicalDisplayId + " overrides="
+ Arrays.toString(overrides) + ")");
}
synchronized (getSyncRoot()) {
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
if (DEBUG) {
Slog.d(TAG, "Received frame rate override event for unhandled physical"
+ " display: physicalDisplayId=" + physicalDisplayId);
}
return;
}
device.onFrameRateOverridesChanged(overrides);
}
}
}
@VisibleForTesting
static class SurfaceControlProxy {
public SurfaceControl.DynamicDisplayInfo getDynamicDisplayInfo(IBinder token) {
return SurfaceControl.getDynamicDisplayInfo(token);
}
public long[] getPhysicalDisplayIds() {
return SurfaceControl.getPhysicalDisplayIds();
}
public IBinder getPhysicalDisplayToken(long physicalDisplayId) {
return SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
}
public SurfaceControl.StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
return SurfaceControl.getStaticDisplayInfo(displayToken);
}
public SurfaceControl.DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(
IBinder displayToken) {
return SurfaceControl.getDesiredDisplayModeSpecs(displayToken);
}
public boolean setDesiredDisplayModeSpecs(IBinder token,
SurfaceControl.DesiredDisplayModeSpecs specs) {
return SurfaceControl.setDesiredDisplayModeSpecs(token, specs);
}
public void setDisplayPowerMode(IBinder displayToken, int mode) {
SurfaceControl.setDisplayPowerMode(displayToken, mode);
}
public boolean setActiveColorMode(IBinder displayToken, int colorMode) {
return SurfaceControl.setActiveColorMode(displayToken, colorMode);
}
public void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
SurfaceControl.setAutoLowLatencyMode(displayToken, on);
}
public void setGameContentType(IBinder displayToken, boolean on) {
SurfaceControl.setGameContentType(displayToken, on);
}
public boolean getDisplayBrightnessSupport(IBinder displayToken) {
return SurfaceControl.getDisplayBrightnessSupport(displayToken);
}
public boolean setDisplayBrightness(IBinder displayToken, float brightness) {
return SurfaceControl.setDisplayBrightness(displayToken, brightness);
}
public boolean setDisplayBrightness(IBinder displayToken, float sdrBacklight,
float sdrNits, float displayBacklight, float displayNits) {
return SurfaceControl.setDisplayBrightness(displayToken, sdrBacklight, sdrNits,
displayBacklight, displayNits);
}
}
static class BacklightAdapter {
private final IBinder mDisplayToken;
private final LogicalLight mBacklight;
private final boolean mUseSurfaceControlBrightness;
private final SurfaceControlProxy mSurfaceControlProxy;
private boolean mForceSurfaceControl = false;
/**
* @param displayToken Token for display associated with this backlight.
* @param isDefaultDisplay {@code true} if it is the default display.
*/
BacklightAdapter(IBinder displayToken, boolean isDefaultDisplay,
SurfaceControlProxy surfaceControlProxy) {
mDisplayToken = displayToken;
mSurfaceControlProxy = surfaceControlProxy;
mUseSurfaceControlBrightness = mSurfaceControlProxy
.getDisplayBrightnessSupport(mDisplayToken);
if (!mUseSurfaceControlBrightness && isDefaultDisplay) {
LightsManager lights = LocalServices.getService(LightsManager.class);
mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
} else {
mBacklight = null;
}
}
// Set backlight within min and max backlight values
void setBacklight(float sdrBacklight, float sdrNits, float backlight, float nits) {
if (mUseSurfaceControlBrightness || mForceSurfaceControl) {
if (BrightnessSynchronizer.floatEquals(
sdrBacklight, PowerManager.BRIGHTNESS_INVALID_FLOAT)) {
mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight);
} else {
mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, sdrBacklight, sdrNits,
backlight, nits);
}
} else if (mBacklight != null) {
mBacklight.setBrightness(backlight);
}
}
void setVrMode(boolean isVrModeEnabled) {
if (mBacklight != null) {
mBacklight.setVrMode(isVrModeEnabled);
}
}
void setForceSurfaceControl(boolean forceSurfaceControl) {
mForceSurfaceControl = forceSurfaceControl;
}
@Override
public String toString() {
return "BacklightAdapter [useSurfaceControl=" + mUseSurfaceControlBrightness
+ " (force_anyway? " + mForceSurfaceControl + ")"
+ ", backlight=" + mBacklight + "]";
}
}
}