Report app standby state to batterystats

Also reduce idle checks to the target user if possible.
Optimized calls to some internal methods

Bug: 21639147
Change-Id: If1faf26f862e5c4ca905f2603a4ba52a8d1af954
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a6efc58..f76192e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1177,8 +1177,13 @@
         public static final int EVENT_ALARM = 0x000e;
         // Record that we have decided we need to collect new stats data.
         public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000f;
+        // Event for a package becoming inactive due to being unused for a period of time.
+        public static final int EVENT_PACKAGE_INACTIVE = 0x0010;
+        // Event for a package becoming active due to an interaction.
+        public static final int EVENT_PACKAGE_ACTIVE = 0x0011;
+
         // Number of event types.
-        public static final int EVENT_COUNT = 0x0010;
+        public static final int EVENT_COUNT = 0x0012;
         // Mask to extract out only the type part of the event.
         public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
 
@@ -1835,12 +1840,12 @@
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
             "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
-            "motion", "active", "pkginst", "pkgunin", "alarm", "stats"
+            "motion", "active", "pkginst", "pkgunin", "alarm", "stats", "inactive", "active"
     };
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
             "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
-            "Esm", "Eac", "Epi", "Epu", "Eal", "Est"
+            "Esm", "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa"
     };
 
     /**
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index 9d3db16..e3c0868 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -27,34 +27,27 @@
  */
 public class AppIdleHistory {
 
-    private SparseArray<ArrayMap<String,byte[]>> idleHistory = new SparseArray<>();
+    private SparseArray<ArrayMap<String,byte[]>> mIdleHistory = new SparseArray<>();
     private long lastPeriod = 0;
     private static final long ONE_MINUTE = 60 * 1000;
     private static final int HISTORY_SIZE = 100;
     private static final int FLAG_LAST_STATE = 2;
     private static final int FLAG_PARTIAL_ACTIVE = 1;
-    private static final long PERIOD_DURATION = UsageStatsService.DEBUG ? ONE_MINUTE
+    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 = idleHistory.get(userId);
-        if (userHistory == null) {
-            userHistory = new ArrayMap<>();
-            idleHistory.put(userId, userHistory);
-        }
-        byte[] packageHistory = userHistory.get(packageName);
-        if (packageHistory == null) {
-            packageHistory = new byte[HISTORY_SIZE];
-            userHistory.put(packageName, packageHistory);
-        }
+        ArrayMap<String, byte[]> userHistory = getUserHistory(userId);
+        byte[] packageHistory = getPackageHistory(userHistory, packageName);
+
         long thisPeriod = timeNow / 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);
-            final int NUSERS = idleHistory.size();
+            final int NUSERS = mIdleHistory.size();
             for (int u = 0; u < NUSERS; u++) {
-                userHistory = idleHistory.valueAt(u);
+                userHistory = mIdleHistory.valueAt(u);
                 for (byte[] history : userHistory.values()) {
                     // Shift left
                     System.arraycopy(history, diff, history, 0, HISTORY_SIZE - diff);
@@ -74,12 +67,36 @@
         }
     }
 
+    private ArrayMap<String, byte[]> getUserHistory(int userId) {
+        ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
+        if (userHistory == null) {
+            userHistory = new ArrayMap<>();
+            mIdleHistory.put(userId, userHistory);
+        }
+        return userHistory;
+    }
+
+    private byte[] getPackageHistory(ArrayMap<String, byte[]> userHistory, String packageName) {
+        byte[] packageHistory = userHistory.get(packageName);
+        if (packageHistory == null) {
+            packageHistory = new byte[HISTORY_SIZE];
+            userHistory.put(packageName, packageHistory);
+        }
+        return packageHistory;
+    }
+
     public void removeUser(int userId) {
-        idleHistory.remove(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 void dump(IndentingPrintWriter idpw, int userId) {
-        ArrayMap<String, byte[]> userHistory = idleHistory.get(userId);
+        ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
         if (userHistory == null) return;
         final int P = userHistory.size();
         for (int p = 0; p < P; p++) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7a34757..8e1895e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -43,6 +43,7 @@
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.BatteryManager;
+import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -65,6 +66,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.DeviceIdleController;
@@ -91,7 +93,7 @@
     static final String TAG = "UsageStatsService";
 
     static final boolean DEBUG = false;
-    private static final boolean COMPRESS_TIME = false;
+    static final boolean COMPRESS_TIME = false;
 
     private static final long TEN_SECONDS = 10 * 1000;
     private static final long ONE_MINUTE = 60 * 1000;
@@ -128,6 +130,7 @@
     IDeviceIdleController mDeviceIdleController;
     private DisplayManager mDisplayManager;
     private PowerManager mPowerManager;
+    private IBatteryStats mBatteryStats;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
     private File mUsageStatsDir;
@@ -200,6 +203,8 @@
             mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
             mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                     ServiceManager.getService(DeviceIdleController.SERVICE_NAME));
+            mBatteryStats = IBatteryStats.Stub.asInterface(
+                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
             mDisplayManager = (DisplayManager) getContext().getSystemService(
                     Context.DISPLAY_SERVICE);
             mPowerManager = getContext().getSystemService(PowerManager.class);
@@ -228,7 +233,7 @@
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) {
                 if (userId >=0) {
-                    postCheckIdleStates();
+                    postCheckIdleStates(userId);
                 }
             }
         }
@@ -307,7 +312,7 @@
                     mLastAppIdleParoledTime = checkAndGetTimeLocked();
                     postNextParoleTimeout();
                 }
-                postCheckIdleStates();
+                postCheckIdleStates(UserHandle.USER_ALL);
             }
         }
     }
@@ -332,22 +337,25 @@
         mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, DEFAULT_PAROLE_DURATION);
     }
 
-    void postCheckIdleStates() {
-        mHandler.removeMessages(MSG_CHECK_IDLE_STATES);
-        mHandler.sendEmptyMessage(MSG_CHECK_IDLE_STATES);
+    void postCheckIdleStates(int userId) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
     }
 
-    /** Check all running users' apps to see if they enter an idle state. */
-    void checkIdleStates() {
-        final int[] runningUsers;
+    /** Check all running users' or specified user's apps to see if they enter an idle state. */
+    void checkIdleStates(int checkUserId) {
+        final int[] userIds;
         try {
-            runningUsers = ActivityManagerNative.getDefault().getRunningUserIds();
+            if (checkUserId == UserHandle.USER_ALL) {
+                userIds = ActivityManagerNative.getDefault().getRunningUserIds();
+            } else {
+                userIds = new int[] { checkUserId };
+            }
         } catch (RemoteException re) {
             return;
         }
 
-        for (int i = 0; i < runningUsers.length; i++) {
-            final int userId = runningUsers[i];
+        for (int i = 0; i < userIds.length; i++) {
+            final int userId = userIds[i];
             List<PackageInfo> packages =
                     getContext().getPackageManager().getInstalledPackages(
                             PackageManager.GET_DISABLED_COMPONENTS
@@ -355,17 +363,22 @@
                             userId);
             synchronized (mLock) {
                 final long timeNow = checkAndGetTimeLocked();
+                final long screenOnTime = getScreenOnTimeLocked(timeNow);
+                UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId,
+                        timeNow);
                 final int packageCount = packages.size();
                 for (int p = 0; p < packageCount; p++) {
                     final String packageName = packages.get(p).packageName;
-                    final boolean isIdle = isAppIdleFiltered(packageName, userId, timeNow);
+                    final boolean isIdle = isAppIdleFiltered(packageName, userId, service, timeNow,
+                            screenOnTime);
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
                             userId, isIdle ? 1 : 0, packageName));
                     mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
                 }
             }
         }
-        mHandler.sendEmptyMessageDelayed(MSG_CHECK_IDLE_STATES, mCheckIdleIntervalMillis);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, checkUserId, 0),
+                mCheckIdleIntervalMillis);
     }
 
     /** Check if it's been a while since last parole and let idle apps do some work */
@@ -386,6 +399,20 @@
         }
     }
 
+    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
+        try {
+            int uid = AppGlobals.getPackageManager().getPackageUid(packageName, userId);
+            if (idle) {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                        packageName, uid);
+            } else {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                        packageName, uid);
+            }
+        } catch (RemoteException re) {
+        }
+    }
+
     void updateDisplayLocked() {
         boolean screenOn = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState()
                 == Display.STATE_ON;
@@ -545,7 +572,7 @@
             final long lastUsedTime = service.getSystemLastUsedTime(event.mPackage);
             final boolean previouslyIdle = hasPassedIdleTimeoutLocked(beginIdleTime,
                     lastUsedTime, screenOnTime, timeNow);
-            service.reportEvent(event, getScreenOnTimeLocked(timeNow));
+            service.reportEvent(event, screenOnTime);
             // Inform listeners if necessary
             if ((event.mEventType == Event.MOVE_TO_FOREGROUND
                     || event.mEventType == Event.MOVE_TO_BACKGROUND
@@ -555,6 +582,7 @@
                     // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                             /* idle = */ 0, event.mPackage));
+                    notifyBatteryStats(event.mPackage, userId, false);
                     mAppIdleHistory.addEntry(event.mPackage, userId, false, timeNow);
                 }
             }
@@ -586,6 +614,9 @@
                 // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                         /* idle = */ idle ? 1 : 0, packageName));
+                if (!idle) {
+                    notifyBatteryStats(packageName, userId, idle);
+                }
                 mAppIdleHistory.addEntry(packageName, userId, idle, timeNow);
             }
         }
@@ -660,19 +691,20 @@
         }
     }
 
-    private boolean isAppIdleUnfiltered(String packageName, int userId, long timeNow) {
+    private boolean isAppIdleUnfiltered(String packageName, UserUsageStatsService userService,
+            long timeNow, long screenOnTime) {
         synchronized (mLock) {
-            final long screenOnTime = getScreenOnTimeLocked(timeNow);
-            final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
-            long beginIdleTime = service.getBeginIdleTime(packageName);
-            long lastUsedTime = service.getSystemLastUsedTime(packageName);
-            return hasPassedIdleTimeoutLocked(beginIdleTime, lastUsedTime, screenOnTime, timeNow);
+            long beginIdleTime = userService.getBeginIdleTime(packageName);
+            long lastUsedTime = userService.getSystemLastUsedTime(packageName);
+            return hasPassedIdleTimeoutLocked(beginIdleTime, lastUsedTime, screenOnTime,
+                    timeNow);
         }
     }
 
     /**
-     * @param timestamp when the app was last used in device usage timebase
+     * @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
      */
@@ -696,18 +728,29 @@
         }
     }
 
+    boolean isAppIdleFiltered(String packageName, int userId, long timeNow) {
+        final UserUsageStatsService userService;
+        final long screenOnTime;
+        synchronized (mLock) {
+            if (timeNow == -1) {
+                timeNow = checkAndGetTimeLocked();
+            }
+            userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+            screenOnTime = getScreenOnTimeLocked(timeNow);
+        }
+        return isAppIdleFiltered(packageName, userId, userService, timeNow, screenOnTime);
+    }
+
     /**
      * Checks if an app has been idle for a while and filters out apps that are excluded.
      * It returns false if the current system state allows all apps to be considered active.
      * This happens if the device is plugged in or temporarily allowed to make exceptions.
      * Called by interface impls.
      */
-    boolean isAppIdleFiltered(String packageName, int userId, long timeNow) {
+    private boolean isAppIdleFiltered(String packageName, int userId,
+            UserUsageStatsService userService, long timeNow, long screenOnTime) {
         if (packageName == null) return false;
         synchronized (mLock) {
-            if (timeNow == -1) {
-                timeNow = checkAndGetTimeLocked();
-            }
             // Temporary exemption, probably due to device charging or occasional allowance to
             // be allowed to sync, etc.
             if (mAppIdleParoled) {
@@ -735,7 +778,7 @@
             return false;
         }
 
-        return isAppIdleUnfiltered(packageName, userId, timeNow);
+        return isAppIdleUnfiltered(packageName, userService, timeNow, screenOnTime);
     }
 
     void setAppIdle(String packageName, boolean idle, int userId) {
@@ -844,7 +887,7 @@
                     break;
 
                 case MSG_CHECK_IDLE_STATES:
-                    checkIdleStates();
+                    checkIdleStates(msg.arg1);
                     break;
 
                 case MSG_CHECK_PAROLE_TIMEOUT:
@@ -884,7 +927,7 @@
                     UserHandle.USER_OWNER);
             mCheckIdleIntervalMillis = Math.min(DEFAULT_CHECK_IDLE_INTERVAL,
                     mAppIdleDurationMillis / 4);
-            postCheckIdleStates();
+            postCheckIdleStates(UserHandle.USER_ALL);
         }
     }