• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.display;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.hardware.display.BrightnessInfo;
22 import android.hardware.display.DisplayManager;
23 import android.os.Handler;
24 import android.os.HandlerExecutor;
25 import android.os.IThermalEventListener;
26 import android.os.IThermalService;
27 import android.os.PowerManager;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.Temperature;
31 import android.provider.DeviceConfig;
32 import android.provider.DeviceConfigInterface;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
37 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
38 
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * This class monitors various conditions, such as skin temperature throttling status, and limits
47  * the allowed brightness range accordingly.
48  */
49 class BrightnessThrottler {
50     private static final String TAG = "BrightnessThrottler";
51     private static final boolean DEBUG = false;
52 
53     private static final int THROTTLING_INVALID = -1;
54 
55     private final Injector mInjector;
56     private final Handler mHandler;
57     // We need a separate handler for unit testing. These two handlers are the same throughout the
58     // non-test code.
59     private final Handler mDeviceConfigHandler;
60     private final Runnable mThrottlingChangeCallback;
61     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
62     private final DeviceConfigListener mDeviceConfigListener;
63     private final DeviceConfigInterface mDeviceConfig;
64 
65     private int mThrottlingStatus;
66     private BrightnessThrottlingData mThrottlingData;
67     private BrightnessThrottlingData mDdcThrottlingData;
68     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
69     private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
70         BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
71     private String mUniqueDisplayId;
72 
73     // The most recent string that has been set from DeviceConfig
74     private String mBrightnessThrottlingDataString;
75 
76     // This is a collection of brightness throttling data that has been written as overrides from
77     // the DeviceConfig. This will always take priority over the display device config data.
78     private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride =
79             new HashMap<>(1);
80 
BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, String uniqueDisplayId)81     BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
82             Runnable throttlingChangeCallback, String uniqueDisplayId) {
83         this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback,
84                 uniqueDisplayId);
85     }
86 
87     @VisibleForTesting
BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, String uniqueDisplayId)88     BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
89             BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback,
90             String uniqueDisplayId) {
91         mInjector = injector;
92 
93         mHandler = handler;
94         mDeviceConfigHandler = deviceConfigHandler;
95         mThrottlingData = throttlingData;
96         mDdcThrottlingData = throttlingData;
97         mThrottlingChangeCallback = throttlingChangeCallback;
98         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
99 
100         mUniqueDisplayId = uniqueDisplayId;
101         mDeviceConfig = injector.getDeviceConfig();
102         mDeviceConfigListener = new DeviceConfigListener();
103 
104         resetThrottlingData(mThrottlingData, mUniqueDisplayId);
105     }
106 
deviceSupportsThrottling()107     boolean deviceSupportsThrottling() {
108         return mThrottlingData != null;
109     }
110 
getBrightnessCap()111     float getBrightnessCap() {
112         return mBrightnessCap;
113     }
114 
getBrightnessMaxReason()115     int getBrightnessMaxReason() {
116         return mBrightnessMaxReason;
117     }
118 
isThrottled()119     boolean isThrottled() {
120         return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
121     }
122 
stop()123     void stop() {
124         mSkinThermalStatusObserver.stopObserving();
125         mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
126         // We're asked to stop throttling, so reset brightness restrictions.
127         mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
128         mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
129 
130         // We set throttling status to an invalid value here so that we act on the first throttling
131         // value received from the thermal service after registration, even if that throttling value
132         // is THROTTLING_NONE.
133         mThrottlingStatus = THROTTLING_INVALID;
134     }
135 
resetThrottlingData()136     private void resetThrottlingData() {
137         resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId);
138     }
139 
resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId)140     void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) {
141         stop();
142 
143         mUniqueDisplayId = displayId;
144         mDdcThrottlingData = throttlingData;
145         mDeviceConfigListener.startListening();
146         reloadBrightnessThrottlingDataOverride();
147         mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId,
148                 throttlingData);
149 
150         if (deviceSupportsThrottling()) {
151             mSkinThermalStatusObserver.startObserving();
152         }
153     }
154 
verifyAndConstrainBrightnessCap(float brightness)155     private float verifyAndConstrainBrightnessCap(float brightness) {
156         if (brightness < PowerManager.BRIGHTNESS_MIN) {
157             Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible "
158                     + "brightness " + PowerManager.BRIGHTNESS_MIN);
159             brightness = PowerManager.BRIGHTNESS_MIN;
160         }
161 
162         if (brightness > PowerManager.BRIGHTNESS_MAX) {
163             Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible "
164                     + "brightness " + PowerManager.BRIGHTNESS_MAX);
165             brightness = PowerManager.BRIGHTNESS_MAX;
166         }
167 
168         return brightness;
169     }
170 
thermalStatusChanged(@emperature.ThrottlingStatus int newStatus)171     private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
172         if (mThrottlingStatus != newStatus) {
173             mThrottlingStatus = newStatus;
174             updateThrottling();
175         }
176     }
177 
updateThrottling()178     private void updateThrottling() {
179         if (!deviceSupportsThrottling()) {
180             return;
181         }
182 
183         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
184         int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
185 
186         if (mThrottlingStatus != THROTTLING_INVALID) {
187             // Throttling levels are sorted by increasing severity
188             for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
189                 if (level.thermalStatus <= mThrottlingStatus) {
190                     brightnessCap = level.brightness;
191                     brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
192                 } else {
193                     // Throttling levels that are greater than the current status are irrelevant
194                     break;
195                 }
196             }
197         }
198 
199         if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) {
200             mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap);
201             mBrightnessMaxReason = brightnessMaxReason;
202 
203             if (DEBUG) {
204                 Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap
205                         + ", mBrightnessMaxReason = "
206                         + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
207             }
208 
209             if (mThrottlingChangeCallback != null) {
210                 mThrottlingChangeCallback.run();
211             }
212         }
213     }
214 
dump(PrintWriter pw)215     void dump(PrintWriter pw) {
216         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
217     }
218 
dumpLocal(PrintWriter pw)219     private void dumpLocal(PrintWriter pw) {
220         pw.println("BrightnessThrottler:");
221         pw.println("  mThrottlingData=" + mThrottlingData);
222         pw.println("  mDdcThrottlingData=" + mDdcThrottlingData);
223         pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
224         pw.println("  mThrottlingStatus=" + mThrottlingStatus);
225         pw.println("  mBrightnessCap=" + mBrightnessCap);
226         pw.println("  mBrightnessMaxReason=" +
227             BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
228         pw.println("  mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride);
229         pw.println("  mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString);
230 
231         mSkinThermalStatusObserver.dump(pw);
232     }
233 
getBrightnessThrottlingDataString()234     private String getBrightnessThrottlingDataString() {
235         return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
236                 DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
237                 /* defaultValue= */ null);
238     }
239 
parseAndSaveData(@onNull String strArray, @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData)240     private boolean parseAndSaveData(@NonNull String strArray,
241             @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) {
242         boolean validConfig = true;
243         String[] items = strArray.split(",");
244         int i = 0;
245 
246         try {
247             String uniqueDisplayId = items[i++];
248 
249             // number of throttling points
250             int noOfThrottlingPoints = Integer.parseInt(items[i++]);
251             List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
252 
253             // throttling level and point
254             for (int j = 0; j < noOfThrottlingPoints; j++) {
255                 String severity = items[i++];
256                 int status = parseThermalStatus(severity);
257 
258                 float brightnessPoint = parseBrightness(items[i++]);
259 
260                 throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
261             }
262             BrightnessThrottlingData toSave =
263                     DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels);
264             tempBrightnessThrottlingData.put(uniqueDisplayId, toSave);
265         } catch (NumberFormatException | IndexOutOfBoundsException
266                 | UnknownThermalStatusException e) {
267             validConfig = false;
268             Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
269         }
270 
271         if (i != items.length) {
272             validConfig = false;
273         }
274 
275         return validConfig;
276     }
277 
reloadBrightnessThrottlingDataOverride()278     public void reloadBrightnessThrottlingDataOverride() {
279         HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData =
280                 new HashMap<>(1);
281         mBrightnessThrottlingDataString = getBrightnessThrottlingDataString();
282         boolean validConfig = true;
283         mBrightnessThrottlingDataOverride.clear();
284         if (mBrightnessThrottlingDataString != null) {
285             String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";");
286             for (String s : throttlingDataSplits) {
287                 if (!parseAndSaveData(s, tempBrightnessThrottlingData)) {
288                     validConfig = false;
289                     break;
290                 }
291             }
292 
293             if (validConfig) {
294                 mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData);
295                 tempBrightnessThrottlingData.clear();
296             }
297 
298         } else {
299             Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null");
300         }
301     }
302 
303     /**
304      * Listens to config data change and updates the brightness throttling data using
305      * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA.
306      * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe,
307      * 0.379518072;local:4619827677550801151,1,moderate,0.75"
308      * In this order:
309      * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
310      * Where the latter part is repeated for each throttling level, and the entirety is repeated
311      * for each display, separated by a semicolon.
312      */
313     public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
314         public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
315 
startListening()316         public void startListening() {
317             mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
318                     mExecutor, this);
319         }
320 
321         @Override
onPropertiesChanged(DeviceConfig.Properties properties)322         public void onPropertiesChanged(DeviceConfig.Properties properties) {
323             reloadBrightnessThrottlingDataOverride();
324             resetThrottlingData();
325         }
326     }
327 
parseBrightness(String intVal)328     private float parseBrightness(String intVal) throws NumberFormatException {
329         float value = Float.parseFloat(intVal);
330         if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
331             throw new NumberFormatException("Brightness constraint value out of bounds.");
332         }
333         return value;
334     }
335 
parseThermalStatus(@onNull String value)336     @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
337             throws UnknownThermalStatusException {
338         switch (value) {
339             case "none":
340                 return PowerManager.THERMAL_STATUS_NONE;
341             case "light":
342                 return PowerManager.THERMAL_STATUS_LIGHT;
343             case "moderate":
344                 return PowerManager.THERMAL_STATUS_MODERATE;
345             case "severe":
346                 return PowerManager.THERMAL_STATUS_SEVERE;
347             case "critical":
348                 return PowerManager.THERMAL_STATUS_CRITICAL;
349             case "emergency":
350                 return PowerManager.THERMAL_STATUS_EMERGENCY;
351             case "shutdown":
352                 return PowerManager.THERMAL_STATUS_SHUTDOWN;
353             default:
354                 throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
355         }
356     }
357 
358     private static class UnknownThermalStatusException extends Exception {
UnknownThermalStatusException(String message)359         UnknownThermalStatusException(String message) {
360             super(message);
361         }
362     }
363 
364     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
365         private final Injector mInjector;
366         private final Handler mHandler;
367 
368         private IThermalService mThermalService;
369         private boolean mStarted;
370 
SkinThermalStatusObserver(Injector injector, Handler handler)371         SkinThermalStatusObserver(Injector injector, Handler handler) {
372             mInjector = injector;
373             mHandler = handler;
374         }
375 
376         @Override
notifyThrottling(Temperature temp)377         public void notifyThrottling(Temperature temp) {
378             if (DEBUG) {
379                 Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
380             }
381             mHandler.post(() -> {
382                 final @Temperature.ThrottlingStatus int status = temp.getStatus();
383                 thermalStatusChanged(status);
384             });
385         }
386 
startObserving()387         void startObserving() {
388             if (mStarted) {
389                 if (DEBUG) {
390                     Slog.d(TAG, "Thermal status observer already started");
391                 }
392                 return;
393             }
394             mThermalService = mInjector.getThermalService();
395             if (mThermalService == null) {
396                 Slog.e(TAG, "Could not observe thermal status. Service not available");
397                 return;
398             }
399             try {
400                 // We get a callback immediately upon registering so there's no need to query
401                 // for the current value.
402                 mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
403                 mStarted = true;
404             } catch (RemoteException e) {
405                 Slog.e(TAG, "Failed to register thermal status listener", e);
406             }
407         }
408 
stopObserving()409         void stopObserving() {
410             if (!mStarted) {
411                 if (DEBUG) {
412                     Slog.d(TAG, "Stop skipped because thermal status observer not started");
413                 }
414                 return;
415             }
416             try {
417                 mThermalService.unregisterThermalEventListener(this);
418                 mStarted = false;
419             } catch (RemoteException e) {
420                 Slog.e(TAG, "Failed to unregister thermal status listener", e);
421             }
422             mThermalService = null;
423         }
424 
dump(PrintWriter writer)425         void dump(PrintWriter writer) {
426             writer.println("  SkinThermalStatusObserver:");
427             writer.println("    mStarted: " + mStarted);
428             if (mThermalService != null) {
429                 writer.println("    ThermalService available");
430             } else {
431                 writer.println("    ThermalService not available");
432             }
433         }
434     }
435 
436     public static class Injector {
getThermalService()437         public IThermalService getThermalService() {
438             return IThermalService.Stub.asInterface(
439                     ServiceManager.getService(Context.THERMAL_SERVICE));
440         }
441 
442         @NonNull
getDeviceConfig()443         public DeviceConfigInterface getDeviceConfig() {
444             return DeviceConfigInterface.REAL;
445         }
446     }
447 }
448