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