/*
|
* Copyright 2017 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.display;
|
|
import android.annotation.Nullable;
|
import android.annotation.UserIdInt;
|
import android.app.ActivityManager;
|
import android.app.ActivityTaskManager;
|
import android.content.BroadcastReceiver;
|
import android.content.ContentResolver;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.content.pm.ParceledListSlice;
|
import android.database.ContentObserver;
|
import android.graphics.PixelFormat;
|
import android.hardware.Sensor;
|
import android.hardware.SensorEvent;
|
import android.hardware.SensorEventListener;
|
import android.hardware.SensorManager;
|
import android.hardware.display.AmbientBrightnessDayStats;
|
import android.hardware.display.BrightnessChangeEvent;
|
import android.hardware.display.ColorDisplayManager;
|
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManagerInternal;
|
import android.hardware.display.DisplayedContentSample;
|
import android.hardware.display.DisplayedContentSamplingAttributes;
|
import android.net.Uri;
|
import android.os.BatteryManager;
|
import android.os.Environment;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.PowerManager;
|
import android.os.RemoteException;
|
import android.os.SystemClock;
|
import android.os.UserHandle;
|
import android.os.UserManager;
|
import android.provider.Settings;
|
import android.util.AtomicFile;
|
import android.util.Slog;
|
import android.util.Xml;
|
import android.view.Display;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.os.BackgroundThread;
|
import com.android.internal.util.FastXmlSerializer;
|
import com.android.internal.util.RingBuffer;
|
import com.android.server.LocalServices;
|
|
import libcore.io.IoUtils;
|
|
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlSerializer;
|
|
import java.io.File;
|
import java.io.FileInputStream;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.OutputStream;
|
import java.io.PrintWriter;
|
import java.nio.charset.StandardCharsets;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayDeque;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.Deque;
|
import java.util.HashMap;
|
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* Class that tracks recent brightness settings changes and stores
|
* associated information such as light sensor readings.
|
*/
|
public class BrightnessTracker {
|
|
static final String TAG = "BrightnessTracker";
|
static final boolean DEBUG = false;
|
|
private static final String EVENTS_FILE = "brightness_events.xml";
|
private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml";
|
private static final int MAX_EVENTS = 100;
|
// Discard events when reading or writing that are older than this.
|
private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
|
// Time over which we keep lux sensor readings.
|
private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
|
|
private static final String TAG_EVENTS = "events";
|
private static final String TAG_EVENT = "event";
|
private static final String ATTR_NITS = "nits";
|
private static final String ATTR_TIMESTAMP = "timestamp";
|
private static final String ATTR_PACKAGE_NAME = "packageName";
|
private static final String ATTR_USER = "user";
|
private static final String ATTR_LUX = "lux";
|
private static final String ATTR_LUX_TIMESTAMPS = "luxTimestamps";
|
private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
|
private static final String ATTR_NIGHT_MODE = "nightMode";
|
private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
|
private static final String ATTR_LAST_NITS = "lastNits";
|
private static final String ATTR_DEFAULT_CONFIG = "defaultConfig";
|
private static final String ATTR_POWER_SAVE = "powerSaveFactor";
|
private static final String ATTR_USER_POINT = "userPoint";
|
private static final String ATTR_COLOR_SAMPLE_DURATION = "colorSampleDuration";
|
private static final String ATTR_COLOR_VALUE_BUCKETS = "colorValueBuckets";
|
|
private static final int MSG_BACKGROUND_START = 0;
|
private static final int MSG_BRIGHTNESS_CHANGED = 1;
|
private static final int MSG_STOP_SENSOR_LISTENER = 2;
|
private static final int MSG_START_SENSOR_LISTENER = 3;
|
|
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
|
|
private static final long COLOR_SAMPLE_DURATION = TimeUnit.SECONDS.toSeconds(10);
|
// Sample chanel 2 of HSV which is the Value component.
|
private static final int COLOR_SAMPLE_COMPONENT_MASK = 0x1 << 2;
|
|
// Lock held while accessing mEvents, is held while writing events to flash.
|
private final Object mEventsLock = new Object();
|
@GuardedBy("mEventsLock")
|
private RingBuffer<BrightnessChangeEvent> mEvents
|
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
|
@GuardedBy("mEventsLock")
|
private boolean mEventsDirty;
|
|
private volatile boolean mWriteBrightnessTrackerStateScheduled;
|
|
private AmbientBrightnessStatsTracker mAmbientBrightnessStatsTracker;
|
|
private final UserManager mUserManager;
|
private final Context mContext;
|
private final ContentResolver mContentResolver;
|
private final Handler mBgHandler;
|
|
// These members should only be accessed on the mBgHandler thread.
|
private BroadcastReceiver mBroadcastReceiver;
|
private SensorListener mSensorListener;
|
private SettingsObserver mSettingsObserver;
|
private DisplayListener mDisplayListener;
|
private boolean mSensorRegistered;
|
private boolean mColorSamplingEnabled;
|
private int mNoFramesToSample;
|
private float mFrameRate;
|
// End of block of members that should only be accessed on the mBgHandler thread.
|
|
private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;
|
|
// Lock held while collecting data related to brightness changes.
|
private final Object mDataCollectionLock = new Object();
|
@GuardedBy("mDataCollectionLock")
|
private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
|
@GuardedBy("mDataCollectionLock")
|
private float mLastBatteryLevel = Float.NaN;
|
@GuardedBy("mDataCollectionLock")
|
private float mLastBrightness = -1;
|
@GuardedBy("mDataCollectionLock")
|
private boolean mStarted;
|
|
private final Injector mInjector;
|
|
public BrightnessTracker(Context context, @Nullable Injector injector) {
|
// Note this will be called very early in boot, other system
|
// services may not be present.
|
mContext = context;
|
mContentResolver = context.getContentResolver();
|
if (injector != null) {
|
mInjector = injector;
|
} else {
|
mInjector = new Injector();
|
}
|
mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper());
|
mUserManager = mContext.getSystemService(UserManager.class);
|
}
|
|
/**
|
* Start listening for brightness slider events
|
*
|
* @param initialBrightness the initial screen brightness
|
*/
|
public void start(float initialBrightness) {
|
if (DEBUG) {
|
Slog.d(TAG, "Start");
|
}
|
mCurrentUserId = ActivityManager.getCurrentUser();
|
mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget();
|
}
|
|
private void backgroundStart(float initialBrightness) {
|
readEvents();
|
readAmbientBrightnessStats();
|
|
mSensorListener = new SensorListener();
|
|
mSettingsObserver = new SettingsObserver(mBgHandler);
|
mInjector.registerBrightnessModeObserver(mContentResolver, mSettingsObserver);
|
startSensorListener();
|
|
final IntentFilter intentFilter = new IntentFilter();
|
intentFilter.addAction(Intent.ACTION_SHUTDOWN);
|
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
mBroadcastReceiver = new Receiver();
|
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
|
|
mInjector.scheduleIdleJob(mContext);
|
synchronized (mDataCollectionLock) {
|
mLastBrightness = initialBrightness;
|
mStarted = true;
|
}
|
enableColorSampling();
|
}
|
|
/** Stop listening for events */
|
@VisibleForTesting
|
void stop() {
|
if (DEBUG) {
|
Slog.d(TAG, "Stop");
|
}
|
mBgHandler.removeMessages(MSG_BACKGROUND_START);
|
stopSensorListener();
|
mInjector.unregisterSensorListener(mContext, mSensorListener);
|
mInjector.unregisterBrightnessModeObserver(mContext, mSettingsObserver);
|
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
|
mInjector.cancelIdleJob(mContext);
|
|
synchronized (mDataCollectionLock) {
|
mStarted = false;
|
}
|
disableColorSampling();
|
}
|
|
public void onSwitchUser(@UserIdInt int newUserId) {
|
if (DEBUG) {
|
Slog.d(TAG, "Used id updated from " + mCurrentUserId + " to " + newUserId);
|
}
|
mCurrentUserId = newUserId;
|
}
|
|
/**
|
* @param userId userId to fetch data for.
|
* @param includePackage if false we will null out BrightnessChangeEvent.packageName
|
* @return List of recent {@link BrightnessChangeEvent}s
|
*/
|
public ParceledListSlice<BrightnessChangeEvent> getEvents(int userId, boolean includePackage) {
|
BrightnessChangeEvent[] events;
|
synchronized (mEventsLock) {
|
events = mEvents.toArray();
|
}
|
int[] profiles = mInjector.getProfileIds(mUserManager, userId);
|
Map<Integer, Boolean> toRedact = new HashMap<>();
|
for (int i = 0; i < profiles.length; ++i) {
|
int profileId = profiles[i];
|
// Include slider interactions when a managed profile app is in the
|
// foreground but always redact the package name.
|
boolean redact = (!includePackage) || profileId != userId;
|
toRedact.put(profiles[i], redact);
|
}
|
ArrayList<BrightnessChangeEvent> out = new ArrayList<>(events.length);
|
for (int i = 0; i < events.length; ++i) {
|
Boolean redact = toRedact.get(events[i].userId);
|
if (redact != null) {
|
if (!redact) {
|
out.add(events[i]);
|
} else {
|
BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]),
|
/* redactPackage */ true);
|
out.add(event);
|
}
|
}
|
}
|
return new ParceledListSlice<>(out);
|
}
|
|
public void persistBrightnessTrackerState() {
|
scheduleWriteBrightnessTrackerState();
|
}
|
|
/**
|
* Notify the BrightnessTracker that the user has changed the brightness of the display.
|
*/
|
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
|
float powerBrightnessFactor, boolean isUserSetBrightness,
|
boolean isDefaultBrightnessConfig) {
|
if (DEBUG) {
|
Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
|
brightness, userInitiated));
|
}
|
Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
|
userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
|
powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
|
mInjector.currentTimeMillis()));
|
m.sendToTarget();
|
}
|
|
private void handleBrightnessChanged(float brightness, boolean userInitiated,
|
float powerBrightnessFactor, boolean isUserSetBrightness,
|
boolean isDefaultBrightnessConfig, long timestamp) {
|
BrightnessChangeEvent.Builder builder;
|
|
synchronized (mDataCollectionLock) {
|
if (!mStarted) {
|
// Not currently gathering brightness change information
|
return;
|
}
|
|
float previousBrightness = mLastBrightness;
|
mLastBrightness = brightness;
|
|
if (!userInitiated) {
|
// We want to record what current brightness is so that we know what the user
|
// changed it from, but if it wasn't user initiated then we don't want to record it
|
// as a BrightnessChangeEvent.
|
return;
|
}
|
|
builder = new BrightnessChangeEvent.Builder();
|
builder.setBrightness(brightness);
|
builder.setTimeStamp(timestamp);
|
builder.setPowerBrightnessFactor(powerBrightnessFactor);
|
builder.setUserBrightnessPoint(isUserSetBrightness);
|
builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
|
|
final int readingCount = mLastSensorReadings.size();
|
if (readingCount == 0) {
|
// No sensor data so ignore this.
|
return;
|
}
|
|
float[] luxValues = new float[readingCount];
|
long[] luxTimestamps = new long[readingCount];
|
|
int pos = 0;
|
|
// Convert sensor timestamp in elapsed time nanos to current time millis.
|
long currentTimeMillis = mInjector.currentTimeMillis();
|
long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
|
for (LightData reading : mLastSensorReadings) {
|
luxValues[pos] = reading.lux;
|
luxTimestamps[pos] = currentTimeMillis -
|
TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
|
++pos;
|
}
|
builder.setLuxValues(luxValues);
|
builder.setLuxTimestamps(luxTimestamps);
|
|
builder.setBatteryLevel(mLastBatteryLevel);
|
builder.setLastBrightness(previousBrightness);
|
}
|
|
try {
|
final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
|
if (focusedStack != null && focusedStack.topActivity != null) {
|
builder.setUserId(focusedStack.userId);
|
builder.setPackageName(focusedStack.topActivity.getPackageName());
|
} else {
|
// Ignore the event because we can't determine user / package.
|
if (DEBUG) {
|
Slog.d(TAG, "Ignoring event due to null focusedStack.");
|
}
|
return;
|
}
|
} catch (RemoteException e) {
|
// Really shouldn't be possible.
|
return;
|
}
|
|
builder.setNightMode(mInjector.isNightDisplayActivated(mContext));
|
builder.setColorTemperature(mInjector.getNightDisplayColorTemperature(mContext));
|
|
if (mColorSamplingEnabled) {
|
DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample);
|
if (sample != null && sample.getSampleComponent(
|
DisplayedContentSample.ColorComponent.CHANNEL2) != null) {
|
float numMillis = (sample.getNumFrames() / mFrameRate) * 1000.0f;
|
builder.setColorValues(
|
sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2),
|
Math.round(numMillis));
|
}
|
}
|
|
BrightnessChangeEvent event = builder.build();
|
if (DEBUG) {
|
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
|
}
|
synchronized (mEventsLock) {
|
mEventsDirty = true;
|
mEvents.append(event);
|
}
|
}
|
|
private void startSensorListener() {
|
if (!mSensorRegistered
|
&& mInjector.isInteractive(mContext)
|
&& mInjector.isBrightnessModeAutomatic(mContentResolver)) {
|
mAmbientBrightnessStatsTracker.start();
|
mSensorRegistered = true;
|
mInjector.registerSensorListener(mContext, mSensorListener,
|
mInjector.getBackgroundHandler());
|
}
|
}
|
|
private void stopSensorListener() {
|
if (mSensorRegistered) {
|
mAmbientBrightnessStatsTracker.stop();
|
mInjector.unregisterSensorListener(mContext, mSensorListener);
|
mSensorRegistered = false;
|
}
|
}
|
|
private void scheduleWriteBrightnessTrackerState() {
|
if (!mWriteBrightnessTrackerStateScheduled) {
|
mBgHandler.post(() -> {
|
mWriteBrightnessTrackerStateScheduled = false;
|
writeEvents();
|
writeAmbientBrightnessStats();
|
});
|
mWriteBrightnessTrackerStateScheduled = true;
|
}
|
}
|
|
private void writeEvents() {
|
synchronized (mEventsLock) {
|
if (!mEventsDirty) {
|
// Nothing to write
|
return;
|
}
|
|
final AtomicFile writeTo = mInjector.getFile(EVENTS_FILE);
|
if (writeTo == null) {
|
return;
|
}
|
if (mEvents.isEmpty()) {
|
if (writeTo.exists()) {
|
writeTo.delete();
|
}
|
mEventsDirty = false;
|
} else {
|
FileOutputStream output = null;
|
try {
|
output = writeTo.startWrite();
|
writeEventsLocked(output);
|
writeTo.finishWrite(output);
|
mEventsDirty = false;
|
} catch (IOException e) {
|
writeTo.failWrite(output);
|
Slog.e(TAG, "Failed to write change mEvents.", e);
|
}
|
}
|
}
|
}
|
|
private void writeAmbientBrightnessStats() {
|
final AtomicFile writeTo = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
|
if (writeTo == null) {
|
return;
|
}
|
FileOutputStream output = null;
|
try {
|
output = writeTo.startWrite();
|
mAmbientBrightnessStatsTracker.writeStats(output);
|
writeTo.finishWrite(output);
|
} catch (IOException e) {
|
writeTo.failWrite(output);
|
Slog.e(TAG, "Failed to write ambient brightness stats.", e);
|
}
|
}
|
|
private void readEvents() {
|
synchronized (mEventsLock) {
|
// Read might prune events so mark as dirty.
|
mEventsDirty = true;
|
mEvents.clear();
|
final AtomicFile readFrom = mInjector.getFile(EVENTS_FILE);
|
if (readFrom != null && readFrom.exists()) {
|
FileInputStream input = null;
|
try {
|
input = readFrom.openRead();
|
readEventsLocked(input);
|
} catch (IOException e) {
|
readFrom.delete();
|
Slog.e(TAG, "Failed to read change mEvents.", e);
|
} finally {
|
IoUtils.closeQuietly(input);
|
}
|
}
|
}
|
}
|
|
private void readAmbientBrightnessStats() {
|
mAmbientBrightnessStatsTracker = new AmbientBrightnessStatsTracker(mUserManager, null);
|
final AtomicFile readFrom = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
|
if (readFrom != null && readFrom.exists()) {
|
FileInputStream input = null;
|
try {
|
input = readFrom.openRead();
|
mAmbientBrightnessStatsTracker.readStats(input);
|
} catch (IOException e) {
|
readFrom.delete();
|
Slog.e(TAG, "Failed to read ambient brightness stats.", e);
|
} finally {
|
IoUtils.closeQuietly(input);
|
}
|
}
|
}
|
|
@VisibleForTesting
|
@GuardedBy("mEventsLock")
|
void writeEventsLocked(OutputStream stream) throws IOException {
|
XmlSerializer out = new FastXmlSerializer();
|
out.setOutput(stream, StandardCharsets.UTF_8.name());
|
out.startDocument(null, true);
|
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
|
out.startTag(null, TAG_EVENTS);
|
BrightnessChangeEvent[] toWrite = mEvents.toArray();
|
// Clear events, code below will add back the ones that are still within the time window.
|
mEvents.clear();
|
if (DEBUG) {
|
Slog.d(TAG, "Writing events " + toWrite.length);
|
}
|
final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
|
for (int i = 0; i < toWrite.length; ++i) {
|
int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
|
if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
|
mEvents.append(toWrite[i]);
|
out.startTag(null, TAG_EVENT);
|
out.attribute(null, ATTR_NITS, Float.toString(toWrite[i].brightness));
|
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
|
out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
|
out.attribute(null, ATTR_USER, Integer.toString(userSerialNo));
|
out.attribute(null, ATTR_BATTERY_LEVEL, Float.toString(toWrite[i].batteryLevel));
|
out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode));
|
out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString(
|
toWrite[i].colorTemperature));
|
out.attribute(null, ATTR_LAST_NITS,
|
Float.toString(toWrite[i].lastBrightness));
|
out.attribute(null, ATTR_DEFAULT_CONFIG,
|
Boolean.toString(toWrite[i].isDefaultBrightnessConfig));
|
out.attribute(null, ATTR_POWER_SAVE,
|
Float.toString(toWrite[i].powerBrightnessFactor));
|
out.attribute(null, ATTR_USER_POINT,
|
Boolean.toString(toWrite[i].isUserSetBrightness));
|
StringBuilder luxValues = new StringBuilder();
|
StringBuilder luxTimestamps = new StringBuilder();
|
for (int j = 0; j < toWrite[i].luxValues.length; ++j) {
|
if (j > 0) {
|
luxValues.append(',');
|
luxTimestamps.append(',');
|
}
|
luxValues.append(Float.toString(toWrite[i].luxValues[j]));
|
luxTimestamps.append(Long.toString(toWrite[i].luxTimestamps[j]));
|
}
|
out.attribute(null, ATTR_LUX, luxValues.toString());
|
out.attribute(null, ATTR_LUX_TIMESTAMPS, luxTimestamps.toString());
|
if (toWrite[i].colorValueBuckets != null
|
&& toWrite[i].colorValueBuckets.length > 0) {
|
out.attribute(null, ATTR_COLOR_SAMPLE_DURATION,
|
Long.toString(toWrite[i].colorSampleDuration));
|
StringBuilder buckets = new StringBuilder();
|
for (int j = 0; j < toWrite[i].colorValueBuckets.length; ++j) {
|
if (j > 0) {
|
buckets.append(',');
|
}
|
buckets.append(Long.toString(toWrite[i].colorValueBuckets[j]));
|
}
|
out.attribute(null, ATTR_COLOR_VALUE_BUCKETS, buckets.toString());
|
}
|
out.endTag(null, TAG_EVENT);
|
}
|
}
|
out.endTag(null, TAG_EVENTS);
|
out.endDocument();
|
stream.flush();
|
}
|
|
@VisibleForTesting
|
@GuardedBy("mEventsLock")
|
void readEventsLocked(InputStream stream) throws IOException {
|
try {
|
XmlPullParser parser = Xml.newPullParser();
|
parser.setInput(stream, StandardCharsets.UTF_8.name());
|
|
int type;
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
&& type != XmlPullParser.START_TAG) {
|
}
|
String tag = parser.getName();
|
if (!TAG_EVENTS.equals(tag)) {
|
throw new XmlPullParserException(
|
"Events not found in brightness tracker file " + tag);
|
}
|
|
final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
|
|
parser.next();
|
int outerDepth = parser.getDepth();
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
|
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
|
continue;
|
}
|
tag = parser.getName();
|
if (TAG_EVENT.equals(tag)) {
|
BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
|
|
String brightness = parser.getAttributeValue(null, ATTR_NITS);
|
builder.setBrightness(Float.parseFloat(brightness));
|
String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
|
builder.setTimeStamp(Long.parseLong(timestamp));
|
builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME));
|
String user = parser.getAttributeValue(null, ATTR_USER);
|
builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user)));
|
String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
|
builder.setBatteryLevel(Float.parseFloat(batteryLevel));
|
String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
|
builder.setNightMode(Boolean.parseBoolean(nightMode));
|
String colorTemperature =
|
parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
|
builder.setColorTemperature(Integer.parseInt(colorTemperature));
|
String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS);
|
builder.setLastBrightness(Float.parseFloat(lastBrightness));
|
|
String luxValue = parser.getAttributeValue(null, ATTR_LUX);
|
String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
|
|
String[] luxValuesStrings = luxValue.split(",");
|
String[] luxTimestampsStrings = luxTimestamp.split(",");
|
if (luxValuesStrings.length != luxTimestampsStrings.length) {
|
continue;
|
}
|
float[] luxValues = new float[luxValuesStrings.length];
|
long[] luxTimestamps = new long[luxValuesStrings.length];
|
for (int i = 0; i < luxValues.length; ++i) {
|
luxValues[i] = Float.parseFloat(luxValuesStrings[i]);
|
luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]);
|
}
|
builder.setLuxValues(luxValues);
|
builder.setLuxTimestamps(luxTimestamps);
|
|
String defaultConfig = parser.getAttributeValue(null, ATTR_DEFAULT_CONFIG);
|
if (defaultConfig != null) {
|
builder.setIsDefaultBrightnessConfig(Boolean.parseBoolean(defaultConfig));
|
}
|
String powerSave = parser.getAttributeValue(null, ATTR_POWER_SAVE);
|
if (powerSave != null) {
|
builder.setPowerBrightnessFactor(Float.parseFloat(powerSave));
|
} else {
|
builder.setPowerBrightnessFactor(1.0f);
|
}
|
String userPoint = parser.getAttributeValue(null, ATTR_USER_POINT);
|
if (userPoint != null) {
|
builder.setUserBrightnessPoint(Boolean.parseBoolean(userPoint));
|
}
|
|
String colorSampleDurationString =
|
parser.getAttributeValue(null, ATTR_COLOR_SAMPLE_DURATION);
|
String colorValueBucketsString =
|
parser.getAttributeValue(null, ATTR_COLOR_VALUE_BUCKETS);
|
if (colorSampleDurationString != null && colorValueBucketsString != null) {
|
long colorSampleDuration = Long.parseLong(colorSampleDurationString);
|
String[] buckets = colorValueBucketsString.split(",");
|
long[] bucketValues = new long[buckets.length];
|
for (int i = 0; i < bucketValues.length; ++i) {
|
bucketValues[i] = Long.parseLong(buckets[i]);
|
}
|
builder.setColorValues(bucketValues, colorSampleDuration);
|
}
|
|
BrightnessChangeEvent event = builder.build();
|
if (DEBUG) {
|
Slog.i(TAG, "Read event " + event.brightness
|
+ " " + event.packageName);
|
}
|
|
if (event.userId != -1 && event.timeStamp > timeCutOff
|
&& event.luxValues.length > 0) {
|
mEvents.append(event);
|
}
|
}
|
}
|
} catch (NullPointerException | NumberFormatException | XmlPullParserException
|
| IOException e) {
|
// Failed to parse something, just start with an empty event log.
|
mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
|
Slog.e(TAG, "Failed to parse brightness event", e);
|
// Re-throw so we will delete the bad file.
|
throw new IOException("failed to parse file", e);
|
}
|
}
|
|
public void dump(final PrintWriter pw) {
|
pw.println("BrightnessTracker state:");
|
synchronized (mDataCollectionLock) {
|
pw.println(" mStarted=" + mStarted);
|
pw.println(" mLastBatteryLevel=" + mLastBatteryLevel);
|
pw.println(" mLastBrightness=" + mLastBrightness);
|
pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
|
if (!mLastSensorReadings.isEmpty()) {
|
pw.println(" mLastSensorReadings time span "
|
+ mLastSensorReadings.peekFirst().timestamp + "->"
|
+ mLastSensorReadings.peekLast().timestamp);
|
}
|
}
|
synchronized (mEventsLock) {
|
pw.println(" mEventsDirty=" + mEventsDirty);
|
pw.println(" mEvents.size=" + mEvents.size());
|
BrightnessChangeEvent[] events = mEvents.toArray();
|
for (int i = 0; i < events.length; ++i) {
|
pw.print(" " + FORMAT.format(new Date(events[i].timeStamp)));
|
pw.print(", userId=" + events[i].userId);
|
pw.print(", " + events[i].lastBrightness + "->" + events[i].brightness);
|
pw.print(", isUserSetBrightness=" + events[i].isUserSetBrightness);
|
pw.print(", powerBrightnessFactor=" + events[i].powerBrightnessFactor);
|
pw.print(", isDefaultBrightnessConfig=" + events[i].isDefaultBrightnessConfig);
|
pw.print(" {");
|
for (int j = 0; j < events[i].luxValues.length; ++j){
|
if (j != 0) {
|
pw.print(", ");
|
}
|
pw.print("(" + events[i].luxValues[j] + "," + events[i].luxTimestamps[j] + ")");
|
}
|
pw.println("}");
|
}
|
}
|
pw.println(" mWriteBrightnessTrackerStateScheduled="
|
+ mWriteBrightnessTrackerStateScheduled);
|
mBgHandler.runWithScissors(() -> dumpLocal(pw), 1000);
|
if (mAmbientBrightnessStatsTracker != null) {
|
pw.println();
|
mAmbientBrightnessStatsTracker.dump(pw);
|
}
|
}
|
|
private void dumpLocal(PrintWriter pw) {
|
pw.println(" mSensorRegistered=" + mSensorRegistered);
|
pw.println(" mColorSamplingEnabled=" + mColorSamplingEnabled);
|
pw.println(" mNoFramesToSample=" + mNoFramesToSample);
|
pw.println(" mFrameRate=" + mFrameRate);
|
}
|
|
private void enableColorSampling() {
|
if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
|
|| !mInjector.isInteractive(mContext)
|
|| mColorSamplingEnabled) {
|
return;
|
}
|
|
mFrameRate = mInjector.getFrameRate(mContext);
|
if (mFrameRate <= 0) {
|
Slog.wtf(TAG, "Default display has a zero or negative framerate.");
|
return;
|
}
|
mNoFramesToSample = (int) (mFrameRate * COLOR_SAMPLE_DURATION);
|
|
DisplayedContentSamplingAttributes attributes = mInjector.getSamplingAttributes();
|
if (DEBUG && attributes != null) {
|
Slog.d(TAG, "Color sampling"
|
+ " mask=0x" + Integer.toHexString(attributes.getComponentMask())
|
+ " dataSpace=0x" + Integer.toHexString(attributes.getDataspace())
|
+ " pixelFormat=0x" + Integer.toHexString(attributes.getPixelFormat()));
|
}
|
// Do we support sampling the Value component of HSV
|
if (attributes != null && attributes.getPixelFormat() == PixelFormat.HSV_888
|
&& (attributes.getComponentMask() & COLOR_SAMPLE_COMPONENT_MASK) != 0) {
|
|
mColorSamplingEnabled = mInjector.enableColorSampling(/* enable= */true,
|
mNoFramesToSample);
|
if (DEBUG) {
|
Slog.i(TAG, "turning on color sampling for "
|
+ mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
|
}
|
}
|
if (mColorSamplingEnabled && mDisplayListener == null) {
|
mDisplayListener = new DisplayListener();
|
mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
|
}
|
}
|
|
private void disableColorSampling() {
|
if (!mColorSamplingEnabled) {
|
return;
|
}
|
mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
|
mColorSamplingEnabled = false;
|
if (mDisplayListener != null) {
|
mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
|
mDisplayListener = null;
|
}
|
if (DEBUG) {
|
Slog.i(TAG, "turning off color sampling");
|
}
|
}
|
|
private void updateColorSampling() {
|
if (!mColorSamplingEnabled) {
|
return;
|
}
|
float frameRate = mInjector.getFrameRate(mContext);
|
if (frameRate != mFrameRate) {
|
disableColorSampling();
|
enableColorSampling();
|
}
|
}
|
|
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId) {
|
if (mAmbientBrightnessStatsTracker != null) {
|
ArrayList<AmbientBrightnessDayStats> stats =
|
mAmbientBrightnessStatsTracker.getUserStats(userId);
|
if (stats != null) {
|
return new ParceledListSlice<>(stats);
|
}
|
}
|
return ParceledListSlice.emptyList();
|
}
|
|
// Not allowed to keep the SensorEvent so used to copy the data we care about.
|
private static class LightData {
|
public float lux;
|
// Time in elapsedRealtimeNanos
|
public long timestamp;
|
}
|
|
private void recordSensorEvent(SensorEvent event) {
|
long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
|
synchronized (mDataCollectionLock) {
|
if (DEBUG) {
|
Slog.v(TAG, "Sensor event " + event);
|
}
|
if (!mLastSensorReadings.isEmpty()
|
&& event.timestamp < mLastSensorReadings.getLast().timestamp) {
|
// Ignore event that came out of order.
|
return;
|
}
|
LightData data = null;
|
while (!mLastSensorReadings.isEmpty()
|
&& mLastSensorReadings.getFirst().timestamp < horizon) {
|
// Remove data that has fallen out of the window.
|
data = mLastSensorReadings.removeFirst();
|
}
|
// We put back the last one we removed so we know how long
|
// the first sensor reading was valid for.
|
if (data != null) {
|
mLastSensorReadings.addFirst(data);
|
}
|
|
data = new LightData();
|
data.timestamp = event.timestamp;
|
data.lux = event.values[0];
|
mLastSensorReadings.addLast(data);
|
}
|
}
|
|
private void recordAmbientBrightnessStats(SensorEvent event) {
|
mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
|
}
|
|
private void batteryLevelChanged(int level, int scale) {
|
synchronized (mDataCollectionLock) {
|
mLastBatteryLevel = (float) level / (float) scale;
|
}
|
}
|
|
private final class SensorListener implements SensorEventListener {
|
@Override
|
public void onSensorChanged(SensorEvent event) {
|
recordSensorEvent(event);
|
recordAmbientBrightnessStats(event);
|
}
|
|
@Override
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
}
|
}
|
|
private final class DisplayListener implements DisplayManager.DisplayListener {
|
|
@Override
|
public void onDisplayAdded(int displayId) {
|
// Ignore
|
}
|
|
@Override
|
public void onDisplayRemoved(int displayId) {
|
// Ignore
|
}
|
|
@Override
|
public void onDisplayChanged(int displayId) {
|
if (displayId == Display.DEFAULT_DISPLAY) {
|
updateColorSampling();
|
}
|
}
|
}
|
|
private final class SettingsObserver extends ContentObserver {
|
public SettingsObserver(Handler handler) {
|
super(handler);
|
}
|
|
@Override
|
public void onChange(boolean selfChange, Uri uri) {
|
if (DEBUG) {
|
Slog.v(TAG, "settings change " + uri);
|
}
|
if (mInjector.isBrightnessModeAutomatic(mContentResolver)) {
|
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
|
} else {
|
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
|
}
|
}
|
}
|
|
private final class Receiver extends BroadcastReceiver {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
if (DEBUG) {
|
Slog.d(TAG, "Received " + intent.getAction());
|
}
|
String action = intent.getAction();
|
if (Intent.ACTION_SHUTDOWN.equals(action)) {
|
stop();
|
scheduleWriteBrightnessTrackerState();
|
} else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
|
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
|
if (level != -1 && scale != 0) {
|
batteryLevelChanged(level, scale);
|
}
|
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
|
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
|
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
|
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
|
}
|
}
|
}
|
|
private final class TrackerHandler extends Handler {
|
public TrackerHandler(Looper looper) {
|
super(looper, null, true /*async*/);
|
}
|
public void handleMessage(Message msg) {
|
switch (msg.what) {
|
case MSG_BACKGROUND_START:
|
backgroundStart((float)msg.obj /*initial brightness*/);
|
break;
|
case MSG_BRIGHTNESS_CHANGED:
|
BrightnessChangeValues values = (BrightnessChangeValues) msg.obj;
|
boolean userInitiatedChange = (msg.arg1 == 1);
|
handleBrightnessChanged(values.brightness, userInitiatedChange,
|
values.powerBrightnessFactor, values.isUserSetBrightness,
|
values.isDefaultBrightnessConfig, values.timestamp);
|
break;
|
case MSG_START_SENSOR_LISTENER:
|
startSensorListener();
|
enableColorSampling();
|
break;
|
case MSG_STOP_SENSOR_LISTENER:
|
stopSensorListener();
|
disableColorSampling();
|
break;
|
}
|
}
|
}
|
|
private static class BrightnessChangeValues {
|
final float brightness;
|
final float powerBrightnessFactor;
|
final boolean isUserSetBrightness;
|
final boolean isDefaultBrightnessConfig;
|
final long timestamp;
|
|
BrightnessChangeValues(float brightness, float powerBrightnessFactor,
|
boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
|
long timestamp) {
|
this.brightness = brightness;
|
this.powerBrightnessFactor = powerBrightnessFactor;
|
this.isUserSetBrightness = isUserSetBrightness;
|
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
|
this.timestamp = timestamp;
|
}
|
}
|
|
@VisibleForTesting
|
static class Injector {
|
public void registerSensorListener(Context context,
|
SensorEventListener sensorListener, Handler handler) {
|
SensorManager sensorManager = context.getSystemService(SensorManager.class);
|
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
|
sensorManager.registerListener(sensorListener,
|
lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
|
}
|
|
public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
|
SensorManager sensorManager = context.getSystemService(SensorManager.class);
|
sensorManager.unregisterListener(sensorListener);
|
}
|
|
public void registerBrightnessModeObserver(ContentResolver resolver,
|
ContentObserver settingsObserver) {
|
resolver.registerContentObserver(Settings.System.getUriFor(
|
Settings.System.SCREEN_BRIGHTNESS_MODE),
|
false, settingsObserver, UserHandle.USER_ALL);
|
}
|
|
public void unregisterBrightnessModeObserver(Context context,
|
ContentObserver settingsObserver) {
|
context.getContentResolver().unregisterContentObserver(settingsObserver);
|
}
|
|
public void registerReceiver(Context context,
|
BroadcastReceiver receiver, IntentFilter filter) {
|
context.registerReceiver(receiver, filter);
|
}
|
|
public void unregisterReceiver(Context context,
|
BroadcastReceiver receiver) {
|
context.unregisterReceiver(receiver);
|
}
|
|
public Handler getBackgroundHandler() {
|
return BackgroundThread.getHandler();
|
}
|
|
public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
|
return Settings.System.getIntForUser(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
|
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT)
|
== Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
|
}
|
|
public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
|
int userId) {
|
return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
|
}
|
|
public AtomicFile getFile(String filename) {
|
return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), filename));
|
}
|
|
public long currentTimeMillis() {
|
return System.currentTimeMillis();
|
}
|
|
public long elapsedRealtimeNanos() {
|
return SystemClock.elapsedRealtimeNanos();
|
}
|
|
public int getUserSerialNumber(UserManager userManager, int userId) {
|
return userManager.getUserSerialNumber(userId);
|
}
|
|
public int getUserId(UserManager userManager, int userSerialNumber) {
|
return userManager.getUserHandle(userSerialNumber);
|
}
|
|
public int[] getProfileIds(UserManager userManager, int userId) {
|
if (userManager != null) {
|
return userManager.getProfileIds(userId, false);
|
} else {
|
return new int[]{userId};
|
}
|
}
|
|
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
|
return ActivityTaskManager.getService().getFocusedStackInfo();
|
}
|
|
public void scheduleIdleJob(Context context) {
|
BrightnessIdleJob.scheduleJob(context);
|
}
|
|
public void cancelIdleJob(Context context) {
|
BrightnessIdleJob.cancelJob(context);
|
}
|
|
public boolean isInteractive(Context context) {
|
return context.getSystemService(PowerManager.class).isInteractive();
|
}
|
|
public int getNightDisplayColorTemperature(Context context) {
|
return context.getSystemService(ColorDisplayManager.class)
|
.getNightDisplayColorTemperature();
|
}
|
|
public boolean isNightDisplayActivated(Context context) {
|
return context.getSystemService(ColorDisplayManager.class).isNightDisplayActivated();
|
}
|
|
public DisplayedContentSample sampleColor(int noFramesToSample) {
|
final DisplayManagerInternal displayManagerInternal =
|
LocalServices.getService(DisplayManagerInternal.class);
|
return displayManagerInternal.getDisplayedContentSample(
|
Display.DEFAULT_DISPLAY, noFramesToSample, 0);
|
}
|
|
public float getFrameRate(Context context) {
|
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
|
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
|
return display.getRefreshRate();
|
}
|
|
public DisplayedContentSamplingAttributes getSamplingAttributes() {
|
final DisplayManagerInternal displayManagerInternal =
|
LocalServices.getService(DisplayManagerInternal.class);
|
return displayManagerInternal.getDisplayedContentSamplingAttributes(
|
Display.DEFAULT_DISPLAY);
|
}
|
|
public boolean enableColorSampling(boolean enable, int noFrames) {
|
final DisplayManagerInternal displayManagerInternal =
|
LocalServices.getService(DisplayManagerInternal.class);
|
return displayManagerInternal.setDisplayedContentSamplingEnabled(
|
Display.DEFAULT_DISPLAY, enable, COLOR_SAMPLE_COMPONENT_MASK, noFrames);
|
}
|
|
public void registerDisplayListener(Context context,
|
DisplayManager.DisplayListener listener, Handler handler) {
|
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
|
displayManager.registerDisplayListener(listener, handler);
|
}
|
|
public void unRegisterDisplayListener(Context context,
|
DisplayManager.DisplayListener listener) {
|
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
|
displayManager.unregisterDisplayListener(listener);
|
}
|
}
|
}
|