diff options
8 files changed, 564 insertions, 477 deletions
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index a88aa312550d..2937ccc879a5 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -47,20 +47,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} */ public long mTotalTimeInForeground; @@ -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() { @@ -127,25 +111,6 @@ public final class UsageStats implements Parcelable { } /** - * @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 @@ 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; } diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 7d7be07443c0..071ec1b025f0 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -15,6 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 000000000000..9ccb1a618568 --- /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 e3c0868ea3a2..3e2b43d2af5c 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 @@ 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"; + + // 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); - long thisPeriod = timeNow / PERIOD_DURATION; + 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 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); + 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 7f379fe34452..f541f70d4334 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 @@ 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); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7774038740fe..46ad8a10534e 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.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) { - if (mAppIdleParoled) { - return false; - } + int getAppId(String packageName) { 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) { + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + return ai.uid; + } catch (NameNotFoundException re) { + return -1; } - 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(); + boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime) { + if (mAppIdleParoled) { + return false; } - 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 diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index f2ca3a4047fa..c95ff2364f2c 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.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++) { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index f2045d31f69c..7d003f3bf61a 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 @@ 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(); |