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.util.IntArray;
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     @VisibleForTesting
35     static final int GLOBAL_ID = -1;
36 
37     private boolean mLoggingEnabled;
38 
39     private final Listener mListener;
40 
41     @Nullable
42     private final VotesStatsReporter mVotesStatsReporter;
43 
44     private final Object mStorageLock = new Object();
45     // A map from the display ID to the collection of votes and their priority. The latter takes
46     // the form of another map from the priority to the vote itself so that each priority is
47     // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
48     @GuardedBy("mStorageLock")
49     private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
50 
VotesStorage(@onNull Listener listener, @Nullable VotesStatsReporter votesStatsReporter)51     VotesStorage(@NonNull Listener listener, @Nullable VotesStatsReporter votesStatsReporter) {
52         mListener = listener;
53         mVotesStatsReporter = votesStatsReporter;
54     }
55     /** sets logging enabled/disabled for this class */
setLoggingEnabled(boolean loggingEnabled)56     void setLoggingEnabled(boolean loggingEnabled) {
57         mLoggingEnabled = loggingEnabled;
58     }
59     /**
60      * gets all votes for specific display, note that global display votes are also added to result
61      */
62     @NonNull
getVotes(int displayId)63     SparseArray<Vote> getVotes(int displayId) {
64         SparseArray<Vote> votesLocal;
65         SparseArray<Vote> globalVotesLocal;
66         synchronized (mStorageLock) {
67             SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
68             votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
69             SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
70             globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
71         }
72         for (int i = 0; i < globalVotesLocal.size(); i++) {
73             int priority = globalVotesLocal.keyAt(i);
74             if (!votesLocal.contains(priority)) {
75                 votesLocal.put(priority, globalVotesLocal.valueAt(i));
76             }
77         }
78         return votesLocal;
79     }
80 
81     /** updates vote storage for all displays */
updateGlobalVote(@ote.Priority int priority, @Nullable Vote vote)82     void updateGlobalVote(@Vote.Priority int priority, @Nullable Vote vote) {
83         updateVote(GLOBAL_ID, priority, vote);
84     }
85 
86     /** updates vote storage */
updateVote(int displayId, @Vote.Priority int priority, @Nullable Vote vote)87     void updateVote(int displayId, @Vote.Priority int priority, @Nullable Vote vote) {
88         if (mLoggingEnabled) {
89             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
90                     + ", priority=" + Vote.priorityToString(priority)
91                     + ", vote=" + vote + ")");
92         }
93         if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
94             Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
95                     + " priority=" + Vote.priorityToString(priority)
96                     + ", vote=" + vote);
97             return;
98         }
99         boolean changed = false;
100         SparseArray<Vote> votes;
101         synchronized (mStorageLock) {
102             if (mVotesByDisplay.contains(displayId)) {
103                 votes = mVotesByDisplay.get(displayId);
104             } else {
105                 votes = new SparseArray<>();
106                 mVotesByDisplay.put(displayId, votes);
107             }
108             var currentVote = votes.get(priority);
109             if (vote != null && !vote.equals(currentVote)) {
110                 votes.put(priority, vote);
111                 changed = true;
112             } else if (vote == null && currentVote != null) {
113                 votes.remove(priority);
114                 changed = true;
115             }
116         }
117         if (mLoggingEnabled) {
118             Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
119         }
120         if (changed) {
121             if (mVotesStatsReporter != null) {
122                 mVotesStatsReporter.reportVoteChanged(displayId, priority, vote);
123             }
124             mListener.onChanged();
125         }
126     }
127 
128     /** removes all votes with certain priority from vote storage */
removeAllVotesForPriority(@ote.Priority int priority)129     void removeAllVotesForPriority(@Vote.Priority int priority) {
130         if (mLoggingEnabled) {
131             Slog.i(TAG, "removeAllVotesForPriority(priority="
132                     + Vote.priorityToString(priority) + ")");
133         }
134         if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
135             Slog.w(TAG, "Received an invalid priority, ignoring:"
136                     + " priority=" + Vote.priorityToString(priority));
137             return;
138         }
139         IntArray removedVotesDisplayIds = new IntArray();
140         synchronized (mStorageLock) {
141             int size = mVotesByDisplay.size();
142             for (int i = 0; i < size; i++) {
143                 SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
144                 if (votes.get(priority) != null) {
145                     votes.remove(priority);
146                     removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i));
147                 }
148             }
149         }
150         if (mLoggingEnabled) {
151             Slog.i(TAG, "Removed votes with priority=" + priority
152                     + " for displays=" + removedVotesDisplayIds);
153         }
154         int removedVotesSize = removedVotesDisplayIds.size();
155         if (removedVotesSize > 0) {
156             if (mVotesStatsReporter != null) {
157                 for (int i = 0; i < removedVotesSize; i++) {
158                     mVotesStatsReporter.reportVoteChanged(
159                             removedVotesDisplayIds.get(i), priority, null);
160                 }
161             }
162             mListener.onChanged();
163         }
164     }
165 
166     /** dump class values, for debugging */
dump(@onNull PrintWriter pw)167     void dump(@NonNull PrintWriter pw) {
168         SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
169         synchronized (mStorageLock) {
170             for (int i = 0; i < mVotesByDisplay.size(); i++) {
171                 votesByDisplayLocal.put(mVotesByDisplay.keyAt(i),
172                         mVotesByDisplay.valueAt(i).clone());
173             }
174         }
175         pw.println("  mVotesByDisplay:");
176         for (int i = 0; i < votesByDisplayLocal.size(); i++) {
177             SparseArray<Vote> votes = votesByDisplayLocal.valueAt(i);
178             if (votes.size() == 0) {
179                 continue;
180             }
181             pw.println("    " + votesByDisplayLocal.keyAt(i) + ":");
182             for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
183                 Vote vote = votes.get(p);
184                 if (vote == null) {
185                     continue;
186                 }
187                 pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
188             }
189         }
190     }
191 
192     @VisibleForTesting
injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay)193     void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
194         synchronized (mStorageLock) {
195             mVotesByDisplay.clear();
196             for (int i = 0; i < votesByDisplay.size(); i++) {
197                 mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i));
198             }
199         }
200     }
201 
202     interface Listener {
onChanged()203         void onChanged();
204     }
205 }
206