1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import static android.os.Trace.TRACE_TAG_VIEW; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.os.Trace; 24 import android.util.Log; 25 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 29 /** 30 * Controller to request refresh rate preference operations to the {@link ViewRootImpl}. 31 * 32 * @hide 33 */ 34 public class ViewRootRefreshRateController { 35 36 private static final String TAG = "VRRefreshRateController"; 37 38 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 39 40 private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f; 41 42 @NonNull 43 private final ViewRootImpl mViewRootImpl; 44 45 private final RefreshRateParams mRateParams; 46 47 private final boolean mHasPreferredRefreshRate; 48 49 private int mRefreshRatePref = RefreshRatePref.NONE; 50 51 private boolean mMaxRefreshRateOverride = false; 52 53 @IntDef(value = { 54 RefreshRatePref.NONE, 55 RefreshRatePref.LOWER, 56 RefreshRatePref.RESTORE, 57 }) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface RefreshRatePref { 60 /** 61 * Indicates that no refresh rate preference. 62 */ 63 int NONE = 0; 64 65 /** 66 * Indicates that apply the lower refresh rate. 67 */ 68 int LOWER = 1; 69 70 /** 71 * Indicates that restore to previous refresh rate. 72 */ 73 int RESTORE = 2; 74 } 75 ViewRootRefreshRateController(@onNull ViewRootImpl viewRoot)76 public ViewRootRefreshRateController(@NonNull ViewRootImpl viewRoot) { 77 mViewRootImpl = viewRoot; 78 mRateParams = new RefreshRateParams(getLowerSupportedRefreshRate()); 79 mHasPreferredRefreshRate = hasPreferredRefreshRate(); 80 if (mHasPreferredRefreshRate && DEBUG) { 81 Log.d(TAG, "App has preferred refresh rate. name:" + viewRoot); 82 } 83 } 84 85 /** 86 * Updates the preference to {@link ViewRootRefreshRateController#mRefreshRatePref}, 87 * and check if it's needed to update the preferred refresh rate on demand. Like if the 88 * user is typing, try to apply the {@link RefreshRateParams#mTargetRefreshRate}. 89 * 90 * @param refreshRatePref to indicate the refresh rate preference 91 */ updateRefreshRatePreference(@efreshRatePref int refreshRatePref)92 public void updateRefreshRatePreference(@RefreshRatePref int refreshRatePref) { 93 mRefreshRatePref = refreshRatePref; 94 doRefreshRateCheck(); 95 } 96 doRefreshRateCheck()97 private void doRefreshRateCheck() { 98 if (mRefreshRatePref == RefreshRatePref.NONE) { 99 return; 100 } 101 if (mHasPreferredRefreshRate) { 102 return; 103 } 104 if (DEBUG) { 105 Log.d(TAG, "mMaxRefreshRateOverride:" + mMaxRefreshRateOverride 106 + ", mRefreshRatePref:" + refreshRatePrefToString(mRefreshRatePref)); 107 } 108 109 switch (mRefreshRatePref) { 110 case RefreshRatePref.LOWER : 111 if (!mMaxRefreshRateOverride) { 112 // Save previous preferred rate before update 113 mRateParams.savePreviousRefreshRateParams(mViewRootImpl.mWindowAttributes); 114 updateMaxRefreshRate(); 115 } else if (mViewRootImpl.mDisplay.getRefreshRate() 116 > mRateParams.mTargetRefreshRate) { 117 // Boosted, try to update again. 118 updateMaxRefreshRate(); 119 } 120 break; 121 case RefreshRatePref.RESTORE : 122 resetRefreshRate(); 123 break; 124 default : 125 throw new RuntimeException("Unexpected value: " + mRefreshRatePref); 126 } 127 } 128 updateMaxRefreshRate()129 private void updateMaxRefreshRate() { 130 Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.updateMaxRefreshRate"); 131 WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; 132 params.preferredMaxDisplayRefreshRate = mRateParams.mTargetRefreshRate; 133 mViewRootImpl.setLayoutParams(params, false); 134 mMaxRefreshRateOverride = true; 135 Trace.instant(TRACE_TAG_VIEW, "VRRC update preferredMax=" 136 + mRateParams.mTargetRefreshRate); 137 Trace.traceEnd(TRACE_TAG_VIEW); 138 if (DEBUG) { 139 Log.d(TAG, "update max refresh rate to: " + params.preferredMaxDisplayRefreshRate); 140 } 141 } 142 resetRefreshRate()143 private void resetRefreshRate() { 144 if (!mMaxRefreshRateOverride) { 145 return; 146 } 147 Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.resetRefreshRate"); 148 WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; 149 params.preferredMaxDisplayRefreshRate = mRateParams.mPreviousPreferredMaxRefreshRate; 150 mViewRootImpl.setLayoutParams(params, false); 151 mMaxRefreshRateOverride = false; 152 Trace.instant(TRACE_TAG_VIEW, "VRRC restore previous=" 153 + mRateParams.mPreviousPreferredMaxRefreshRate); 154 Trace.traceEnd(TRACE_TAG_VIEW); 155 if (DEBUG) { 156 Log.d(TAG, "reset max refresh rate to: " + params.preferredMaxDisplayRefreshRate); 157 } 158 } 159 hasPreferredRefreshRate()160 private boolean hasPreferredRefreshRate() { 161 WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes; 162 return params.preferredRefreshRate > 0 163 || params.preferredMaxDisplayRefreshRate > 0 164 || params.preferredMinDisplayRefreshRate > 0 165 || params.preferredDisplayModeId > 0; 166 } 167 getLowerSupportedRefreshRate()168 private float getLowerSupportedRefreshRate() { 169 final Display display = mViewRootImpl.mDisplay; 170 final Display.Mode defaultMode = display.getDefaultMode(); 171 float targetRefreshRate = defaultMode.getRefreshRate(); 172 for (Display.Mode mode : display.getSupportedModes()) { 173 if (mode.getRefreshRate() < targetRefreshRate) { 174 targetRefreshRate = mode.getRefreshRate(); 175 } 176 } 177 if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) { 178 targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND; 179 } 180 return targetRefreshRate; 181 } 182 refreshRatePrefToString(@efreshRatePref int pref)183 private static String refreshRatePrefToString(@RefreshRatePref int pref) { 184 switch (pref) { 185 case RefreshRatePref.NONE: 186 return "NONE"; 187 case RefreshRatePref.LOWER: 188 return "LOWER"; 189 case RefreshRatePref.RESTORE: 190 return "RESTORE"; 191 default: 192 return "Unknown pref=" + pref; 193 } 194 } 195 196 /** 197 * A class for recording refresh rate parameters of the target view, including the target 198 * refresh rate we want to apply when entering particular states, and the original preferred 199 * refresh rate for restoring when leaving the state. 200 */ 201 private static class RefreshRateParams { 202 float mTargetRefreshRate; 203 204 float mPreviousPreferredMaxRefreshRate = 0; 205 RefreshRateParams(float targetRefreshRate)206 RefreshRateParams(float targetRefreshRate) { 207 mTargetRefreshRate = targetRefreshRate; 208 if (DEBUG) { 209 Log.d(TAG, "The target rate: " + targetRefreshRate); 210 } 211 } savePreviousRefreshRateParams(WindowManager.LayoutParams param)212 void savePreviousRefreshRateParams(WindowManager.LayoutParams param) { 213 mPreviousPreferredMaxRefreshRate = param.preferredMaxDisplayRefreshRate; 214 if (DEBUG) { 215 Log.d(TAG, "Save previous params, preferred: " + param.preferredRefreshRate 216 + ", Max: " + param.preferredMaxDisplayRefreshRate); 217 } 218 } 219 } 220 } 221