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