Merge "Daily idle job for writing out brightness events."

This commit is contained in:
Kenny Guy
2017-11-21 17:57:39 +00:00
committed by Android (Google) Code Review
6 changed files with 202 additions and 6 deletions

View File

@@ -173,6 +173,11 @@ public abstract class DisplayManagerInternal {
* */
public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
/**
* Persist brightness slider events.
*/
public abstract void persistBrightnessSliderEvents();
/**
* Describes the requested power state of the display.
*

View File

@@ -3958,6 +3958,10 @@
<service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>
<service android:name="com.android.server.display.BrightnessIdleJob"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>
</manifest>

View File

@@ -0,0 +1,81 @@
/*
* 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.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.util.Slog;
import com.android.server.LocalServices;
import java.util.concurrent.TimeUnit;
/**
* JobService used to persists brightness slider events when the device
* is idle and charging.
*/
public class BrightnessIdleJob extends JobService {
// Must be unique within the system server uid.
private static final int JOB_ID = 3923512;
public static void scheduleJob(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
JobInfo jobInfo =
new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
if (pending != null && !pending.equals(jobInfo)) {
jobScheduler.cancel(JOB_ID);
pending = null;
}
if (pending == null) {
jobScheduler.schedule(jobInfo);
}
}
public static void cancelJob(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.cancel(JOB_ID);
}
@Override
public boolean onStartJob(JobParameters params) {
if (BrightnessTracker.DEBUG) {
Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
}
DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
dmi.persistBrightnessSliderEvents();
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}

View File

@@ -61,12 +61,12 @@ 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -75,8 +75,8 @@ import java.util.concurrent.TimeUnit;
*/
public class BrightnessTracker {
private static final String TAG = "BrightnessTracker";
private static final boolean DEBUG = false;
static final String TAG = "BrightnessTracker";
static final boolean DEBUG = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final int MAX_EVENTS = 100;
@@ -103,6 +103,8 @@ public class BrightnessTracker {
@GuardedBy("mEventsLock")
private RingBuffer<BrightnessChangeEvent> mEvents
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
@GuardedBy("mEventsLock")
private boolean mEventsDirty;
private final Runnable mEventsWriter = () -> writeEvents();
private volatile boolean mWriteEventsScheduled;
@@ -170,6 +172,8 @@ public class BrightnessTracker {
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
mInjector.scheduleIdleJob(mContext);
}
/** Stop listening for events */
@@ -181,6 +185,7 @@ public class BrightnessTracker {
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +216,10 @@ public class BrightnessTracker {
brightness, userId);
}
public void persistEvents() {
scheduleWriteEvents();
}
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@ public class BrightnessTracker {
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +301,12 @@ public class BrightnessTracker {
private void writeEvents() {
mWriteEventsScheduled = false;
// TODO kick off write on handler thread e.g. every 24 hours.
synchronized (mEventsLock) {
if (!mEventsDirty) {
// Nothing to write
return;
}
final AtomicFile writeTo = mInjector.getFile();
if (writeTo == null) {
return;
@@ -301,12 +315,14 @@ public class BrightnessTracker {
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);
@@ -317,6 +333,8 @@ public class BrightnessTracker {
private void readEvents() {
synchronized (mEventsLock) {
// Read might prune events so mark as dirty.
mEventsDirty = true;
mEvents.clear();
final AtomicFile readFrom = mInjector.getFile();
if (readFrom != null && readFrom.exists()) {
@@ -344,13 +362,16 @@ public class BrightnessTracker {
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 = System.currentTimeMillis() - MAX_EVENT_AGE;
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_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +486,17 @@ public class BrightnessTracker {
}
}
public void dump(PrintWriter pw) {
synchronized (mEventsLock) {
pw.println("BrightnessTracker state:");
pw.println(" mEvents.size=" + mEvents.size());
pw.println(" mEventsDirty=" + mEventsDirty);
}
synchronized (mDataCollectionLock) {
pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
}
}
// Not allowed to keep the SensorEvent so used to copy the data we care about.
private static class LightData {
public float lux;
@@ -635,5 +667,13 @@ public class BrightnessTracker {
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
return ActivityManager.getService().getFocusedStackInfo();
}
public void scheduleIdleJob(Context context) {
BrightnessIdleJob.scheduleJob(context);
}
public void cancelIdleJob(Context context) {
BrightnessIdleJob.cancelJob(context);
}
}
}

View File

@@ -1285,6 +1285,9 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mPersistentDataStore.dump(pw);
pw.println();
mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@ public final class DisplayManagerService extends SystemService {
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
@Override
public void persistBrightnessSliderEvents() {
mBrightnessTracker.persistEvents();
}
}
}

View File

@@ -99,10 +99,12 @@ public class BrightnessTrackerTest {
assertNotNull(mInjector.mSensorListener);
assertNotNull(mInjector.mSettingsObserver);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
mTracker.stop();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mSettingsObserver);
assertNull(mInjector.mBroadcastReceiver);
assertFalse(mInjector.mIdleScheduled);
}
@Test
@@ -398,6 +400,52 @@ public class BrightnessTrackerTest {
assertEquals(3339, event.colorTemperature);
}
@Test
public void testWritePrunesOldEvents() throws Exception {
final int brightness = 20;
mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
startTracker(mTracker);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 100));
mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
final long sensorTime = mInjector.currentTimeMillis();
mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS));
// 31 days later
mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS));
final long eventTime = mInjector.currentTimeMillis();
List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList();
assertEquals(2, events.size());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mTracker.writeEventsLocked(baos);
events = mTracker.getEvents(0).getList();
mTracker.stop();
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
assertEquals(eventTime, event.timeStamp);
// We will keep one of the old sensor events because we keep 1 event outside the window.
assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
assertEquals(brightness, event.brightness);
assertEquals(0.3, event.batteryLevel, 0.01f);
assertTrue(event.nightMode);
assertEquals(3339, event.colorTemperature);
}
@Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
@@ -516,6 +564,7 @@ public class BrightnessTrackerTest {
long mCurrentTimeMillis = System.currentTimeMillis();
long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
Handler mHandler;
boolean mIdleScheduled;
public TestInjector(Handler handler) {
mHandler = handler;
@@ -636,5 +685,14 @@ public class BrightnessTrackerTest {
focusedStack.topActivity = new ComponentName("a.package", "a.class");
return focusedStack;
}
public void scheduleIdleJob(Context context) {
// Don't actually schedule jobs during unit tests.
mIdleScheduled = true;
}
public void cancelIdleJob(Context context) {
mIdleScheduled = false;
}
}
}