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