blob: 6e571bd75946dc2f8fe0b9d57c68c70c1f4dab52 [file] [log] [blame]
Peeyush Agarwalcc155dd2018-01-10 11:51:33 +00001/*
2 * Copyright 2018 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
17package com.android.server.display;
18
19import android.annotation.Nullable;
20import android.annotation.UserIdInt;
21import android.hardware.display.AmbientBrightnessDayStats;
22import android.os.SystemClock;
23import android.os.UserManager;
24import android.util.Slog;
25import android.util.Xml;
26
27import com.android.internal.annotations.VisibleForTesting;
28import com.android.internal.util.FastXmlSerializer;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.io.PrintWriter;
38import java.nio.charset.StandardCharsets;
39import java.time.LocalDate;
40import java.time.format.DateTimeParseException;
41import java.util.ArrayDeque;
42import java.util.ArrayList;
43import java.util.Deque;
44import java.util.HashMap;
45import java.util.Map;
46
47/**
48 * Class that stores stats of ambient brightness regions as histogram.
49 */
50public class AmbientBrightnessStatsTracker {
51
52 private static final String TAG = "AmbientBrightnessStatsTracker";
53 private static final boolean DEBUG = false;
54
55 @VisibleForTesting
56 static final float[] BUCKET_BOUNDARIES_FOR_NEW_STATS =
57 {0, 0.1f, 0.3f, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000};
58 @VisibleForTesting
59 static final int MAX_DAYS_TO_TRACK = 7;
60
61 private final AmbientBrightnessStats mAmbientBrightnessStats;
62 private final Timer mTimer;
63 private final Injector mInjector;
64 private final UserManager mUserManager;
65 private float mCurrentAmbientBrightness;
66 private @UserIdInt int mCurrentUserId;
67
68 public AmbientBrightnessStatsTracker(UserManager userManager, @Nullable Injector injector) {
69 mUserManager = userManager;
70 if (injector != null) {
71 mInjector = injector;
72 } else {
73 mInjector = new Injector();
74 }
75 mAmbientBrightnessStats = new AmbientBrightnessStats();
76 mTimer = new Timer(() -> mInjector.elapsedRealtimeMillis());
77 mCurrentAmbientBrightness = -1;
78 }
79
80 public synchronized void start() {
81 mTimer.reset();
82 mTimer.start();
83 }
84
85 public synchronized void stop() {
86 if (mTimer.isRunning()) {
87 mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(),
88 mCurrentAmbientBrightness, mTimer.totalDurationSec());
89 }
90 mTimer.reset();
91 mCurrentAmbientBrightness = -1;
92 }
93
94 public synchronized void add(@UserIdInt int userId, float newAmbientBrightness) {
95 if (mTimer.isRunning()) {
96 if (userId == mCurrentUserId) {
97 mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(),
98 mCurrentAmbientBrightness, mTimer.totalDurationSec());
99 } else {
100 if (DEBUG) {
101 Slog.v(TAG, "User switched since last sensor event.");
102 }
103 mCurrentUserId = userId;
104 }
105 mTimer.reset();
106 mTimer.start();
107 mCurrentAmbientBrightness = newAmbientBrightness;
108 } else {
109 if (DEBUG) {
110 Slog.e(TAG, "Timer not running while trying to add brightness stats.");
111 }
112 }
113 }
114
115 public synchronized void writeStats(OutputStream stream) throws IOException {
116 mAmbientBrightnessStats.writeToXML(stream);
117 }
118
119 public synchronized void readStats(InputStream stream) throws IOException {
120 mAmbientBrightnessStats.readFromXML(stream);
121 }
122
123 public synchronized ArrayList<AmbientBrightnessDayStats> getUserStats(int userId) {
124 return mAmbientBrightnessStats.getUserStats(userId);
125 }
126
127 public synchronized void dump(PrintWriter pw) {
128 pw.println("AmbientBrightnessStats:");
129 pw.print(mAmbientBrightnessStats);
130 }
131
132 /**
133 * AmbientBrightnessStats tracks ambient brightness stats across users over multiple days.
134 * This class is not ThreadSafe.
135 */
136 class AmbientBrightnessStats {
137
138 private static final String TAG_AMBIENT_BRIGHTNESS_STATS = "ambient-brightness-stats";
139 private static final String TAG_AMBIENT_BRIGHTNESS_DAY_STATS =
140 "ambient-brightness-day-stats";
141 private static final String ATTR_USER = "user";
142 private static final String ATTR_LOCAL_DATE = "local-date";
143 private static final String ATTR_BUCKET_BOUNDARIES = "bucket-boundaries";
144 private static final String ATTR_BUCKET_STATS = "bucket-stats";
145
146 private Map<Integer, Deque<AmbientBrightnessDayStats>> mStats;
147
148 public AmbientBrightnessStats() {
149 mStats = new HashMap<>();
150 }
151
152 public void log(@UserIdInt int userId, LocalDate localDate, float ambientBrightness,
153 float durationSec) {
154 Deque<AmbientBrightnessDayStats> userStats = getOrCreateUserStats(mStats, userId);
155 AmbientBrightnessDayStats dayStats = getOrCreateDayStats(userStats, localDate);
156 dayStats.log(ambientBrightness, durationSec);
157 }
158
159 public ArrayList<AmbientBrightnessDayStats> getUserStats(@UserIdInt int userId) {
160 if (mStats.containsKey(userId)) {
161 return new ArrayList<>(mStats.get(userId));
162 } else {
163 return null;
164 }
165 }
166
167 public void writeToXML(OutputStream stream) throws IOException {
168 XmlSerializer out = new FastXmlSerializer();
169 out.setOutput(stream, StandardCharsets.UTF_8.name());
170 out.startDocument(null, true);
171 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
172
173 final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK);
174 out.startTag(null, TAG_AMBIENT_BRIGHTNESS_STATS);
175 for (Map.Entry<Integer, Deque<AmbientBrightnessDayStats>> entry : mStats.entrySet()) {
176 for (AmbientBrightnessDayStats userDayStats : entry.getValue()) {
177 int userSerialNumber = mInjector.getUserSerialNumber(mUserManager,
178 entry.getKey());
179 if (userSerialNumber != -1 && userDayStats.getLocalDate().isAfter(cutOffDate)) {
180 out.startTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS);
181 out.attribute(null, ATTR_USER, Integer.toString(userSerialNumber));
182 out.attribute(null, ATTR_LOCAL_DATE,
183 userDayStats.getLocalDate().toString());
184 StringBuilder bucketBoundariesValues = new StringBuilder();
185 StringBuilder timeSpentValues = new StringBuilder();
186 for (int i = 0; i < userDayStats.getBucketBoundaries().length; i++) {
187 if (i > 0) {
188 bucketBoundariesValues.append(",");
189 timeSpentValues.append(",");
190 }
191 bucketBoundariesValues.append(userDayStats.getBucketBoundaries()[i]);
192 timeSpentValues.append(userDayStats.getStats()[i]);
193 }
194 out.attribute(null, ATTR_BUCKET_BOUNDARIES,
195 bucketBoundariesValues.toString());
196 out.attribute(null, ATTR_BUCKET_STATS, timeSpentValues.toString());
197 out.endTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS);
198 }
199 }
200 }
201 out.endTag(null, TAG_AMBIENT_BRIGHTNESS_STATS);
202 out.endDocument();
203 stream.flush();
204 }
205
206 public void readFromXML(InputStream stream) throws IOException {
207 try {
208 Map<Integer, Deque<AmbientBrightnessDayStats>> parsedStats = new HashMap<>();
209 XmlPullParser parser = Xml.newPullParser();
210 parser.setInput(stream, StandardCharsets.UTF_8.name());
211
212 int type;
213 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
214 && type != XmlPullParser.START_TAG) {
215 }
216 String tag = parser.getName();
217 if (!TAG_AMBIENT_BRIGHTNESS_STATS.equals(tag)) {
218 throw new XmlPullParserException(
219 "Ambient brightness stats not found in tracker file " + tag);
220 }
221
222 final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK);
223 parser.next();
224 int outerDepth = parser.getDepth();
225 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
226 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
227 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
228 continue;
229 }
230 tag = parser.getName();
231 if (TAG_AMBIENT_BRIGHTNESS_DAY_STATS.equals(tag)) {
232 String userSerialNumber = parser.getAttributeValue(null, ATTR_USER);
233 LocalDate localDate = LocalDate.parse(
234 parser.getAttributeValue(null, ATTR_LOCAL_DATE));
235 String[] bucketBoundaries = parser.getAttributeValue(null,
236 ATTR_BUCKET_BOUNDARIES).split(",");
237 String[] bucketStats = parser.getAttributeValue(null,
238 ATTR_BUCKET_STATS).split(",");
239 if (bucketBoundaries.length != bucketStats.length
240 || bucketBoundaries.length < 1) {
241 throw new IOException("Invalid brightness stats string.");
242 }
243 float[] parsedBucketBoundaries = new float[bucketBoundaries.length];
244 float[] parsedBucketStats = new float[bucketStats.length];
245 for (int i = 0; i < bucketBoundaries.length; i++) {
246 parsedBucketBoundaries[i] = Float.parseFloat(bucketBoundaries[i]);
247 parsedBucketStats[i] = Float.parseFloat(bucketStats[i]);
248 }
249 int userId = mInjector.getUserId(mUserManager,
250 Integer.parseInt(userSerialNumber));
251 if (userId != -1 && localDate.isAfter(cutOffDate)) {
252 Deque<AmbientBrightnessDayStats> userStats = getOrCreateUserStats(
253 parsedStats, userId);
254 userStats.offer(
255 new AmbientBrightnessDayStats(localDate,
256 parsedBucketBoundaries, parsedBucketStats));
257 }
258 }
259 }
260 mStats = parsedStats;
261 } catch (NullPointerException | NumberFormatException | XmlPullParserException |
262 DateTimeParseException | IOException e) {
263 throw new IOException("Failed to parse brightness stats file.", e);
264 }
265 }
266
267 @Override
268 public String toString() {
269 StringBuilder builder = new StringBuilder();
270 for (Map.Entry<Integer, Deque<AmbientBrightnessDayStats>> entry : mStats.entrySet()) {
271 for (AmbientBrightnessDayStats dayStats : entry.getValue()) {
272 builder.append(" ");
273 builder.append(entry.getKey()).append(" ");
274 builder.append(dayStats).append("\n");
275 }
276 }
277 return builder.toString();
278 }
279
280 private Deque<AmbientBrightnessDayStats> getOrCreateUserStats(
281 Map<Integer, Deque<AmbientBrightnessDayStats>> stats, @UserIdInt int userId) {
282 if (!stats.containsKey(userId)) {
283 stats.put(userId, new ArrayDeque<>());
284 }
285 return stats.get(userId);
286 }
287
288 private AmbientBrightnessDayStats getOrCreateDayStats(
289 Deque<AmbientBrightnessDayStats> userStats, LocalDate localDate) {
290 AmbientBrightnessDayStats lastBrightnessStats = userStats.peekLast();
291 if (lastBrightnessStats != null && lastBrightnessStats.getLocalDate().equals(
292 localDate)) {
293 return lastBrightnessStats;
294 } else {
295 AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate,
296 BUCKET_BOUNDARIES_FOR_NEW_STATS);
297 if (userStats.size() == MAX_DAYS_TO_TRACK) {
298 userStats.poll();
299 }
300 userStats.offer(dayStats);
301 return dayStats;
302 }
303 }
304 }
305
306 @VisibleForTesting
307 interface Clock {
308 long elapsedTimeMillis();
309 }
310
311 @VisibleForTesting
312 static class Timer {
313
314 private final Clock clock;
315 private long startTimeMillis;
316 private boolean started;
317
318 public Timer(Clock clock) {
319 this.clock = clock;
320 }
321
322 public void reset() {
323 started = false;
324 }
325
326 public void start() {
327 if (!started) {
328 startTimeMillis = clock.elapsedTimeMillis();
329 started = true;
330 }
331 }
332
333 public boolean isRunning() {
334 return started;
335 }
336
337 public float totalDurationSec() {
338 if (started) {
339 return (float) ((clock.elapsedTimeMillis() - startTimeMillis) / 1000.0);
340 }
341 return 0;
342 }
343 }
344
345 @VisibleForTesting
346 static class Injector {
347 public long elapsedRealtimeMillis() {
348 return SystemClock.elapsedRealtime();
349 }
350
351 public int getUserSerialNumber(UserManager userManager, int userId) {
352 return userManager.getUserSerialNumber(userId);
353 }
354
355 public int getUserId(UserManager userManager, int userSerialNumber) {
356 return userManager.getUserHandle(userSerialNumber);
357 }
358
359 public LocalDate getLocalDate() {
360 return LocalDate.now();
361 }
362 }
363}