Add Ambient Brightness tracker API

Test: atest com.android.server.display.AmbientBrightnessStatsTrackerTest
&& atest android.hardware.display.AmbientBrightnessDayStatsTest
Bug: 69406079
Change-Id: I4b13c6bdd3e9fdded8086371f46dba0fd3102b98
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
new file mode 100644
index 0000000..6e571bd
--- /dev/null
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class that stores stats of ambient brightness regions as histogram.
+ */
+public class AmbientBrightnessStatsTracker {
+
+    private static final String TAG = "AmbientBrightnessStatsTracker";
+    private static final boolean DEBUG = false;
+
+    @VisibleForTesting
+    static final float[] BUCKET_BOUNDARIES_FOR_NEW_STATS =
+            {0, 0.1f, 0.3f, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000};
+    @VisibleForTesting
+    static final int MAX_DAYS_TO_TRACK = 7;
+
+    private final AmbientBrightnessStats mAmbientBrightnessStats;
+    private final Timer mTimer;
+    private final Injector mInjector;
+    private final UserManager mUserManager;
+    private float mCurrentAmbientBrightness;
+    private @UserIdInt int mCurrentUserId;
+
+    public AmbientBrightnessStatsTracker(UserManager userManager, @Nullable Injector injector) {
+        mUserManager = userManager;
+        if (injector != null) {
+            mInjector = injector;
+        } else {
+            mInjector = new Injector();
+        }
+        mAmbientBrightnessStats = new AmbientBrightnessStats();
+        mTimer = new Timer(() -> mInjector.elapsedRealtimeMillis());
+        mCurrentAmbientBrightness = -1;
+    }
+
+    public synchronized void start() {
+        mTimer.reset();
+        mTimer.start();
+    }
+
+    public synchronized void stop() {
+        if (mTimer.isRunning()) {
+            mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(),
+                    mCurrentAmbientBrightness, mTimer.totalDurationSec());
+        }
+        mTimer.reset();
+        mCurrentAmbientBrightness = -1;
+    }
+
+    public synchronized void add(@UserIdInt int userId, float newAmbientBrightness) {
+        if (mTimer.isRunning()) {
+            if (userId == mCurrentUserId) {
+                mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(),
+                        mCurrentAmbientBrightness, mTimer.totalDurationSec());
+            } else {
+                if (DEBUG) {
+                    Slog.v(TAG, "User switched since last sensor event.");
+                }
+                mCurrentUserId = userId;
+            }
+            mTimer.reset();
+            mTimer.start();
+            mCurrentAmbientBrightness = newAmbientBrightness;
+        } else {
+            if (DEBUG) {
+                Slog.e(TAG, "Timer not running while trying to add brightness stats.");
+            }
+        }
+    }
+
+    public synchronized void writeStats(OutputStream stream) throws IOException {
+        mAmbientBrightnessStats.writeToXML(stream);
+    }
+
+    public synchronized void readStats(InputStream stream) throws IOException {
+        mAmbientBrightnessStats.readFromXML(stream);
+    }
+
+    public synchronized ArrayList<AmbientBrightnessDayStats> getUserStats(int userId) {
+        return mAmbientBrightnessStats.getUserStats(userId);
+    }
+
+    public synchronized void dump(PrintWriter pw) {
+        pw.println("AmbientBrightnessStats:");
+        pw.print(mAmbientBrightnessStats);
+    }
+
+    /**
+     * AmbientBrightnessStats tracks ambient brightness stats across users over multiple days.
+     * This class is not ThreadSafe.
+     */
+    class AmbientBrightnessStats {
+
+        private static final String TAG_AMBIENT_BRIGHTNESS_STATS = "ambient-brightness-stats";
+        private static final String TAG_AMBIENT_BRIGHTNESS_DAY_STATS =
+                "ambient-brightness-day-stats";
+        private static final String ATTR_USER = "user";
+        private static final String ATTR_LOCAL_DATE = "local-date";
+        private static final String ATTR_BUCKET_BOUNDARIES = "bucket-boundaries";
+        private static final String ATTR_BUCKET_STATS = "bucket-stats";
+
+        private Map<Integer, Deque<AmbientBrightnessDayStats>> mStats;
+
+        public AmbientBrightnessStats() {
+            mStats = new HashMap<>();
+        }
+
+        public void log(@UserIdInt int userId, LocalDate localDate, float ambientBrightness,
+                float durationSec) {
+            Deque<AmbientBrightnessDayStats> userStats = getOrCreateUserStats(mStats, userId);
+            AmbientBrightnessDayStats dayStats = getOrCreateDayStats(userStats, localDate);
+            dayStats.log(ambientBrightness, durationSec);
+        }
+
+        public ArrayList<AmbientBrightnessDayStats> getUserStats(@UserIdInt int userId) {
+            if (mStats.containsKey(userId)) {
+                return new ArrayList<>(mStats.get(userId));
+            } else {
+                return null;
+            }
+        }
+
+        public void writeToXML(OutputStream stream) throws IOException {
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK);
+            out.startTag(null, TAG_AMBIENT_BRIGHTNESS_STATS);
+            for (Map.Entry<Integer, Deque<AmbientBrightnessDayStats>> entry : mStats.entrySet()) {
+                for (AmbientBrightnessDayStats userDayStats : entry.getValue()) {
+                    int userSerialNumber = mInjector.getUserSerialNumber(mUserManager,
+                            entry.getKey());
+                    if (userSerialNumber != -1 && userDayStats.getLocalDate().isAfter(cutOffDate)) {
+                        out.startTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS);
+                        out.attribute(null, ATTR_USER, Integer.toString(userSerialNumber));
+                        out.attribute(null, ATTR_LOCAL_DATE,
+                                userDayStats.getLocalDate().toString());
+                        StringBuilder bucketBoundariesValues = new StringBuilder();
+                        StringBuilder timeSpentValues = new StringBuilder();
+                        for (int i = 0; i < userDayStats.getBucketBoundaries().length; i++) {
+                            if (i > 0) {
+                                bucketBoundariesValues.append(",");
+                                timeSpentValues.append(",");
+                            }
+                            bucketBoundariesValues.append(userDayStats.getBucketBoundaries()[i]);
+                            timeSpentValues.append(userDayStats.getStats()[i]);
+                        }
+                        out.attribute(null, ATTR_BUCKET_BOUNDARIES,
+                                bucketBoundariesValues.toString());
+                        out.attribute(null, ATTR_BUCKET_STATS, timeSpentValues.toString());
+                        out.endTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS);
+                    }
+                }
+            }
+            out.endTag(null, TAG_AMBIENT_BRIGHTNESS_STATS);
+            out.endDocument();
+            stream.flush();
+        }
+
+        public void readFromXML(InputStream stream) throws IOException {
+            try {
+                Map<Integer, Deque<AmbientBrightnessDayStats>> parsedStats = new HashMap<>();
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && type != XmlPullParser.START_TAG) {
+                }
+                String tag = parser.getName();
+                if (!TAG_AMBIENT_BRIGHTNESS_STATS.equals(tag)) {
+                    throw new XmlPullParserException(
+                            "Ambient brightness stats not found in tracker file " + tag);
+                }
+
+                final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK);
+                parser.next();
+                int outerDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    tag = parser.getName();
+                    if (TAG_AMBIENT_BRIGHTNESS_DAY_STATS.equals(tag)) {
+                        String userSerialNumber = parser.getAttributeValue(null, ATTR_USER);
+                        LocalDate localDate = LocalDate.parse(
+                                parser.getAttributeValue(null, ATTR_LOCAL_DATE));
+                        String[] bucketBoundaries = parser.getAttributeValue(null,
+                                ATTR_BUCKET_BOUNDARIES).split(",");
+                        String[] bucketStats = parser.getAttributeValue(null,
+                                ATTR_BUCKET_STATS).split(",");
+                        if (bucketBoundaries.length != bucketStats.length
+                                || bucketBoundaries.length < 1) {
+                            throw new IOException("Invalid brightness stats string.");
+                        }
+                        float[] parsedBucketBoundaries = new float[bucketBoundaries.length];
+                        float[] parsedBucketStats = new float[bucketStats.length];
+                        for (int i = 0; i < bucketBoundaries.length; i++) {
+                            parsedBucketBoundaries[i] = Float.parseFloat(bucketBoundaries[i]);
+                            parsedBucketStats[i] = Float.parseFloat(bucketStats[i]);
+                        }
+                        int userId = mInjector.getUserId(mUserManager,
+                                Integer.parseInt(userSerialNumber));
+                        if (userId != -1 && localDate.isAfter(cutOffDate)) {
+                            Deque<AmbientBrightnessDayStats> userStats = getOrCreateUserStats(
+                                    parsedStats, userId);
+                            userStats.offer(
+                                    new AmbientBrightnessDayStats(localDate,
+                                            parsedBucketBoundaries, parsedBucketStats));
+                        }
+                    }
+                }
+                mStats = parsedStats;
+            } catch (NullPointerException | NumberFormatException | XmlPullParserException |
+                    DateTimeParseException | IOException e) {
+                throw new IOException("Failed to parse brightness stats file.", e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            for (Map.Entry<Integer, Deque<AmbientBrightnessDayStats>> entry : mStats.entrySet()) {
+                for (AmbientBrightnessDayStats dayStats : entry.getValue()) {
+                    builder.append("  ");
+                    builder.append(entry.getKey()).append(" ");
+                    builder.append(dayStats).append("\n");
+                }
+            }
+            return builder.toString();
+        }
+
+        private Deque<AmbientBrightnessDayStats> getOrCreateUserStats(
+                Map<Integer, Deque<AmbientBrightnessDayStats>> stats, @UserIdInt int userId) {
+            if (!stats.containsKey(userId)) {
+                stats.put(userId, new ArrayDeque<>());
+            }
+            return stats.get(userId);
+        }
+
+        private AmbientBrightnessDayStats getOrCreateDayStats(
+                Deque<AmbientBrightnessDayStats> userStats, LocalDate localDate) {
+            AmbientBrightnessDayStats lastBrightnessStats = userStats.peekLast();
+            if (lastBrightnessStats != null && lastBrightnessStats.getLocalDate().equals(
+                    localDate)) {
+                return lastBrightnessStats;
+            } else {
+                AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate,
+                        BUCKET_BOUNDARIES_FOR_NEW_STATS);
+                if (userStats.size() == MAX_DAYS_TO_TRACK) {
+                    userStats.poll();
+                }
+                userStats.offer(dayStats);
+                return dayStats;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    interface Clock {
+        long elapsedTimeMillis();
+    }
+
+    @VisibleForTesting
+    static class Timer {
+
+        private final Clock clock;
+        private long startTimeMillis;
+        private boolean started;
+
+        public Timer(Clock clock) {
+            this.clock = clock;
+        }
+
+        public void reset() {
+            started = false;
+        }
+
+        public void start() {
+            if (!started) {
+                startTimeMillis = clock.elapsedTimeMillis();
+                started = true;
+            }
+        }
+
+        public boolean isRunning() {
+            return started;
+        }
+
+        public float totalDurationSec() {
+            if (started) {
+                return (float) ((clock.elapsedTimeMillis() - startTimeMillis) / 1000.0);
+            }
+            return 0;
+        }
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        public long elapsedRealtimeMillis() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userManager.getUserSerialNumber(userId);
+        }
+
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userManager.getUserHandle(userSerialNumber);
+        }
+
+        public LocalDate getLocalDate() {
+            return LocalDate.now();
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
index 876acf4..b0a41cb 100644
--- a/services/core/java/com/android/server/display/BrightnessIdleJob.java
+++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java
@@ -70,7 +70,7 @@
             Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
         }
         DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
-        dmi.persistBrightnessSliderEvents();
+        dmi.persistBrightnessTrackerState();
         return false;
     }
 
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index bcf8bfe..ac76fae 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -28,8 +29,8 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
-import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.Handler;
@@ -81,6 +82,7 @@
     static final boolean DEBUG = false;
 
     private static final String EVENTS_FILE = "brightness_events.xml";
+    private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml";
     private static final int MAX_EVENTS = 100;
     // Discard events when reading or writing that are older than this.
     private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
@@ -113,6 +115,10 @@
     private final Runnable mEventsWriter = () -> writeEvents();
     private volatile boolean mWriteEventsScheduled;
 
+    private AmbientBrightnessStatsTracker mAmbientBrightnessStatsTracker;
+    private final Runnable mAmbientBrightnessStatsWriter = () -> writeAmbientBrightnessStats();
+    private volatile boolean mWriteBrightnessStatsScheduled;
+
     private UserManager mUserManager;
     private final Context mContext;
     private final ContentResolver mContentResolver;
@@ -120,6 +126,7 @@
     // mBroadcastReceiver and mSensorListener should only be used on the mBgHandler thread.
     private BroadcastReceiver mBroadcastReceiver;
     private SensorListener mSensorListener;
+    private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;
 
     // Lock held while collecting data related to brightness changes.
     private final Object mDataCollectionLock = new Object();
@@ -157,12 +164,19 @@
         }
         mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper());
         mUserManager = mContext.getSystemService(UserManager.class);
-
+        try {
+            final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
+            mCurrentUserId = focusedStack.userId;
+        } catch (RemoteException e) {
+            // Really shouldn't be possible.
+            return;
+        }
         mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget();
     }
 
     private void backgroundStart(float initialBrightness) {
         readEvents();
+        readAmbientBrightnessStats();
 
         mSensorListener = new SensorListener();
 
@@ -196,12 +210,20 @@
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
         mInjector.cancelIdleJob(mContext);
+        mAmbientBrightnessStatsTracker.stop();
 
         synchronized (mDataCollectionLock) {
             mStarted = false;
         }
     }
 
+    public void onSwitchUser(@UserIdInt int newUserId) {
+        if (DEBUG) {
+            Slog.d(TAG, "Used id updated from " + mCurrentUserId + " to " + newUserId);
+        }
+        mCurrentUserId = newUserId;
+    }
+
     /**
      * @param userId userId to fetch data for.
      * @param includePackage if false we will null out BrightnessChangeEvent.packageName
@@ -228,8 +250,8 @@
         return new ParceledListSlice<>(out);
     }
 
-    public void persistEvents() {
-        scheduleWriteEvents();
+    public void persistBrightnessTrackerState() {
+        scheduleWriteBrightnessTrackerState();
     }
 
     /**
@@ -321,11 +343,15 @@
         }
     }
 
-    private void scheduleWriteEvents() {
+    private void scheduleWriteBrightnessTrackerState() {
         if (!mWriteEventsScheduled) {
             mBgHandler.post(mEventsWriter);
             mWriteEventsScheduled = true;
         }
+        if (!mWriteBrightnessStatsScheduled) {
+            mBgHandler.post(mAmbientBrightnessStatsWriter);
+            mWriteBrightnessStatsScheduled = true;
+        }
     }
 
     private void writeEvents() {
@@ -336,7 +362,7 @@
                 return;
             }
 
-            final AtomicFile writeTo = mInjector.getFile();
+            final AtomicFile writeTo = mInjector.getFile(EVENTS_FILE);
             if (writeTo == null) {
                 return;
             }
@@ -360,12 +386,29 @@
         }
     }
 
+    private void writeAmbientBrightnessStats() {
+        mWriteBrightnessStatsScheduled = false;
+        final AtomicFile writeTo = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
+        if (writeTo == null) {
+            return;
+        }
+        FileOutputStream output = null;
+        try {
+            output = writeTo.startWrite();
+            mAmbientBrightnessStatsTracker.writeStats(output);
+            writeTo.finishWrite(output);
+        } catch (IOException e) {
+            writeTo.failWrite(output);
+            Slog.e(TAG, "Failed to write ambient brightness stats.", e);
+        }
+    }
+
     private void readEvents() {
         synchronized (mEventsLock) {
             // Read might prune events so mark as dirty.
             mEventsDirty = true;
             mEvents.clear();
-            final AtomicFile readFrom = mInjector.getFile();
+            final AtomicFile readFrom = mInjector.getFile(EVENTS_FILE);
             if (readFrom != null && readFrom.exists()) {
                 FileInputStream input = null;
                 try {
@@ -381,6 +424,23 @@
         }
     }
 
+    private void readAmbientBrightnessStats() {
+        mAmbientBrightnessStatsTracker = new AmbientBrightnessStatsTracker(mUserManager, null);
+        final AtomicFile readFrom = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
+        if (readFrom != null && readFrom.exists()) {
+            FileInputStream input = null;
+            try {
+                input = readFrom.openRead();
+                mAmbientBrightnessStatsTracker.readStats(input);
+            } catch (IOException e) {
+                readFrom.delete();
+                Slog.e(TAG, "Failed to read ambient brightness stats.", e);
+            } finally {
+                IoUtils.closeQuietly(input);
+            }
+        }
+    }
+
     @VisibleForTesting
     @GuardedBy("mEventsLock")
     void writeEventsLocked(OutputStream stream) throws IOException {
@@ -545,6 +605,13 @@
                 pw.println("}");
             }
         }
+        if (mAmbientBrightnessStatsTracker != null) {
+            mAmbientBrightnessStatsTracker.dump(pw);
+        }
+    }
+
+    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId) {
+        return new ParceledListSlice<>(mAmbientBrightnessStatsTracker.getUserStats(userId));
     }
 
     // Not allowed to keep the SensorEvent so used to copy the data we care about.
@@ -584,6 +651,10 @@
         }
     }
 
+    private void recordAmbientBrightnessStats(SensorEvent event) {
+        mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
+    }
+
     private void batteryLevelChanged(int level, int scale) {
         synchronized (mDataCollectionLock) {
             mLastBatteryLevel = (float) level / (float) scale;
@@ -594,6 +665,7 @@
         @Override
         public void onSensorChanged(SensorEvent event) {
             recordSensorEvent(event);
+            recordAmbientBrightnessStats(event);
         }
 
         @Override
@@ -611,7 +683,7 @@
             String action = intent.getAction();
             if (Intent.ACTION_SHUTDOWN.equals(action)) {
                 stop();
-                scheduleWriteEvents();
+                scheduleWriteBrightnessTrackerState();
             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
                 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
@@ -619,8 +691,10 @@
                     batteryLevelChanged(level, scale);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mAmbientBrightnessStatsTracker.stop();
                 mInjector.unregisterSensorListener(mContext, mSensorListener);
             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mAmbientBrightnessStatsTracker.start();
                 mInjector.registerSensorListener(mContext, mSensorListener,
                         mInjector.getBackgroundHandler());
             }
@@ -679,8 +753,8 @@
             return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
         }
 
-        public AtomicFile getFile() {
-            return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), EVENTS_FILE));
+        public AtomicFile getFile(String filename) {
+            return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), filename));
         }
 
         public long currentTimeMillis() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0c2ff05..a5c1fe2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -38,6 +38,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.DisplayManagerGlobal;
@@ -359,6 +360,7 @@
                         mPersistentDataStore.getBrightnessConfiguration(userSerial);
                 mDisplayPowerController.setBrightnessConfiguration(config);
             }
+            mDisplayPowerController.onSwitchUser(newUserId);
         }
     }
 
@@ -1835,6 +1837,23 @@
         }
 
         @Override // Binder call
+        public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats() {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS,
+                    "Permission required to to access ambient light stats.");
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mSyncRoot) {
+                    return mDisplayPowerController.getAmbientBrightnessStats(userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void setBrightnessConfigurationForUser(
                 BrightnessConfiguration c, @UserIdInt int userId, String packageName) {
             mContext.enforceCallingOrSelfPermission(
@@ -2039,9 +2058,9 @@
         }
 
         @Override
-        public void persistBrightnessSliderEvents() {
+        public void persistBrightnessTrackerState() {
             synchronized (mSyncRoot) {
-                mDisplayPowerController.persistBrightnessSliderEvents();
+                mDisplayPowerController.persistBrightnessTrackerState();
             }
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 056c3e6..f2a7d81 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -34,6 +34,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
@@ -498,11 +499,20 @@
         return mBrightnessTracker.getEvents(userId, includePackage);
     }
 
+    public void onSwitchUser(@UserIdInt int newUserId) {
+        mBrightnessTracker.onSwitchUser(newUserId);
+    }
+
+    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
+            @UserIdInt int userId) {
+        return mBrightnessTracker.getAmbientBrightnessStats(userId);
+    }
+
     /**
-     * Persist the brightness slider events to disk.
+     * Persist the brightness slider events and ambient brightness stats to disk.
      */
-    public void persistBrightnessSliderEvents() {
-        mBrightnessTracker.persistEvents();
+    public void persistBrightnessTrackerState() {
+        mBrightnessTracker.persistBrightnessTrackerState();
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
new file mode 100644
index 0000000..8502e69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AmbientBrightnessStatsTrackerTest {
+
+    private TestInjector mTestInjector;
+
+    @Before
+    public void setUp() {
+        mTestInjector = new TestInjector();
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverSingleDay() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Test case where no user data
+        userStats = statsTracker.getUserStats(0);
+        assertNull(userStats);
+        // Test after adding some user data
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(1000);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Test after adding some more user data
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 2;
+        expectedStats[1] = 1.5f;
+        expectedStats[11] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for day 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for day 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleUsers() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for user 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for user 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        userStats = statsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMaxDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        // Add 10 extra days of data over the buffer limit
+        for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) {
+            mTestInjector.incrementDate(1);
+            statsTracker.start();
+            statsTracker.add(0, 10);
+            mTestInjector.incrementTime(1000);
+            statsTracker.add(0, 20);
+            mTestInjector.incrementTime(1000);
+            statsTracker.stop();
+        }
+        // Assert that we are only tracking last "MAX_DAYS_TO_TRACK"
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size());
+        LocalDate runningDate = mTestInjector.getLocalDate();
+        for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) {
+            assertEquals(runningDate, userStats.get(i).getLocalDate());
+            runningDate = runningDate.minusDays(1);
+        }
+    }
+
+    @Test
+    public void testReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        String statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Old stats that shouldn't be read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        statsTracker.readStats(getInputStream(statsFile));
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(new AmbientBrightnessDayStats(date.minusDays(1),
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0));
+        assertEquals(new AmbientBrightnessDayStats(date,
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1));
+    }
+
+    @Test
+    public void testFailedReadAmbientBrightnessStatsWithException() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        String statsFile;
+        // Test with parse error
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Incorrect since bucket boundaries not parsable
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"asdf,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (IOException e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with incorrect data (bucket boundaries length not equal to stats length)
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Correct data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        // Incorrect data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with missing attribute
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        + "<ambientBrightnessDayStats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+    }
+
+    @Test
+    public void testWriteThenReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Generate some dummy data
+        // Data: very old which should not be read
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 1
+        mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1);
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 2
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Data: day 2 user 1
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Write them
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        statsTracker.writeStats(baos);
+        baos.flush();
+        // Read them back and assert that it's the same
+        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker();
+        newStatsTracker.readStats(input);
+        userStats = newStatsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        // Check day 1 user 1
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Check day 2 user 1
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[11] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+        userStats = newStatsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        // Check day 1 user 2
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testTimer() {
+        AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer(
+                () -> mTestInjector.elapsedRealtimeMillis());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start timer
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertTrue(timer.isRunning());
+        assertEquals(1, timer.totalDurationSec(), 0);
+        // Reset timer
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start again
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(2000);
+        assertTrue(timer.isRunning());
+        assertEquals(2, timer.totalDurationSec(), 0);
+        // Reset again
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+    }
+
+    private class TestInjector extends AmbientBrightnessStatsTracker.Injector {
+
+        private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        private LocalDate mLocalDate = LocalDate.now();
+
+        public void incrementTime(long timeMillis) {
+            mElapsedRealtimeMillis += timeMillis;
+        }
+
+        public void incrementDate(int numDays) {
+            mLocalDate = mLocalDate.plusDays(numDays);
+        }
+
+        @Override
+        public long elapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userId + 10;
+        }
+
+        @Override
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userSerialNumber - 10;
+        }
+
+        @Override
+        public LocalDate getLocalDate() {
+            return LocalDate.from(mLocalDate);
+        }
+    }
+
+    private AmbientBrightnessStatsTracker getTestStatsTracker() {
+        return new AmbientBrightnessStatsTracker(
+                InstrumentationRegistry.getContext().getSystemService(UserManager.class),
+                mTestInjector);
+    }
+
+    private float[] getEmptyStatsArray() {
+        return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length];
+    }
+
+    private InputStream getInputStream(String data) {
+        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index edc7d74..8277184 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -639,7 +639,7 @@
         }
 
         @Override
-        public AtomicFile getFile() {
+        public AtomicFile getFile(String filename) {
             // Don't have the test write / read from anywhere.
             return null;
         }