Add Connectivity Metrics Logger service

This service can be used to pass events to metrics collection service

Change-Id: I213874300693cd121f2c8676f70893315de4c4c0
diff --git a/Android.mk b/Android.mk
index 7164778..017ee94 100644
--- a/Android.mk
+++ b/Android.mk
@@ -189,6 +189,8 @@
 	core/java/android/hardware/usb/IUsbManager.aidl \
 	core/java/android/net/ICaptivePortal.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
+	core/java/android/net/IConnectivityMetricsLogger.aidl \
+	core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl \
 	core/java/android/net/IEthernetManager.aidl \
 	core/java/android/net/IEthernetServiceListener.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index f7608b0..b4cad03 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18207,6 +18207,17 @@
     method public abstract void onNetworkActive();
   }
 
+  public class ConnectivityMetricsEvent implements android.os.Parcelable {
+    ctor public ConnectivityMetricsEvent(long, int, int, android.os.Parcelable);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.ConnectivityMetricsEvent> CREATOR;
+    field public final int componentTag;
+    field public final android.os.Parcelable data;
+    field public final int eventTag;
+    field public final long timestamp;
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
diff --git a/api/system-current.txt b/api/system-current.txt
index a03f328..3dcfcd1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19720,6 +19720,17 @@
     method public abstract void onNetworkActive();
   }
 
+  public class ConnectivityMetricsEvent implements android.os.Parcelable {
+    ctor public ConnectivityMetricsEvent(long, int, int, android.os.Parcelable);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.ConnectivityMetricsEvent> CREATOR;
+    field public final int componentTag;
+    field public final android.os.Parcelable data;
+    field public final int eventTag;
+    field public final long timestamp;
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
diff --git a/core/java/android/net/ConnectivityMetricsEvent.aidl b/core/java/android/net/ConnectivityMetricsEvent.aidl
new file mode 100644
index 0000000..da17561
--- /dev/null
+++ b/core/java/android/net/ConnectivityMetricsEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.net;
+
+parcelable ConnectivityMetricsEvent;
diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
new file mode 100644
index 0000000..d040a85
--- /dev/null
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 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.net;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+@SystemApi
+public class ConnectivityMetricsEvent implements Parcelable {
+
+    /**  The time when this event was collected, as returned by System.currentTimeMillis(). */
+    final public long timestamp;
+
+    /** The subsystem that generated the event. One of the COMPONENT_TAG_xxx constants. */
+    final public int componentTag;
+
+    /** The subsystem-specific event ID. */
+    final public int eventTag;
+
+    /** Opaque event-specific data. */
+    final public Parcelable data;
+
+    public ConnectivityMetricsEvent(long timestamp, int componentTag,
+                                    int eventTag, Parcelable data) {
+        this.timestamp = timestamp;
+        this.componentTag = componentTag;
+        this.eventTag = eventTag;
+        this.data = data;
+    }
+
+    /** Implement the Parcelable interface */
+    public static final Parcelable.Creator<ConnectivityMetricsEvent> CREATOR
+            = new Parcelable.Creator<ConnectivityMetricsEvent> (){
+        public ConnectivityMetricsEvent createFromParcel(Parcel source) {
+            final long timestamp = source.readLong();
+            final int componentTag = source.readInt();
+            final int eventTag = source.readInt();
+            final Parcelable data = source.readParcelable(null);
+            return new ConnectivityMetricsEvent(timestamp, componentTag,
+                    eventTag, data);
+        }
+
+        public ConnectivityMetricsEvent[] newArray(int size) {
+            return new ConnectivityMetricsEvent[size];
+        }
+    };
+
+    /** Implement the Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(timestamp);
+        dest.writeInt(componentTag);
+        dest.writeInt(eventTag);
+        dest.writeParcelable(data, 0);
+    }
+
+    public String toString() {
+        return String.format("ConnectivityMetricsEvent(%d, %d, %d)", timestamp,
+                componentTag, eventTag);
+    }
+}
diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java
new file mode 100644
index 0000000..3ef8050
--- /dev/null
+++ b/core/java/android/net/ConnectivityMetricsLogger.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.net;
+
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/** {@hide} */
+public class ConnectivityMetricsLogger {
+    private static String TAG = "ConnectivityMetricsLogger";
+    private static final boolean DBG = true;
+
+    public static final String CONNECTIVITY_METRICS_LOGGER_SERVICE = "connectivity_metrics_logger";
+
+    // Component Tags
+    public static final int COMPONENT_TAG_CONNECTIVITY = 1;
+    public static final int COMPONENT_TAG_BLUETOOTH = 2;
+    public static final int COMPONENT_TAG_WIFI = 3;
+    public static final int COMPONENT_TAG_TELECOM = 4;
+    public static final int COMPONENT_TAG_TELEPHONY = 5;
+
+    private IConnectivityMetricsLogger mService;
+
+    public ConnectivityMetricsLogger() {
+        mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService(
+                CONNECTIVITY_METRICS_LOGGER_SERVICE));
+    }
+
+    public void logEvent(long timestamp, int componentTag, int eventTag, Parcelable data) {
+        if (mService == null) {
+            if (DBG) {
+                Log.d(TAG, "logEvent(" + componentTag + "," + eventTag + ") Service not ready");
+            }
+        } else {
+            try {
+                mService.logEvent(new ConnectivityMetricsEvent(timestamp, componentTag, eventTag, data));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error logging event " + e.getMessage());
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/IConnectivityMetricsLogger.aidl b/core/java/android/net/IConnectivityMetricsLogger.aidl
new file mode 100644
index 0000000..2778671
--- /dev/null
+++ b/core/java/android/net/IConnectivityMetricsLogger.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.net;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IConnectivityMetricsLoggerSubscriber;
+
+/** {@hide} */
+interface IConnectivityMetricsLogger {
+
+    void logEvent(in ConnectivityMetricsEvent event);
+    void logEvents(in ConnectivityMetricsEvent[] events);
+
+    boolean subscribe(in IConnectivityMetricsLoggerSubscriber subscriber);
+    void unsubscribe(in IConnectivityMetricsLoggerSubscriber subscriber);
+}
diff --git a/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl b/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl
new file mode 100644
index 0000000..a2c62cd
--- /dev/null
+++ b/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.net;
+
+import android.net.ConnectivityMetricsEvent;
+
+/** {@hide} */
+oneway interface IConnectivityMetricsLoggerSubscriber {
+
+    void onEvents(in ConnectivityMetricsEvent[] events);
+}
diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
new file mode 100644
index 0000000..f6dc9b9
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.connectivity;
+
+import com.android.server.SystemService;
+
+import android.content.Context;
+import android.net.ConnectivityMetricsEvent;
+import android.net.ConnectivityMetricsLogger;
+import android.net.IConnectivityMetricsLogger;
+import android.net.IConnectivityMetricsLoggerSubscriber;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** {@hide} */
+public class MetricsLoggerService extends SystemService {
+    private static String TAG = "ConnectivityMetricsLoggerService";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    public MetricsLoggerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
+            publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE,
+                    mBinder);
+        }
+    }
+
+    private final int MAX_NUMBER_OF_EVENTS = 100;
+    private final int MAX_TIME_OFFSET = 15*60*1000; // 15 minutes
+    private final List<ConnectivityMetricsEvent> mEvents = new ArrayList<>();
+    private long mLastSentEventTimeMillis = System.currentTimeMillis();
+
+    private final void enforceConnectivityInternalPermission() {
+        getContext().enforceCallingPermission(
+                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                "MetricsLoggerService");
+    }
+
+    /**
+     * Implementation of the IConnectivityMetricsLogger interface.
+     */
+    private final IConnectivityMetricsLogger.Stub mBinder = new IConnectivityMetricsLogger.Stub() {
+
+        private final ArrayMap<IConnectivityMetricsLoggerSubscriber,
+                IBinder.DeathRecipient> mSubscribers = new ArrayMap<>();
+
+
+        private ConnectivityMetricsEvent[] prepareEventsToSendIfReady() {
+            ConnectivityMetricsEvent[] eventsToSend = null;
+            final long currentTimeMillis = System.currentTimeMillis();
+            final long timeOffset = currentTimeMillis - mLastSentEventTimeMillis;
+            if (timeOffset >= MAX_TIME_OFFSET
+                    || timeOffset < 0 // system time has changed
+                    || mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
+                // batch events
+                mLastSentEventTimeMillis = currentTimeMillis;
+                eventsToSend = new ConnectivityMetricsEvent[mEvents.size()];
+                mEvents.toArray(eventsToSend);
+                mEvents.clear();
+            }
+            return eventsToSend;
+        }
+
+        private void maybeSendEventsToSubscribers(ConnectivityMetricsEvent[] eventsToSend) {
+            if (eventsToSend == null || eventsToSend.length == 0) return;
+            synchronized (mSubscribers) {
+                for (IConnectivityMetricsLoggerSubscriber s : mSubscribers.keySet()) {
+                    try {
+                        s.onEvents(eventsToSend);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "RemoteException " + ex);
+                    }
+                }
+            }
+        }
+
+        public void logEvent(ConnectivityMetricsEvent event) {
+            ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
+            logEvents(events);
+        }
+
+        public void logEvents(ConnectivityMetricsEvent[] events) {
+            enforceConnectivityInternalPermission();
+            ConnectivityMetricsEvent[] eventsToSend;
+
+            if (VDBG) {
+                for (ConnectivityMetricsEvent e : events) {
+                    Log.v(TAG, "writeEvent(" + e.toString() + ")");
+                }
+            }
+
+            synchronized (mEvents) {
+                for (ConnectivityMetricsEvent e : events) {
+                    mEvents.add(e);
+                }
+
+                eventsToSend = prepareEventsToSendIfReady();
+            }
+
+            maybeSendEventsToSubscribers(eventsToSend);
+        }
+
+        public boolean subscribe(IConnectivityMetricsLoggerSubscriber subscriber) {
+            enforceConnectivityInternalPermission();
+            if (VDBG) Log.v(TAG, "subscribe");
+
+            synchronized (mSubscribers) {
+                if (mSubscribers.containsKey(subscriber)) {
+                    Log.e(TAG, "subscriber is already subscribed");
+                    return false;
+                }
+                final IConnectivityMetricsLoggerSubscriber s = subscriber;
+                IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        if (VDBG) Log.v(TAG, "subscriber died");
+                        synchronized (mSubscribers) {
+                            mSubscribers.remove(s);
+                        }
+                    }
+                };
+
+                try {
+                    subscriber.asBinder().linkToDeath(dr, 0);
+                    mSubscribers.put(subscriber, dr);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "subscribe failed: " + e);
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public void unsubscribe(IConnectivityMetricsLoggerSubscriber subscriber) {
+            enforceConnectivityInternalPermission();
+            if (VDBG) Log.v(TAG, "unsubscribe");
+            synchronized (mSubscribers) {
+                IBinder.DeathRecipient dr = mSubscribers.remove(subscriber);
+                if (dr == null) {
+                    Log.e(TAG, "subscriber is not subscribed");
+                    return;
+                }
+                subscriber.asBinder().unlinkToDeath(dr, 0);
+            }
+        }
+    };
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 81c5a1a..09a1100 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -56,6 +56,7 @@
 import com.android.server.audio.AudioService;
 import com.android.server.camera.CameraService;
 import com.android.server.clipboard.ClipboardService;
+import com.android.server.connectivity.MetricsLoggerService;
 import com.android.server.content.ContentService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
@@ -552,6 +553,10 @@
             } else {
                 mSystemServiceManager.startService(BluetoothService.class);
             }
+
+            traceBeginAndSlog("ConnectivityMetricsLoggerService");
+            mSystemServiceManager.startService(MetricsLoggerService.class);
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
         } catch (RuntimeException e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service", e);