1 /*
2  * Copyright 2017 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;
18 
19 import android.annotation.Nullable;
20 import android.annotation.UserIdInt;
21 import android.app.ActivityManager;
22 import android.app.ActivityTaskManager;
23 import android.content.BroadcastReceiver;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.ParceledListSlice;
29 import android.database.ContentObserver;
30 import android.graphics.PixelFormat;
31 import android.hardware.Sensor;
32 import android.hardware.SensorEvent;
33 import android.hardware.SensorEventListener;
34 import android.hardware.SensorManager;
35 import android.hardware.display.AmbientBrightnessDayStats;
36 import android.hardware.display.BrightnessChangeEvent;
37 import android.hardware.display.ColorDisplayManager;
38 import android.hardware.display.DisplayManager;
39 import android.hardware.display.DisplayManagerInternal;
40 import android.hardware.display.DisplayedContentSample;
41 import android.hardware.display.DisplayedContentSamplingAttributes;
42 import android.net.Uri;
43 import android.os.BatteryManager;
44 import android.os.Environment;
45 import android.os.Handler;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.os.PowerManager;
49 import android.os.RemoteException;
50 import android.os.SystemClock;
51 import android.os.UserHandle;
52 import android.os.UserManager;
53 import android.provider.Settings;
54 import android.util.AtomicFile;
55 import android.util.Slog;
56 import android.util.Xml;
57 import android.view.Display;
58 
59 import com.android.internal.annotations.GuardedBy;
60 import com.android.internal.annotations.VisibleForTesting;
61 import com.android.internal.os.BackgroundThread;
62 import com.android.internal.util.FastXmlSerializer;
63 import com.android.internal.util.RingBuffer;
64 import com.android.server.LocalServices;
65 
66 import libcore.io.IoUtils;
67 
68 import org.xmlpull.v1.XmlPullParser;
69 import org.xmlpull.v1.XmlPullParserException;
70 import org.xmlpull.v1.XmlSerializer;
71 
72 import java.io.File;
73 import java.io.FileInputStream;
74 import java.io.FileOutputStream;
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.io.OutputStream;
78 import java.io.PrintWriter;
79 import java.nio.charset.StandardCharsets;
80 import java.text.SimpleDateFormat;
81 import java.util.ArrayDeque;
82 import java.util.ArrayList;
83 import java.util.Date;
84 import java.util.Deque;
85 import java.util.HashMap;
86 import java.util.Map;
87 import java.util.concurrent.TimeUnit;
88 
89 /**
90  * Class that tracks recent brightness settings changes and stores
91  * associated information such as light sensor readings.
92  */
93 public class BrightnessTracker {
94 
95     static final String TAG = "BrightnessTracker";
96     static final boolean DEBUG = false;
97     @VisibleForTesting
98     static final boolean ENABLE_COLOR_SAMPLING = false;
99 
100     private static final String EVENTS_FILE = "brightness_events.xml";
101     private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml";
102     private static final int MAX_EVENTS = 100;
103     // Discard events when reading or writing that are older than this.
104     private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
105     // Time over which we keep lux sensor readings.
106     private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
107 
108     private static final String TAG_EVENTS = "events";
109     private static final String TAG_EVENT = "event";
110     private static final String ATTR_NITS = "nits";
111     private static final String ATTR_TIMESTAMP = "timestamp";
112     private static final String ATTR_PACKAGE_NAME = "packageName";
113     private static final String ATTR_USER = "user";
114     private static final String ATTR_LUX = "lux";
115     private static final String ATTR_LUX_TIMESTAMPS = "luxTimestamps";
116     private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
117     private static final String ATTR_NIGHT_MODE = "nightMode";
118     private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
119     private static final String ATTR_LAST_NITS = "lastNits";
120     private static final String ATTR_DEFAULT_CONFIG = "defaultConfig";
121     private static final String ATTR_POWER_SAVE = "powerSaveFactor";
122     private static final String ATTR_USER_POINT = "userPoint";
123     private static final String ATTR_COLOR_SAMPLE_DURATION = "colorSampleDuration";
124     private static final String ATTR_COLOR_VALUE_BUCKETS = "colorValueBuckets";
125 
126     private static final int MSG_BACKGROUND_START = 0;
127     private static final int MSG_BRIGHTNESS_CHANGED = 1;
128     private static final int MSG_STOP_SENSOR_LISTENER = 2;
129     private static final int MSG_START_SENSOR_LISTENER = 3;
130 
131     private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
132 
133     private static final long COLOR_SAMPLE_DURATION = TimeUnit.SECONDS.toSeconds(10);
134     // Sample chanel 2 of HSV which is the Value component.
135     private static final int COLOR_SAMPLE_COMPONENT_MASK = 0x1 << 2;
136 
137     // Lock held while accessing mEvents, is held while writing events to flash.
138     private final Object mEventsLock = new Object();
139     @GuardedBy("mEventsLock")
140     private RingBuffer<BrightnessChangeEvent> mEvents
141             = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
142     @GuardedBy("mEventsLock")
143     private boolean mEventsDirty;
144 
145     private volatile boolean mWriteBrightnessTrackerStateScheduled;
146 
147     private AmbientBrightnessStatsTracker mAmbientBrightnessStatsTracker;
148 
149     private final UserManager mUserManager;
150     private final Context mContext;
151     private final ContentResolver mContentResolver;
152     private final Handler mBgHandler;
153 
154     // These members should only be accessed on the mBgHandler thread.
155     private BroadcastReceiver mBroadcastReceiver;
156     private SensorListener mSensorListener;
157     private SettingsObserver mSettingsObserver;
158     private DisplayListener mDisplayListener;
159     private boolean mSensorRegistered;
160     private boolean mColorSamplingEnabled;
161     private int mNoFramesToSample;
162     private float mFrameRate;
163     // End of block of members that should only be accessed on the mBgHandler thread.
164 
165     private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;
166 
167     // Lock held while collecting data related to brightness changes.
168     private final Object mDataCollectionLock = new Object();
169     @GuardedBy("mDataCollectionLock")
170     private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
171     @GuardedBy("mDataCollectionLock")
172     private float mLastBatteryLevel = Float.NaN;
173     @GuardedBy("mDataCollectionLock")
174     private float mLastBrightness = -1;
175     @GuardedBy("mDataCollectionLock")
176     private boolean mStarted;
177 
178     private final Injector mInjector;
179 
BrightnessTracker(Context context, @Nullable Injector injector)180     public BrightnessTracker(Context context, @Nullable Injector injector) {
181         // Note this will be called very early in boot, other system
182         // services may not be present.
183         mContext = context;
184         mContentResolver = context.getContentResolver();
185         if (injector != null) {
186             mInjector = injector;
187         } else {
188             mInjector = new Injector();
189         }
190         mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper());
191         mUserManager = mContext.getSystemService(UserManager.class);
192     }
193 
194     /**
195      * Start listening for brightness slider events
196      *
197      * @param initialBrightness the initial screen brightness
198      */
start(float initialBrightness)199     public void start(float initialBrightness) {
200         if (DEBUG) {
201             Slog.d(TAG, "Start");
202         }
203         mCurrentUserId = ActivityManager.getCurrentUser();
204         mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget();
205     }
206 
backgroundStart(float initialBrightness)207     private void backgroundStart(float initialBrightness) {
208         readEvents();
209         readAmbientBrightnessStats();
210 
211         mSensorListener = new SensorListener();
212 
213         mSettingsObserver = new SettingsObserver(mBgHandler);
214         mInjector.registerBrightnessModeObserver(mContentResolver, mSettingsObserver);
215         startSensorListener();
216 
217         final IntentFilter intentFilter = new IntentFilter();
218         intentFilter.addAction(Intent.ACTION_SHUTDOWN);
219         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
220         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
221         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
222         mBroadcastReceiver = new Receiver();
223         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
224 
225         mInjector.scheduleIdleJob(mContext);
226         synchronized (mDataCollectionLock) {
227             mLastBrightness = initialBrightness;
228             mStarted = true;
229         }
230         enableColorSampling();
231     }
232 
233     /** Stop listening for events */
234     @VisibleForTesting
stop()235     void stop() {
236         if (DEBUG) {
237             Slog.d(TAG, "Stop");
238         }
239         mBgHandler.removeMessages(MSG_BACKGROUND_START);
240         stopSensorListener();
241         mInjector.unregisterSensorListener(mContext, mSensorListener);
242         mInjector.unregisterBrightnessModeObserver(mContext, mSettingsObserver);
243         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
244         mInjector.cancelIdleJob(mContext);
245 
246         synchronized (mDataCollectionLock) {
247             mStarted = false;
248         }
249         disableColorSampling();
250     }
251 
onSwitchUser(@serIdInt int newUserId)252     public void onSwitchUser(@UserIdInt int newUserId) {
253         if (DEBUG) {
254             Slog.d(TAG, "Used id updated from " + mCurrentUserId + " to " + newUserId);
255         }
256         mCurrentUserId = newUserId;
257     }
258 
259     /**
260      * @param userId userId to fetch data for.
261      * @param includePackage if false we will null out BrightnessChangeEvent.packageName
262      * @return List of recent {@link BrightnessChangeEvent}s
263      */
getEvents(int userId, boolean includePackage)264     public ParceledListSlice<BrightnessChangeEvent> getEvents(int userId, boolean includePackage) {
265         BrightnessChangeEvent[] events;
266         synchronized (mEventsLock) {
267             events = mEvents.toArray();
268         }
269         int[] profiles = mInjector.getProfileIds(mUserManager, userId);
270         Map<Integer, Boolean> toRedact = new HashMap<>();
271         for (int i = 0; i < profiles.length; ++i) {
272             int profileId = profiles[i];
273             // Include slider interactions when a managed profile app is in the
274             // foreground but always redact the package name.
275             boolean redact = (!includePackage) || profileId != userId;
276             toRedact.put(profiles[i], redact);
277         }
278         ArrayList<BrightnessChangeEvent> out = new ArrayList<>(events.length);
279         for (int i = 0; i < events.length; ++i) {
280             Boolean redact = toRedact.get(events[i].userId);
281             if (redact != null) {
282                 if (!redact) {
283                     out.add(events[i]);
284                 } else {
285                     BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]),
286                             /* redactPackage */ true);
287                     out.add(event);
288                 }
289             }
290         }
291         return new ParceledListSlice<>(out);
292     }
293 
persistBrightnessTrackerState()294     public void persistBrightnessTrackerState() {
295         scheduleWriteBrightnessTrackerState();
296     }
297 
298     /**
299      * Notify the BrightnessTracker that the user has changed the brightness of the display.
300      */
notifyBrightnessChanged(float brightness, boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig)301     public void notifyBrightnessChanged(float brightness, boolean userInitiated,
302             float powerBrightnessFactor, boolean isUserSetBrightness,
303             boolean isDefaultBrightnessConfig) {
304         if (DEBUG) {
305             Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
306                         brightness, userInitiated));
307         }
308         Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
309                 userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
310                         powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
311                         mInjector.currentTimeMillis()));
312         m.sendToTarget();
313     }
314 
handleBrightnessChanged(float brightness, boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, long timestamp)315     private void handleBrightnessChanged(float brightness, boolean userInitiated,
316             float powerBrightnessFactor, boolean isUserSetBrightness,
317             boolean isDefaultBrightnessConfig, long timestamp) {
318         BrightnessChangeEvent.Builder builder;
319 
320         synchronized (mDataCollectionLock) {
321             if (!mStarted) {
322                 // Not currently gathering brightness change information
323                 return;
324             }
325 
326             float previousBrightness = mLastBrightness;
327             mLastBrightness = brightness;
328 
329             if (!userInitiated) {
330                 // We want to record what current brightness is so that we know what the user
331                 // changed it from, but if it wasn't user initiated then we don't want to record it
332                 // as a BrightnessChangeEvent.
333                 return;
334             }
335 
336             builder = new BrightnessChangeEvent.Builder();
337             builder.setBrightness(brightness);
338             builder.setTimeStamp(timestamp);
339             builder.setPowerBrightnessFactor(powerBrightnessFactor);
340             builder.setUserBrightnessPoint(isUserSetBrightness);
341             builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
342 
343             final int readingCount = mLastSensorReadings.size();
344             if (readingCount == 0) {
345                 // No sensor data so ignore this.
346                 return;
347             }
348 
349             float[] luxValues = new float[readingCount];
350             long[] luxTimestamps = new long[readingCount];
351 
352             int pos = 0;
353 
354             // Convert sensor timestamp in elapsed time nanos to current time millis.
355             long currentTimeMillis = mInjector.currentTimeMillis();
356             long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
357             for (LightData reading : mLastSensorReadings) {
358                 luxValues[pos] = reading.lux;
359                 luxTimestamps[pos] = currentTimeMillis -
360                         TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
361                 ++pos;
362             }
363             builder.setLuxValues(luxValues);
364             builder.setLuxTimestamps(luxTimestamps);
365 
366             builder.setBatteryLevel(mLastBatteryLevel);
367             builder.setLastBrightness(previousBrightness);
368         }
369 
370         try {
371             final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
372             if (focusedStack != null && focusedStack.topActivity != null) {
373                 builder.setUserId(focusedStack.userId);
374                 builder.setPackageName(focusedStack.topActivity.getPackageName());
375             } else {
376                 // Ignore the event because we can't determine user / package.
377                 if (DEBUG) {
378                     Slog.d(TAG, "Ignoring event due to null focusedStack.");
379                 }
380                 return;
381             }
382         } catch (RemoteException e) {
383             // Really shouldn't be possible.
384             return;
385         }
386 
387         builder.setNightMode(mInjector.isNightDisplayActivated(mContext));
388         builder.setColorTemperature(mInjector.getNightDisplayColorTemperature(mContext));
389 
390         if (mColorSamplingEnabled) {
391             DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample);
392             if (sample != null && sample.getSampleComponent(
393                     DisplayedContentSample.ColorComponent.CHANNEL2) != null) {
394                 float numMillis = (sample.getNumFrames() / mFrameRate) * 1000.0f;
395                 builder.setColorValues(
396                         sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2),
397                         Math.round(numMillis));
398             }
399         }
400 
401         BrightnessChangeEvent event = builder.build();
402         if (DEBUG) {
403             Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
404         }
405         synchronized (mEventsLock) {
406             mEventsDirty = true;
407             mEvents.append(event);
408         }
409     }
410 
startSensorListener()411     private void startSensorListener() {
412         if (!mSensorRegistered
413                 && mInjector.isInteractive(mContext)
414                 && mInjector.isBrightnessModeAutomatic(mContentResolver)) {
415             mAmbientBrightnessStatsTracker.start();
416             mSensorRegistered = true;
417             mInjector.registerSensorListener(mContext, mSensorListener,
418                     mInjector.getBackgroundHandler());
419         }
420     }
421 
stopSensorListener()422     private void stopSensorListener() {
423         if (mSensorRegistered) {
424             mAmbientBrightnessStatsTracker.stop();
425             mInjector.unregisterSensorListener(mContext, mSensorListener);
426             mSensorRegistered = false;
427         }
428     }
429 
scheduleWriteBrightnessTrackerState()430     private void scheduleWriteBrightnessTrackerState() {
431         if (!mWriteBrightnessTrackerStateScheduled) {
432             mBgHandler.post(() -> {
433                 mWriteBrightnessTrackerStateScheduled = false;
434                 writeEvents();
435                 writeAmbientBrightnessStats();
436             });
437             mWriteBrightnessTrackerStateScheduled = true;
438         }
439     }
440 
writeEvents()441     private void writeEvents() {
442         synchronized (mEventsLock) {
443             if (!mEventsDirty) {
444                 // Nothing to write
445                 return;
446             }
447 
448             final AtomicFile writeTo = mInjector.getFile(EVENTS_FILE);
449             if (writeTo == null) {
450                 return;
451             }
452             if (mEvents.isEmpty()) {
453                 if (writeTo.exists()) {
454                     writeTo.delete();
455                 }
456                 mEventsDirty = false;
457             } else {
458                 FileOutputStream output = null;
459                 try {
460                     output = writeTo.startWrite();
461                     writeEventsLocked(output);
462                     writeTo.finishWrite(output);
463                     mEventsDirty = false;
464                 } catch (IOException e) {
465                     writeTo.failWrite(output);
466                     Slog.e(TAG, "Failed to write change mEvents.", e);
467                 }
468             }
469         }
470     }
471 
writeAmbientBrightnessStats()472     private void writeAmbientBrightnessStats() {
473         final AtomicFile writeTo = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
474         if (writeTo == null) {
475             return;
476         }
477         FileOutputStream output = null;
478         try {
479             output = writeTo.startWrite();
480             mAmbientBrightnessStatsTracker.writeStats(output);
481             writeTo.finishWrite(output);
482         } catch (IOException e) {
483             writeTo.failWrite(output);
484             Slog.e(TAG, "Failed to write ambient brightness stats.", e);
485         }
486     }
487 
readEvents()488     private void readEvents() {
489         synchronized (mEventsLock) {
490             // Read might prune events so mark as dirty.
491             mEventsDirty = true;
492             mEvents.clear();
493             final AtomicFile readFrom = mInjector.getFile(EVENTS_FILE);
494             if (readFrom != null && readFrom.exists()) {
495                 FileInputStream input = null;
496                 try {
497                     input = readFrom.openRead();
498                     readEventsLocked(input);
499                 } catch (IOException e) {
500                     readFrom.delete();
501                     Slog.e(TAG, "Failed to read change mEvents.", e);
502                 } finally {
503                     IoUtils.closeQuietly(input);
504                 }
505             }
506         }
507     }
508 
readAmbientBrightnessStats()509     private void readAmbientBrightnessStats() {
510         mAmbientBrightnessStatsTracker = new AmbientBrightnessStatsTracker(mUserManager, null);
511         final AtomicFile readFrom = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
512         if (readFrom != null && readFrom.exists()) {
513             FileInputStream input = null;
514             try {
515                 input = readFrom.openRead();
516                 mAmbientBrightnessStatsTracker.readStats(input);
517             } catch (IOException e) {
518                 readFrom.delete();
519                 Slog.e(TAG, "Failed to read ambient brightness stats.", e);
520             } finally {
521                 IoUtils.closeQuietly(input);
522             }
523         }
524     }
525 
526     @VisibleForTesting
527     @GuardedBy("mEventsLock")
writeEventsLocked(OutputStream stream)528     void writeEventsLocked(OutputStream stream) throws IOException {
529         XmlSerializer out = new FastXmlSerializer();
530         out.setOutput(stream, StandardCharsets.UTF_8.name());
531         out.startDocument(null, true);
532         out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
533 
534         out.startTag(null, TAG_EVENTS);
535         BrightnessChangeEvent[] toWrite = mEvents.toArray();
536         // Clear events, code below will add back the ones that are still within the time window.
537         mEvents.clear();
538         if (DEBUG) {
539             Slog.d(TAG, "Writing events " + toWrite.length);
540         }
541         final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
542         for (int i = 0; i < toWrite.length; ++i) {
543             int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
544             if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
545                 mEvents.append(toWrite[i]);
546                 out.startTag(null, TAG_EVENT);
547                 out.attribute(null, ATTR_NITS, Float.toString(toWrite[i].brightness));
548                 out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
549                 out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
550                 out.attribute(null, ATTR_USER, Integer.toString(userSerialNo));
551                 out.attribute(null, ATTR_BATTERY_LEVEL, Float.toString(toWrite[i].batteryLevel));
552                 out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode));
553                 out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString(
554                         toWrite[i].colorTemperature));
555                 out.attribute(null, ATTR_LAST_NITS,
556                         Float.toString(toWrite[i].lastBrightness));
557                 out.attribute(null, ATTR_DEFAULT_CONFIG,
558                         Boolean.toString(toWrite[i].isDefaultBrightnessConfig));
559                 out.attribute(null, ATTR_POWER_SAVE,
560                         Float.toString(toWrite[i].powerBrightnessFactor));
561                 out.attribute(null, ATTR_USER_POINT,
562                         Boolean.toString(toWrite[i].isUserSetBrightness));
563                 StringBuilder luxValues = new StringBuilder();
564                 StringBuilder luxTimestamps = new StringBuilder();
565                 for (int j = 0; j < toWrite[i].luxValues.length; ++j) {
566                     if (j > 0) {
567                         luxValues.append(',');
568                         luxTimestamps.append(',');
569                     }
570                     luxValues.append(Float.toString(toWrite[i].luxValues[j]));
571                     luxTimestamps.append(Long.toString(toWrite[i].luxTimestamps[j]));
572                 }
573                 out.attribute(null, ATTR_LUX, luxValues.toString());
574                 out.attribute(null, ATTR_LUX_TIMESTAMPS, luxTimestamps.toString());
575                 if (toWrite[i].colorValueBuckets != null
576                         && toWrite[i].colorValueBuckets.length > 0) {
577                     out.attribute(null, ATTR_COLOR_SAMPLE_DURATION,
578                             Long.toString(toWrite[i].colorSampleDuration));
579                     StringBuilder buckets = new StringBuilder();
580                     for (int j = 0; j < toWrite[i].colorValueBuckets.length; ++j) {
581                         if (j > 0) {
582                             buckets.append(',');
583                         }
584                         buckets.append(Long.toString(toWrite[i].colorValueBuckets[j]));
585                     }
586                     out.attribute(null, ATTR_COLOR_VALUE_BUCKETS, buckets.toString());
587                 }
588                 out.endTag(null, TAG_EVENT);
589             }
590         }
591         out.endTag(null, TAG_EVENTS);
592         out.endDocument();
593         stream.flush();
594     }
595 
596     @VisibleForTesting
597     @GuardedBy("mEventsLock")
readEventsLocked(InputStream stream)598     void readEventsLocked(InputStream stream) throws IOException {
599         try {
600             XmlPullParser parser = Xml.newPullParser();
601             parser.setInput(stream, StandardCharsets.UTF_8.name());
602 
603             int type;
604             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
605                     && type != XmlPullParser.START_TAG) {
606             }
607             String tag = parser.getName();
608             if (!TAG_EVENTS.equals(tag)) {
609                 throw new XmlPullParserException(
610                         "Events not found in brightness tracker file " + tag);
611             }
612 
613             final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
614 
615             parser.next();
616             int outerDepth = parser.getDepth();
617             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
618                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
619                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
620                     continue;
621                 }
622                 tag = parser.getName();
623                 if (TAG_EVENT.equals(tag)) {
624                     BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
625 
626                     String brightness = parser.getAttributeValue(null, ATTR_NITS);
627                     builder.setBrightness(Float.parseFloat(brightness));
628                     String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
629                     builder.setTimeStamp(Long.parseLong(timestamp));
630                     builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME));
631                     String user = parser.getAttributeValue(null, ATTR_USER);
632                     builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user)));
633                     String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
634                     builder.setBatteryLevel(Float.parseFloat(batteryLevel));
635                     String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
636                     builder.setNightMode(Boolean.parseBoolean(nightMode));
637                     String colorTemperature =
638                             parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
639                     builder.setColorTemperature(Integer.parseInt(colorTemperature));
640                     String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS);
641                     builder.setLastBrightness(Float.parseFloat(lastBrightness));
642 
643                     String luxValue = parser.getAttributeValue(null, ATTR_LUX);
644                     String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
645 
646                     String[] luxValuesStrings = luxValue.split(",");
647                     String[] luxTimestampsStrings = luxTimestamp.split(",");
648                     if (luxValuesStrings.length != luxTimestampsStrings.length) {
649                         continue;
650                     }
651                     float[] luxValues = new float[luxValuesStrings.length];
652                     long[] luxTimestamps = new long[luxValuesStrings.length];
653                     for (int i = 0; i < luxValues.length; ++i) {
654                         luxValues[i] = Float.parseFloat(luxValuesStrings[i]);
655                         luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]);
656                     }
657                     builder.setLuxValues(luxValues);
658                     builder.setLuxTimestamps(luxTimestamps);
659 
660                     String defaultConfig = parser.getAttributeValue(null, ATTR_DEFAULT_CONFIG);
661                     if (defaultConfig != null) {
662                         builder.setIsDefaultBrightnessConfig(Boolean.parseBoolean(defaultConfig));
663                     }
664                     String powerSave = parser.getAttributeValue(null, ATTR_POWER_SAVE);
665                     if (powerSave != null) {
666                         builder.setPowerBrightnessFactor(Float.parseFloat(powerSave));
667                     } else {
668                         builder.setPowerBrightnessFactor(1.0f);
669                     }
670                     String userPoint = parser.getAttributeValue(null, ATTR_USER_POINT);
671                     if (userPoint != null) {
672                         builder.setUserBrightnessPoint(Boolean.parseBoolean(userPoint));
673                     }
674 
675                     String colorSampleDurationString =
676                             parser.getAttributeValue(null, ATTR_COLOR_SAMPLE_DURATION);
677                     String colorValueBucketsString =
678                             parser.getAttributeValue(null, ATTR_COLOR_VALUE_BUCKETS);
679                     if (colorSampleDurationString != null && colorValueBucketsString != null) {
680                         long colorSampleDuration = Long.parseLong(colorSampleDurationString);
681                         String[] buckets = colorValueBucketsString.split(",");
682                         long[] bucketValues = new long[buckets.length];
683                         for (int i = 0; i < bucketValues.length; ++i) {
684                             bucketValues[i] = Long.parseLong(buckets[i]);
685                         }
686                         builder.setColorValues(bucketValues, colorSampleDuration);
687                     }
688 
689                     BrightnessChangeEvent event = builder.build();
690                     if (DEBUG) {
691                         Slog.i(TAG, "Read event " + event.brightness
692                                 + " " + event.packageName);
693                     }
694 
695                     if (event.userId != -1 && event.timeStamp > timeCutOff
696                             && event.luxValues.length > 0) {
697                         mEvents.append(event);
698                     }
699                 }
700             }
701         } catch (NullPointerException | NumberFormatException | XmlPullParserException
702                 | IOException e) {
703             // Failed to parse something, just start with an empty event log.
704             mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
705             Slog.e(TAG, "Failed to parse brightness event", e);
706             // Re-throw so we will delete the bad file.
707             throw new IOException("failed to parse file", e);
708         }
709     }
710 
dump(final PrintWriter pw)711     public void dump(final PrintWriter pw) {
712         pw.println("BrightnessTracker state:");
713         synchronized (mDataCollectionLock) {
714             pw.println("  mStarted=" + mStarted);
715             pw.println("  mLastBatteryLevel=" + mLastBatteryLevel);
716             pw.println("  mLastBrightness=" + mLastBrightness);
717             pw.println("  mLastSensorReadings.size=" + mLastSensorReadings.size());
718             if (!mLastSensorReadings.isEmpty()) {
719                 pw.println("  mLastSensorReadings time span "
720                         + mLastSensorReadings.peekFirst().timestamp + "->"
721                         + mLastSensorReadings.peekLast().timestamp);
722             }
723         }
724         synchronized (mEventsLock) {
725             pw.println("  mEventsDirty=" + mEventsDirty);
726             pw.println("  mEvents.size=" + mEvents.size());
727             BrightnessChangeEvent[] events = mEvents.toArray();
728             for (int i = 0; i < events.length; ++i) {
729                 pw.print("    " + FORMAT.format(new Date(events[i].timeStamp)));
730                 pw.print(", userId=" + events[i].userId);
731                 pw.print(", " + events[i].lastBrightness + "->" + events[i].brightness);
732                 pw.print(", isUserSetBrightness=" + events[i].isUserSetBrightness);
733                 pw.print(", powerBrightnessFactor=" + events[i].powerBrightnessFactor);
734                 pw.print(", isDefaultBrightnessConfig=" + events[i].isDefaultBrightnessConfig);
735                 pw.print(" {");
736                 for (int j = 0; j < events[i].luxValues.length; ++j){
737                     if (j != 0) {
738                         pw.print(", ");
739                     }
740                     pw.print("(" + events[i].luxValues[j] + "," + events[i].luxTimestamps[j] + ")");
741                 }
742                 pw.println("}");
743             }
744         }
745         pw.println("  mWriteBrightnessTrackerStateScheduled="
746                 + mWriteBrightnessTrackerStateScheduled);
747         mBgHandler.runWithScissors(() -> dumpLocal(pw), 1000);
748         if (mAmbientBrightnessStatsTracker != null) {
749             pw.println();
750             mAmbientBrightnessStatsTracker.dump(pw);
751         }
752     }
753 
dumpLocal(PrintWriter pw)754     private void dumpLocal(PrintWriter pw) {
755         pw.println("  mSensorRegistered=" + mSensorRegistered);
756         pw.println("  mColorSamplingEnabled=" + mColorSamplingEnabled);
757         pw.println("  mNoFramesToSample=" + mNoFramesToSample);
758         pw.println("  mFrameRate=" + mFrameRate);
759     }
760 
enableColorSampling()761     private void enableColorSampling() {
762         if (!ENABLE_COLOR_SAMPLING
763                 || !mInjector.isBrightnessModeAutomatic(mContentResolver)
764                 || !mInjector.isInteractive(mContext)
765                 || mColorSamplingEnabled) {
766             return;
767         }
768 
769         mFrameRate = mInjector.getFrameRate(mContext);
770         if (mFrameRate <= 0) {
771             Slog.wtf(TAG, "Default display has a zero or negative framerate.");
772             return;
773         }
774         mNoFramesToSample = (int) (mFrameRate * COLOR_SAMPLE_DURATION);
775 
776         DisplayedContentSamplingAttributes attributes = mInjector.getSamplingAttributes();
777         if (DEBUG && attributes != null) {
778             Slog.d(TAG, "Color sampling"
779                     + " mask=0x" + Integer.toHexString(attributes.getComponentMask())
780                     + " dataSpace=0x" + Integer.toHexString(attributes.getDataspace())
781                     + " pixelFormat=0x" + Integer.toHexString(attributes.getPixelFormat()));
782         }
783         // Do we support sampling the Value component of HSV
784         if (attributes != null && attributes.getPixelFormat() == PixelFormat.HSV_888
785                 && (attributes.getComponentMask() & COLOR_SAMPLE_COMPONENT_MASK) != 0) {
786 
787             mColorSamplingEnabled = mInjector.enableColorSampling(/* enable= */true,
788                     mNoFramesToSample);
789             if (DEBUG) {
790                 Slog.i(TAG, "turning on color sampling for "
791                         + mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
792             }
793         }
794         if (mColorSamplingEnabled && mDisplayListener == null) {
795             mDisplayListener = new DisplayListener();
796             mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
797         }
798     }
799 
disableColorSampling()800     private void disableColorSampling() {
801         if (!mColorSamplingEnabled) {
802             return;
803         }
804         mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
805         mColorSamplingEnabled = false;
806         if (mDisplayListener != null) {
807             mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
808             mDisplayListener = null;
809         }
810         if (DEBUG) {
811             Slog.i(TAG, "turning off color sampling");
812         }
813     }
814 
updateColorSampling()815     private void updateColorSampling() {
816         if (!mColorSamplingEnabled) {
817             return;
818         }
819         float frameRate = mInjector.getFrameRate(mContext);
820         if (frameRate != mFrameRate) {
821             disableColorSampling();
822             enableColorSampling();
823         }
824     }
825 
getAmbientBrightnessStats(int userId)826     public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId) {
827         if (mAmbientBrightnessStatsTracker != null) {
828             ArrayList<AmbientBrightnessDayStats> stats =
829                     mAmbientBrightnessStatsTracker.getUserStats(userId);
830             if (stats != null) {
831                 return new ParceledListSlice<>(stats);
832             }
833         }
834         return ParceledListSlice.emptyList();
835     }
836 
837     // Not allowed to keep the SensorEvent so used to copy the data we care about.
838     private static class LightData {
839         public float lux;
840         // Time in elapsedRealtimeNanos
841         public long timestamp;
842     }
843 
recordSensorEvent(SensorEvent event)844     private void recordSensorEvent(SensorEvent event) {
845         long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
846         synchronized (mDataCollectionLock) {
847             if (DEBUG) {
848                 Slog.v(TAG, "Sensor event " + event);
849             }
850             if (!mLastSensorReadings.isEmpty()
851                     && event.timestamp < mLastSensorReadings.getLast().timestamp) {
852                 // Ignore event that came out of order.
853                 return;
854             }
855             LightData data = null;
856             while (!mLastSensorReadings.isEmpty()
857                     && mLastSensorReadings.getFirst().timestamp < horizon) {
858                 // Remove data that has fallen out of the window.
859                 data = mLastSensorReadings.removeFirst();
860             }
861             // We put back the last one we removed so we know how long
862             // the first sensor reading was valid for.
863             if (data != null) {
864                 mLastSensorReadings.addFirst(data);
865             }
866 
867             data = new LightData();
868             data.timestamp = event.timestamp;
869             data.lux = event.values[0];
870             mLastSensorReadings.addLast(data);
871         }
872     }
873 
recordAmbientBrightnessStats(SensorEvent event)874     private void recordAmbientBrightnessStats(SensorEvent event) {
875         mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
876     }
877 
batteryLevelChanged(int level, int scale)878     private void batteryLevelChanged(int level, int scale) {
879         synchronized (mDataCollectionLock) {
880             mLastBatteryLevel = (float) level / (float) scale;
881         }
882     }
883 
884     private final class SensorListener implements SensorEventListener {
885         @Override
onSensorChanged(SensorEvent event)886         public void onSensorChanged(SensorEvent event) {
887             recordSensorEvent(event);
888             recordAmbientBrightnessStats(event);
889         }
890 
891         @Override
onAccuracyChanged(Sensor sensor, int accuracy)892         public void onAccuracyChanged(Sensor sensor, int accuracy) {
893 
894         }
895     }
896 
897     private final class DisplayListener implements DisplayManager.DisplayListener {
898 
899         @Override
onDisplayAdded(int displayId)900         public void onDisplayAdded(int displayId) {
901             // Ignore
902         }
903 
904         @Override
onDisplayRemoved(int displayId)905         public void onDisplayRemoved(int displayId) {
906             // Ignore
907         }
908 
909         @Override
onDisplayChanged(int displayId)910         public void onDisplayChanged(int displayId) {
911             if (displayId == Display.DEFAULT_DISPLAY) {
912                 updateColorSampling();
913             }
914         }
915     }
916 
917     private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)918         public SettingsObserver(Handler handler) {
919             super(handler);
920         }
921 
922         @Override
onChange(boolean selfChange, Uri uri)923         public void onChange(boolean selfChange, Uri uri) {
924             if (DEBUG) {
925                 Slog.v(TAG, "settings change " + uri);
926             }
927             if (mInjector.isBrightnessModeAutomatic(mContentResolver)) {
928                 mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
929             } else {
930                 mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
931             }
932         }
933     }
934 
935     private final class Receiver extends BroadcastReceiver {
936         @Override
onReceive(Context context, Intent intent)937         public void onReceive(Context context, Intent intent) {
938             if (DEBUG) {
939                 Slog.d(TAG, "Received " + intent.getAction());
940             }
941             String action = intent.getAction();
942             if (Intent.ACTION_SHUTDOWN.equals(action)) {
943                 stop();
944                 scheduleWriteBrightnessTrackerState();
945             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
946                 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
947                 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
948                 if (level != -1 && scale != 0) {
949                     batteryLevelChanged(level, scale);
950                 }
951             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
952                 mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
953             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
954                 mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
955             }
956         }
957     }
958 
959     private final class TrackerHandler extends Handler {
TrackerHandler(Looper looper)960         public TrackerHandler(Looper looper) {
961             super(looper, null, true /*async*/);
962         }
handleMessage(Message msg)963         public void handleMessage(Message msg) {
964             switch (msg.what) {
965                 case MSG_BACKGROUND_START:
966                     backgroundStart((float)msg.obj /*initial brightness*/);
967                     break;
968                 case MSG_BRIGHTNESS_CHANGED:
969                     BrightnessChangeValues values = (BrightnessChangeValues) msg.obj;
970                     boolean userInitiatedChange = (msg.arg1 == 1);
971                     handleBrightnessChanged(values.brightness, userInitiatedChange,
972                             values.powerBrightnessFactor, values.isUserSetBrightness,
973                             values.isDefaultBrightnessConfig, values.timestamp);
974                     break;
975                 case MSG_START_SENSOR_LISTENER:
976                     startSensorListener();
977                     enableColorSampling();
978                     break;
979                 case MSG_STOP_SENSOR_LISTENER:
980                     stopSensorListener();
981                     disableColorSampling();
982                     break;
983             }
984         }
985     }
986 
987     private static class BrightnessChangeValues {
988         final float brightness;
989         final float powerBrightnessFactor;
990         final boolean isUserSetBrightness;
991         final boolean isDefaultBrightnessConfig;
992         final long timestamp;
993 
BrightnessChangeValues(float brightness, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, long timestamp)994         BrightnessChangeValues(float brightness, float powerBrightnessFactor,
995                 boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
996                 long timestamp) {
997             this.brightness = brightness;
998             this.powerBrightnessFactor = powerBrightnessFactor;
999             this.isUserSetBrightness = isUserSetBrightness;
1000             this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
1001             this.timestamp = timestamp;
1002         }
1003     }
1004 
1005     @VisibleForTesting
1006     static class Injector {
registerSensorListener(Context context, SensorEventListener sensorListener, Handler handler)1007         public void registerSensorListener(Context context,
1008                 SensorEventListener sensorListener, Handler handler) {
1009             SensorManager sensorManager = context.getSystemService(SensorManager.class);
1010             Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
1011             sensorManager.registerListener(sensorListener,
1012                     lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
1013         }
1014 
unregisterSensorListener(Context context, SensorEventListener sensorListener)1015         public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
1016             SensorManager sensorManager = context.getSystemService(SensorManager.class);
1017             sensorManager.unregisterListener(sensorListener);
1018         }
1019 
registerBrightnessModeObserver(ContentResolver resolver, ContentObserver settingsObserver)1020         public void registerBrightnessModeObserver(ContentResolver resolver,
1021                 ContentObserver settingsObserver) {
1022             resolver.registerContentObserver(Settings.System.getUriFor(
1023                     Settings.System.SCREEN_BRIGHTNESS_MODE),
1024                     false, settingsObserver, UserHandle.USER_ALL);
1025         }
1026 
unregisterBrightnessModeObserver(Context context, ContentObserver settingsObserver)1027         public void unregisterBrightnessModeObserver(Context context,
1028                 ContentObserver settingsObserver) {
1029             context.getContentResolver().unregisterContentObserver(settingsObserver);
1030         }
1031 
registerReceiver(Context context, BroadcastReceiver receiver, IntentFilter filter)1032         public void registerReceiver(Context context,
1033                 BroadcastReceiver receiver, IntentFilter filter) {
1034             context.registerReceiver(receiver, filter);
1035         }
1036 
unregisterReceiver(Context context, BroadcastReceiver receiver)1037         public void unregisterReceiver(Context context,
1038                 BroadcastReceiver receiver) {
1039             context.unregisterReceiver(receiver);
1040         }
1041 
getBackgroundHandler()1042         public Handler getBackgroundHandler() {
1043             return BackgroundThread.getHandler();
1044         }
1045 
isBrightnessModeAutomatic(ContentResolver resolver)1046         public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
1047             return Settings.System.getIntForUser(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
1048                     Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT)
1049                     == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
1050         }
1051 
getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue, int userId)1052         public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
1053                 int userId) {
1054             return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
1055         }
1056 
getFile(String filename)1057         public AtomicFile getFile(String filename) {
1058             return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), filename));
1059         }
1060 
currentTimeMillis()1061         public long currentTimeMillis() {
1062             return System.currentTimeMillis();
1063         }
1064 
elapsedRealtimeNanos()1065         public long elapsedRealtimeNanos() {
1066             return SystemClock.elapsedRealtimeNanos();
1067         }
1068 
getUserSerialNumber(UserManager userManager, int userId)1069         public int getUserSerialNumber(UserManager userManager, int userId) {
1070             return userManager.getUserSerialNumber(userId);
1071         }
1072 
getUserId(UserManager userManager, int userSerialNumber)1073         public int getUserId(UserManager userManager, int userSerialNumber) {
1074             return userManager.getUserHandle(userSerialNumber);
1075         }
1076 
getProfileIds(UserManager userManager, int userId)1077         public int[] getProfileIds(UserManager userManager, int userId) {
1078             if (userManager != null) {
1079                 return userManager.getProfileIds(userId, false);
1080             } else {
1081                 return new int[]{userId};
1082             }
1083         }
1084 
getFocusedStack()1085         public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
1086             return ActivityTaskManager.getService().getFocusedStackInfo();
1087         }
1088 
scheduleIdleJob(Context context)1089         public void scheduleIdleJob(Context context) {
1090             BrightnessIdleJob.scheduleJob(context);
1091         }
1092 
cancelIdleJob(Context context)1093         public void cancelIdleJob(Context context) {
1094             BrightnessIdleJob.cancelJob(context);
1095         }
1096 
isInteractive(Context context)1097         public boolean isInteractive(Context context) {
1098             return context.getSystemService(PowerManager.class).isInteractive();
1099         }
1100 
getNightDisplayColorTemperature(Context context)1101         public int getNightDisplayColorTemperature(Context context) {
1102             return context.getSystemService(ColorDisplayManager.class)
1103                     .getNightDisplayColorTemperature();
1104         }
1105 
isNightDisplayActivated(Context context)1106         public boolean isNightDisplayActivated(Context context) {
1107             return context.getSystemService(ColorDisplayManager.class).isNightDisplayActivated();
1108         }
1109 
sampleColor(int noFramesToSample)1110         public DisplayedContentSample sampleColor(int noFramesToSample) {
1111             final DisplayManagerInternal displayManagerInternal =
1112                     LocalServices.getService(DisplayManagerInternal.class);
1113             return displayManagerInternal.getDisplayedContentSample(
1114                    Display.DEFAULT_DISPLAY, noFramesToSample, 0);
1115         }
1116 
getFrameRate(Context context)1117         public float getFrameRate(Context context) {
1118             final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
1119             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1120             return display.getRefreshRate();
1121         }
1122 
getSamplingAttributes()1123         public DisplayedContentSamplingAttributes getSamplingAttributes() {
1124             final DisplayManagerInternal displayManagerInternal =
1125                     LocalServices.getService(DisplayManagerInternal.class);
1126             return displayManagerInternal.getDisplayedContentSamplingAttributes(
1127                     Display.DEFAULT_DISPLAY);
1128         }
1129 
enableColorSampling(boolean enable, int noFrames)1130         public boolean enableColorSampling(boolean enable, int noFrames) {
1131             final DisplayManagerInternal displayManagerInternal =
1132                     LocalServices.getService(DisplayManagerInternal.class);
1133             return displayManagerInternal.setDisplayedContentSamplingEnabled(
1134                     Display.DEFAULT_DISPLAY, enable, COLOR_SAMPLE_COMPONENT_MASK, noFrames);
1135         }
1136 
registerDisplayListener(Context context, DisplayManager.DisplayListener listener, Handler handler)1137         public void registerDisplayListener(Context context,
1138                 DisplayManager.DisplayListener listener, Handler handler) {
1139             final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
1140             displayManager.registerDisplayListener(listener, handler);
1141         }
1142 
unRegisterDisplayListener(Context context, DisplayManager.DisplayListener listener)1143         public void unRegisterDisplayListener(Context context,
1144                 DisplayManager.DisplayListener listener) {
1145             final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
1146             displayManager.unregisterDisplayListener(listener);
1147         }
1148     }
1149 }
1150