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.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Trace;
22 import android.util.Slog;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.io.PrintWriter;
29 
30 class VotesStorage {
31     private static final String TAG = "VotesStorage";
32     // Special ID used to indicate that given vote is to be applied globally, rather than to a
33     // specific display.
34     private static final int GLOBAL_ID = -1;
35 
36     private boolean mLoggingEnabled;
37 
38     private final Listener mListener;
39 
40     private final Object mStorageLock = new Object();
41     // A map from the display ID to the collection of votes and their priority. The latter takes
42     // the form of another map from the priority to the vote itself so that each priority is
43     // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
44     @GuardedBy("mStorageLock")
45     private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
46 
VotesStorage(@onNull Listener listener)47     VotesStorage(@NonNull Listener listener) {
48         mListener = listener;
49     }
50     /** sets logging enabled/disabled for this class */
setLoggingEnabled(boolean loggingEnabled)51     void setLoggingEnabled(boolean loggingEnabled) {
52         mLoggingEnabled = loggingEnabled;
53     }
54     /**
55      * gets all votes for specific display, note that global display votes are also added to result
56      */
57     @NonNull
getVotes(int displayId)58     SparseArray<Vote> getVotes(int displayId) {
59         SparseArray<Vote> votesLocal;
60         SparseArray<Vote> globalVotesLocal;
61         synchronized (mStorageLock) {
62             SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
63             votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
64             SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
65             globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
66         }
67         for (int i = 0; i < globalVotesLocal.size(); i++) {
68             int priority = globalVotesLocal.keyAt(i);
69             if (!votesLocal.contains(priority)) {
70                 votesLocal.put(priority, globalVotesLocal.valueAt(i));
71             }
72         }
73         return votesLocal;
74     }
75 
76     /** updates vote storage for all displays */
updateGlobalVote(int priority, @Nullable Vote vote)77     void updateGlobalVote(int priority, @Nullable Vote vote) {
78         updateVote(GLOBAL_ID, priority, vote);
79     }
80 
81     /** updates vote storage */
updateVote(int displayId, int priority, @Nullable Vote vote)82     void updateVote(int displayId, int priority, @Nullable Vote vote) {
83         if (mLoggingEnabled) {
84             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
85                     + ", priority=" + Vote.priorityToString(priority)
86                     + ", vote=" + vote + ")");
87         }
88         if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
89             Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
90                     + " priority=" + Vote.priorityToString(priority)
91                     + ", vote=" + vote);
92             return;
93         }
94         SparseArray<Vote> votes;
95         synchronized (mStorageLock) {
96             if (mVotesByDisplay.contains(displayId)) {
97                 votes = mVotesByDisplay.get(displayId);
98             } else {
99                 votes = new SparseArray<>();
100                 mVotesByDisplay.put(displayId, votes);
101             }
102             if (vote != null) {
103                 votes.put(priority, vote);
104             } else {
105                 votes.remove(priority);
106             }
107         }
108         Trace.traceCounter(Trace.TRACE_TAG_POWER,
109                 TAG + "." + displayId + ":" + Vote.priorityToString(priority),
110                 getMaxPhysicalRefreshRate(vote));
111         if (mLoggingEnabled) {
112             Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
113         }
114         mListener.onChanged();
115     }
116 
117     /** dump class values, for debugging */
dump(@onNull PrintWriter pw)118     void dump(@NonNull PrintWriter pw) {
119         SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
120         synchronized (mStorageLock) {
121             for (int i = 0; i < mVotesByDisplay.size(); i++) {
122                 votesByDisplayLocal.put(mVotesByDisplay.keyAt(i),
123                         mVotesByDisplay.valueAt(i).clone());
124             }
125         }
126         pw.println("  mVotesByDisplay:");
127         for (int i = 0; i < votesByDisplayLocal.size(); i++) {
128             SparseArray<Vote> votes = votesByDisplayLocal.valueAt(i);
129             if (votes.size() == 0) {
130                 continue;
131             }
132             pw.println("    " + votesByDisplayLocal.keyAt(i) + ":");
133             for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
134                 Vote vote = votes.get(p);
135                 if (vote == null) {
136                     continue;
137                 }
138                 pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
139             }
140         }
141     }
142 
143     @VisibleForTesting
injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay)144     void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
145         synchronized (mStorageLock) {
146             mVotesByDisplay.clear();
147             for (int i = 0; i < votesByDisplay.size(); i++) {
148                 mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i));
149             }
150         }
151     }
152 
getMaxPhysicalRefreshRate(@ullable Vote vote)153     private int getMaxPhysicalRefreshRate(@Nullable Vote vote) {
154         if (vote == null) {
155             return -1;
156         } else if (vote.refreshRateRanges.physical.max == Float.POSITIVE_INFINITY) {
157             return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable
158         }
159         return (int) vote.refreshRateRanges.physical.max;
160     }
161 
162     interface Listener {
onChanged()163         void onChanged();
164     }
165 }
166