Merge "Daily idle job for writing out brightness events."
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user