ConnectivityMetricsLogger subclass for IpConnectivity

IpConnectivityEvent was using ConnectivityMetricsLogger directly for
logging events. However ConnectivityMetricsLogger keeps track in a
thread-unsafe way of skipped events rejected by MetricsLoggerService.

This patch introduces a subclass of ConnectivityMetricsLogger that
does not track skipped events, for using in IpConnectivityEvent.

It also qualifies the mServiceBlockedTimestampMillis variable as
volatile so that throttling is effective accross concurrent callers
of logEvent.

Bug: 28204408
Bug: 29023888
Change-Id: I33707ba1d07487b42f3ce9a1ad9a66d785e99fa7
diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java
index d8cdde9..7373495 100644
--- a/core/java/android/net/ConnectivityMetricsLogger.java
+++ b/core/java/android/net/ConnectivityMetricsLogger.java
@@ -33,28 +33,39 @@
 
     // Component Tags
     public static final int COMPONENT_TAG_CONNECTIVITY = 0;
-    public static final int COMPONENT_TAG_BLUETOOTH = 1;
-    public static final int COMPONENT_TAG_WIFI = 2;
-    public static final int COMPONENT_TAG_TELECOM = 3;
-    public static final int COMPONENT_TAG_TELEPHONY = 4;
-
-    public static final int NUMBER_OF_COMPONENTS = 5;
+    public static final int COMPONENT_TAG_BLUETOOTH    = 1;
+    public static final int COMPONENT_TAG_WIFI         = 2;
+    public static final int COMPONENT_TAG_TELECOM      = 3;
+    public static final int COMPONENT_TAG_TELEPHONY    = 4;
+    public static final int NUMBER_OF_COMPONENTS       = 5;
 
     // Event Tag
     public static final int TAG_SKIPPED_EVENTS = -1;
 
     public static final String DATA_KEY_EVENTS_COUNT = "count";
 
-    private IConnectivityMetricsLogger mService;
-
-    private long mServiceUnblockedTimestampMillis = 0;
-    private int mNumSkippedEvents = 0;
+    /** {@hide} */ protected final IConnectivityMetricsLogger mService;
+    /** {@hide} */ protected volatile long mServiceUnblockedTimestampMillis;
+    private int mNumSkippedEvents;
 
     public ConnectivityMetricsLogger() {
         mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService(
                 CONNECTIVITY_METRICS_LOGGER_SERVICE));
     }
 
+    /**
+     * Log a ConnectivityMetricsEvent.
+     *
+     * This method keeps track of skipped events when MetricsLoggerService throttles input events.
+     * It skips logging when MetricsLoggerService is active. When throttling ends, it logs a
+     * meta-event containing the number of events dropped. It is not safe to call this method
+     * concurrently from different threads.
+     *
+     * @param timestamp is the epoch timestamp of the event in ms.
+     * @param componentTag is the COMPONENT_* constant the event belongs to.
+     * @param eventTag is an event type constant whose meaning is specific to the component tag.
+     * @param data is a Parcelable instance representing the event.
+     */
     public void logEvent(long timestamp, int componentTag, int eventTag, Parcelable data) {
         if (mService == null) {
             if (DBG) {
@@ -104,7 +115,7 @@
                 }
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error logging event " + e.getMessage());
+            Log.e(TAG, "Error logging event", e);
         }
     }
 
@@ -121,8 +132,8 @@
     public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
         try {
             return mService.getEvents(reference);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.getEvents: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.getEvents", e);
             return null;
         }
     }
@@ -133,8 +144,8 @@
     public boolean register(PendingIntent newEventsIntent) {
         try {
             return mService.register(newEventsIntent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.register: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.register", e);
             return false;
         }
     }
@@ -142,8 +153,8 @@
     public boolean unregister(PendingIntent newEventsIntent) {
         try {
             mService.unregister(newEventsIntent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.unregister: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
             return false;
         }
 
diff --git a/core/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
index 95576c2..d3771dc 100644
--- a/core/java/android/net/metrics/IpConnectivityEvent.java
+++ b/core/java/android/net/metrics/IpConnectivityEvent.java
@@ -16,20 +16,13 @@
 
 package android.net.metrics;
 
-import android.net.ConnectivityMetricsLogger;
-import android.os.Parcel;
 import android.os.Parcelable;
 
-/**
- * {@hide}
- */
+/** {@hide} */
 public abstract class IpConnectivityEvent {
-    private static final int COMPONENT_TAG = ConnectivityMetricsLogger.COMPONENT_TAG_CONNECTIVITY;
+    private static final IpConnectivityLog sMetricsLog = new IpConnectivityLog();
 
-    private static final ConnectivityMetricsLogger sMetricsLogger = new ConnectivityMetricsLogger();
-
-    public static <T extends IpConnectivityEvent & Parcelable> void logEvent(T event) {
-        // TODO: consider using different component for DNS event.
-        sMetricsLogger.logEvent(System.currentTimeMillis(), COMPONENT_TAG, 0, event);
+    static <T extends IpConnectivityEvent & Parcelable> void logEvent(T event) {
+        sMetricsLog.log(System.currentTimeMillis(), event);
     }
-};
+}
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
new file mode 100644
index 0000000..233ff74
--- /dev/null
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -0,0 +1,70 @@
+/*
+ * 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.metrics;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.ConnectivityMetricsLogger;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Specialization of the ConnectivityMetricsLogger class for recording IP connectivity events.
+ * {@hide}
+ */
+class IpConnectivityLog extends ConnectivityMetricsLogger {
+    private static String TAG = "IpConnectivityMetricsLogger";
+    private static final boolean DBG = false;
+
+    /**
+     * Log an IpConnectivity event. Contrary to logEvent(), this method does not
+     * keep track of skipped events and is thread-safe for callers.
+     *
+     * @param timestamp is the epoch timestamp of the event in ms.
+     * @param data is a Parcelable IpConnectivityEvent instance representing the event.
+     *
+     * @return true if the event was successfully logged.
+     */
+    public <T extends IpConnectivityEvent & Parcelable> boolean log(long timestamp, T data) {
+        if (mService == null) {
+            if (DBG) {
+                Log.d(TAG, CONNECTIVITY_METRICS_LOGGER_SERVICE + " service not ready");
+            }
+            return false;
+        }
+
+        if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) {
+            if (DBG) {
+                Log.d(TAG, "skipping logging due to throttling for IpConnectivity component");
+            }
+            return false;
+        }
+
+        try {
+            final ConnectivityMetricsEvent event =
+                new ConnectivityMetricsEvent(timestamp, COMPONENT_TAG_CONNECTIVITY, 0, data);
+            final long result = mService.logEvent(event);
+            if (result >= 0) {
+                mServiceUnblockedTimestampMillis = result;
+            }
+            return (result == 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error logging event", e);
+            return false;
+        }
+    }
+}