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