Adds an Observer interface to observes binder API calls.

This is the first step to move BinderCallsStats implementation to the
server package in order to have an implementation more specific to the
system server (tracking charger on/off).

Removes the volatile keywords in BinderCallsStats.

Also exposes a clean API to StatsCompanionService instead of
BinderCallsStats directly.

Test: unit test
Change-Id: Ied98c555acc8489420104098561b4608346509b5
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
index 2b8b8f2..9fad19f 100644
--- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
@@ -22,6 +22,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderInternal.CallSession;
 
 import java.util.Random;
 
@@ -61,7 +62,7 @@
         Binder b = new Binder();
         int i = 0;
         while (state.keepRunning()) {
-            BinderCallsStats.CallSession s = mBinderCallsStats.callStarted(b, i % 100);
+            CallSession s = mBinderCallsStats.callStarted(b, i % 100);
             mBinderCallsStats.callEnded(s, 0, 0);
             i++;
         }
@@ -73,7 +74,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         Binder b = new Binder();
         while (state.keepRunning()) {
-            BinderCallsStats.CallSession s = mBinderCallsStats.callStarted(b, 0);
+            CallSession s = mBinderCallsStats.callStarted(b, 0);
             mBinderCallsStats.callEnded(s, 0, 0);
         }
     }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 175b405..662d130 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -22,8 +22,8 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
@@ -93,6 +93,11 @@
     private static volatile TransactionTracker sTransactionTracker = null;
 
     /**
+     * Global observer for this process.
+     */
+    private static BinderInternal.Observer sObserver = null;
+
+    /**
      * Guestimate of native memory associated with a Binder.
      */
     private static final int NATIVE_ALLOCATION_SIZE = 500;
@@ -106,6 +111,7 @@
                 Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
     }
 
+
     // Transaction tracking code.
 
     /**
@@ -151,6 +157,15 @@
         return sTransactionTracker;
     }
 
+    /**
+     * Get the binder transaction observer for this process.
+     *
+     * @hide
+     */
+    public static void setObserver(@Nullable BinderInternal.Observer observer) {
+        sObserver = observer;
+    }
+
     /** {@hide} */
     static volatile boolean sWarnOnBlocking = false;
 
@@ -721,8 +736,10 @@
     // Entry point from android_util_Binder.cpp's onTransact
     private boolean execTransact(int code, long dataObj, long replyObj,
             int flags) {
-        BinderCallsStats binderCallsStats = BinderCallsStats.getInstance();
-        BinderCallsStats.CallSession callSession = binderCallsStats.callStarted(this, code);
+        // Make sure the observer won't change while processing a transaction.
+        final BinderInternal.Observer observer = sObserver;
+        final CallSession callSession =
+                observer != null ? observer.callStarted(this, code) : null;
         Parcel data = Parcel.obtain(dataObj);
         Parcel reply = Parcel.obtain(replyObj);
         // theoretically, we should call transact, which will call onTransact,
@@ -738,7 +755,9 @@
             }
             res = onTransact(code, data, reply, flags);
         } catch (RemoteException|RuntimeException e) {
-            binderCallsStats.callThrewException(callSession, e);
+            if (observer != null) {
+                observer.callThrewException(callSession, e);
+            }
             if (LOG_RUNTIME_EXCEPTION) {
                 Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
             }
@@ -770,9 +789,10 @@
         // to the main transaction loop to wait for another incoming transaction.  Either
         // way, strict mode begone!
         StrictMode.clearGatheredViolations();
-        binderCallsStats.callEnded(callSession, requestSizeBytes, replySizeBytes);
+        if (observer != null) {
+            observer.callEnded(callSession, requestSizeBytes, replySizeBytes);
+        }
 
         return res;
     }
 }
-
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 63c7dd0..f3195a3 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
@@ -47,7 +48,7 @@
  * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
  * per thread, uid or call description.
  */
-public class BinderCallsStats {
+public class BinderCallsStats implements BinderInternal.Observer {
     public static final boolean ENABLED_DEFAULT = true;
     public static final boolean DETAILED_TRACKING_DEFAULT = true;
     public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
@@ -57,12 +58,9 @@
     private static final int PERIODIC_SAMPLING_INTERVAL = 10;
     private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
     private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
-    private static final CallSession NOT_ENABLED = new CallSession();
-    private static final BinderCallsStats sInstance = new BinderCallsStats(new Random());
 
-    private volatile boolean mEnabled = ENABLED_DEFAULT;
-    private volatile boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
-    private volatile int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
+    private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
+    private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
     @GuardedBy("mLock")
     private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
     @GuardedBy("mLock")
@@ -72,28 +70,24 @@
     private final Random mRandom;
     private long mStartTime = System.currentTimeMillis();
 
-    @VisibleForTesting  // Use getInstance() instead.
     public BinderCallsStats(Random random) {
         this.mRandom = random;
     }
 
+    @Override
     public CallSession callStarted(Binder binder, int code) {
         return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
     }
 
     private CallSession callStarted(String className, int code, @Nullable String methodName) {
-        if (!mEnabled) {
-            return NOT_ENABLED;
-        }
-
         CallSession s = mCallSessionsPool.poll();
         if (s == null) {
             s = new CallSession();
         }
 
-        s.callStat.className = className;
-        s.callStat.msg = code;
-        s.callStat.methodName = methodName;
+        s.className = className;
+        s.transactionCode = code;
+        s.methodName = methodName;
         s.exceptionThrown = false;
         s.cpuTimeStarted = -1;
         s.timeStarted = -1;
@@ -109,9 +103,9 @@
         return s;
     }
 
-    public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
-        Preconditions.checkNotNull(s);
-        if (s == NOT_ENABLED) {
+    @Override
+    public void callEnded(@Nullable CallSession s, int parcelRequestSize, int parcelReplySize) {
+        if (s == null) {
             return;
         }
 
@@ -124,10 +118,6 @@
 
     private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
         synchronized (mLock) {
-            if (!mEnabled) {
-                return;
-            }
-
             final int callingUid = getCallingUid();
             UidEntry uidEntry = mUidEntries.get(callingUid);
             if (uidEntry == null) {
@@ -135,7 +125,7 @@
                 mUidEntries.put(callingUid, uidEntry);
             }
             uidEntry.callCount++;
-            CallStat callStat = uidEntry.getOrCreate(s.callStat);
+            CallStat callStat = uidEntry.getOrCreate(s.className, s.transactionCode);
             callStat.callCount++;
 
             // Non-negative time signals we need to record data for this call.
@@ -147,7 +137,7 @@
                 uidEntry.recordedCallCount++;
 
                 callStat.recordedCallCount++;
-                callStat.methodName = s.callStat.methodName;
+                callStat.methodName = s.methodName;
                 callStat.cpuTimeMicros += duration;
                 callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
                 callStat.latencyMicros += latencyDuration;
@@ -164,16 +154,9 @@
         }
     }
 
-    /**
-     * Called if an exception is thrown while executing the binder transaction.
-     *
-     * <li>BinderCallsStats#callEnded will be called afterwards.
-     * <li>Do not throw an exception in this method, it will swallow the original exception thrown
-     * by the binder transaction.
-     */
-    public void callThrewException(CallSession s, Exception exception) {
-        Preconditions.checkNotNull(s);
-        if (!mEnabled) {
+    @Override
+    public void callThrewException(@Nullable CallSession s, Exception exception) {
+        if (s == null) {
             return;
         }
         s.exceptionThrown = true;
@@ -208,7 +191,7 @@
                     exported.uid = entry.uid;
                     exported.className = stat.className;
                     exported.methodName = stat.methodName == null
-                            ? String.valueOf(stat.msg) : stat.methodName;
+                            ? String.valueOf(stat.transactionCode) : stat.methodName;
                     exported.cpuTimeMicros = stat.cpuTimeMicros;
                     exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                     exported.latencyMicros = stat.latencyMicros;
@@ -233,11 +216,6 @@
     }
 
     private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
-        if (!mEnabled) {
-            pw.println("Binder calls stats disabled.");
-            return;
-        }
-
         long totalCallsCount = 0;
         long totalRecordedCallsCount = 0;
         long totalCpuTime = 0;
@@ -340,10 +318,6 @@
         return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
     }
 
-    public static BinderCallsStats getInstance() {
-        return sInstance;
-    }
-
     public void setDetailedTracking(boolean enabled) {
         synchronized (mLock) {
             if (enabled != mDetailedTracking) {
@@ -353,15 +327,6 @@
         }
     }
 
-    public void setEnabled(boolean enabled) {
-        synchronized (mLock) {
-            if (enabled != mEnabled) {
-                mEnabled = enabled;
-                reset();
-            }
-        }
-    }
-
     public void setSamplingInterval(int samplingInterval) {
         synchronized (mLock) {
             if (samplingInterval != mPeriodicSamplingInterval) {
@@ -400,7 +365,7 @@
     @VisibleForTesting
     public static class CallStat {
         public String className;
-        public int msg;
+        public int transactionCode;
         // Method name might be null when we cannot resolve the transaction code. For instance, if
         // the binder was not generated by AIDL.
         public @Nullable String methodName;
@@ -427,40 +392,41 @@
         CallStat() {
         }
 
-        CallStat(String className, int msg) {
+        CallStat(String className, int transactionCode) {
             this.className = className;
-            this.msg = msg;
+            this.transactionCode = transactionCode;
         }
 
         @Override
+        public String toString() {
+            return className + "#" + (methodName == null ? transactionCode : methodName);
+        }
+    }
+
+    /** Key used to store CallStat object in a Map. */
+    public static class CallStatKey {
+        public String className;
+        public int transactionCode;
+
+        @Override
         public boolean equals(Object o) {
             if (this == o) {
                 return true;
             }
 
-            CallStat callStat = (CallStat) o;
-            return msg == callStat.msg && (className.equals(callStat.className));
+            CallStatKey key = (CallStatKey) o;
+            return transactionCode == key.transactionCode
+                    && (className.equals(key.className));
         }
 
         @Override
         public int hashCode() {
             int result = className.hashCode();
-            result = 31 * result + msg;
+            result = 31 * result + transactionCode;
             return result;
         }
-
-        @Override
-        public String toString() {
-            return className + "#" + (methodName == null ? msg : methodName);
-        }
     }
 
-    public static class CallSession {
-        long cpuTimeStarted;
-        long timeStarted;
-        boolean exceptionThrown;
-        final CallStat callStat = new CallStat();
-    }
 
     @VisibleForTesting
     public static class UidEntry {
@@ -480,14 +446,21 @@
         }
 
         // Aggregate time spent per each call name: call_desc -> cpu_time_micros
-        Map<CallStat, CallStat> mCallStats = new ArrayMap<>();
+        private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>();
+        private CallStatKey mTempKey = new CallStatKey();
 
-        CallStat getOrCreate(CallStat callStat) {
-            CallStat mapCallStat = mCallStats.get(callStat);
+        CallStat getOrCreate(String className, int transactionCode) {
+            // Use a global temporary key to avoid creating new objects for every lookup.
+            mTempKey.className = className;
+            mTempKey.transactionCode = transactionCode;
+            CallStat mapCallStat = mCallStats.get(mTempKey);
             // Only create CallStat if it's a new entry, otherwise update existing instance
             if (mapCallStat == null) {
-                mapCallStat = new CallStat(callStat.className, callStat.msg);
-                mCallStats.put(mapCallStat, mapCallStat);
+                mapCallStat = new CallStat(className, transactionCode);
+                CallStatKey key = new CallStatKey();
+                key.className = className;
+                key.transactionCode = transactionCode;
+                mCallStats.put(key, mapCallStat);
             }
             return mapCallStat;
         }
@@ -496,7 +469,7 @@
          * Returns list of calls sorted by CPU time
          */
         public List<CallStat> getCallStatsList() {
-            List<CallStat> callStats = new ArrayList<>(mCallStats.keySet());
+            List<CallStat> callStats = new ArrayList<>(mCallStats.values());
             callStats.sort((o1, o2) -> {
                 if (o1.cpuTimeMicros < o2.cpuTimeMicros) {
                     return 1;
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 5bddd2f..4b93c86 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -17,6 +17,7 @@
 package com.android.internal.os;
 
 import android.annotation.NonNull;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemClock;
@@ -33,7 +34,7 @@
 
 /**
  * Private and debugging Binder APIs.
- * 
+ *
  * @see IBinder
  */
 public class BinderInternal {
@@ -70,18 +71,64 @@
     }
 
     /**
+     * A session used by {@link Observer} in order to keep track of some data.
+     */
+    public static class CallSession {
+        // Binder interface descriptor.
+        public String className;
+        // Binder transaction code.
+        public int transactionCode;
+        // Binder transaction method name.
+        public String methodName;
+        // CPU time at the beginning of the call.
+        long cpuTimeStarted;
+        // System time at the beginning of the call.
+        long timeStarted;
+        // Should be set to one when an exception is thrown.
+        boolean exceptionThrown;
+    }
+
+    /**
+     * Allows to track various steps of an API call.
+     */
+    public interface Observer {
+        /**
+         * Called when a binder call starts.
+         *
+         * @return a CallSession to pass to the callEnded method.
+         */
+        CallSession callStarted(Binder binder, int code);
+
+        /**
+         * Called when a binder call stops.
+         *
+         * <li>This method will be called even when an exception is thrown.
+         */
+        void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize);
+
+        /**
+         * Called if an exception is thrown while executing the binder transaction.
+         *
+         * <li>BinderCallsStats#callEnded will be called afterwards.
+         * <li>Do not throw an exception in this method, it will swallow the original exception
+         * thrown by the binder transaction.
+         */
+        public void callThrewException(CallSession s, Exception exception);
+    }
+
+    /**
      * Add the calling thread to the IPC thread pool.  This function does
      * not return until the current process is exiting.
      */
     public static final native void joinThreadPool();
-    
+
     /**
      * Return the system time (as reported by {@link SystemClock#uptimeMillis
      * SystemClock.uptimeMillis()}) that the last garbage collection occurred
      * in this process.  This is not for general application use, and the
      * meaning of "when a garbage collection occurred" will change as the
      * garbage collector evolves.
-     * 
+     *
      * @return Returns the time as per {@link SystemClock#uptimeMillis
      * SystemClock.uptimeMillis()} of the last garbage collection.
      */
@@ -95,7 +142,7 @@
      * other services.
      */
     public static final native IBinder getContextObject();
-    
+
     /**
      * Special for system process to not allow incoming calls to run at
      * background scheduling priority.
@@ -104,14 +151,14 @@
     public static final native void disableBackgroundScheduling(boolean disable);
 
     public static final native void setMaxThreads(int numThreads);
-    
+
     static native final void handleGc();
-    
+
     public static void forceGc(String reason) {
         EventLog.writeEvent(2741, reason);
         VMRuntime.getRuntime().requestConcurrentGC();
     }
-    
+
     static void forceBinderGc() {
         forceGc("Binder");
     }
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 07e2af8..20dc872 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -23,6 +23,8 @@
 import android.util.ArrayMap;
 import android.util.SparseArray;
 
+import com.android.internal.os.BinderInternal.CallSession;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,7 +54,7 @@
         bcs.setSamplingInterval(5);
 
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -65,7 +67,7 @@
         assertEquals(1, uidEntry.recordedCallCount);
         assertEquals(10, uidEntry.cpuTimeMicros);
         assertEquals(binder.getClass().getName(), callStatsList.get(0).className);
-        assertEquals(1, callStatsList.get(0).msg);
+        assertEquals(1, callStatsList.get(0).transactionCode);
 
         // CPU usage is sampled, should not be tracked here.
         callSession = bcs.callStarted(binder, 1);
@@ -93,7 +95,7 @@
         bcs.setDetailedTracking(true);
 
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -109,7 +111,7 @@
         assertEquals(1, callStatsList.get(0).callCount);
         assertEquals(10, callStatsList.get(0).cpuTimeMicros);
         assertEquals(binder.getClass().getName(), callStatsList.get(0).className);
-        assertEquals(1, callStatsList.get(0).msg);
+        assertEquals(1, callStatsList.get(0).transactionCode);
 
         callSession = bcs.callStarted(binder, 1);
         bcs.time += 20;
@@ -134,44 +136,21 @@
     }
 
     @Test
-    public void testDisabled() {
-        TestBinderCallsStats bcs = new TestBinderCallsStats();
-        bcs.setEnabled(false);
-
-        Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.time += 10;
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
-
-        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
-        assertEquals(0, uidEntries.size());
-    }
-
-    @Test
-    public void testDisableInBetweenCall() {
-        TestBinderCallsStats bcs = new TestBinderCallsStats();
-        bcs.setEnabled(true);
-
-        Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.time += 10;
-        bcs.setEnabled(false);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
-
-        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
-        assertEquals(0, uidEntries.size());
-    }
-
-    @Test
     public void testEnableInBetweenCall() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
-        bcs.setEnabled(false);
-
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
-        bcs.time += 10;
-        bcs.setEnabled(true);
-        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE);
+
+        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+        assertEquals(0, uidEntries.size());
+    }
+
+    @Test
+    public void testInBetweenCallWhenExceptionThrown() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        Binder binder = new Binder();
+        bcs.callThrewException(null, new IllegalStateException());
+        bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE);
 
         SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
         assertEquals(0, uidEntries.size());
@@ -184,7 +163,7 @@
         bcs.setSamplingInterval(2);
 
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -221,7 +200,7 @@
         bcs.setSamplingInterval(2);
 
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -261,16 +240,16 @@
         Binder binder = new Binder() {
             @Override
             public String getTransactionName(int code) {
-              return "resolved";
+                return "resolved";
             }
         };
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
         List<BinderCallsStats.CallStat> callStatsList =
                 bcs.getUidEntries().get(TEST_UID).getCallStatsList();
-        assertEquals(1, callStatsList.get(0).msg);
+        assertEquals(1, callStatsList.get(0).transactionCode);
         assertEquals("resolved", callStatsList.get(0).methodName);
     }
 
@@ -279,7 +258,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -295,7 +274,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 50;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -314,7 +293,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.elapsedTime += 5;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -341,7 +320,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.callThrewException(callSession, new IllegalStateException());
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -364,7 +343,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.callThrewException(callSession, new IllegalStateException());
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
@@ -377,7 +356,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(false);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
         assertEquals(0, bcs.getExportedCallStats().size());
@@ -388,7 +367,7 @@
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
         Binder binder = new Binder();
-        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        CallSession callSession = bcs.callStarted(binder, 1);
         bcs.time += 10;
         bcs.elapsedTime += 20;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -415,14 +394,14 @@
         long elapsedTime = 0;
 
         TestBinderCallsStats() {
-          // Make random generator not random.
-          super(new Random() {
-            int mCallCount = 0;
+            // Make random generator not random.
+            super(new Random() {
+                int mCallCount = 0;
 
-            public int nextInt() {
-              return mCallCount++;
-            }
-          });
+                public int nextInt() {
+                    return mCallCount++;
+                }
+            });
         }
 
         @Override
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 3d779d8..b0ad60b 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -32,12 +32,16 @@
 
 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;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 
 public class BinderCallsStatsService extends Binder {
 
@@ -53,15 +57,18 @@
         private static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data";
         private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
 
+        private boolean mEnabled;
         private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
         private final Context mContext;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
+        private final BinderCallsStats mBinderCallsStats;
 
-        public SettingsObserver(Context context) {
+        public SettingsObserver(Context context, BinderCallsStats binderCallsStats) {
             super(BackgroundThread.getHandler());
             mContext = context;
             context.getContentResolver().registerContentObserver(mUri, false, this,
                     UserHandle.USER_SYSTEM);
+            mBinderCallsStats = binderCallsStats;
             // Always kick once to ensure that we match current state
             onChange();
         }
@@ -79,20 +86,41 @@
               return;
             }
 
-            BinderCallsStats stats = BinderCallsStats.getInstance();
             try {
                     mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
                             Settings.Global.BINDER_CALLS_STATS));
             } catch (IllegalArgumentException e) {
                     Slog.e(TAG, "Bad binder call stats settings", e);
             }
-            stats.setEnabled(
-                    mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT));
-            stats.setDetailedTracking(mParser.getBoolean(
+            mBinderCallsStats.setDetailedTracking(mParser.getBoolean(
                     SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT));
-            stats.setSamplingInterval(mParser.getInt(
+            mBinderCallsStats.setSamplingInterval(mParser.getInt(
                     SETTINGS_SAMPLING_INTERVAL_KEY,
                     BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
+
+
+            final boolean enabled =
+                    mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
+            if (mEnabled != enabled) {
+                Binder.setObserver(enabled ? mBinderCallsStats : null);
+                mEnabled = enabled;
+                mBinderCallsStats.reset();
+            }
+        }
+    }
+
+    /**
+     * @hide Only for use within the system server.
+     */
+    public static class Internal {
+        private final BinderCallsStats mBinderCallsStats;
+
+        Internal(BinderCallsStats binderCallsStats) {
+            this.mBinderCallsStats = binderCallsStats;
+        }
+
+        public ArrayList<BinderCallsStats.ExportedCallStat> getExportedCallStats() {
+            return mBinderCallsStats.getExportedCallStats();
         }
     }
 
@@ -105,7 +133,9 @@
 
         @Override
         public void onStart() {
-            mService = new BinderCallsStatsService();
+            BinderCallsStats binderCallsStats = new BinderCallsStats(new Random());
+            mService = new BinderCallsStatsService(binderCallsStats);
+            LocalServices.addService(Internal.class, new Internal(binderCallsStats));
             publishBinderService("binder_calls_stats", mService);
             boolean detailedTrackingEnabled = SystemProperties.getBoolean(
                     PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
@@ -114,7 +144,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.getInstance().setDetailedTracking(true);
+                binderCallsStats.setDetailedTracking(true);
             }
         }
 
@@ -127,14 +157,19 @@
     }
 
     private SettingsObserver mSettingsObserver;
+    private final BinderCallsStats mBinderCallsStats;
 
-    public void systemReady(Context context) {
-        mSettingsObserver = new SettingsObserver(context);
+    BinderCallsStatsService(BinderCallsStats binderCallsStats) {
+        mBinderCallsStats = binderCallsStats;
     }
 
-    public static void reset() {
+    public void systemReady(Context context) {
+        mSettingsObserver = new SettingsObserver(context, mBinderCallsStats);
+    }
+
+    public void reset() {
         Slog.i(TAG, "Resetting stats");
-        BinderCallsStats.getInstance().reset();
+        mBinderCallsStats.reset();
     }
 
     @Override
@@ -150,12 +185,12 @@
                     return;
                 } else if ("--enable-detailed-tracking".equals(arg)) {
                     SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
-                    BinderCallsStats.getInstance().setDetailedTracking(true);
+                    mBinderCallsStats.setDetailedTracking(true);
                     pw.println("Detailed tracking enabled");
                     return;
                 } else if ("--disable-detailed-tracking".equals(arg)) {
                     SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
-                    BinderCallsStats.getInstance().setDetailedTracking(false);
+                    mBinderCallsStats.setDetailedTracking(false);
                     pw.println("Detailed tracking disabled");
                     return;
                 } else if ("-h".equals(arg)) {
@@ -169,7 +204,7 @@
                 }
             }
         }
-        BinderCallsStats.getInstance().dump(pw, getAppIdToPackagesMap(), verbose);
+        mBinderCallsStats.dump(pw, getAppIdToPackagesMap(), verbose);
     }
 
     private Map<Integer, String> getAppIdToPackagesMap() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5a9b046..58eb5c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -345,7 +345,6 @@
 import com.android.server.AlarmManagerInternal;
 import com.android.server.AppOpsService;
 import com.android.server.AttributeCache;
-import com.android.server.BinderCallsStatsService;
 import com.android.server.DeviceIdleController;
 import com.android.server.IntentResolver;
 import com.android.server.IoThread;
@@ -2924,7 +2923,6 @@
 
     @Override
     public void batteryStatsReset() {
-        BinderCallsStatsService.reset();
         mOomAdjProfiler.reset();
     }
 
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 2175772..eb962d2 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -63,7 +63,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.NetworkStatsFactory;
-import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelUidCpuTimeReader;
@@ -74,6 +73,7 @@
 import com.android.internal.os.KernelWakelockStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -895,7 +895,9 @@
     }
 
     private void pullBinderCallsStats(int tagId, List<StatsLogEventWrapper> pulledData) {
-        List<ExportedCallStat> callStats = BinderCallsStats.getInstance().getExportedCallStats();
+        BinderCallsStatsService.Internal binderStats =
+                LocalServices.getService(BinderCallsStatsService.Internal.class);
+        List<ExportedCallStat> callStats = binderStats.getExportedCallStats();
         long elapsedNanos = SystemClock.elapsedRealtimeNanos();
         for (ExportedCallStat callStat : callStats) {
             StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 11 /* fields */);