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