Add support for Telecom analytics extensions

Add classes for call events and a field for them in
ParcelableCallAnalytics.
Add classes for timing information on user-visible operations.
Add the TelecomAnalytics class as the top-level object for analytics.
Add log session timing information to the TelecomAnalytics class,
independent of individual calls.

Bug: 28623064
Change-Id: Ifee5fdf6b6b341869ff0ff26c4388d357e3d9922
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index e7c9672..0ee9bab 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -20,11 +20,168 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @hide
  */
 @SystemApi
 public class ParcelableCallAnalytics implements Parcelable {
+    public static final class AnalyticsEvent implements Parcelable {
+        public static final int SET_SELECT_PHONE_ACCOUNT = 0;
+        public static final int SET_ACTIVE = 1;
+        public static final int SET_DISCONNECTED = 2;
+        public static final int START_CONNECTION = 3;
+        public static final int SET_DIALING = 4;
+        public static final int BIND_CS = 5;
+        public static final int CS_BOUND = 6;
+        public static final int REQUEST_ACCEPT = 7;
+        public static final int REQUEST_REJECT = 8;
+
+        public static final int SCREENING_SENT = 100;
+        public static final int SCREENING_COMPLETED = 101;
+        public static final int DIRECT_TO_VM_INITIATED = 102;
+        public static final int DIRECT_TO_VM_FINISHED = 103;
+        public static final int BLOCK_CHECK_INITIATED = 104;
+        public static final int BLOCK_CHECK_FINISHED = 105;
+        public static final int FILTERING_INITIATED = 106;
+        public static final int FILTERING_COMPLETED = 107;
+        public static final int FILTERING_TIMED_OUT = 108;
+
+        public static final int SKIP_RINGING = 200;
+        public static final int SILENCE = 201;
+        public static final int MUTE = 202;
+        public static final int UNMUTE = 203;
+        public static final int AUDIO_ROUTE_BT = 204;
+        public static final int AUDIO_ROUTE_EARPIECE = 205;
+        public static final int AUDIO_ROUTE_HEADSET = 206;
+        public static final int AUDIO_ROUTE_SPEAKER = 207;
+
+        public static final int CONFERENCE_WITH = 300;
+        public static final int SPLIT_CONFERENCE = 301;
+        public static final int SET_PARENT = 302;
+
+        public static final int REQUEST_HOLD = 400;
+        public static final int REQUEST_UNHOLD = 401;
+        public static final int REMOTELY_HELD = 402;
+        public static final int REMOTELY_UNHELD = 403;
+        public static final int SET_HOLD = 404;
+        public static final int SWAP = 405;
+
+        public static final int REQUEST_PULL = 500;
+
+
+        public static final Parcelable.Creator<AnalyticsEvent> CREATOR =
+                new Parcelable.Creator<AnalyticsEvent> () {
+
+                    @Override
+                    public AnalyticsEvent createFromParcel(Parcel in) {
+                        return new AnalyticsEvent(in);
+                    }
+
+                    @Override
+                    public AnalyticsEvent[] newArray(int size) {
+                        return new AnalyticsEvent[size];
+                    }
+                };
+
+        private int mEventName;
+        private long mTimeSinceLastEvent;
+
+        public AnalyticsEvent(int eventName, long timestamp) {
+            mEventName = eventName;
+            mTimeSinceLastEvent = timestamp;
+        }
+
+        AnalyticsEvent(Parcel in) {
+            mEventName = in.readInt();
+            mTimeSinceLastEvent = in.readLong();
+        }
+
+        public int getEventName() {
+            return mEventName;
+        }
+
+        public long getTimeSinceLastEvent() {
+            return mTimeSinceLastEvent;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mEventName);
+            out.writeLong(mTimeSinceLastEvent);
+        }
+    }
+
+    public static final class EventTiming implements Parcelable {
+        public static final int ACCEPT_TIMING = 0;
+        public static final int REJECT_TIMING = 1;
+        public static final int DISCONNECT_TIMING = 2;
+        public static final int HOLD_TIMING = 3;
+        public static final int UNHOLD_TIMING = 4;
+        public static final int OUTGOING_TIME_TO_DIALING_TIMING = 5;
+        public static final int BIND_CS_TIMING = 6;
+        public static final int SCREENING_COMPLETED_TIMING = 7;
+        public static final int DIRECT_TO_VM_FINISHED_TIMING = 8;
+        public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
+        public static final int FILTERING_COMPLETED_TIMING = 10;
+        public static final int FILTERING_TIMED_OUT_TIMING = 11;
+
+        public static final int INVALID = 999999;
+
+        public static final Parcelable.Creator<EventTiming> CREATOR =
+                new Parcelable.Creator<EventTiming> () {
+
+                    @Override
+                    public EventTiming createFromParcel(Parcel in) {
+                        return new EventTiming(in);
+                    }
+
+                    @Override
+                    public EventTiming[] newArray(int size) {
+                        return new EventTiming[size];
+                    }
+                };
+
+        private int mName;
+        private long mTime;
+
+        public EventTiming(int name, long time) {
+            this.mName = name;
+            this.mTime = time;
+        }
+
+        private EventTiming(Parcel in) {
+            mName = in.readInt();
+            mTime = in.readLong();
+        }
+
+        public int getName() {
+            return mName;
+        }
+
+        public long getTime() {
+            return mTime;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mName);
+            out.writeLong(mTime);
+        }
+    }
+
     public static final int CALLTYPE_UNKNOWN = 0;
     public static final int CALLTYPE_INCOMING = 1;
     public static final int CALLTYPE_OUTGOING = 2;
@@ -87,10 +244,17 @@
     // Whether the call object was created from an existing connection.
     private final boolean isCreatedFromExistingConnection;
 
+    // A list of events that are associated with this call
+    private final List<AnalyticsEvent> analyticsEvents;
+
+    // A map from event-pair names to their durations.
+    private final List<EventTiming> eventTimings;
+
     public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType,
             boolean isAdditionalCall, boolean isInterrupted, int callTechnologies,
             int callTerminationCode, boolean isEmergencyCall, String connectionService,
-            boolean isCreatedFromExistingConnection) {
+            boolean isCreatedFromExistingConnection, List<AnalyticsEvent> analyticsEvents,
+            List<EventTiming> eventTimings) {
         this.startTimeMillis = startTimeMillis;
         this.callDurationMillis = callDurationMillis;
         this.callType = callType;
@@ -101,6 +265,8 @@
         this.isEmergencyCall = isEmergencyCall;
         this.connectionService = connectionService;
         this.isCreatedFromExistingConnection = isCreatedFromExistingConnection;
+        this.analyticsEvents = analyticsEvents;
+        this.eventTimings = eventTimings;
     }
 
     public ParcelableCallAnalytics(Parcel in) {
@@ -114,6 +280,10 @@
         isEmergencyCall = readByteAsBoolean(in);
         connectionService = in.readString();
         isCreatedFromExistingConnection = readByteAsBoolean(in);
+        analyticsEvents = new ArrayList<>();
+        in.readTypedList(analyticsEvents, AnalyticsEvent.CREATOR);
+        eventTimings = new ArrayList<>();
+        in.readTypedList(eventTimings, EventTiming.CREATOR);
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -127,6 +297,8 @@
         writeBooleanAsByte(out, isEmergencyCall);
         out.writeString(connectionService);
         writeBooleanAsByte(out, isCreatedFromExistingConnection);
+        out.writeTypedList(analyticsEvents);
+        out.writeTypedList(eventTimings);
     }
 
     public long getStartTimeMillis() {
@@ -169,6 +341,14 @@
         return isCreatedFromExistingConnection;
     }
 
+    public List<AnalyticsEvent> analyticsEvents() {
+        return analyticsEvents;
+    }
+
+    public List<EventTiming> getEventTimings() {
+        return eventTimings;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/telecomm/java/android/telecom/TelecomAnalytics.aidl
similarity index 94%
rename from telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
rename to telecomm/java/android/telecom/TelecomAnalytics.aidl
index b7e78d1..08ad0a2 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
+++ b/telecomm/java/android/telecom/TelecomAnalytics.aidl
@@ -19,4 +19,4 @@
 /**
  * {@hide}
  */
-parcelable ParcelableCallAnalytics;
+parcelable TelecomAnalytics;
diff --git a/telecomm/java/android/telecom/TelecomAnalytics.java b/telecomm/java/android/telecom/TelecomAnalytics.java
new file mode 100644
index 0000000..6e0d02c
--- /dev/null
+++ b/telecomm/java/android/telecom/TelecomAnalytics.java
@@ -0,0 +1,148 @@
+/*
+ * 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.telecom;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class TelecomAnalytics implements Parcelable {
+    public static final Parcelable.Creator<TelecomAnalytics> CREATOR =
+            new Parcelable.Creator<TelecomAnalytics> () {
+
+                @Override
+                public TelecomAnalytics createFromParcel(Parcel in) {
+                    return new TelecomAnalytics(in);
+                }
+
+                @Override
+                public TelecomAnalytics[] newArray(int size) {
+                    return new TelecomAnalytics[size];
+                }
+            };
+
+    public static final class SessionTiming extends TimedEvent<Integer> implements Parcelable {
+        public static final Parcelable.Creator<SessionTiming> CREATOR =
+                new Parcelable.Creator<SessionTiming> () {
+
+                    @Override
+                    public SessionTiming createFromParcel(Parcel in) {
+                        return new SessionTiming(in);
+                    }
+
+                    @Override
+                    public SessionTiming[] newArray(int size) {
+                        return new SessionTiming[size];
+                    }
+                };
+
+        public static final int ICA_ANSWER_CALL = 1;
+        public static final int ICA_REJECT_CALL = 2;
+        public static final int ICA_DISCONNECT_CALL = 3;
+        public static final int ICA_HOLD_CALL = 4;
+        public static final int ICA_UNHOLD_CALL = 5;
+        public static final int ICA_MUTE = 6;
+        public static final int ICA_SET_AUDIO_ROUTE = 7;
+        public static final int ICA_CONFERENCE = 8;
+
+        public static final int CSW_HANDLE_CREATE_CONNECTION_COMPLETE = 100;
+        public static final int CSW_SET_ACTIVE = 101;
+        public static final int CSW_SET_RINGING = 102;
+        public static final int CSW_SET_DIALING = 103;
+        public static final int CSW_SET_DISCONNECTED = 104;
+        public static final int CSW_SET_ON_HOLD = 105;
+        public static final int CSW_REMOVE_CALL = 106;
+        public static final int CSW_SET_IS_CONFERENCED = 107;
+        public static final int CSW_ADD_CONFERENCE_CALL = 108;
+
+        private int mId;
+        private long mTime;
+
+        public SessionTiming(int id, long time) {
+            this.mId = id;
+            this.mTime = time;
+        }
+
+        private SessionTiming(Parcel in) {
+            mId = in.readInt();
+            mTime = in.readLong();
+        }
+
+        @Override
+        public Integer getKey() {
+            return mId;
+        }
+
+        @Override
+        public long getTime() {
+            return mTime;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mId);
+            out.writeLong(mTime);
+        }
+    }
+
+    private List<SessionTiming> mSessionTimings;
+    private List<ParcelableCallAnalytics> mCallAnalytics;
+
+    public TelecomAnalytics(List<SessionTiming> sessionTimings,
+            List<ParcelableCallAnalytics> callAnalytics) {
+        this.mSessionTimings = sessionTimings;
+        this.mCallAnalytics = callAnalytics;
+    }
+
+    private TelecomAnalytics(Parcel in) {
+        mSessionTimings = new ArrayList<>();
+        in.readTypedList(mSessionTimings, SessionTiming.CREATOR);
+        mCallAnalytics = new ArrayList<>();
+        in.readTypedList(mCallAnalytics, ParcelableCallAnalytics.CREATOR);
+    }
+
+    public List<SessionTiming> getSessionTimings() {
+        return mSessionTimings;
+    }
+
+    public List<ParcelableCallAnalytics> getCallAnalytics() {
+        return mCallAnalytics;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedList(mSessionTimings);
+        out.writeTypedList(mCallAnalytics);
+    }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ff5daaf..f12886a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1443,9 +1443,9 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.DUMP)
-    public List<ParcelableCallAnalytics> dumpAnalytics() {
+    public TelecomAnalytics dumpAnalytics() {
         ITelecomService service = getTelecomService();
-        List<ParcelableCallAnalytics> result = null;
+        TelecomAnalytics result = null;
         if (service != null) {
             try {
                 result = service.dumpCallAnalytics();
diff --git a/telecomm/java/android/telecom/TimedEvent.java b/telecomm/java/android/telecom/TimedEvent.java
new file mode 100644
index 0000000..e484e79
--- /dev/null
+++ b/telecomm/java/android/telecom/TimedEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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.telecom;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public abstract class TimedEvent<T> {
+    public abstract long getTime();
+    public abstract T getKey();
+
+    public static <T> Map<T, Double> averageTimings(Collection<? extends TimedEvent<T>> events) {
+        HashMap<T, Integer> counts = new HashMap<>();
+        HashMap<T, Double> result = new HashMap<>();
+
+        for (TimedEvent<T> entry : events) {
+            if (counts.containsKey(entry.getKey())) {
+                counts.put(entry.getKey(), counts.get(entry.getKey()) + 1);
+                result.put(entry.getKey(), result.get(entry.getKey()) + entry.getTime());
+            } else {
+                counts.put(entry.getKey(), 1);
+                result.put(entry.getKey(), (double) entry.getTime());
+            }
+        }
+
+        for (Map.Entry<T, Double> entry : result.entrySet()) {
+            result.put(entry.getKey(), entry.getValue() / counts.get(entry.getKey()));
+        }
+
+        return result;
+    }
+}
+