Handle DISPLAY_EVENT_FRAME_RATE_OVERRIDE
When getting DISPLAY_EVENT_FRAME_RATE_OVERRIDE from SurfaceFlinger,
expose the overridden frame rate to the relevant application
if the current refresh rate allows that.
Bug: 169271059
Bug: 169271062
Bug: 170503758
Test: manual test using SF backdoor
adb shell service call SurfaceFlinger 1039 i32 <uid> f <refresh rate>
Change-Id: I6ae1a98e6ca13e9d3d095a5713a6b0ca99652256
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b046d1d..7b4889f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -358,7 +358,7 @@
// listener.
DisplayInfo display = getDisplayInfoLocked(displayId);
if (display != null) {
- float refreshRate = display.getMode().getRefreshRate();
+ float refreshRate = display.getRefreshRate();
// Signal native callbacks if we ever set a refresh rate.
nSignalNativeCallbacks(refreshRate);
}
@@ -862,7 +862,7 @@
if (display != null) {
// We need to tell AChoreographer instances the current refresh rate so that apps
// can get it for free once a callback first registers.
- float refreshRate = display.getMode().getRefreshRate();
+ float refreshRate = display.getRefreshRate();
nSignalNativeCallbacks(refreshRate);
}
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 3da3184..59299f6 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -276,7 +276,7 @@
private static float getRefreshRate() {
DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
Display.DEFAULT_DISPLAY);
- return di.getMode().getRefreshRate();
+ return di.getRefreshRate();
}
/**
@@ -944,7 +944,7 @@
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
- super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
+ super(looper, vsyncSource, 0);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3021aa6a..4c6c97f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -456,6 +456,9 @@
// TODO (b/114338689): Remove the flag and use WindowManager#REMOVE_CONTENT_MODE_DESTROY
public static final int REMOVE_MODE_DESTROY_CONTENT = 1;
+ /** @hide */
+ public static final int DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE = 0xFF;
+
/**
* Internal method to create a display.
* The display created with this method will have a static {@link DisplayAdjustments} applied.
@@ -886,7 +889,7 @@
public float getRefreshRate() {
synchronized (this) {
updateDisplayInfoLocked();
- return mDisplayInfo.getMode().getRefreshRate();
+ return mDisplayInfo.getRefreshRate();
}
}
@@ -1509,6 +1512,16 @@
Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
}
+ /**
+ * Returns {@code true} if this mode equals to the other mode in all parameters except
+ * the refresh rate.
+ *
+ * @hide
+ */
+ public boolean equalsExceptRefreshRate(@Nullable Display.Mode other) {
+ return mWidth == other.mWidth && mHeight == other.mHeight;
+ }
+
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index e8a4ed4..5d4a4e5 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -23,6 +23,8 @@
import android.os.MessageQueue;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import dalvik.annotation.optimization.FastNative;
import dalvik.system.CloseGuard;
@@ -56,18 +58,18 @@
public static final int VSYNC_SOURCE_SURFACE_FLINGER = 1;
/**
- * Specifies to suppress config changed events from being generated from Surface Flinger.
- * <p>
- * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
- */
- public static final int CONFIG_CHANGED_EVENT_SUPPRESS = 0;
-
- /**
* Specifies to generate config changed events from Surface Flinger.
* <p>
* Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
*/
- public static final int CONFIG_CHANGED_EVENT_DISPATCH = 1;
+ public static final int EVENT_REGISTRATION_CONFIG_CHANGED_FLAG = 0x1;
+
+ /**
+ * Specifies to generate frame rate override events from Surface Flinger.
+ * <p>
+ * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+ */
+ public static final int EVENT_REGISTRATION_FRAME_RATE_OVERRIDE_FLAG = 0x2;
private static final String TAG = "DisplayEventReceiver";
@@ -81,7 +83,7 @@
private MessageQueue mMessageQueue;
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
- MessageQueue messageQueue, int vsyncSource, int configChanged);
+ MessageQueue messageQueue, int vsyncSource, int eventRegistration);
private static native void nativeDispose(long receiverPtr);
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
@@ -93,7 +95,7 @@
*/
@UnsupportedAppUsage
public DisplayEventReceiver(Looper looper) {
- this(looper, VSYNC_SOURCE_APP, CONFIG_CHANGED_EVENT_SUPPRESS);
+ this(looper, VSYNC_SOURCE_APP, 0);
}
/**
@@ -101,17 +103,17 @@
*
* @param looper The looper to use when invoking callbacks.
* @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
- * @param configChanged Whether to dispatch config changed events. Must be one of the
- * CONFIG_CHANGED_EVENT_* values.
+ * @param eventRegistration Which events to dispatch. Must be a bitfield consist of the
+ * EVENT_REGISTRATION_*_FLAG values.
*/
- public DisplayEventReceiver(Looper looper, int vsyncSource, int configChanged) {
+ public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
- vsyncSource, configChanged);
+ vsyncSource, eventRegistration);
mCloseGuard.open("dispose");
}
@@ -206,6 +208,41 @@
}
/**
+ * Represents a mapping between a UID and an override frame rate
+ */
+ public static class FrameRateOverride {
+ // The application uid
+ public final int uid;
+
+ // The frame rate that this application runs at
+ public final float frameRateHz;
+
+
+ @VisibleForTesting
+ public FrameRateOverride(int uid, float frameRateHz) {
+ this.uid = uid;
+ this.frameRateHz = frameRateHz;
+ }
+
+ @Override
+ public String toString() {
+ return "{uid=" + uid + " frameRateHz=" + frameRateHz + "}";
+ }
+ }
+
+ /**
+ * Called when frame rate override event is received.
+ *
+ * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+ * timebase.
+ * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+ * @param overrides The mappings from uid to frame rates
+ */
+ public void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
+ FrameRateOverride[] overrides) {
+ }
+
+ /**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
@@ -240,4 +277,11 @@
onConfigChanged(timestampNanos, physicalDisplayId, configId);
}
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,
+ FrameRateOverride[] overrides) {
+ onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
+ }
+
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index fe9a1a7..0201605 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -261,6 +261,11 @@
public String ownerPackageName;
/**
+ * The refresh rate override for this app. 0 means no override.
+ */
+ public float refreshRateOverride;
+
+ /**
* @hide
* Get current remove mode of the display - what actions should be performed with the display's
* content when it is removed.
@@ -332,7 +337,8 @@
&& state == other.state
&& ownerUid == other.ownerUid
&& Objects.equals(ownerPackageName, other.ownerPackageName)
- && removeMode == other.removeMode;
+ && removeMode == other.removeMode
+ && refreshRateOverride == other.refreshRateOverride;
}
@Override
@@ -376,6 +382,7 @@
ownerUid = other.ownerUid;
ownerPackageName = other.ownerPackageName;
removeMode = other.removeMode;
+ refreshRateOverride = other.refreshRateOverride;
}
public void readFromParcel(Parcel source) {
@@ -421,6 +428,7 @@
ownerPackageName = source.readString8();
uniqueId = source.readString8();
removeMode = source.readInt();
+ refreshRateOverride = source.readFloat();
}
@Override
@@ -465,6 +473,7 @@
dest.writeString8(ownerPackageName);
dest.writeString8(uniqueId);
dest.writeInt(removeMode);
+ dest.writeFloat(refreshRateOverride);
}
@Override
@@ -472,6 +481,17 @@
return 0;
}
+ /**
+ * Returns the refresh rate the application would experience.
+ */
+ public float getRefreshRate() {
+ if (refreshRateOverride > 0) {
+ return refreshRateOverride;
+ }
+
+ return getMode().getRefreshRate();
+ }
+
public Display.Mode getMode() {
return findMode(modeId);
}
@@ -675,6 +695,9 @@
}
sb.append(", removeMode ");
sb.append(removeMode);
+ sb.append(", refreshRateOverride ");
+ sb.append(refreshRateOverride);
+
sb.append("}");
return sb.toString();
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index e7e9c31..6337680 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -41,14 +41,21 @@
jmethodID dispatchVsync;
jmethodID dispatchHotplug;
jmethodID dispatchConfigChanged;
+ jmethodID dispatchFrameRateOverrides;
+
+ struct {
+ jclass clazz;
+ jmethodID init;
+ } frameRateOverrideClassInfo;
+
} gDisplayEventReceiverClassInfo;
class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
- NativeDisplayEventReceiver(JNIEnv* env,
- jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource,
- jint configChanged);
+ NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+ const sp<MessageQueue>& messageQueue, jint vsyncSource,
+ jint eventRegistration);
void dispose();
@@ -64,16 +71,17 @@
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
int32_t configId, nsecs_t vsyncPeriod) override;
+ void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
+ std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
};
-
-NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
- jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource,
- jint configChanged) :
- DisplayEventDispatcher(messageQueue->getLooper(),
- static_cast<ISurfaceComposer::VsyncSource>(vsyncSource),
- static_cast<ISurfaceComposer::ConfigChanged>(configChanged)),
+NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+ const sp<MessageQueue>& messageQueue,
+ jint vsyncSource, jint eventRegistration)
+ : DisplayEventDispatcher(messageQueue->getLooper(),
+ static_cast<ISurfaceComposer::VsyncSource>(vsyncSource),
+ static_cast<ISurfaceComposer::EventRegistration>(eventRegistration)),
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
mMessageQueue(messageQueue) {
ALOGV("receiver %p ~ Initializing display event receiver.", this);
@@ -137,16 +145,48 @@
mMessageQueue->raiseAndClearException(env, "dispatchConfigChanged");
}
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
- jobject messageQueueObj, jint vsyncSource, jint configChanged) {
+void NativeDisplayEventReceiver::dispatchFrameRateOverrides(
+ nsecs_t timestamp, PhysicalDisplayId displayId, std::vector<FrameRateOverride> overrides) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking FrameRateOverride handler.", this);
+ const auto frameRateOverrideClass =
+ gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz;
+ const auto frameRateOverrideInit =
+ gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.init;
+ auto frameRateOverrideInitObject =
+ env->NewObject(frameRateOverrideClass, frameRateOverrideInit, 0, 0);
+ auto frameRateOverrideArray = env->NewObjectArray(overrides.size(), frameRateOverrideClass,
+ frameRateOverrideInitObject);
+ for (size_t i = 0; i < overrides.size(); i++) {
+ auto FrameRateOverrideObject =
+ env->NewObject(frameRateOverrideClass, frameRateOverrideInit, overrides[i].uid,
+ overrides[i].frameRateHz);
+ env->SetObjectArrayElement(frameRateOverrideArray, i, FrameRateOverrideObject);
+ }
+
+ env->CallVoidMethod(receiverObj.get(),
+ gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides, timestamp,
+ displayId.value, frameRateOverrideArray);
+ ALOGV("receiver %p ~ Returned from FrameRateOverride handler.", this);
+ }
+
+ mMessageQueue->raiseAndClearException(env, "dispatchConfigChanged");
+}
+
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
+ jint vsyncSource, jint eventRegistration) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
- sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
- receiverWeak, messageQueue, vsyncSource, configChanged);
+ sp<NativeDisplayEventReceiver> receiver =
+ new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
+ eventRegistration);
status_t status = receiver->initialize();
if (status) {
String8 message;
@@ -205,6 +245,18 @@
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchConfigChanged = GetMethodIDOrDie(env,
gDisplayEventReceiverClassInfo.clazz, "dispatchConfigChanged", "(JJI)V");
+ gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
+ "dispatchFrameRateOverrides",
+ "(JJ[Landroid/view/DisplayEventReceiver$FrameRateOverride;)V");
+
+ jclass frameRateOverrideClazz =
+ FindClassOrDie(env, "android/view/DisplayEventReceiver$FrameRateOverride");
+ gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz =
+ MakeGlobalRefOrDie(env, frameRateOverrideClazz);
+ gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.init =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz,
+ "<init>", "(IF)V");
return res;
}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index a7f2739..cb4dd9e 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -1163,7 +1163,7 @@
// heuristic we don't need to be always 100% correct.
Mode activeMode = display.getMode();
nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
- activeMode.getRefreshRate(), maxRefreshRate,
+ display.getRefreshRate(), maxRefreshRate,
wideColorDataspace.mNativeDataspace, display.getAppVsyncOffsetNanos(),
display.getPresentationDeadlineNanos());
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index fe6500e..468d825 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -22,6 +22,7 @@
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayCutout;
+import android.view.DisplayEventReceiver;
import android.view.Surface;
import java.util.Arrays;
@@ -333,6 +334,9 @@
*/
public String ownerPackageName;
+ public DisplayEventReceiver.FrameRateOverride[] frameRateOverrides =
+ new DisplayEventReceiver.FrameRateOverride[0];
+
public void setAssumedDensityForExternalDisplay(int width, int height) {
densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
// Technically, these values should be smaller than the apparent density
@@ -386,7 +390,8 @@
|| !Objects.equals(address, other.address)
|| !Objects.equals(deviceProductInfo, other.deviceProductInfo)
|| ownerUid != other.ownerUid
- || !Objects.equals(ownerPackageName, other.ownerPackageName)) {
+ || !Objects.equals(ownerPackageName, other.ownerPackageName)
+ || !Objects.equals(frameRateOverrides, other.frameRateOverrides)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -425,6 +430,7 @@
state = other.state;
ownerUid = other.ownerUid;
ownerPackageName = other.ownerPackageName;
+ frameRateOverrides = other.frameRateOverrides;
}
// For debugging purposes
@@ -461,6 +467,10 @@
sb.append(", owner ").append(ownerPackageName);
sb.append(" (uid ").append(ownerUid).append(")");
}
+ sb.append(", frameRateOverride ");
+ for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
+ sb.append(frameRateOverride).append(" ");
+ }
sb.append(flagsToString(flags));
sb.append("}");
return sb.toString();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 004e481..3ccd208 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -37,6 +37,9 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -83,7 +86,9 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.sysprop.DisplayProperties;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
@@ -92,6 +97,7 @@
import android.util.SparseIntArray;
import android.util.Spline;
import android.view.Display;
+import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import android.view.IDisplayFoldListener;
import android.view.Surface;
@@ -114,6 +120,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
@@ -181,6 +188,7 @@
private static final int MSG_REQUEST_TRAVERSAL = 4;
private static final int MSG_UPDATE_VIEWPORT = 5;
private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
+ private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
private final Context mContext;
private final DisplayManagerHandler mHandler;
@@ -357,6 +365,30 @@
// Received notifications of the display-fold action
private DisplayFoldListener mDisplayFoldListener;
+ private final boolean mAllowNonNativeRefreshRateOverride;
+
+ private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+
+ /**
+ * Applications use {@link android.view.Display#getRefreshRate} and
+ * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
+ * Starting with Android S, the platform might throttle down applications frame rate to a
+ * divisor of the refresh rate if it is more preferable (for example if the application called
+ * to {@link android.view.Surface#setFrameRate}).
+ * Applications will experience {@link android.view.Choreographer#postFrameCallback} callbacks
+ * and backpressure at the throttled frame rate.
+ *
+ * {@link android.view.Display#getRefreshRate} will always return the application frame rate
+ * and not the physical display refresh rate to allow applications to do frame pacing correctly.
+ *
+ * {@link android.view.Display.Mode#getRefreshRate} will return the application frame rate if
+ * compiled to a previous release and starting with Android S it will return the physical
+ * display refresh rate.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE = 170503758L;
+
public DisplayManagerService(Context context) {
this(context, new Injector());
}
@@ -389,6 +421,7 @@
mCurrentUserId = UserHandle.USER_SYSTEM;
ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
mWideColorSpace = colorSpaces[1];
+ mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
mSystemReady = false;
}
@@ -677,11 +710,82 @@
Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED, 1, UserHandle.USER_CURRENT) != 0;
}
+ private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
+ frameRateOverrides, DisplayInfo info, int callingUid) {
+ float frameRateHz = 0;
+ for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
+ if (frameRateOverride.uid == callingUid) {
+ frameRateHz = frameRateOverride.frameRateHz;
+ break;
+ }
+ }
+ if (frameRateHz == 0) {
+ return info;
+ }
+
+ // Override the refresh rate only if it is a divider of the current
+ // refresh rate. This calculation needs to be in sync with the native code
+ // in RefreshRateConfigs::getRefreshRateDividerForUid
+ Display.Mode currentMode = info.getMode();
+ float numPeriods = currentMode.getRefreshRate() / frameRateHz;
+ float numPeriodsRound = Math.round(numPeriods);
+ if (Math.abs(numPeriods - numPeriodsRound) > THRESHOLD_FOR_REFRESH_RATES_DIVIDERS) {
+ return info;
+ }
+ frameRateHz = currentMode.getRefreshRate() / numPeriodsRound;
+
+ DisplayInfo overriddenInfo = new DisplayInfo();
+ overriddenInfo.copyFrom(info);
+ for (Display.Mode mode : info.supportedModes) {
+ if (!mode.equalsExceptRefreshRate(currentMode)) {
+ continue;
+ }
+
+ if (mode.getRefreshRate() >= frameRateHz - THRESHOLD_FOR_REFRESH_RATES_DIVIDERS
+ && mode.getRefreshRate()
+ <= frameRateHz + THRESHOLD_FOR_REFRESH_RATES_DIVIDERS) {
+ if (DEBUG) {
+ Slog.d(TAG, "found matching modeId " + mode.getModeId());
+ }
+ overriddenInfo.refreshRateOverride = mode.getRefreshRate();
+
+ if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+ callingUid)) {
+ overriddenInfo.modeId = mode.getModeId();
+ }
+ return overriddenInfo;
+ }
+ }
+
+ if (mAllowNonNativeRefreshRateOverride) {
+ overriddenInfo.refreshRateOverride = frameRateHz;
+ if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+ callingUid)) {
+ overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+ info.supportedModes.length + 1);
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+ new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+ currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+ overriddenInfo.refreshRateOverride);
+ overriddenInfo.modeId =
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+ .getModeId();
+ }
+ return overriddenInfo;
+ }
+
+
+
+ return info;
+ }
+
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
synchronized (mSyncRoot) {
LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
- DisplayInfo info = display.getDisplayInfoLocked();
+ DisplayInfo info =
+ getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(),
+ display.getDisplayInfoLocked(), callingUid);
if (info.hasAccess(callingUid)
|| isUidPresentOnDisplayInternal(callingUid, displayId)) {
return info;
@@ -691,14 +795,15 @@
}
}
- private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid) {
+ private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid,
+ int callingUid) {
synchronized (mSyncRoot) {
if (mCallbacks.get(callingPid) != null) {
throw new SecurityException("The calling process has already "
+ "registered an IDisplayManagerCallback.");
}
- CallbackRecord record = new CallbackRecord(callingPid, callback);
+ CallbackRecord record = new CallbackRecord(callingPid, callingUid, callback);
try {
IBinder binder = callback.asBinder();
binder.linkToDeath(record, 0);
@@ -1034,6 +1139,16 @@
scheduleTraversalLocked(false);
}
+ private void handleLogicalDisplayFrameRateOverridesChangedLocked(
+ @NonNull LogicalDisplay display) {
+ final int displayId = display.getDisplayIdLocked();
+ // We don't bother invalidating the display info caches here because any changes to the
+ // display info will trigger a cache invalidation inside of LogicalDisplay before we hit
+ // this point.
+ sendDisplayEventFrameRateOverrideLocked(displayId);
+ scheduleTraversalLocked(false);
+ }
+
private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
mDisplayPowerControllers.delete(displayId);
@@ -1545,6 +1660,12 @@
mHandler.sendMessage(msg);
}
+ private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
+ Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
+ displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ mHandler.sendMessage(msg);
+ }
+
// Requests that performTraversals be called at a
// later time to apply changes to surfaces and displays.
private void scheduleTraversalLocked(boolean inTraversal) {
@@ -1558,7 +1679,7 @@
// Runs on Handler thread.
// Delivers display event notifications to callbacks.
- private void deliverDisplayEvent(int displayId, int event) {
+ private void deliverDisplayEvent(int displayId, ArraySet<Integer> uids, int event) {
if (DEBUG) {
Slog.d(TAG, "Delivering display event: displayId="
+ displayId + ", event=" + event);
@@ -1570,12 +1691,14 @@
count = mCallbacks.size();
mTempCallbacks.clear();
for (int i = 0; i < count; i++) {
- mTempCallbacks.add(mCallbacks.valueAt(i));
+ if (uids == null || uids.contains(mCallbacks.valueAt(i).mUid)) {
+ mTempCallbacks.add(mCallbacks.valueAt(i));
+ }
}
}
// After releasing the lock, send the notifications out.
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < mTempCallbacks.size(); i++) {
mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
}
mTempCallbacks.clear();
@@ -1691,6 +1814,11 @@
long getDefaultDisplayDelayTimeout() {
return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
}
+
+ boolean getAllowNonNativeRefreshRateOverride() {
+ return DisplayProperties
+ .debug_allow_non_native_refresh_rate_override().orElse(false);
+ }
}
@VisibleForTesting
@@ -1760,7 +1888,7 @@
break;
case MSG_DELIVER_DISPLAY_EVENT:
- deliverDisplayEvent(msg.arg1, msg.arg2);
+ deliverDisplayEvent(msg.arg1, null, msg.arg2);
break;
case MSG_REQUEST_TRAVERSAL:
@@ -1787,6 +1915,17 @@
case MSG_LOAD_BRIGHTNESS_CONFIGURATION:
loadBrightnessConfiguration();
break;
+
+ case MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+ ArraySet<Integer> uids;
+ synchronized (mSyncRoot) {
+ int displayId = msg.arg1;
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ uids = display.getPendingFrameRateOverrideUids();
+ display.clearPendingFrameRateOverrideUids();
+ }
+ deliverDisplayEvent(msg.arg1, uids, msg.arg2);
+ break;
}
}
}
@@ -1810,6 +1949,10 @@
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_SWAPPED:
handleLogicalDisplaySwappedLocked(display);
break;
+
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
+ handleLogicalDisplayFrameRateOverridesChangedLocked(display);
+ break;
}
}
@@ -1823,12 +1966,14 @@
private final class CallbackRecord implements DeathRecipient {
public final int mPid;
+ public final int mUid;
private final IDisplayManagerCallback mCallback;
public boolean mWifiDisplayScanRequested;
- public CallbackRecord(int pid, IDisplayManagerCallback callback) {
+ CallbackRecord(int pid, int uid, IDisplayManagerCallback callback) {
mPid = pid;
+ mUid = uid;
mCallback = callback;
}
@@ -1918,9 +2063,10 @@
}
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- registerCallbackInternal(callback, callingPid);
+ registerCallbackInternal(callback, callingPid, callingUid);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index f657858..74ea2d7 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -208,6 +208,9 @@
private DisplayDeviceConfig mDisplayDeviceConfig;
+ private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
+ new DisplayEventReceiver.FrameRateOverride[0];
+
LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs,
int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
@@ -625,6 +628,8 @@
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;
}
@@ -882,6 +887,13 @@
}
}
+ public void onFrameRateOverridesChanged(
+ DisplayEventReceiver.FrameRateOverride[] overrides) {
+ if (updateFrameRateOverridesLocked(overrides)) {
+ updateDeviceInfoLocked();
+ }
+ }
+
public boolean updateActiveModeLocked(int activeConfigId) {
if (mActiveConfigId == activeConfigId) {
return false;
@@ -895,6 +907,16 @@
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;
@@ -1102,23 +1124,39 @@
public interface DisplayEventListener {
void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId);
+ 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, CONFIG_CHANGED_EVENT_DISPATCH);
+ super(looper, VSYNC_SOURCE_APP,
+ EVENT_REGISTRATION_CONFIG_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 onConfigChanged(long timestampNanos, long physicalDisplayId, int configId) {
mListener.onConfigChanged(timestampNanos, physicalDisplayId, configId);
}
+
+ @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) {
@@ -1128,6 +1166,8 @@
}
}
}
+
+ @Override
public void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId) {
if (DEBUG) {
Slog.d(TAG, "onConfigChanged("
@@ -1147,5 +1187,26 @@
device.onActiveDisplayConfigChangedLocked(configId);
}
}
+
+ @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);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 979c3b8..d80e168 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -20,8 +20,11 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal;
+import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
+import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -123,10 +126,27 @@
*/
private boolean mIsEnabled = true;
+ /**
+ * The UID mappings for refresh rate override
+ */
+ private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides;
+
+ /**
+ * Holds a set of UIDs that their frame rate override changed and needs to be notified
+ */
+ private ArraySet<Integer> mPendingFrameRateOverrideUids;
+
+ /**
+ * Temporary frame rate override list, used when needed.
+ */
+ private final SparseArray<Float> mTempFrameRateOverride;
+
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
+ mPendingFrameRateOverrideUids = new ArraySet<>();
+ mTempFrameRateOverride = new SparseArray<>();
}
/**
@@ -179,6 +199,27 @@
}
/**
+ * Returns the frame rate overrides list
+ */
+ public DisplayEventReceiver.FrameRateOverride[] getFrameRateOverrides() {
+ return mFrameRateOverrides;
+ }
+
+ /**
+ * Returns the list of uids that needs to be updated about their frame rate override
+ */
+ public ArraySet<Integer> getPendingFrameRateOverrideUids() {
+ return mPendingFrameRateOverrideUids;
+ }
+
+ /**
+ * Clears the list of uids that needs to be updated about their frame rate override
+ */
+ public void clearPendingFrameRateOverrideUids() {
+ mPendingFrameRateOverrideUids = new ArraySet<>();
+ }
+
+ /**
* @see DisplayManagerInternal#getNonOverrideDisplayInfo(int, DisplayInfo)
*/
void getNonOverrideDisplayInfoLocked(DisplayInfo outInfo) {
@@ -324,12 +365,40 @@
(deviceInfo.flags & DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT) != 0;
mBaseDisplayInfo.displayCutout = maskCutout ? null : deviceInfo.displayCutout;
mBaseDisplayInfo.displayId = mDisplayId;
+ updateFrameRateOverrides(deviceInfo);
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
}
+ private void updateFrameRateOverrides(DisplayDeviceInfo deviceInfo) {
+ mTempFrameRateOverride.clear();
+ if (mFrameRateOverrides != null) {
+ for (DisplayEventReceiver.FrameRateOverride frameRateOverride
+ : mFrameRateOverrides) {
+ mTempFrameRateOverride.put(frameRateOverride.uid,
+ frameRateOverride.frameRateHz);
+ }
+ }
+ mFrameRateOverrides = deviceInfo.frameRateOverrides;
+ if (mFrameRateOverrides != null) {
+ for (DisplayEventReceiver.FrameRateOverride frameRateOverride
+ : mFrameRateOverrides) {
+ float refreshRate = mTempFrameRateOverride.get(frameRateOverride.uid, 0f);
+ if (refreshRate == 0 || frameRateOverride.frameRateHz != refreshRate) {
+ mTempFrameRateOverride.put(frameRateOverride.uid,
+ frameRateOverride.frameRateHz);
+ } else {
+ mTempFrameRateOverride.delete(frameRateOverride.uid);
+ }
+ }
+ }
+ for (int i = 0; i < mTempFrameRateOverride.size(); i++) {
+ mPendingFrameRateOverrideUids.add(mTempFrameRateOverride.keyAt(i));
+ }
+ }
+
/**
* Return the insets currently applied to the display.
*
@@ -638,5 +707,7 @@
pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
+ pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
+ pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 45c38b4..ec8af78 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -23,6 +23,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
+import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import com.android.internal.util.IndentingPrintWriter;
@@ -52,6 +53,7 @@
public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
+ public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
/**
* Temporary display info, used for comparing display configurations.
@@ -331,6 +333,8 @@
mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
+ DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
+ display.getFrameRateOverrides();
display.updateLocked(mDisplayDeviceRepo);
if (!display.isValidLocked()) {
mLogicalDisplays.removeAt(i);
@@ -364,6 +368,9 @@
final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId)
? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED;
mListener.onLogicalDisplayEventLocked(display, eventMsg);
+ } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
+ mListener.onLogicalDisplayEventLocked(display,
+ LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
} else {
// While applications shouldn't know nor care about the non-overridden info, we
// still need to let WindowManager know so it can update its own internal state for
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index 49f0d35..cd3a453 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -16,8 +16,6 @@
package com.android.server.display;
-import com.android.internal.util.DumpUtils;
-
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
@@ -30,12 +28,14 @@
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.WindowManager;
-import android.view.TextureView.SurfaceTextureListener;
import android.widget.TextView;
+import com.android.internal.util.DumpUtils;
+
import java.io.PrintWriter;
/**
@@ -319,7 +319,7 @@
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
int width, int height) {
mListener.onWindowCreated(surfaceTexture,
- mDefaultDisplayInfo.getMode().getRefreshRate(),
+ mDefaultDisplayInfo.getRefreshRate(),
mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state);
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 1f72374..8ca057a 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -49,6 +49,8 @@
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
+ "junit",
+ "platform-compat-test-rules",
],
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 026db42..640d6e5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -44,8 +45,10 @@
import android.hardware.input.InputManagerInternal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.view.Display;
import android.view.DisplayCutout;
+import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -57,13 +60,17 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.display.DisplayDeviceInfo;
import com.android.server.display.DisplayManagerService.SyncRoot;
import com.android.server.lights.LightsManager;
import com.android.server.wm.WindowManagerInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -80,6 +87,9 @@
private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private Context mContext;
private final DisplayManagerService.Injector mShortMockedInjector =
@@ -95,15 +105,31 @@
return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
}
};
- private final DisplayManagerService.Injector mBasicInjector =
- new DisplayManagerService.Injector() {
+
+ class BasicInjector extends DisplayManagerService.Injector {
+ @Override
+ VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ (String name, boolean secure) -> mMockDisplayToken);
+ }
+ }
+
+ private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+
+ private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
+ new BasicInjector() {
@Override
- VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
- Context context, Handler handler,
- DisplayAdapter.Listener displayAdapterListener) {
- return new VirtualDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener,
- (String name, boolean secure) -> mMockDisplayToken);
+ boolean getAllowNonNativeRefreshRateOverride() {
+ return true;
+ }
+ };
+
+ private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
+ new BasicInjector() {
+ @Override
+ boolean getAllowNonNativeRefreshRateOverride() {
+ return false;
}
};
@@ -575,6 +601,337 @@
assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
}
+ /**
+ * Tests that there should be a display change notification if the frame rate overrides
+ * list is updated.
+ */
+ @Test
+ public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ int myUid = Process.myUid();
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+ });
+ assertTrue(callback.mCalled);
+ callback.clear();
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+ new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+ });
+ assertFalse(callback.mCalled);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 20f),
+ new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+ new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+ });
+ assertTrue(callback.mCalled);
+ callback.clear();
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+ new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+ });
+ assertTrue(callback.mCalled);
+ callback.clear();
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+ });
+ assertFalse(callback.mCalled);
+ }
+
+ /**
+ * Tests that the DisplayInfo is updated correctly with a frame rate override
+ */
+ @Test
+ public void testDisplayInfoFrameRateOverride() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f),
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid() + 1, 30f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+
+ // Changing the mode to 30Hz should not override the refresh rate to 20Hz anymore
+ // as 20 is not a divider of 30.
+ updateModeId(displayManager, displayDevice, 2);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(30f, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ /**
+ * Tests that the frame rate override is updated accordingly to the
+ * allowNonNativeRefreshRateOverride policy.
+ */
+ @Test
+ public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
+ testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
+ testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+ }
+
+ /**
+ * Tests that the mode reflects the frame rate override is in compat mode
+ */
+ @Test
+ @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
+ testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
+ }
+
+ /**
+ * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+ */
+ @Test
+ @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoFrameRateOverrideMode() throws Exception {
+ testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
+ }
+
+ /**
+ * Tests that the mode reflects the frame rate override is in compat mode and accordingly to the
+ * allowNonNativeRefreshRateOverride policy.
+ */
+ @Test
+ @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
+ testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
+ /*compatChangeEnabled*/ false);
+ testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
+ /*compatChangeEnabled*/ false);
+ }
+
+ /**
+ * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+ */
+ @Test
+ @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
+ testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
+ /*compatChangeEnabled*/ true);
+ testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
+ /*compatChangeEnabled*/ true);
+ }
+
+ private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
+ throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f),
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid() + 1, 30f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+ Display.Mode expectedMode;
+ if (compatChangeEnabled) {
+ expectedMode = new Display.Mode(1, 100, 200, 60f);
+ } else {
+ expectedMode = new Display.Mode(3, 100, 200, 20f);
+ }
+ assertEquals(expectedMode, displayInfo.getMode());
+ }
+
+ private void testDisplayInfoNonNativeFrameRateOverrideMode(
+ DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, injector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ Display.Mode expectedMode;
+ if (compatChangeEnabled) {
+ expectedMode = new Display.Mode(1, 100, 200, 60f);
+ } else if (injector.getAllowNonNativeRefreshRateOverride()) {
+ expectedMode = new Display.Mode(255, 100, 200, 20f);
+ } else {
+ expectedMode = new Display.Mode(1, 100, 200, 60f);
+ }
+ assertEquals(expectedMode, displayInfo.getMode());
+ }
+
+ private void testDisplayInfoNonNativeFrameRateOverride(
+ DisplayManagerService.Injector injector) {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, injector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
+ assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ private int getDisplayIdForDisplayDevice(
+ DisplayManagerService displayManager,
+ DisplayManagerService.BinderService displayManagerBinderService,
+ FakeDisplayDevice displayDevice) {
+
+ final int[] displayIds = displayManagerBinderService.getDisplayIds();
+ assertTrue(displayIds.length > 0);
+ int displayId = Display.INVALID_DISPLAY;
+ for (int i = 0; i < displayIds.length; i++) {
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]);
+ if (displayDevice.getDisplayDeviceInfoLocked().equals(ddi)) {
+ displayId = displayIds[i];
+ break;
+ }
+ }
+ assertFalse(displayId == Display.INVALID_DISPLAY);
+ return displayId;
+ }
+
+ private void updateDisplayDeviceInfo(DisplayManagerService displayManager,
+ FakeDisplayDevice displayDevice,
+ DisplayDeviceInfo displayDeviceInfo) {
+ displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
+ Handler handler = displayManager.getDisplayHandler();
+ handler.runWithScissors(() -> {
+ }, 0 /* now */);
+ }
+
+ private void updateFrameRateOverride(DisplayManagerService displayManager,
+ FakeDisplayDevice displayDevice,
+ DisplayEventReceiver.FrameRateOverride[] frameRateOverrides) {
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+ displayDeviceInfo.frameRateOverrides = frameRateOverrides;
+ updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+ }
+
+ private void updateModeId(DisplayManagerService displayManager,
+ FakeDisplayDevice displayDevice,
+ int modeId) {
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+ displayDeviceInfo.modeId = modeId;
+ updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+ }
+
+ private FakeDisplayManagerCallback registerDisplayListenerCallback(
+ DisplayManagerService displayManager,
+ DisplayManagerService.BinderService displayManagerBinderService,
+ FakeDisplayDevice displayDevice) {
+ // Find the display id of the added FakeDisplayDevice
+ DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+
+ Handler handler = displayManager.getDisplayHandler();
+ handler.runWithScissors(() -> {
+ }, 0 /* now */);
+
+ // register display listener callback
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
+ displayManagerBinderService.registerCallback(callback);
+ return callback;
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ float[] refreshRates) {
+ FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ int width = 100;
+ int height = 200;
+ displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
+ for (int i = 0; i < refreshRates.length; i++) {
+ displayDeviceInfo.supportedModes[i] =
+ new Display.Mode(i + 1, width, height, refreshRates[i]);
+ }
+ displayDeviceInfo.modeId = 1;
+ displayDeviceInfo.width = width;
+ displayDeviceInfo.height = height;
+ final Rect zeroRect = new Rect();
+ displayDeviceInfo.displayCutout = new DisplayCutout(
+ Insets.of(0, 10, 0, 0),
+ zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+ displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
+ displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+ return displayDevice;
+ }
+
private void registerDefaultDisplays(DisplayManagerService displayManager) {
Handler handler = displayManager.getDisplayHandler();
// Would prefer to call displayManager.onStart() directly here but it performs binderService
@@ -598,6 +955,10 @@
mCalled = true;
}
}
+
+ public void clear() {
+ mCalled = false;
+ }
}
private class FakeDisplayDevice extends DisplayDevice {