• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.server.wm;
18 
19 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
20 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
21 
22 import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
23 import android.view.Display;
24 import android.view.Display.Mode;
25 import android.view.DisplayInfo;
26 
27 import java.util.HashMap;
28 
29 /**
30  * Policy to select a lower refresh rate for the display if applicable.
31  */
32 class RefreshRatePolicy {
33 
34     class PackageRefreshRate {
35         private final HashMap<String, RefreshRateRange> mPackages = new HashMap<>();
36 
add(String s, float minRefreshRate, float maxRefreshRate)37         public void add(String s, float minRefreshRate, float maxRefreshRate) {
38             float minSupportedRefreshRate =
39                     Math.max(RefreshRatePolicy.this.mMinSupportedRefreshRate, minRefreshRate);
40             float maxSupportedRefreshRate =
41                     Math.min(RefreshRatePolicy.this.mMaxSupportedRefreshRate, maxRefreshRate);
42 
43             mPackages.put(s,
44                     new RefreshRateRange(minSupportedRefreshRate, maxSupportedRefreshRate));
45         }
46 
get(String s)47         public RefreshRateRange get(String s) {
48             return mPackages.get(s);
49         }
50 
remove(String s)51         public void remove(String s) {
52             mPackages.remove(s);
53         }
54     }
55 
56     private final DisplayInfo mDisplayInfo;
57     private final Mode mDefaultMode;
58     private final Mode mLowRefreshRateMode;
59     private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
60     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
61     private final WindowManagerService mWmService;
62     private float mMinSupportedRefreshRate;
63     private float mMaxSupportedRefreshRate;
64 
65     /**
66      * The following constants represent priority of the window. SF uses this information when
67      * deciding which window has a priority when deciding about the refresh rate of the screen.
68      * Priority 0 is considered the highest priority. -1 means that the priority is unset.
69      */
70     static final int LAYER_PRIORITY_UNSET = -1;
71     /** Windows that are in focus and voted for the preferred mode ID have the highest priority. */
72     static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0;
73     /**
74      * This is a default priority for all windows that are in focus, but have not requested a
75      * specific mode ID.
76      */
77     static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1;
78     /**
79      * Windows that are not in focus, but voted for a specific mode ID should be
80      * acknowledged by SF. For example, there are two applications in a split screen.
81      * One voted for a given mode ID, and the second one doesn't care. Even though the
82      * second one might be in focus, we can honor the mode ID of the first one.
83      */
84     static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
85 
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo, HighRefreshRateDenylist denylist)86     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
87             HighRefreshRateDenylist denylist) {
88         mDisplayInfo = displayInfo;
89         mDefaultMode = displayInfo.getDefaultMode();
90         mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
91         mHighRefreshRateDenylist = denylist;
92         mWmService = wmService;
93     }
94 
95     /**
96      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
97      * default mode.
98      */
findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode)99     private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
100         float[] refreshRates = displayInfo.getDefaultRefreshRates();
101         float bestRefreshRate = defaultMode.getRefreshRate();
102         mMinSupportedRefreshRate = bestRefreshRate;
103         mMaxSupportedRefreshRate = bestRefreshRate;
104         for (int i = refreshRates.length - 1; i >= 0; i--) {
105             mMinSupportedRefreshRate = Math.min(mMinSupportedRefreshRate, refreshRates[i]);
106             mMaxSupportedRefreshRate = Math.max(mMaxSupportedRefreshRate, refreshRates[i]);
107 
108             if (refreshRates[i] >= 60f && refreshRates[i] < bestRefreshRate) {
109                 bestRefreshRate = refreshRates[i];
110             }
111         }
112         return displayInfo.findDefaultModeByRefreshRate(bestRefreshRate);
113     }
114 
addRefreshRateRangeForPackage(String packageName, float minRefreshRate, float maxRefreshRate)115     void addRefreshRateRangeForPackage(String packageName,
116             float minRefreshRate, float maxRefreshRate) {
117         mNonHighRefreshRatePackages.add(packageName, minRefreshRate, maxRefreshRate);
118         mWmService.requestTraversal();
119     }
120 
removeRefreshRateRangeForPackage(String packageName)121     void removeRefreshRateRangeForPackage(String packageName) {
122         mNonHighRefreshRatePackages.remove(packageName);
123         mWmService.requestTraversal();
124     }
125 
getPreferredModeId(WindowState w)126     int getPreferredModeId(WindowState w) {
127         final int preferredDisplayModeId = w.mAttrs.preferredDisplayModeId;
128         if (preferredDisplayModeId <= 0) {
129             // Unspecified, use default mode.
130             return 0;
131         }
132 
133         // If app is animating, it's not able to control refresh rate because we want the animation
134         // to run in default refresh rate. But if the display size of default mode is different
135         // from the using preferred mode, then still keep the preferred mode to avoid disturbing
136         // the animation.
137         if (w.isAnimating(TRANSITION | PARENTS)) {
138             Display.Mode preferredMode = null;
139             for (Display.Mode mode : mDisplayInfo.supportedModes) {
140                 if (preferredDisplayModeId == mode.getModeId()) {
141                     preferredMode = mode;
142                     break;
143                 }
144             }
145             if (preferredMode != null) {
146                 final int pW = preferredMode.getPhysicalWidth();
147                 final int pH = preferredMode.getPhysicalHeight();
148                 if ((pW != mDefaultMode.getPhysicalWidth()
149                         || pH != mDefaultMode.getPhysicalHeight())
150                         && pW == mDisplayInfo.getNaturalWidth()
151                         && pH == mDisplayInfo.getNaturalHeight()) {
152                     // Prefer not to change display size when animating.
153                     return preferredDisplayModeId;
154                 }
155             }
156             return 0;
157         }
158 
159         return preferredDisplayModeId;
160     }
161 
162     /**
163      * Calculate the priority based on whether the window is in focus and whether the application
164      * voted for a specific refresh rate.
165      *
166      * TODO(b/144307188): This is a very basic algorithm version. Explore other signals that might
167      * be useful in edge cases when we are deciding which layer should get priority when deciding
168      * about the refresh rate.
169      */
calculatePriority(WindowState w)170     int calculatePriority(WindowState w) {
171         boolean isFocused = w.isFocused();
172         int preferredModeId = getPreferredModeId(w);
173 
174         if (!isFocused && preferredModeId > 0) {
175             return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE;
176         }
177         if (isFocused && preferredModeId == 0) {
178             return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE;
179         }
180         if (isFocused && preferredModeId > 0) {
181             return LAYER_PRIORITY_FOCUSED_WITH_MODE;
182         }
183         return LAYER_PRIORITY_UNSET;
184     }
185 
getPreferredRefreshRate(WindowState w)186     float getPreferredRefreshRate(WindowState w) {
187         // If app is animating, it's not able to control refresh rate because we want the animation
188         // to run in default refresh rate.
189         if (w.isAnimating(TRANSITION | PARENTS)) {
190             return 0;
191         }
192 
193         // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
194         // of that mode id.
195         final int preferredModeId = w.mAttrs.preferredDisplayModeId;
196         if (preferredModeId > 0) {
197             for (Display.Mode mode : mDisplayInfo.supportedModes) {
198                 if (preferredModeId == mode.getModeId()) {
199                     return mode.getRefreshRate();
200                 }
201             }
202         }
203 
204         if (w.mAttrs.preferredRefreshRate > 0) {
205             return w.mAttrs.preferredRefreshRate;
206         }
207 
208         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
209         // list, we return the low refresh rate as the preferred one.
210         final String packageName = w.getOwningPackage();
211         if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
212             return mLowRefreshRateMode.getRefreshRate();
213         }
214 
215         return 0;
216     }
217 
getPreferredMinRefreshRate(WindowState w)218     float getPreferredMinRefreshRate(WindowState w) {
219         // If app is animating, it's not able to control refresh rate because we want the animation
220         // to run in default refresh rate.
221         if (w.isAnimating(TRANSITION | PARENTS)) {
222             return 0;
223         }
224 
225         if (w.mAttrs.preferredMinDisplayRefreshRate > 0) {
226             return w.mAttrs.preferredMinDisplayRefreshRate;
227         }
228 
229         String packageName = w.getOwningPackage();
230         // If app is using Camera, we set both the min and max refresh rate to the camera's
231         // preferred refresh rate to make sure we don't end up with a refresh rate lower
232         // than the camera capture rate, which will lead to dropping camera frames.
233         RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName);
234         if (range != null) {
235             return range.min;
236         }
237 
238         return 0;
239     }
240 
getPreferredMaxRefreshRate(WindowState w)241     float getPreferredMaxRefreshRate(WindowState w) {
242         // If app is animating, it's not able to control refresh rate because we want the animation
243         // to run in default refresh rate.
244         if (w.isAnimating(TRANSITION | PARENTS)) {
245             return 0;
246         }
247 
248         if (w.mAttrs.preferredMaxDisplayRefreshRate > 0) {
249             return w.mAttrs.preferredMaxDisplayRefreshRate;
250         }
251 
252         final String packageName = w.getOwningPackage();
253         // If app is using Camera, force it to default (lower) refresh rate.
254         RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName);
255         if (range != null) {
256             return range.max;
257         }
258 
259         return 0;
260     }
261 }
262