Add support for battery/screen state changes.

Only collect data when the device is charging to be consistent with what
battery stats is doing.

Add a screen interactive dimension to have more context to analysis the
binder calls data.

Test: unit test
Change-Id: Id31c53ae315d905e5d8e67918f64780f34ff5d72
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
index 05293a2..66a2600d 100644
--- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
@@ -48,7 +48,7 @@
 
     @Before
     public void setUp() {
-        mBinderCallsStats = new BinderCallsStats(new Random());
+        mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
     }
 
     @After
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index baf6a4d..eb369e1 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -17,12 +17,19 @@
 package com.android.internal.os;
 
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.Binder;
+import android.os.OsProtoEnums;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -31,6 +38,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
 import java.lang.reflect.InvocationTargetException;
@@ -74,12 +82,99 @@
     private final Random mRandom;
     private long mStartTime = System.currentTimeMillis();
 
-    public BinderCallsStats(Random random) {
-        this.mRandom = random;
+    // State updated by the broadcast receiver below.
+    private boolean mScreenInteractive;
+    private boolean mCharging;
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_BATTERY_CHANGED:
+                    mCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+                    break;
+                case Intent.ACTION_SCREEN_ON:
+                    mScreenInteractive = true;
+                    break;
+                case Intent.ACTION_SCREEN_OFF:
+                    mScreenInteractive = false;
+                    break;
+            }
+        }
+    };
+
+    /** Injector for {@link BinderCallsStats}. */
+    public static class Injector {
+        public Random getRandomGenerator() {
+            return new Random();
+        }
+    }
+
+    public BinderCallsStats(Injector injector) {
+        this.mRandom = injector.getRandomGenerator();
+    }
+
+    public void systemReady(Context context) {
+        registerBroadcastReceiver(context);
+        setInitialState(queryScreenInteractive(context), queryIsCharging());
+    }
+
+    /**
+     * Listens for screen/battery state changes.
+     */
+    @VisibleForTesting
+    public void registerBroadcastReceiver(Context context) {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        context.registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    /**
+     * Sets the battery/screen initial state.
+     *
+     * This has to be updated *after* the broadcast receiver is installed.
+     */
+    @VisibleForTesting
+    public void setInitialState(boolean isScreenInteractive, boolean isCharging) {
+        this.mScreenInteractive = isScreenInteractive;
+        this.mCharging = isCharging;
+        // Data collected previously was not accurate since the battery/screen state was not set.
+        reset();
+    }
+
+    private boolean queryIsCharging() {
+        final BatteryManagerInternal batteryManager =
+                LocalServices.getService(BatteryManagerInternal.class);
+        if (batteryManager == null) {
+            Slog.wtf(TAG, "BatteryManager null while starting BinderCallsStatsService");
+            // Default to true to not collect any data.
+            return true;
+        } else {
+            return batteryManager.getPlugType() != OsProtoEnums.BATTERY_PLUGGED_NONE;
+        }
+    }
+
+    private boolean queryScreenInteractive(Context context) {
+        final PowerManager powerManager = context.getSystemService(PowerManager.class);
+        final boolean screenInteractive;
+        if (powerManager == null) {
+            Slog.wtf(TAG, "PowerManager null while starting BinderCallsStatsService",
+                    new Throwable());
+            return true;
+        } else {
+            return powerManager.isInteractive();
+        }
     }
 
     @Override
+    @Nullable
     public CallSession callStarted(Binder binder, int code) {
+        if (mCharging) {
+            return null;
+        }
+
         final CallSession s = obtainCallSession();
         s.binderClass = binder.getClass();
         s.transactionCode = code;
@@ -126,9 +221,15 @@
         final int callingUid = getCallingUid();
 
         synchronized (mLock) {
+            // This was already checked in #callStart but check again while synchronized.
+            if (mCharging) {
+                return;
+            }
+
             final UidEntry uidEntry = getUidEntry(callingUid);
             uidEntry.callCount++;
-            final CallStat callStat = uidEntry.getOrCreate(s.binderClass, s.transactionCode);
+            final CallStat callStat = uidEntry.getOrCreate(
+                    s.binderClass, s.transactionCode, mScreenInteractive);
             callStat.callCount++;
 
             if (recordCall) {
@@ -178,7 +279,7 @@
             }
         } catch (RuntimeException e) {
             // Do not propagate the exception. We do not want to swallow original exception.
-            Log.wtf(TAG, "Unexpected exception while updating mExceptionCounts", e);
+            Slog.wtf(TAG, "Unexpected exception while updating mExceptionCounts");
         }
     }
 
@@ -225,6 +326,7 @@
                     exported.className = stat.binderClass.getName();
                     exported.binderClass = stat.binderClass;
                     exported.transactionCode = stat.transactionCode;
+                    exported.screenInteractive = stat.screenInteractive;
                     exported.cpuTimeMicros = stat.cpuTimeMicros;
                     exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                     exported.latencyMicros = stat.latencyMicros;
@@ -308,7 +410,8 @@
         final List<UidEntry> topEntries = verbose ? entries
                 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
         pw.println("Per-UID raw data " + datasetSizeDesc
-                + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, "
+                + "(package/uid, call_desc, screen_interactive, "
+                + "cpu_time_micros, max_cpu_time_micros, "
                 + "latency_time_micros, max_latency_time_micros, exception_count, "
                 + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
                 + "call_count):");
@@ -320,6 +423,7 @@
                     .append(uidToString(e.uid, appIdToPkgNameMap))
                     .append(',').append(e.className)
                     .append('#').append(e.methodName)
+                    .append(',').append(e.screenInteractive)
                     .append(',').append(e.cpuTimeMicros)
                     .append(',').append(e.maxCpuTimeMicros)
                     .append(',').append(e.latencyMicros)
@@ -441,6 +545,8 @@
     public static class CallStat {
         public Class<? extends Binder> binderClass;
         public int transactionCode;
+        // True if the screen was interactive when the call ended.
+        public boolean screenInteractive;
         // Number of calls for which we collected data for. We do not record data for all the calls
         // when sampling is on.
         public long recordedCallCount;
@@ -461,9 +567,11 @@
         public long maxReplySizeBytes;
         public long exceptionCount;
 
-        CallStat(Class<? extends Binder> binderClass, int transactionCode) {
+        CallStat(Class<? extends Binder> binderClass, int transactionCode,
+                boolean screenInteractive) {
             this.binderClass = binderClass;
             this.transactionCode = transactionCode;
+            this.screenInteractive = screenInteractive;
         }
     }
 
@@ -471,6 +579,7 @@
     public static class CallStatKey {
         public Class<? extends Binder> binderClass;
         public int transactionCode;
+        private boolean screenInteractive;
 
         @Override
         public boolean equals(Object o) {
@@ -480,6 +589,7 @@
 
             final CallStatKey key = (CallStatKey) o;
             return transactionCode == key.transactionCode
+                    && screenInteractive == key.screenInteractive
                     && (binderClass.equals(key.binderClass));
         }
 
@@ -487,6 +597,7 @@
         public int hashCode() {
             int result = binderClass.hashCode();
             result = 31 * result + transactionCode;
+            result = 31 * result + (screenInteractive ? 1231 : 1237);
             return result;
         }
     }
@@ -513,17 +624,20 @@
         private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>();
         private CallStatKey mTempKey = new CallStatKey();
 
-        CallStat getOrCreate(Class<? extends Binder> binderClass, int transactionCode) {
+        CallStat getOrCreate(Class<? extends Binder> binderClass, int transactionCode,
+                boolean screenInteractive) {
             // Use a global temporary key to avoid creating new objects for every lookup.
             mTempKey.binderClass = binderClass;
             mTempKey.transactionCode = transactionCode;
+            mTempKey.screenInteractive = screenInteractive;
             CallStat mapCallStat = mCallStats.get(mTempKey);
             // Only create CallStat if it's a new entry, otherwise update existing instance
             if (mapCallStat == null) {
-                mapCallStat = new CallStat(binderClass, transactionCode);
+                mapCallStat = new CallStat(binderClass, transactionCode, screenInteractive);
                 CallStatKey key = new CallStatKey();
                 key.binderClass = binderClass;
                 key.transactionCode = transactionCode;
+                key.screenInteractive = screenInteractive;
                 mCallStats.put(key, mapCallStat);
             }
             return mapCallStat;
@@ -592,6 +706,11 @@
         return result;
     }
 
+    @VisibleForTesting
+    public BroadcastReceiver getBroadcastReceiver() {
+        return mBroadcastReceiver;
+    }
+
     private static int compareByCpuDesc(
             ExportedCallStat a, ExportedCallStat b) {
         return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 5599ee6..2f83190 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -16,7 +16,11 @@
 
 package com.android.internal.os;
 
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
 import android.os.Binder;
+import android.os.OsProtoEnums;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -392,6 +396,113 @@
     }
 
     @Test
+    public void testDataResetWhenInitialStateSet() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.time += 10;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        bcs.setInitialState(true, true);
+
+        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+        assertEquals(0, uidEntries.size());
+    }
+
+    @Test
+    public void testScreenAndChargerInitialStates() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Binder binder = new Binder();
+        bcs.setInitialState(true /** screen iteractive */, false);
+
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.time += 10;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        List<BinderCallsStats.CallStat> callStatsList =
+                new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList());
+        assertEquals(true, callStatsList.get(0).screenInteractive);
+    }
+
+    @Test
+    public void testNoDataCollectedOnCharger() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
+        bcs.getBroadcastReceiver().onReceive(null, intent);
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        assertEquals(0, bcs.getUidEntries().size());
+    }
+
+    @Test
+    public void testScreenOff() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_OFF));
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+        assertEquals(1, uidEntries.size());
+        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
+        Assert.assertNotNull(uidEntry);
+        List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList());
+        assertEquals(false, callStatsList.get(0).screenInteractive);
+    }
+
+    @Test
+    public void testScreenOn() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+        assertEquals(1, uidEntries.size());
+        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
+        Assert.assertNotNull(uidEntry);
+        List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList());
+        assertEquals(true, callStatsList.get(0).screenInteractive);
+    }
+
+    @Test
+    public void testOnCharger() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
+        bcs.getBroadcastReceiver().onReceive(null, intent);
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        assertEquals(0, bcs.getExportedCallStats().size());
+    }
+
+    @Test
+    public void testOnBattery() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_NONE);
+        bcs.getBroadcastReceiver().onReceive(null, intent);
+        Binder binder = new Binder();
+        CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        assertEquals(1, bcs.getExportedCallStats().size());
+    }
+
+    @Test
     public void testDumpDoesNotThrowException() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
@@ -419,6 +530,8 @@
     public void testGetExportedStatsWhenDetailedTrackingEnabled() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
+        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));
+
         Binder binder = new Binder();
         CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
@@ -430,6 +543,7 @@
         assertEquals(TEST_UID, stat.uid);
         assertEquals("android.os.Binder", stat.className);
         assertEquals("1", stat.methodName);
+        assertEquals(true, stat.screenInteractive);
         assertEquals(10, stat.cpuTimeMicros);
         assertEquals(10, stat.maxCpuTimeMicros);
         assertEquals(20, stat.latencyMicros);
@@ -462,11 +576,15 @@
 
         TestBinderCallsStats() {
             // Make random generator not random.
-            super(new Random() {
-                int mCallCount = 0;
+            super(new Injector() {
+                public Random getRandomGenerator() {
+                    return new Random() {
+                        int mCallCount = 0;
 
-                public int nextInt() {
-                    return mCallCount++;
+                        public int nextInt() {
+                            return mCallCount++;
+                        }
+                    };
                 }
             });
         }
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index c63b8f4..9a7c345 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -34,7 +34,6 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
-import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -131,6 +130,7 @@
 
     public static class LifeCycle extends SystemService {
         private BinderCallsStatsService mService;
+        private BinderCallsStats mBinderCallsStats;
 
         public LifeCycle(Context context) {
             super(context);
@@ -138,9 +138,9 @@
 
         @Override
         public void onStart() {
-            BinderCallsStats binderCallsStats = new BinderCallsStats(new Random());
-            mService = new BinderCallsStatsService(binderCallsStats);
-            LocalServices.addService(Internal.class, new Internal(binderCallsStats));
+            mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
+            mService = new BinderCallsStatsService(mBinderCallsStats);
+            publishLocalService(Internal.class, new Internal(mBinderCallsStats));
             publishBinderService("binder_calls_stats", mService);
             boolean detailedTrackingEnabled = SystemProperties.getBoolean(
                     PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
@@ -149,7 +149,7 @@
                 Slog.i(TAG, "Enabled CPU usage tracking for binder calls. Controlled by "
                         + PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
                         + " or via dumpsys binder_calls_stats --enable-detailed-tracking");
-                binderCallsStats.setDetailedTracking(true);
+                mBinderCallsStats.setDetailedTracking(true);
             }
         }
 
@@ -157,6 +157,7 @@
         public void onBootPhase(int phase) {
             if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
                 mService.systemReady(getContext());
+                mBinderCallsStats.systemReady(getContext());
             }
         }
     }