Set up StatsCompanionService.java

Introduces StatsCompanionService.java and sets it up as a system service.

This service is a helper for statsd. It will be responsible for setting
and cancelling alarms related to polling stats and anomaly detection.

It currently does not have selinux permission, so must be started
manually (or with selinux temporarily disabled).

Test: disable enforcing selinux, then set and cancel alarms and make
sure they work.

Change-Id: I3bd73acdd998ee424696cce40965134c14220d8f
diff --git a/Android.mk b/Android.mk
index 76ca9b9..8e09267 100644
--- a/Android.mk
+++ b/Android.mk
@@ -270,6 +270,7 @@
 	core/java/android/os/IRecoverySystemProgressListener.aidl \
 	core/java/android/os/IRemoteCallback.aidl \
 	core/java/android/os/ISchedulingPolicyService.aidl \
+	core/java/android/os/IStatsCompanionService.aidl \
 	core/java/android/os/IStatsManager.aidl \
 	core/java/android/os/IThermalEventListener.aidl \
 	core/java/android/os/IThermalService.aidl \
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2d8249a..03e4dfe 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2991,6 +2991,7 @@
             //@hide: CONTEXTHUB_SERVICE,
             SYSTEM_HEALTH_SERVICE,
             //@hide: INCIDENT_SERVICE,
+            //@hide: STATS_COMPANION_SERVICE,
             COMPANION_DEVICE_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -4020,6 +4021,12 @@
     public static final String INCIDENT_SERVICE = "incident";
 
     /**
+     * Service to assist statsd in obtaining general stats.
+     * @hide
+     */
+    public static final String STATS_COMPANION_SERVICE = "statscompanion";
+
+    /**
      * 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
new file mode 100644
index 0000000..a29e375
--- /dev/null
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 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.os;
+
+/**
+  * Binder interface to communicate with the Java-based statistics service helper.
+  * {@hide}
+  */
+interface IStatsCompanionService {
+    /**
+    * Register an alarm for anomaly detection to fire at the given timestamp (ms since epoch).
+    * If anomaly alarm had already been registered, it will be replaced with the new timestamp.
+    * Uses AlarmManager.set API, so  if the timestamp is in the past, alarm fires immediately, and
+    * alarm is inexact.
+    */
+    void setAnomalyAlarm(long timestampMs);
+    /** Cancel any anomaly detection alarm. */
+    void cancelAnomalyAlarm();
+
+    /**
+      * Register a repeating alarm for polling to fire at the given timestamp and every
+      * intervalMs thereafter (in ms since epoch).
+      * If polling alarm had already been registered, it will be replaced by new one.
+      * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
+      * and alarm is inexact.
+      */
+    void setPollingAlarms(long timestampMs, long intervalMs);
+    /** Cancel any repeating polling alarm. */
+    void cancelPollingAlarms();
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d6f67f6..6ac352e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3074,6 +3074,12 @@
     <permission android:name="android.permission.BATTERY_STATS"
         android:protectionLevel="signature|privileged|development" />
 
+    <!--Allows an application to manage statscompanion.
+    <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.STATSCOMPANION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control the backup and restore process.
     <p>Not for use by third-party applications.
          @hide pending API council -->
@@ -3854,6 +3860,16 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.stats.StatsCompanionService$AnomalyAlarmReceiver"
+                  android:permission="android.permission.STATSCOMPANION"
+                  android:exported="false">
+        </receiver>
+
+        <receiver android:name="com.android.server.stats.StatsCompanionService$PollingAlarmReceiver"
+                  android:permission="android.permission.STATSCOMPANION"
+                  android:exported="false">
+        </receiver>
+
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
new file mode 100644
index 0000000..5b77b1e
--- /dev/null
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 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 com.android.server.stats;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IStatsCompanionService;
+import android.os.IStatsManager;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * Helper service for statsd (the native stats management service in cmds/statsd/).
+ * Used for registering and receiving alarms on behalf of statsd.
+ */
+public class StatsCompanionService extends IStatsCompanionService.Stub {
+    static final String TAG = "StatsCompanionService";
+    static final boolean DEBUG = true;
+
+    private final Context mContext;
+    private final AlarmManager mAlarmManager;
+    private final IStatsManager mStatsd;
+
+    private final PendingIntent mAnomalyAlarmIntent;
+    private final PendingIntent mPollingAlarmIntent;
+
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
+            // TODO: mStatsd.informAlarm(); // should be twoway so device won't sleep before acting?
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    public final static class PollingAlarmReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Slog.d(TAG, "Time to poll something.");
+            // TODO: mStatsd.poll(); // should be twoway so device won't sleep before acting?
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    public StatsCompanionService(Context context) {
+        super();
+        mContext = context;
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+        mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, AnomalyAlarmReceiver.class), 0);
+        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, PollingAlarmReceiver.class), 0);
+
+        mStatsd = getStatsdService();
+    }
+
+    /** Returns the statsd IBinder service */
+    public static IStatsManager getStatsdService() {
+        return IStatsManager.Stub.asInterface(ServiceManager.getService("statsd"));
+    }
+
+    public static final class Lifecycle extends SystemService {
+        private StatsCompanionService mStatsCompanionService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mStatsCompanionService = new StatsCompanionService(getContext());
+            try {
+                publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService);
+                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to publishBinderService", e);
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void setAnomalyAlarm(long timestampMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm.
+            mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelAnomalyAlarm() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void setPollingAlarms(long timestampMs, long intervalMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
+                + " every " + intervalMs + "ms");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
+                    mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelPollingAlarms() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    private void enforceCallingPermission() {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+    }
+
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 57271fa..d411c74 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.stats.StatsCompanionService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
@@ -1536,6 +1537,11 @@
             traceEnd();
         }
 
+        // Statsd helper
+        traceBeginAndSlog("StartStatsCompanionService");
+        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        traceEnd();
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();