Merge "Rewrite app standby stats"

This commit is contained in:
Amith Yamasani
2016-02-05 19:41:03 +00:00
committed by Android (Google) Code Review
8 changed files with 567 additions and 480 deletions

View File

@@ -46,20 +46,6 @@ public final class UsageStats implements Parcelable {
*/
public long mLastTimeUsed;
/**
* The last time the package was used via implicit, non-user initiated actions (service
* was bound, etc).
* {@hide}
*/
public long mLastTimeSystemUsed;
/**
* Last time the package was used and the beginning of the idle countdown.
* This uses a different timebase that is about how much the device has been in use in general.
* {@hide}
*/
public long mBeginIdleTime;
/**
* {@hide}
*/
@@ -89,8 +75,6 @@ public final class UsageStats implements Parcelable {
mTotalTimeInForeground = stats.mTotalTimeInForeground;
mLaunchCount = stats.mLaunchCount;
mLastEvent = stats.mLastEvent;
mBeginIdleTime = stats.mBeginIdleTime;
mLastTimeSystemUsed = stats.mLastTimeSystemUsed;
}
public String getPackageName() {
@@ -126,25 +110,6 @@ public final class UsageStats implements Parcelable {
return mLastTimeUsed;
}
/**
* @hide
* Get the last time this package was used by the system (not the user). This can be different
* from {@link #getLastTimeUsed()} when the system binds to one of this package's services.
* See {@link System#currentTimeMillis()}.
*/
public long getLastTimeSystemUsed() {
return mLastTimeSystemUsed;
}
/**
* @hide
* Get the last time this package was active, measured in milliseconds. This timestamp
* uses a timebase that represents how much the device was used and not wallclock time.
*/
public long getBeginIdleTime() {
return mBeginIdleTime;
}
/**
* Get the total time this package spent in the foreground, measured in milliseconds.
*/
@@ -172,8 +137,6 @@ public final class UsageStats implements Parcelable {
// regards to their mEndTimeStamp.
mLastEvent = right.mLastEvent;
mLastTimeUsed = right.mLastTimeUsed;
mBeginIdleTime = right.mBeginIdleTime;
mLastTimeSystemUsed = right.mLastTimeSystemUsed;
}
mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
@@ -195,8 +158,6 @@ public final class UsageStats implements Parcelable {
dest.writeLong(mTotalTimeInForeground);
dest.writeInt(mLaunchCount);
dest.writeInt(mLastEvent);
dest.writeLong(mBeginIdleTime);
dest.writeLong(mLastTimeSystemUsed);
}
public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
@@ -210,8 +171,6 @@ public final class UsageStats implements Parcelable {
stats.mTotalTimeInForeground = in.readLong();
stats.mLaunchCount = in.readInt();
stats.mLastEvent = in.readInt();
stats.mBeginIdleTime = in.readLong();
stats.mLastTimeSystemUsed = in.readLong();
return stats;
}

View File

@@ -15,6 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
services.core \
services.devicepolicy \
services.net \
services.usage \
easymocklib \
guava \
android-support-test \

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016 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.usage;
import android.os.FileUtils;
import android.test.AndroidTestCase;
import java.io.File;
public class AppIdleHistoryTests extends AndroidTestCase {
File mStorageDir;
final static String PACKAGE_1 = "com.android.testpackage1";
final static String PACKAGE_2 = "com.android.testpackage2";
@Override
protected void setUp() throws Exception {
super.setUp();
mStorageDir = new File(getContext().getFilesDir(), "appidle");
mStorageDir.mkdirs();
}
@Override
protected void tearDown() throws Exception {
FileUtils.deleteContents(mStorageDir);
super.tearDown();
}
public void testFilesCreation() {
final int userId = 0;
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0);
aih.updateDisplayLocked(true, /* elapsedRealtime= */ 1000);
aih.updateDisplayLocked(false, /* elapsedRealtime= */ 2000);
// Screen On time file should be written right away
assertTrue(aih.getScreenOnTimeFile().exists());
aih.writeAppIdleTimesLocked(userId);
// stats file should be written now
assertTrue(new File(new File(mStorageDir, "users/" + userId),
AppIdleHistory.APP_IDLE_FILENAME).exists());
}
public void testScreenOnTime() {
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
aih.updateDisplayLocked(false, 2000);
assertEquals(aih.getScreenOnTimeLocked(2000), 0);
aih.updateDisplayLocked(true, 3000);
assertEquals(aih.getScreenOnTimeLocked(4000), 1000);
assertEquals(aih.getScreenOnTimeLocked(5000), 2000);
aih.updateDisplayLocked(false, 6000);
// Screen on time should not keep progressing with screen is off
assertEquals(aih.getScreenOnTimeLocked(7000), 3000);
assertEquals(aih.getScreenOnTimeLocked(8000), 3000);
aih.writeElapsedTimeLocked();
// Check if the screen on time is persisted across instantiations
AppIdleHistory aih2 = new AppIdleHistory(mStorageDir, 0);
assertEquals(aih2.getScreenOnTimeLocked(11000), 3000);
aih2.updateDisplayLocked(true, 4000);
aih2.updateDisplayLocked(false, 5000);
assertEquals(aih2.getScreenOnTimeLocked(13000), 4000);
}
public void testPackageEvents() {
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
aih.setThresholds(4000, 1000);
aih.updateDisplayLocked(true, 1000);
// App is not-idle by default
assertFalse(aih.isIdleLocked(PACKAGE_1, 0, 1500));
// Still not idle
assertFalse(aih.isIdleLocked(PACKAGE_1, 0, 3000));
// Idle now
assertTrue(aih.isIdleLocked(PACKAGE_1, 0, 8000));
// Not idle
assertFalse(aih.isIdleLocked(PACKAGE_2, 0, 9000));
// Screen off
aih.updateDisplayLocked(false, 9100);
// Still idle after 10 seconds because screen hasn't been on long enough
assertFalse(aih.isIdleLocked(PACKAGE_2, 0, 20000));
aih.updateDisplayLocked(true, 21000);
assertTrue(aih.isIdleLocked(PACKAGE_2, 0, 23000));
}
}

View File

@@ -16,19 +16,45 @@
package com.android.server.usage;
import android.os.Environment;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Keeps track of recent active state changes in apps.
* Access should be guarded by a lock by the caller.
*/
public class AppIdleHistory {
private SparseArray<ArrayMap<String,byte[]>> mIdleHistory = new SparseArray<>();
private long lastPeriod = 0;
private static final String TAG = "AppIdleHistory";
// History for all users and all packages
private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
private long mLastPeriod = 0;
private static final long ONE_MINUTE = 60 * 1000;
private static final int HISTORY_SIZE = 100;
private static final int FLAG_LAST_STATE = 2;
@@ -36,77 +62,353 @@ public class AppIdleHistory {
private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
: 60 * ONE_MINUTE;
public void addEntry(String packageName, int userId, boolean idle, long timeNow) {
ArrayMap<String, byte[]> userHistory = getUserHistory(userId);
byte[] packageHistory = getPackageHistory(userHistory, packageName);
@VisibleForTesting
static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
private static final String TAG_PACKAGES = "packages";
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
// Screen on timebase time when app was last used
private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
// Elapsed timebase time when app was last used
private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
long thisPeriod = timeNow / PERIOD_DURATION;
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
private long mElapsedDuration; // Total device on duration since device was "born"
// screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot)
private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
private long mScreenOnDuration; // Total screen on duration since device was "born"
private long mElapsedTimeThreshold;
private long mScreenOnTimeThreshold;
private final File mStorageDir;
private boolean mScreenOn;
private static class PackageHistory {
final byte[] recent = new byte[HISTORY_SIZE];
long lastUsedElapsedTime;
long lastUsedScreenTime;
}
AppIdleHistory(long elapsedRealtime) {
this(Environment.getDataSystemDirectory(), elapsedRealtime);
}
@VisibleForTesting
AppIdleHistory(File storageDir, long elapsedRealtime) {
mElapsedSnapshot = elapsedRealtime;
mScreenOnSnapshot = elapsedRealtime;
mStorageDir = storageDir;
readScreenOnTimeLocked();
}
public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
mElapsedTimeThreshold = elapsedTimeThreshold;
mScreenOnTimeThreshold = screenOnTimeThreshold;
}
public void updateDisplayLocked(boolean screenOn, long elapsedRealtime) {
if (screenOn == mScreenOn) return;
mScreenOn = screenOn;
if (mScreenOn) {
mScreenOnSnapshot = elapsedRealtime;
} else {
mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot;
mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
writeScreenOnTimeLocked();
mElapsedSnapshot = elapsedRealtime;
}
}
public long getScreenOnTimeLocked(long elapsedRealtime) {
long screenOnTime = mScreenOnDuration;
if (mScreenOn) {
screenOnTime += elapsedRealtime - mScreenOnSnapshot;
}
return screenOnTime;
}
@VisibleForTesting
File getScreenOnTimeFile() {
return new File(mStorageDir, "screen_on_time");
}
private void readScreenOnTimeLocked() {
File screenOnTimeFile = getScreenOnTimeFile();
if (screenOnTimeFile.exists()) {
try {
BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
mScreenOnDuration = Long.parseLong(reader.readLine());
mElapsedDuration = Long.parseLong(reader.readLine());
reader.close();
} catch (IOException | NumberFormatException e) {
}
} else {
writeScreenOnTimeLocked();
}
}
private void writeScreenOnTimeLocked() {
AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
FileOutputStream fos = null;
try {
fos = screenOnTimeFile.startWrite();
fos.write((Long.toString(mScreenOnDuration) + "\n"
+ Long.toString(mElapsedDuration) + "\n").getBytes());
screenOnTimeFile.finishWrite(fos);
} catch (IOException ioe) {
screenOnTimeFile.failWrite(fos);
}
}
/**
* To be called periodically to keep track of elapsed time when app idle times are written
*/
public void writeElapsedTimeLocked() {
final long elapsedRealtime = SystemClock.elapsedRealtime();
// Only bump up and snapshot the elapsed time. Don't change screen on duration.
mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
mElapsedSnapshot = elapsedRealtime;
writeScreenOnTimeLocked();
}
public void reportUsageLocked(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
elapsedRealtime);
shiftHistoryToNow(userHistory, elapsedRealtime);
packageHistory.lastUsedElapsedTime = mElapsedDuration
+ (elapsedRealtime - mElapsedSnapshot);
packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime);
packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
}
public void setIdle(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
elapsedRealtime);
shiftHistoryToNow(userHistory, elapsedRealtime);
packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
}
private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
long elapsedRealtime) {
long thisPeriod = elapsedRealtime / PERIOD_DURATION;
// Has the period switched over? Slide all users' package histories
if (lastPeriod != 0 && lastPeriod < thisPeriod
&& (thisPeriod - lastPeriod) < HISTORY_SIZE - 1) {
int diff = (int) (thisPeriod - lastPeriod);
if (mLastPeriod != 0 && mLastPeriod < thisPeriod
&& (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
int diff = (int) (thisPeriod - mLastPeriod);
final int NUSERS = mIdleHistory.size();
for (int u = 0; u < NUSERS; u++) {
userHistory = mIdleHistory.valueAt(u);
for (byte[] history : userHistory.values()) {
for (PackageHistory idleState : userHistory.values()) {
// Shift left
System.arraycopy(history, diff, history, 0, HISTORY_SIZE - diff);
System.arraycopy(idleState.recent, diff, idleState.recent, 0,
HISTORY_SIZE - diff);
// Replicate last state across the diff
for (int i = 0; i < diff; i++) {
history[HISTORY_SIZE - i - 1] =
(byte) (history[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
idleState.recent[HISTORY_SIZE - i - 1] =
(byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
}
}
}
}
lastPeriod = thisPeriod;
if (!idle) {
packageHistory[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
} else {
packageHistory[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
}
mLastPeriod = thisPeriod;
}
private ArrayMap<String, byte[]> getUserHistory(int userId) {
ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
private ArrayMap<String, PackageHistory> getUserHistoryLocked(int userId) {
ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
if (userHistory == null) {
userHistory = new ArrayMap<>();
mIdleHistory.put(userId, userHistory);
readAppIdleTimesLocked(userId, userHistory);
}
return userHistory;
}
private byte[] getPackageHistory(ArrayMap<String, byte[]> userHistory, String packageName) {
byte[] packageHistory = userHistory.get(packageName);
private PackageHistory getPackageHistoryLocked(ArrayMap<String, PackageHistory> userHistory,
String packageName, long elapsedRealtime) {
PackageHistory packageHistory = userHistory.get(packageName);
if (packageHistory == null) {
packageHistory = new byte[HISTORY_SIZE];
packageHistory = new PackageHistory();
packageHistory.lastUsedElapsedTime = getElapsedTimeLocked(elapsedRealtime);
packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime);
userHistory.put(packageName, packageHistory);
}
return packageHistory;
}
public void removeUser(int userId) {
public void onUserRemoved(int userId) {
mIdleHistory.remove(userId);
}
public boolean isIdle(int userId, String packageName) {
ArrayMap<String, byte[]> userHistory = getUserHistory(userId);
byte[] packageHistory = getPackageHistory(userHistory, packageName);
return (packageHistory[HISTORY_SIZE - 1] & FLAG_LAST_STATE) == 0;
public boolean isIdleLocked(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
PackageHistory packageHistory =
getPackageHistoryLocked(userHistory, packageName, elapsedRealtime);
if (packageHistory == null) {
return false; // Default to not idle
} else {
return hasPassedThresholdsLocked(packageHistory, elapsedRealtime);
}
}
private long getElapsedTimeLocked(long elapsedRealtime) {
return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
}
public void setIdleLocked(String packageName, int userId, boolean idle, long elapsedRealtime) {
ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
elapsedRealtime);
packageHistory.lastUsedElapsedTime = getElapsedTimeLocked(elapsedRealtime)
- mElapsedTimeThreshold;
packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime)
- (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
}
private boolean hasPassedThresholdsLocked(PackageHistory packageHistory, long elapsedRealtime) {
return (packageHistory.lastUsedScreenTime
<= getScreenOnTimeLocked(elapsedRealtime) - mScreenOnTimeThreshold)
&& (packageHistory.lastUsedElapsedTime
<= getElapsedTimeLocked(elapsedRealtime) - mElapsedTimeThreshold);
}
private File getUserFile(int userId) {
return new File(new File(new File(mStorageDir, "users"),
Integer.toString(userId)), APP_IDLE_FILENAME);
}
private void readAppIdleTimesLocked(int userId, ArrayMap<String, PackageHistory> userHistory) {
FileInputStream fis = null;
try {
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
fis = appIdleFile.openRead();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, StandardCharsets.UTF_8.name());
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Skip
}
if (type != XmlPullParser.START_TAG) {
Slog.e(TAG, "Unable to read app idle file for user " + userId);
return;
}
if (!parser.getName().equals(TAG_PACKAGES)) {
return;
}
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG) {
final String name = parser.getName();
if (name.equals(TAG_PACKAGE)) {
final String packageName = parser.getAttributeValue(null, ATTR_NAME);
PackageHistory packageHistory = new PackageHistory();
packageHistory.lastUsedElapsedTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
packageHistory.lastUsedScreenTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
userHistory.put(packageName, packageHistory);
}
}
}
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Unable to read app idle file for user " + userId);
} finally {
IoUtils.closeQuietly(fis);
}
}
public void writeAppIdleTimesLocked(int userId) {
FileOutputStream fos = null;
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
try {
fos = appIdleFile.startWrite();
final BufferedOutputStream bos = new BufferedOutputStream(fos);
FastXmlSerializer xml = new FastXmlSerializer();
xml.setOutput(bos, StandardCharsets.UTF_8.name());
xml.startDocument(null, true);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, TAG_PACKAGES);
ArrayMap<String,PackageHistory> userHistory = getUserHistoryLocked(userId);
final int N = userHistory.size();
for (int i = 0; i < N; i++) {
String packageName = userHistory.keyAt(i);
PackageHistory history = userHistory.valueAt(i);
xml.startTag(null, TAG_PACKAGE);
xml.attribute(null, ATTR_NAME, packageName);
xml.attribute(null, ATTR_ELAPSED_IDLE,
Long.toString(history.lastUsedElapsedTime));
xml.attribute(null, ATTR_SCREEN_IDLE,
Long.toString(history.lastUsedScreenTime));
xml.endTag(null, TAG_PACKAGE);
}
xml.endTag(null, TAG_PACKAGES);
xml.endDocument();
appIdleFile.finishWrite(fos);
} catch (Exception e) {
appIdleFile.failWrite(fos);
Slog.e(TAG, "Error writing app idle file for user " + userId);
}
}
public void dump(IndentingPrintWriter idpw, int userId) {
ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
idpw.println("Package idle stats:");
idpw.increaseIndent();
ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long totalElapsedTime = getElapsedTimeLocked(elapsedRealtime);
final long screenOnTime = getScreenOnTimeLocked(elapsedRealtime);
if (userHistory == null) return;
final int P = userHistory.size();
for (int p = 0; p < P; p++) {
final String packageName = userHistory.keyAt(p);
final byte[] history = userHistory.valueAt(p);
final PackageHistory packageHistory = userHistory.valueAt(p);
idpw.print("package=" + packageName);
idpw.print(" lastUsedElapsed=");
TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
idpw.print(" lastUsedScreenOn=");
TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
idpw.print(" idle=" + (isIdleLocked(packageName, userId, elapsedRealtime) ? "y" : "n"));
idpw.println();
}
idpw.println();
idpw.print("totalElapsedTime=");
TimeUtils.formatDuration(getElapsedTimeLocked(elapsedRealtime), idpw);
idpw.println();
idpw.print("totalScreenOnTime=");
TimeUtils.formatDuration(getScreenOnTimeLocked(elapsedRealtime), idpw);
idpw.println();
idpw.decreaseIndent();
}
public void dumpHistory(IndentingPrintWriter idpw, int userId) {
ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
if (userHistory == null) return;
final int P = userHistory.size();
for (int p = 0; p < P; p++) {
final String packageName = userHistory.keyAt(p);
final byte[] history = userHistory.valueAt(p).recent;
for (int i = 0; i < HISTORY_SIZE; i++) {
idpw.print(history[i] == 0 ? '.' : 'A');
}
idpw.print(" idle=" + (isIdleLocked(packageName, userId, elapsedRealtime) ? "y" : "n"));
idpw.print(" " + packageName);
idpw.println();
}
}
}
}

View File

@@ -48,7 +48,6 @@ class IntervalStats {
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
usageStats.mBeginIdleTime = 0;
packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
@@ -113,7 +112,6 @@ class IntervalStats {
if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
usageStats.mLastTimeUsed = timeStamp;
}
usageStats.mLastTimeSystemUsed = timeStamp;
usageStats.mEndTimeStamp = timeStamp;
if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
@@ -123,22 +121,6 @@ class IntervalStats {
endTime = timeStamp;
}
/**
* Updates the last active time for the package. The timestamp uses a timebase that
* tracks the device usage time.
* @param packageName
* @param timeStamp
*/
void updateBeginIdleTime(String packageName, long timeStamp) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
usageStats.mBeginIdleTime = timeStamp;
}
void updateSystemLastUsedTime(String packageName, long lastUsedTime) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
usageStats.mLastTimeSystemUsed = lastUsedTime;
}
void updateConfigurationStats(Configuration config, long timeStamp) {
if (activeConfiguration != null) {
ConfigurationStats activeStats = configurations.get(activeConfiguration);

View File

@@ -40,6 +40,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
@@ -62,7 +63,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.Slog;
import android.util.SparseArray;
@@ -77,12 +77,8 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -106,7 +102,7 @@ public class UsageStatsService extends SystemService implements
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
long mAppIdleDurationMillis;
long mAppIdleScreenThresholdMillis;
long mCheckIdleIntervalMillis;
long mAppIdleWallclockThresholdMillis;
long mAppIdleParoleIntervalMillis;
@@ -147,11 +143,8 @@ public class UsageStatsService extends SystemService implements
private volatile boolean mPendingOneTimeCheckIdleStates;
long mScreenOnTime;
long mLastScreenOnEventRealtime;
@GuardedBy("mLock")
private AppIdleHistory mAppIdleHistory = new AppIdleHistory();
private AppIdleHistory mAppIdleHistory;
private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
mPackageAccessListeners = new ArrayList<>();
@@ -191,8 +184,7 @@ public class UsageStatsService extends SystemService implements
synchronized (mLock) {
cleanUpRemovedUsersLocked();
mLastScreenOnEventRealtime = SystemClock.elapsedRealtime();
mScreenOnTime = readScreenOnTimeLocked();
mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
}
mRealTimeSnapshot = SystemClock.elapsedRealtime();
@@ -221,7 +213,7 @@ public class UsageStatsService extends SystemService implements
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mLock) {
updateDisplayLocked();
mAppIdleHistory.updateDisplayLocked(isDisplayOn(), SystemClock.elapsedRealtime());
}
if (mPendingOneTimeCheckIdleStates) {
@@ -232,6 +224,11 @@ public class UsageStatsService extends SystemService implements
}
}
private boolean isDisplayOn() {
return mDisplayManager
.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
}
private class UserActionsReceiver extends BroadcastReceiver {
@Override
@@ -274,7 +271,8 @@ public class UsageStatsService extends SystemService implements
@Override public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
synchronized (UsageStatsService.this.mLock) {
updateDisplayLocked();
mAppIdleHistory.updateDisplayLocked(isDisplayOn(),
SystemClock.elapsedRealtime());
}
}
}
@@ -291,8 +289,25 @@ public class UsageStatsService extends SystemService implements
}
@Override
public long getAppIdleRollingWindowDurationMillis() {
return mAppIdleWallclockThresholdMillis * 2;
public void onNewUpdate(int userId) {
initializeDefaultsForSystemApps(userId);
}
private void initializeDefaultsForSystemApps(int userId) {
Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackagesAsUser(
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_UNINSTALLED_PACKAGES,
userId);
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
final PackageInfo pi = packages.get(i);
String packageName = pi.packageName;
if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
mAppIdleHistory.reportUsageLocked(packageName, userId, elapsedRealtime);
}
}
}
private void cleanUpRemovedUsersLocked() {
@@ -350,7 +365,7 @@ public class UsageStatsService extends SystemService implements
if (timeLeft < 0) {
timeLeft = 0;
}
mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft / 10);
mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
}
private void postParoleEndTimeout() {
@@ -400,28 +415,27 @@ public class UsageStatsService extends SystemService implements
return;
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
for (int i = 0; i < userIds.length; i++) {
final int userId = userIds[i];
List<PackageInfo> packages =
getContext().getPackageManager().getInstalledPackagesAsUser(
PackageManager.GET_DISABLED_COMPONENTS
| PackageManager.GET_UNINSTALLED_PACKAGES,
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_UNINSTALLED_PACKAGES,
userId);
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
final long screenOnTime = getScreenOnTimeLocked();
UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId,
timeNow);
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
final PackageInfo pi = packages.get(p);
final String packageName = pi.packageName;
final boolean isIdle = isAppIdleFiltered(packageName,
UserHandle.getAppId(pi.applicationInfo.uid),
userId, service, timeNow, screenOnTime);
userId, elapsedRealtime);
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
userId, isIdle ? 1 : 0, packageName));
mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
if (isIdle) {
mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
}
}
}
}
@@ -458,62 +472,6 @@ public class UsageStatsService extends SystemService implements
}
}
void updateDisplayLocked() {
boolean screenOn = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState()
== Display.STATE_ON;
if (screenOn == mScreenOn) return;
mScreenOn = screenOn;
long now = SystemClock.elapsedRealtime();
if (mScreenOn) {
mLastScreenOnEventRealtime = now;
} else {
mScreenOnTime += now - mLastScreenOnEventRealtime;
writeScreenOnTimeLocked(mScreenOnTime);
}
}
long getScreenOnTimeLocked() {
long screenOnTime = mScreenOnTime;
if (mScreenOn) {
screenOnTime += SystemClock.elapsedRealtime() - mLastScreenOnEventRealtime;
}
return screenOnTime;
}
private File getScreenOnTimeFile() {
return new File(mUsageStatsDir, UserHandle.USER_SYSTEM + "/screen_on_time");
}
private long readScreenOnTimeLocked() {
long screenOnTime = 0;
File screenOnTimeFile = getScreenOnTimeFile();
if (screenOnTimeFile.exists()) {
try {
BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
screenOnTime = Long.parseLong(reader.readLine());
reader.close();
} catch (IOException | NumberFormatException e) {
}
} else {
writeScreenOnTimeLocked(screenOnTime);
}
return screenOnTime;
}
private void writeScreenOnTimeLocked(long screenOnTime) {
AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
FileOutputStream fos = null;
try {
fos = screenOnTimeFile.startWrite();
fos.write(Long.toString(screenOnTime).getBytes());
screenOnTimeFile.finishWrite(fos);
} catch (IOException ioe) {
screenOnTimeFile.failWrite(fos);
}
}
void onDeviceIdleModeChanged() {
final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
@@ -549,7 +507,7 @@ public class UsageStatsService extends SystemService implements
if (service == null) {
service = new UserUsageStatsService(getContext(), userId,
new File(mUsageStatsDir, Integer.toString(userId)), this);
service.init(currentTimeMillis, getScreenOnTimeLocked());
service.init(currentTimeMillis);
mUserState.put(userId, service);
}
return service;
@@ -569,8 +527,7 @@ public class UsageStatsService extends SystemService implements
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
final UserUsageStatsService service = mUserState.valueAt(i);
service.onTimeChanged(expectedSystemTime, actualSystemTime, getScreenOnTimeLocked(),
false);
service.onTimeChanged(expectedSystemTime, actualSystemTime);
}
mRealTimeSnapshot = actualRealtime;
mSystemTimeSnapshot = actualSystemTime;
@@ -602,26 +559,26 @@ public class UsageStatsService extends SystemService implements
void reportEvent(UsageEvents.Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
final long screenOnTime = getScreenOnTimeLocked();
final long elapsedRealtime = SystemClock.elapsedRealtime();
convertToSystemTimeLocked(event);
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
final long beginIdleTime = service.getBeginIdleTime(event.mPackage);
final long lastUsedTime = service.getSystemLastUsedTime(event.mPackage);
final boolean previouslyIdle = hasPassedIdleTimeoutLocked(beginIdleTime,
lastUsedTime, screenOnTime, timeNow);
service.reportEvent(event, screenOnTime);
// TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
// about apps that are on some kind of whitelist anyway.
final boolean previouslyIdle = mAppIdleHistory.isIdleLocked(
event.mPackage, userId, elapsedRealtime);
service.reportEvent(event);
// Inform listeners if necessary
if ((event.mEventType == Event.MOVE_TO_FOREGROUND
|| event.mEventType == Event.MOVE_TO_BACKGROUND
|| event.mEventType == Event.SYSTEM_INTERACTION
|| event.mEventType == Event.USER_INTERACTION)) {
mAppIdleHistory.reportUsageLocked(event.mPackage, userId, elapsedRealtime);
if (previouslyIdle) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
/* idle = */ 0, event.mPackage));
notifyBatteryStats(event.mPackage, userId, false);
mAppIdleHistory.addEntry(event.mPackage, userId, false, timeNow);
}
}
}
@@ -655,28 +612,23 @@ public class UsageStatsService extends SystemService implements
* the threshold for idle.
*/
void forceIdleState(String packageName, int userId, boolean idle) {
final int appId = getAppId(packageName);
if (appId < 0) return;
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
final long screenOnTime = getScreenOnTimeLocked();
final long deviceUsageTime = screenOnTime - (idle ? mAppIdleDurationMillis : 0) - 5000;
final long elapsedRealtime = SystemClock.elapsedRealtime();
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
final long beginIdleTime = service.getBeginIdleTime(packageName);
final long lastUsedTime = service.getSystemLastUsedTime(packageName);
final boolean previouslyIdle = hasPassedIdleTimeoutLocked(beginIdleTime,
lastUsedTime, screenOnTime, timeNow);
service.setBeginIdleTime(packageName, deviceUsageTime);
service.setSystemLastUsedTime(packageName,
timeNow - (idle ? mAppIdleWallclockThresholdMillis : 0) - 5000);
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
mAppIdleHistory.setIdleLocked(packageName, userId, idle, elapsedRealtime);
final boolean stillIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
// Inform listeners if necessary
if (previouslyIdle != idle) {
if (previouslyIdle != stillIdle) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
/* idle = */ idle ? 1 : 0, packageName));
if (!idle) {
/* idle = */ stillIdle ? 1 : 0, packageName));
if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
mAppIdleHistory.addEntry(packageName, userId, idle, timeNow);
}
}
}
@@ -693,10 +645,11 @@ public class UsageStatsService extends SystemService implements
/**
* Called by the Binder stub.
*/
void removeUser(int userId) {
void onUserRemoved(int userId) {
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
mAppIdleHistory.onUserRemoved(userId);
cleanUpRemovedUsersLocked();
}
}
@@ -750,29 +703,12 @@ public class UsageStatsService extends SystemService implements
}
}
private boolean isAppIdleUnfiltered(String packageName, UserUsageStatsService userService,
long timeNow, long screenOnTime) {
private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
synchronized (mLock) {
long beginIdleTime = userService.getBeginIdleTime(packageName);
long lastUsedTime = userService.getSystemLastUsedTime(packageName);
return hasPassedIdleTimeoutLocked(beginIdleTime, lastUsedTime, screenOnTime,
timeNow);
return mAppIdleHistory.isIdleLocked(packageName, userId, elapsedRealtime);
}
}
/**
* @param beginIdleTime when the app was last used in device usage timebase
* @param lastUsedTime wallclock time of when the app was last used
* @param screenOnTime screen-on timebase time
* @param currentTime current time in device usage timebase
* @return whether it's been used far enough in the past to be considered inactive
*/
boolean hasPassedIdleTimeoutLocked(long beginIdleTime, long lastUsedTime,
long screenOnTime, long currentTime) {
return (beginIdleTime <= screenOnTime - mAppIdleDurationMillis)
&& (lastUsedTime <= currentTime - mAppIdleWallclockThresholdMillis);
}
void addListener(AppIdleStateChangeListener listener) {
synchronized (mLock) {
if (!mPackageAccessListeners.contains(listener)) {
@@ -787,32 +723,22 @@ public class UsageStatsService extends SystemService implements
}
}
boolean isAppIdleFilteredOrParoled(String packageName, int userId, long timeNow) {
int getAppId(String packageName) {
try {
ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS);
return ai.uid;
} catch (NameNotFoundException re) {
return -1;
}
}
boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime) {
if (mAppIdleParoled) {
return false;
}
try {
ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_DISABLED_COMPONENTS);
return isAppIdleFiltered(packageName, ai.uid, userId, timeNow);
} catch (PackageManager.NameNotFoundException e) {
}
return false;
}
boolean isAppIdleFiltered(String packageName, int uidForAppId, int userId, long timeNow) {
final UserUsageStatsService userService;
final long screenOnTime;
synchronized (mLock) {
if (timeNow == -1) {
timeNow = checkAndGetTimeLocked();
}
userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
screenOnTime = getScreenOnTimeLocked();
}
return isAppIdleFiltered(packageName, UserHandle.getAppId(uidForAppId), userId,
userService, timeNow, screenOnTime);
return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
}
/**
@@ -822,7 +748,7 @@ public class UsageStatsService extends SystemService implements
* Called by interface impls.
*/
private boolean isAppIdleFiltered(String packageName, int appId, int userId,
UserUsageStatsService userService, long timeNow, long screenOnTime) {
long elapsedRealtime) {
if (packageName == null) return false;
// If not enabled at all, of course nobody is ever idle.
if (!mAppIdleEnabled) {
@@ -864,7 +790,7 @@ public class UsageStatsService extends SystemService implements
return false;
}
return isAppIdleUnfiltered(packageName, userService, timeNow, screenOnTime);
return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
int[] getIdleUidsForUser(int userId) {
@@ -872,14 +798,7 @@ public class UsageStatsService extends SystemService implements
return new int[0];
}
final long timeNow;
final UserUsageStatsService userService;
final long screenOnTime;
synchronized (mLock) {
timeNow = checkAndGetTimeLocked();
userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
screenOnTime = getScreenOnTimeLocked();
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
List<ApplicationInfo> apps;
try {
@@ -899,12 +818,12 @@ public class UsageStatsService extends SystemService implements
// Now resolve all app state. Iterating over all apps, keeping track of how many
// we find for each uid and how many of those are idle.
for (int i = apps.size()-1; i >= 0; i--) {
for (int i = apps.size() - 1; i >= 0; i--) {
ApplicationInfo ai = apps.get(i);
// Check whether this app is idle.
boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
userId, userService, timeNow, screenOnTime);
userId, elapsedRealtime);
int index = uidStates.indexOfKey(ai.uid);
if (index < 0) {
@@ -990,8 +909,11 @@ public class UsageStatsService extends SystemService implements
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
mAppIdleHistory.writeAppIdleTimesLocked(mUserState.keyAt(i));
}
// Persist elapsed time periodically, in case screen doesn't get toggled
// until the next boot
mAppIdleHistory.writeElapsedTimeLocked();
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
@@ -1000,7 +922,6 @@ public class UsageStatsService extends SystemService implements
*/
void dump(String[] args, PrintWriter pw) {
synchronized (mLock) {
final long screenOnTime = getScreenOnTimeLocked();
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
ArraySet<String> argSet = new ArraySet<>();
argSet.addAll(Arrays.asList(args));
@@ -1011,27 +932,28 @@ public class UsageStatsService extends SystemService implements
idpw.println();
idpw.increaseIndent();
if (argSet.contains("--checkin")) {
mUserState.valueAt(i).checkin(idpw, screenOnTime);
mUserState.valueAt(i).checkin(idpw);
} else {
mUserState.valueAt(i).dump(idpw, screenOnTime);
mUserState.valueAt(i).dump(idpw);
idpw.println();
if (args.length > 0 && "history".equals(args[0])) {
mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
if (args.length > 0) {
if ("history".equals(args[0])) {
mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
} else if ("flush".equals(args[0])) {
UsageStatsService.this.flushToDiskLocked();
pw.println("Flushed stats to disk");
}
}
}
mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
idpw.decreaseIndent();
}
pw.print("Screen On Timebase: ");
pw.print(screenOnTime);
pw.print(" (");
TimeUtils.formatDuration(screenOnTime, pw);
pw.println(")");
pw.println();
pw.println("Settings:");
pw.print(" mAppIdleDurationMillis=");
TimeUtils.formatDuration(mAppIdleDurationMillis, pw);
TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
pw.println();
pw.print(" mAppIdleWallclockThresholdMillis=");
@@ -1057,11 +979,6 @@ public class UsageStatsService extends SystemService implements
pw.print("mLastAppIdleParoledTime=");
TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
pw.println();
pw.print("mScreenOnTime="); TimeUtils.formatDuration(mScreenOnTime, pw);
pw.println();
pw.print("mLastScreenOnEventRealtime=");
TimeUtils.formatDuration(mLastScreenOnEventRealtime, pw);
pw.println();
}
}
@@ -1082,7 +999,7 @@ public class UsageStatsService extends SystemService implements
break;
case MSG_REMOVE_USER:
removeUser(msg.arg1);
onUserRemoved(msg.arg1);
break;
case MSG_INFORM_LISTENERS:
@@ -1179,13 +1096,13 @@ public class UsageStatsService extends SystemService implements
}
// Default: 12 hours of screen-on time sans dream-time
mAppIdleDurationMillis = mParser.getLong(KEY_IDLE_DURATION,
mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
mCheckIdleIntervalMillis = Math.min(mAppIdleDurationMillis / 4,
mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
// Default: 24 hours between paroles
@@ -1194,6 +1111,8 @@ public class UsageStatsService extends SystemService implements
mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
mAppIdleScreenThresholdMillis);
}
}
}
@@ -1284,7 +1203,8 @@ public class UsageStatsService extends SystemService implements
}
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId, -1);
return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
SystemClock.elapsedRealtime());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1304,11 +1224,9 @@ public class UsageStatsService extends SystemService implements
"No permission to change app idle state");
final long token = Binder.clearCallingIdentity();
try {
PackageInfo pi = AppGlobals.getPackageManager().getPackageInfo(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
if (pi == null) return;
final int appId = getAppId(packageName);
if (appId < 0) return;
UsageStatsService.this.setAppIdle(packageName, idle, userId);
} catch (RemoteException re) {
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1335,8 +1253,6 @@ public class UsageStatsService extends SystemService implements
}
UsageStatsService.this.dump(args, pw);
}
}
/**
@@ -1411,7 +1327,8 @@ public class UsageStatsService extends SystemService implements
@Override
public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId, -1);
return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
SystemClock.elapsedRealtime());
}
@Override

View File

@@ -26,7 +26,6 @@ import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.text.TextUtils;
import java.io.IOException;
import java.net.ProtocolException;
@@ -55,13 +54,11 @@ final class UsageStatsXmlV1 {
// Time attributes stored as an offset of the beginTime.
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
private static final String LAST_TIME_ACTIVE_SYSTEM_ATTR = "lastTimeActiveSystem";
private static final String BEGIN_IDLE_TIME_ATTR = "beginIdleTime";
private static final String END_TIME_ATTR = "endTime";
private static final String TIME_ATTR = "time";
private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
throws IOException {
final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
if (pkg == null) {
throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
@@ -72,20 +69,6 @@ final class UsageStatsXmlV1 {
// Apply the offset to the beginTime to find the absolute time.
stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
parser, LAST_TIME_ACTIVE_ATTR);
final String lastTimeUsedSystem = parser.getAttributeValue(null,
LAST_TIME_ACTIVE_SYSTEM_ATTR);
if (TextUtils.isEmpty(lastTimeUsedSystem)) {
// If the field isn't present, use the old one.
stats.mLastTimeSystemUsed = stats.mLastTimeUsed;
} else {
stats.mLastTimeSystemUsed = statsOut.beginTime + Long.parseLong(lastTimeUsedSystem);
}
final String beginIdleTime = parser.getAttributeValue(null, BEGIN_IDLE_TIME_ATTR);
if (!TextUtils.isEmpty(beginIdleTime)) {
stats.mBeginIdleTime = Long.parseLong(beginIdleTime);
}
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
}
@@ -141,13 +124,10 @@ final class UsageStatsXmlV1 {
// Write the time offset.
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
usageStats.mLastTimeUsed - stats.beginTime);
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_SYSTEM_ATTR,
usageStats.mLastTimeSystemUsed - stats.beginTime);
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
XmlUtils.writeLongAttribute(xml, BEGIN_IDLE_TIME_ATTR, usageStats.mBeginIdleTime);
xml.endTag(null, PACKAGE_TAG);
}
@@ -255,7 +235,6 @@ final class UsageStatsXmlV1 {
}
xml.endTag(null, PACKAGES_TAG);
xml.startTag(null, CONFIGURATIONS_TAG);
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {

View File

@@ -59,7 +59,6 @@ class UserUsageStatsService {
private final Context mContext;
private final UsageStatsDatabase mDatabase;
private final IntervalStats[] mCurrentStats;
private IntervalStats mAppIdleRollingWindow;
private boolean mStatsChanged = false;
private final UnixCalendar mDailyExpiryDate;
private final StatsUpdatedListener mListener;
@@ -74,7 +73,11 @@ class UserUsageStatsService {
interface StatsUpdatedListener {
void onStatsUpdated();
void onStatsReloaded();
long getAppIdleRollingWindowDurationMillis();
/**
* Callback that a system update was detected
* @param mUserId user that needs to be initialized
*/
void onNewUpdate(int mUserId);
}
UserUsageStatsService(Context context, int userId, File usageStatsDir,
@@ -88,7 +91,7 @@ class UserUsageStatsService {
mUserId = userId;
}
void init(final long currentTimeMillis, final long deviceUsageTime) {
void init(final long currentTimeMillis) {
mDatabase.init(currentTimeMillis);
int nullCount = 0;
@@ -112,7 +115,7 @@ class UserUsageStatsService {
// By calling loadActiveStats, we will
// generate new stats for each bucket.
loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
loadActiveStats(currentTimeMillis);
} else {
// Set up the expiry date to be one day from the latest daily stat.
// This may actually be today and we will rollover on the first event
@@ -136,54 +139,18 @@ class UserUsageStatsService {
stat.updateConfigurationStats(null, stat.lastTimeSaved);
}
refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
if (mDatabase.isNewUpdate()) {
initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
mDatabase.isFirstUpdate());
notifyNewUpdate();
}
}
/**
* If any of the apps don't have a last-used entry, add one now.
* @param currentTimeMillis the current time
* @param firstUpdate if it is the first update, touch all installed apps, otherwise only
* touch the system apps
*/
private void initializeDefaultsForApps(long currentTimeMillis, long deviceUsageTime,
boolean firstUpdate) {
PackageManager pm = mContext.getPackageManager();
List<PackageInfo> packages = pm.getInstalledPackagesAsUser(0, mUserId);
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
final PackageInfo pi = packages.get(i);
String packageName = pi.packageName;
if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
&& getBeginIdleTime(packageName) == -1) {
for (IntervalStats stats : mCurrentStats) {
stats.update(packageName, currentTimeMillis, Event.SYSTEM_INTERACTION);
stats.updateBeginIdleTime(packageName, deviceUsageTime);
}
mAppIdleRollingWindow.update(packageName, currentTimeMillis,
Event.SYSTEM_INTERACTION);
mAppIdleRollingWindow.updateBeginIdleTime(packageName, deviceUsageTime);
mStatsChanged = true;
}
}
// Persist the new OTA-related access stats.
persistActiveStats();
}
void onTimeChanged(long oldTime, long newTime, long deviceUsageTime,
boolean resetBeginIdleTime) {
void onTimeChanged(long oldTime, long newTime) {
persistActiveStats();
mDatabase.onTimeChanged(newTime - oldTime);
loadActiveStats(newTime, resetBeginIdleTime);
refreshAppIdleRollingWindow(newTime, deviceUsageTime);
loadActiveStats(newTime);
}
void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
void reportEvent(UsageEvents.Event event) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
+ "[" + event.mTimeStamp + "]: "
@@ -192,7 +159,7 @@ class UserUsageStatsService {
if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
// Need to rollover
rolloverStats(event.mTimeStamp, deviceUsageTime);
rolloverStats(event.mTimeStamp);
}
final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
@@ -218,35 +185,9 @@ class UserUsageStatsService {
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
} else {
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
stats.updateBeginIdleTime(event.mPackage, deviceUsageTime);
}
}
if (event.mEventType != Event.CONFIGURATION_CHANGE) {
mAppIdleRollingWindow.update(event.mPackage, event.mTimeStamp, event.mEventType);
mAppIdleRollingWindow.updateBeginIdleTime(event.mPackage, deviceUsageTime);
}
notifyStatsChanged();
}
/**
* Sets the beginIdleTime for each of the intervals.
* @param beginIdleTime
*/
void setBeginIdleTime(String packageName, long beginIdleTime) {
for (IntervalStats stats : mCurrentStats) {
stats.updateBeginIdleTime(packageName, beginIdleTime);
}
mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime);
notifyStatsChanged();
}
void setSystemLastUsedTime(String packageName, long lastUsedTime) {
for (IntervalStats stats : mCurrentStats) {
stats.updateSystemLastUsedTime(packageName, lastUsedTime);
}
mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime);
notifyStatsChanged();
}
@@ -404,24 +345,6 @@ class UserUsageStatsService {
return new UsageEvents(results, table);
}
long getBeginIdleTime(String packageName) {
UsageStats packageUsage;
if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
return -1;
} else {
return packageUsage.getBeginIdleTime();
}
}
long getSystemLastUsedTime(String packageName) {
UsageStats packageUsage;
if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
return -1;
} else {
return packageUsage.getLastTimeSystemUsed();
}
}
void persistActiveStats() {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
@@ -436,7 +359,7 @@ class UserUsageStatsService {
}
}
private void rolloverStats(final long currentTimeMillis, final long deviceUsageTime) {
private void rolloverStats(final long currentTimeMillis) {
final long startTime = SystemClock.elapsedRealtime();
Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
@@ -463,7 +386,7 @@ class UserUsageStatsService {
persistActiveStats();
mDatabase.prune(currentTimeMillis);
loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
loadActiveStats(currentTimeMillis);
final int continueCount = continuePreviousDay.size();
for (int i = 0; i < continueCount; i++) {
@@ -477,8 +400,6 @@ class UserUsageStatsService {
}
persistActiveStats();
refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
final long totalTime = SystemClock.elapsedRealtime() - startTime;
Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
+ " milliseconds");
@@ -491,7 +412,11 @@ class UserUsageStatsService {
}
}
private void loadActiveStats(final long currentTimeMillis, boolean resetBeginIdleTime) {
private void notifyNewUpdate() {
mListener.onNewUpdate(mUserId);
}
private void loadActiveStats(final long currentTimeMillis) {
for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
@@ -514,12 +439,6 @@ class UserUsageStatsService {
mCurrentStats[intervalType].beginTime = currentTimeMillis;
mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
}
if (resetBeginIdleTime) {
for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) {
usageStats.mBeginIdleTime = 0;
}
}
}
mStatsChanged = false;
@@ -538,96 +457,28 @@ class UserUsageStatsService {
mDailyExpiryDate.getTimeInMillis() + ")");
}
private static void mergePackageStats(IntervalStats dst, IntervalStats src,
final long deviceUsageTime) {
dst.endTime = Math.max(dst.endTime, src.endTime);
final int srcPackageCount = src.packageStats.size();
for (int i = 0; i < srcPackageCount; i++) {
final String packageName = src.packageStats.keyAt(i);
final UsageStats srcStats = src.packageStats.valueAt(i);
UsageStats dstStats = dst.packageStats.get(packageName);
if (dstStats == null) {
dstStats = new UsageStats(srcStats);
dst.packageStats.put(packageName, dstStats);
} else {
dstStats.add(src.packageStats.valueAt(i));
}
// App idle times can not begin in the future. This happens if we had a time change.
if (dstStats.mBeginIdleTime > deviceUsageTime) {
dstStats.mBeginIdleTime = deviceUsageTime;
}
}
}
/**
* App idle operates on a rolling window of time. When we roll over time, we end up with a
* period of time where in-memory stats are empty and we don't hit the disk for older stats
* for performance reasons. Suddenly all apps will become idle.
*
* Instead, at times we do a deep query to find all the apps that have run in the past few
* days and keep the cached data up to date.
*
* @param currentTimeMillis
*/
void refreshAppIdleRollingWindow(final long currentTimeMillis, final long deviceUsageTime) {
// Start the rolling window for AppIdle requests.
final long startRangeMillis = currentTimeMillis -
mListener.getAppIdleRollingWindowDurationMillis();
List<IntervalStats> stats = mDatabase.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
startRangeMillis, currentTimeMillis, new StatCombiner<IntervalStats>() {
@Override
public void combine(IntervalStats stats, boolean mutable,
List<IntervalStats> accumulatedResult) {
IntervalStats accum;
if (accumulatedResult.isEmpty()) {
accum = new IntervalStats();
accum.beginTime = stats.beginTime;
accumulatedResult.add(accum);
} else {
accum = accumulatedResult.get(0);
}
mergePackageStats(accum, stats, deviceUsageTime);
}
});
if (stats == null || stats.isEmpty()) {
mAppIdleRollingWindow = new IntervalStats();
mergePackageStats(mAppIdleRollingWindow,
mCurrentStats[UsageStatsManager.INTERVAL_YEARLY], deviceUsageTime);
} else {
mAppIdleRollingWindow = stats.get(0);
}
}
//
// -- DUMP related methods --
//
void checkin(final IndentingPrintWriter pw, final long screenOnTime) {
void checkin(final IndentingPrintWriter pw) {
mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
@Override
public boolean checkin(IntervalStats stats) {
printIntervalStats(pw, stats, screenOnTime, false);
printIntervalStats(pw, stats, false);
return true;
}
});
}
void dump(IndentingPrintWriter pw, final long screenOnTime) {
void dump(IndentingPrintWriter pw) {
// This is not a check-in, only dump in-memory stats.
for (int interval = 0; interval < mCurrentStats.length; interval++) {
pw.print("In-memory ");
pw.print(intervalToString(interval));
pw.println(" stats");
printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
printIntervalStats(pw, mCurrentStats[interval], true);
}
pw.println("AppIdleRollingWindow cache");
printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true);
}
private String formatDateTime(long dateTime, boolean pretty) {
@@ -644,7 +495,7 @@ class UserUsageStatsService {
return Long.toString(elapsedTime);
}
void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, long screenOnTime,
void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
boolean prettyDates) {
if (prettyDates) {
pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
@@ -665,10 +516,6 @@ class UserUsageStatsService {
pw.printPair("totalTime",
formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
pw.printPair("lastTimeSystem",
formatDateTime(usageStats.mLastTimeSystemUsed, prettyDates));
pw.printPair("inactiveTime",
formatElapsedTime(screenOnTime - usageStats.mBeginIdleTime, prettyDates));
pw.println();
}
pw.decreaseIndent();