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