Merge "Daily idle job for writing out brightness events."
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e845359..cd551bd 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@
public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
/**
+ * Persist brightness slider events.
+ */
+ public abstract void persistBrightnessSliderEvents();
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86103e4..feef5ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -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>
diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 361d928..90888f0 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -61,12 +61,12 @@
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 @@
*/
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 @@
@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 @@
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 @@
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +216,10 @@
brightness, userId);
}
+ public void persistEvents() {
+ scheduleWriteEvents();
+ }
+
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
+ mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +301,12 @@
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 @@
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 @@
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 @@
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 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 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);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f1e2011..7530f3e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1285,6 +1285,9 @@
pw.println();
mPersistentDataStore.dump(pw);
+
+ pw.println();
+ mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
+
+ @Override
+ public void persistBrightnessSliderEvents() {
+ mBrightnessTracker.persistEvents();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index d9fac87..6938e0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -99,10 +99,12 @@
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
@@ -399,6 +401,52 @@
}
@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();
BrightnessChangeEvent event = new BrightnessChangeEvent();
@@ -516,6 +564,7 @@
long mCurrentTimeMillis = System.currentTimeMillis();
long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
Handler mHandler;
+ boolean mIdleScheduled;
public TestInjector(Handler handler) {
mHandler = handler;
@@ -636,5 +685,14 @@
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;
+ }
}
}