Rewrite app standby stats
Don't mix up with usage stats. Keep a separate db and history
based on elapsed time and screen on time.
Unit tests for AppIdleHistory class.
Bug: 26989006
Change-Id: If343785b46da1db67f7c1c1263854c2732a232c6
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index a88aa3125..2937ccc 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -47,20 +47,6 @@
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}
*/
public long mTotalTimeInForeground;
@@ -89,8 +75,6 @@
mTotalTimeInForeground = stats.mTotalTimeInForeground;
mLaunchCount = stats.mLaunchCount;
mLastEvent = stats.mLastEvent;
- mBeginIdleTime = stats.mBeginIdleTime;
- mLastTimeSystemUsed = stats.mLastTimeSystemUsed;
}
public String getPackageName() {
@@ -127,25 +111,6 @@
}
/**
- * @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.
*/
public long getTotalTimeInForeground() {
@@ -172,8 +137,6 @@
// 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 @@
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 @@
stats.mTotalTimeInForeground = in.readLong();
stats.mLaunchCount = in.readInt();
stats.mLastEvent = in.readInt();
- stats.mBeginIdleTime = in.readLong();
- stats.mLastTimeSystemUsed = in.readLong();
return stats;
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 3ad26d3..0b87567 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -11,6 +11,7 @@
services.core \
services.devicepolicy \
services.net \
+ services.usage \
easymocklib \
guava \
android-support-test \
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
new file mode 100644
index 0000000..9ccb1a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -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));
+ }
+}
\ No newline at end of file
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index e3c0868..3e2b43d 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -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 @@
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();
}
}
-}
\ No newline at end of file
+}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 7f379fe..f541f70 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -48,7 +48,6 @@
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
- usageStats.mBeginIdleTime = 0;
packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
@@ -113,7 +112,6 @@
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 @@
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);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 77740387..46ad8a1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -40,6 +40,7 @@
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.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.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 @@
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 @@
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 @@
synchronized (mLock) {
cleanUpRemovedUsersLocked();
- mLastScreenOnEventRealtime = SystemClock.elapsedRealtime();
- mScreenOnTime = readScreenOnTimeLocked();
+ mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
}
mRealTimeSnapshot = SystemClock.elapsedRealtime();
@@ -221,7 +213,7 @@
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mLock) {
- updateDisplayLocked();
+ mAppIdleHistory.updateDisplayLocked(isDisplayOn(), SystemClock.elapsedRealtime());
}
if (mPendingOneTimeCheckIdleStates) {
@@ -232,6 +224,11 @@
}
}
+ private boolean isDisplayOn() {
+ return mDisplayManager
+ .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+ }
+
private class UserActionsReceiver extends BroadcastReceiver {
@Override
@@ -274,7 +271,8 @@
@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 @@
}
@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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
* 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 @@
/**
* 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 @@
}
}
- 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 @@
}
}
- 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 @@
* 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 @@
return false;
}
- return isAppIdleUnfiltered(packageName, userService, timeNow, screenOnTime);
+ return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
int[] getIdleUidsForUser(int userId) {
@@ -872,14 +798,7 @@
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 @@
// 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 @@
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 @@
*/
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 @@
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 @@
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 @@
break;
case MSG_REMOVE_USER:
- removeUser(msg.arg1);
+ onUserRemoved(msg.arg1);
break;
case MSG_INFORM_LISTENERS:
@@ -1179,13 +1096,13 @@
}
// 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 @@
mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+ mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+ mAppIdleScreenThresholdMillis);
}
}
}
@@ -1284,7 +1203,8 @@
}
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 @@
"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 @@
}
UsageStatsService.this.dump(args, pw);
}
-
-
}
/**
@@ -1411,7 +1327,8 @@
@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
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index f2ca3a4..c95ff23 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -26,7 +26,6 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
}
xml.endTag(null, PACKAGES_TAG);
-
xml.startTag(null, CONFIGURATIONS_TAG);
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index f2045d3..7d003f3 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -59,7 +59,6 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
}
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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();