Adds client API for interacting with statsd.

This API will primarily be used by GmsCore to send updated configs.
Also, sending a config will implicitly notify the StatsD that this
client wants to know when it should request data for this config.

We send a broadcast so that all interested subscribers can know if
data needs to be pulled.

Test: Manually tested that sending broadcast works via new adb
command added in StatsService.

Change-Id: I23cdd1df706036e14b32c3d01af30c3d4af819fa
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d813d66..e48946f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -137,6 +137,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
+import android.util.StatsManager;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
@@ -452,6 +453,13 @@
                   ctx.mMainThread.getHandler().getLooper());
             }});
 
+        registerService(Context.STATS_MANAGER, StatsManager.class,
+                new StaticServiceFetcher<StatsManager>() {
+                    @Override
+                    public StatsManager createService() throws ServiceNotFoundException {
+                        return new StatsManager();
+                    }});
+
         registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class,
                 new CachedServiceFetcher<StatusBarManager>() {
             @Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 72f75112..19e24ad 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4044,6 +4044,13 @@
     public static final String STATS_COMPANION_SERVICE = "statscompanion";
 
     /**
+     * Use with {@link #getSystemService} to retrieve an {@link android.stats.StatsManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String STATS_MANAGER = "stats";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.content.om.OverlayManager} for managing overlay packages.
      *
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index c0a95cc..3314f60 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -53,4 +53,7 @@
 
     /** Pull the specified data. Results will be sent to statsd when complete. */
     StatsLogEventWrapper[] pullData(int pullCode);
+
+    /** Send a broadcast to the specified pkg and class that it should getData now. */
+    oneway void sendBroadcast(String pkg, String cls);
 }
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 480296c..a6c3009 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -65,13 +65,26 @@
     oneway void informOnePackageRemoved(in String app, in int uid);
 
     /**
-     * Trigger pushLog to force push stats log entries from statsd on client side.
+     * Fetches data for the specified configuration key. Returns a byte array representing proto
+     * wire-encoded of ConfigMetricsReport.
      */
-    void requestPush();
+    byte[] getData(in String key);
 
     /**
-     * Listen to statsd to send stats log entries.
-     * TODO: Limit callbacks with specific configurations.
+     * Sets a configuration with the specified config key and subscribes to updates for this
+     * configuration key. Broadcasts will be sent if this configuration needs to be collected.
+     * The configuration must be a wire-encoded StatsDConfig. The caller specifies the name of the
+     * package and class that should receive these broadcasts.
+     *
+     * Returns if this configuration was correctly registered.
      */
-    void subscribeStatsLog(IStatsCallbacks callbacks);
+    boolean addConfiguration(in String configKey, in byte[] config, in String pkg, in String cls);
+
+    /**
+     * Removes the configuration with the matching config key. No-op if this config key does not
+     * exist.
+     *
+     * Returns if this configuration key was removed.
+     */
+    boolean removeConfiguration(in String configKey);
 }
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
new file mode 100644
index 0000000..55b33a6
--- /dev/null
+++ b/core/java/android/util/StatsManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017 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 android.util;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * API for StatsD clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager {
+    IStatsManager mService;
+    private static final String TAG = "StatsManager";
+
+    /**
+     * Constructor for StatsManagerClient.
+     *
+     * @hide
+     */
+    public StatsManager() {
+    }
+
+    /**
+     * Clients can send a configuration and simultaneously registers the name of a broadcast
+     * receiver that listens for when it should request data.
+     *
+     * @param configKey An arbitrary string that allows clients to track the configuration.
+     * @param config    Wire-encoded StatsDConfig proto that specifies metrics (and all
+     *                  dependencies eg, conditions and matchers).
+     * @param pkg       The package name to receive the broadcast.
+     * @param cls       The name of the class that receives the broadcast.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.addConfiguration(configKey, config, pkg, cls);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove a configuration from logging.
+     *
+     * @param configKey Configuration key to remove.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean removeConfiguration(String configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.removeConfiguration(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Clients can request data with a binder call.
+     *
+     * @param configKey Configuration key to retrieve data from.
+     * @return Serialized ConfigMetricsReport proto. Returns null on failure.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getData(String configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.getData(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connecto statsd when getting data");
+                return null;
+            }
+        }
+    }
+
+    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            synchronized (this) {
+                mService = null;
+            }
+        }
+    }
+
+    private IStatsManager getIStatsManagerLocked() throws RemoteException {
+        if (mService != null) {
+            return mService;
+        }
+        mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        if (mService != null) {
+            mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+        }
+        return mService;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7af1b46..f257d5e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -307,6 +307,8 @@
     <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
     <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
 
+    <protected-broadcast android:name="com.android.server.stats.action.TRIGGER_COLLECTION" />
+
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />