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 com.android.server.display.mode;
18 
19 import android.annotation.Nullable;
20 import android.hardware.display.DisplayManager;
21 import android.os.Handler;
22 import android.os.IThermalEventListener;
23 import android.os.Temperature;
24 import android.util.Slog;
25 import android.util.SparseArray;
26 import android.view.Display;
27 import android.view.DisplayInfo;
28 import android.view.SurfaceControl;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.os.BackgroundThread;
33 
34 import java.io.PrintWriter;
35 
36 final class SkinThermalStatusObserver extends IThermalEventListener.Stub implements
37         DisplayManager.DisplayListener {
38     private static final String TAG = "SkinThermalStatusObserver";
39 
40     private final VotesStorage mVotesStorage;
41     private final DisplayModeDirector.Injector mInjector;
42 
43     private boolean mLoggingEnabled;
44 
45     private final Handler mHandler;
46     private final Object mThermalObserverLock = new Object();
47     @GuardedBy("mThermalObserverLock")
48     @Temperature.ThrottlingStatus
49     private int mStatus = Temperature.THROTTLING_NONE;
50     @GuardedBy("mThermalObserverLock")
51     private final SparseArray<SparseArray<SurfaceControl.RefreshRateRange>>
52             mThermalThrottlingByDisplay = new SparseArray<>();
53 
SkinThermalStatusObserver(DisplayModeDirector.Injector injector, VotesStorage votesStorage)54     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
55             VotesStorage votesStorage) {
56         this(injector, votesStorage, BackgroundThread.getHandler());
57     }
58 
59     @VisibleForTesting
SkinThermalStatusObserver(DisplayModeDirector.Injector injector, VotesStorage votesStorage, Handler handler)60     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
61             VotesStorage votesStorage, Handler handler) {
62         mInjector = injector;
63         mVotesStorage = votesStorage;
64         mHandler = handler;
65     }
66 
67     @Nullable
findBestMatchingRefreshRateRange( @emperature.ThrottlingStatus int currentStatus, SparseArray<SurfaceControl.RefreshRateRange> throttlingMap)68     public static SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
69             @Temperature.ThrottlingStatus int currentStatus,
70             SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
71         SurfaceControl.RefreshRateRange foundRange = null;
72         for (int status = currentStatus; status >= 0; status--) {
73             foundRange = throttlingMap.get(status);
74             if (foundRange != null) {
75                 break;
76             }
77         }
78         return foundRange;
79     }
80 
observe()81     void observe() {
82         // if failed to register thermal service listener, don't register display listener
83         if (!mInjector.registerThermalServiceListener(this)) {
84             return;
85         }
86 
87         mInjector.registerDisplayListener(this, mHandler,
88                 DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
89                         | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
90 
91         populateInitialDisplayInfo();
92     }
93 
setLoggingEnabled(boolean enabled)94     void setLoggingEnabled(boolean enabled) {
95         mLoggingEnabled = enabled;
96     }
97 
98     @Override
notifyThrottling(Temperature temp)99     public void notifyThrottling(Temperature temp) {
100         @Temperature.ThrottlingStatus int currentStatus = temp.getStatus();
101 
102         synchronized (mThermalObserverLock) {
103             if (mStatus == currentStatus) {
104                 return; // status not changed, skip update
105             }
106             mStatus = currentStatus;
107             mHandler.post(this::updateVotes);
108         }
109 
110         if (mLoggingEnabled) {
111             Slog.d(TAG, "New thermal throttling status " + ", current thermal status = "
112                     + currentStatus);
113         }
114     }
115 
116     //region DisplayManager.DisplayListener
117     @Override
onDisplayAdded(int displayId)118     public void onDisplayAdded(int displayId) {
119         updateThermalRefreshRateThrottling(displayId);
120         if (mLoggingEnabled) {
121             Slog.d(TAG, "Display added:" + displayId);
122         }
123     }
124 
125     @Override
onDisplayRemoved(int displayId)126     public void onDisplayRemoved(int displayId) {
127         synchronized (mThermalObserverLock) {
128             mThermalThrottlingByDisplay.remove(displayId);
129             mHandler.post(() -> mVotesStorage.updateVote(displayId,
130                     Vote.PRIORITY_SKIN_TEMPERATURE, null));
131         }
132         if (mLoggingEnabled) {
133             Slog.d(TAG, "Display removed and voted: displayId=" + displayId);
134         }
135     }
136 
137     @Override
onDisplayChanged(int displayId)138     public void onDisplayChanged(int displayId) {
139         updateThermalRefreshRateThrottling(displayId);
140         if (mLoggingEnabled) {
141             Slog.d(TAG, "Display changed:" + displayId);
142         }
143     }
144     //endregion
145 
populateInitialDisplayInfo()146     private void populateInitialDisplayInfo() {
147         DisplayInfo info = new DisplayInfo();
148         Display[] displays = mInjector.getDisplays();
149         int size = displays.length;
150         SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap = new SparseArray<>(
151                 size);
152         for (Display d : displays) {
153             final int displayId = d.getDisplayId();
154             d.getDisplayInfo(info);
155             localMap.put(displayId, info.thermalRefreshRateThrottling);
156         }
157         synchronized (mThermalObserverLock) {
158             for (int i = 0; i < size; i++) {
159                 mThermalThrottlingByDisplay.put(localMap.keyAt(i), localMap.valueAt(i));
160             }
161         }
162         if (mLoggingEnabled) {
163             Slog.d(TAG, "Display initial info:" + localMap);
164         }
165     }
166 
updateThermalRefreshRateThrottling(int displayId)167     private void updateThermalRefreshRateThrottling(int displayId) {
168         DisplayInfo displayInfo = new DisplayInfo();
169         mInjector.getDisplayInfo(displayId, displayInfo);
170         SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
171                 displayInfo.thermalRefreshRateThrottling;
172 
173         synchronized (mThermalObserverLock) {
174             mThermalThrottlingByDisplay.put(displayId, throttlingMap);
175             mHandler.post(() -> updateVoteForDisplay(displayId));
176         }
177         if (mLoggingEnabled) {
178             Slog.d(TAG,
179                     "Thermal throttling updated: display=" + displayId + ", map=" + throttlingMap);
180         }
181     }
182 
183     //region in mHandler thread
updateVotes()184     private void updateVotes() {
185         @Temperature.ThrottlingStatus int localStatus;
186         SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap;
187 
188         synchronized (mThermalObserverLock) {
189             localStatus = mStatus;
190             localMap = mThermalThrottlingByDisplay.clone();
191         }
192         if (mLoggingEnabled) {
193             Slog.d(TAG, "Updating votes for status=" + localStatus + ", map=" + localMap);
194         }
195         int size = localMap.size();
196         for (int i = 0; i < size; i++) {
197             reportThrottlingIfNeeded(localMap.keyAt(i), localStatus, localMap.valueAt(i));
198         }
199     }
200 
updateVoteForDisplay(int displayId)201     private void updateVoteForDisplay(int displayId) {
202         @Temperature.ThrottlingStatus int localStatus;
203         SparseArray<SurfaceControl.RefreshRateRange> localMap;
204 
205         synchronized (mThermalObserverLock) {
206             localStatus = mStatus;
207             localMap = mThermalThrottlingByDisplay.get(displayId);
208         }
209         if (localMap == null) {
210             Slog.d(TAG, "Updating votes, display already removed, display=" + displayId);
211             return;
212         }
213         if (mLoggingEnabled) {
214             Slog.d(TAG, "Updating votes for status=" + localStatus + ", display =" + displayId
215                     + ", map=" + localMap);
216         }
217         reportThrottlingIfNeeded(displayId, localStatus, localMap);
218     }
219 
reportThrottlingIfNeeded(int displayId, @Temperature.ThrottlingStatus int currentStatus, SparseArray<SurfaceControl.RefreshRateRange> throttlingMap)220     private void reportThrottlingIfNeeded(int displayId,
221             @Temperature.ThrottlingStatus int currentStatus,
222             SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
223         if (currentStatus == -1) { // no throttling status reported from thermal sensor yet
224             return;
225         }
226 
227         if (throttlingMap.size() == 0) { // map is not configured, using default behaviour
228             fallbackReportThrottlingIfNeeded(displayId, currentStatus);
229             return;
230         }
231 
232         SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus,
233                 throttlingMap);
234         // if status <= currentStatus not found in the map reset vote
235         Vote vote = null;
236         if (foundRange != null) { // otherwise vote with found range
237             vote = Vote.forRenderFrameRates(foundRange.min, foundRange.max);
238         }
239         mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
240         if (mLoggingEnabled) {
241             Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId);
242         }
243     }
244 
fallbackReportThrottlingIfNeeded(int displayId, @Temperature.ThrottlingStatus int currentStatus)245     private void fallbackReportThrottlingIfNeeded(int displayId,
246             @Temperature.ThrottlingStatus int currentStatus) {
247         Vote vote = null;
248         if (currentStatus >= Temperature.THROTTLING_CRITICAL) {
249             vote = Vote.forRenderFrameRates(0f, 60f);
250         }
251         mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
252         if (mLoggingEnabled) {
253             Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId);
254         }
255     }
256     //endregion
257 
dumpLocked(PrintWriter writer)258     void dumpLocked(PrintWriter writer) {
259         @Temperature.ThrottlingStatus int localStatus;
260         SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap;
261 
262         synchronized (mThermalObserverLock) {
263             localStatus = mStatus;
264             localMap = mThermalThrottlingByDisplay.clone();
265         }
266 
267         writer.println("  SkinThermalStatusObserver:");
268         writer.println("    mStatus: " + localStatus);
269         writer.println("    mThermalThrottlingByDisplay: " + localMap);
270     }
271 }
272