Add logs for Telecom metrics stage 2
Flag: com.android.server.telecom.flags.telecom_metrics_support
Bug: 397554282
Test: atest TeleServiceTests
Test: manual
Change-Id: I47300ddf9205ee2f8812b840445402d4093e176a
diff --git a/proto/pulled_atoms.proto b/proto/pulled_atoms.proto
index 6c9af46..2916dad 100644
--- a/proto/pulled_atoms.proto
+++ b/proto/pulled_atoms.proto
@@ -14,6 +14,8 @@
optional int64 telecom_api_stats_pull_timestamp_millis = 6;
repeated TelecomErrorStats telecom_error_stats = 7;
optional int64 telecom_error_stats_pull_timestamp_millis = 8;
+ repeated TelecomEventStats telecom_event_stats = 9;
+ optional int64 telecom_event_stats_pull_timestamp_millis = 10;
}
/**
@@ -48,6 +50,15 @@
// Average elapsed time between CALL_STATE_ACTIVE to CALL_STATE_DISCONNECTED.
optional int32 average_duration_ms = 8;
+
+ // The disconnect cause of the call. Eg. ERROR, LOCAL, REMOTE, etc.
+ // From frameworks/proto_logging/stats/enums/telecomm/enums.proto
+ optional int32 disconnect_cause = 9;
+
+ // The type of simultaneous call type. Eg. SINGLE, DUAL_SAME_ACCOUNT,
+ // DUAL_DIFF_ACCOUNT, etc.
+ // From frameworks/proto_logging/stats/enums/telecomm/enums.proto
+ optional int32 simultaneous_type = 10;
}
/**
@@ -112,3 +123,22 @@
// The number of times this error occurs
optional int32 count = 3;
}
+
+/**
+ * Pulled atom to capture stats of Telecom critical events
+ */
+message TelecomEventStats {
+ // The event name
+ // From frameworks/proto_logging/stats/enums/telecomm/enums.proto
+ optional int32 event = 1;
+
+ // UID of the caller. This is always -1/unknown for the private space.
+ optional int32 uid = 2;
+
+ // The cause related to the event
+ // From frameworks/proto_logging/stats/enums/telecomm/enums.proto
+ optional int32 event_cause = 3;
+
+ // The number of times this event occurs
+ optional int32 count = 4;
+}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 3b3a35a..8cd51d0 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -131,6 +131,10 @@
private static final char NO_DTMF_TONE = '\0';
+ public static final int CALL_SIMULTANEOUS_UNKNOWN = 0;
+ public static final int CALL_SIMULTANEOUS_SINGLE = 1;
+ public static final int CALL_DIRECTION_DUAL_SAME_ACCOUNT = 2;
+ public static final int CALL_DIRECTION_DUAL_DIFF_ACCOUNT = 3;
/**
* Listener for CallState changes which can be leveraged by a Transaction.
@@ -501,6 +505,11 @@
*/
private DisconnectCause mOverrideDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
+ /**
+ * Simultaneous type of the call.
+ */
+ private int mSimultaneousType = CALL_SIMULTANEOUS_UNKNOWN;
+
private Bundle mIntentExtras = new Bundle();
/**
@@ -5064,4 +5073,12 @@
}
}
}
+
+ public void setSimultaneousType(int simultaneousType) {
+ mSimultaneousType = simultaneousType;
+ }
+
+ public int getSimultaneousType() {
+ return mSimultaneousType;
+ }
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index ef8210d..9d1a382 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -84,6 +84,8 @@
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.metrics.ApiStats;
+import com.android.server.telecom.metrics.EventStats;
+import com.android.server.telecom.metrics.EventStats.CriticalEvent;
import com.android.server.telecom.metrics.TelecomMetricsController;
import com.android.server.telecom.settings.BlockedNumbersActivity;
import com.android.server.telecom.callsequencing.TransactionManager;
@@ -195,8 +197,9 @@
@Override
public void addCall(CallAttributes callAttributes, ICallEventCallback callEventCallback,
String callId, String callingPackage) {
+ int uid = Binder.getCallingUid();
ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDCALL,
- Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
+ uid, ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aC", Log.getPackageAbbreviation(callingPackage));
Log.i(TAG, "addCall: id=[%s], attributes=[%s]", callId, callAttributes);
@@ -213,8 +216,8 @@
// add extras about info used for FGS delegation
Bundle extras = new Bundle();
- extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
- extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+ extras.putInt(CallAttributes.CALLER_UID_KEY, uid);
+ extras.putInt(CallAttributes.CALLER_PID_KEY, uid);
CompletableFuture<CallTransaction> transactionFuture;
@@ -233,6 +236,11 @@
public void onResult(CallTransactionResult result) {
Log.d(TAG, "addCall: onResult");
Call call = result.getCall();
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getEventStats().log(new CriticalEvent(
+ EventStats.ID_ADD_CALL, uid,
+ EventStats.CAUSE_CALL_TRANSACTION_SUCCESS));
+ }
if (call == null || !call.getId().equals(callId)) {
Log.i(TAG, "addCall: onResult: call is null or id mismatch");
@@ -278,6 +286,12 @@
ADD_CALL_ON_ERROR_UUID,
exception.getMessage());
}
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getEventStats().log(new CriticalEvent(
+ EventStats.ID_ADD_CALL, uid,
+ EventStats.CAUSE_CALL_TRANSACTION_BASE
+ + exception.getCode()));
+ }
}
});
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index b66fb10..00341a5 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -49,6 +49,7 @@
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.metrics.EventStats;
import com.android.server.telecom.metrics.TelecomMetricsController;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
diff --git a/src/com/android/server/telecom/metrics/CallStats.java b/src/com/android/server/telecom/metrics/CallStats.java
index f518557..5f00446 100644
--- a/src/com/android/server/telecom/metrics/CallStats.java
+++ b/src/com/android/server/telecom/metrics/CallStats.java
@@ -42,6 +42,7 @@
import com.android.server.telecom.TelecomStatsLog;
import com.android.server.telecom.nano.PulledAtomsClass;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -82,7 +83,8 @@
TelecomStatsLog.buildStatsEvent(getTag(),
v.getCallDirection(), v.getExternalCall(), v.getEmergencyCall(),
v.getMultipleAudioAvailable(), v.getAccountType(), v.getUid(),
- v.getCount(), v.getAverageDurationMs())));
+ v.getCount(), v.getAverageDurationMs(), v.getDisconnectCause(),
+ v.getSimultaneousType())));
mCallStatsMap.clear();
onAggregate();
return StatsManager.PULL_SUCCESS;
@@ -97,10 +99,11 @@
mCallStatsMap = new HashMap<>();
for (PulledAtomsClass.CallStats v : mPulledAtoms.callStats) {
mCallStatsMap.put(new CallStatsKey(v.getCallDirection(),
- v.getExternalCall(), v.getEmergencyCall(),
- v.getMultipleAudioAvailable(),
- v.getAccountType(), v.getUid()),
- new CallStatsData(v.getCount(), v.getAverageDurationMs()));
+ v.getExternalCall(), v.getEmergencyCall(),
+ v.getMultipleAudioAvailable(), v.getAccountType(),
+ v.getUid(), v.getDisconnectCause(), v.getSimultaneousType()),
+ new CallStatsData(
+ v.getCount(), v.getAverageDurationMs()));
}
mLastPulledTimestamps = mPulledAtoms.getCallStatsPullTimestampMillis();
}
@@ -125,6 +128,8 @@
mPulledAtoms.callStats[index[0]].setMultipleAudioAvailable(k.mIsMultipleAudioAvailable);
mPulledAtoms.callStats[index[0]].setAccountType(k.mAccountType);
mPulledAtoms.callStats[index[0]].setUid(k.mUid);
+ mPulledAtoms.callStats[index[0]].setDisconnectCause(k.mCause);
+ mPulledAtoms.callStats[index[0]].setSimultaneousType(k.mSimultaneousType);
mPulledAtoms.callStats[index[0]].setCount(v.mCount);
mPulledAtoms.callStats[index[0]].setAverageDurationMs(v.mAverageDuration);
index[0]++;
@@ -133,10 +138,16 @@
}
public void log(int direction, boolean isExternal, boolean isEmergency,
- boolean isMultipleAudioAvailable, int accountType, int uid, int duration) {
+ boolean isMultipleAudioAvailable, int accountType, int uid, int duration) {
+ log(direction, isExternal, isEmergency, isMultipleAudioAvailable, accountType, uid,
+ 0, 0, duration);
+ }
+ public void log(int direction, boolean isExternal, boolean isEmergency,
+ boolean isMultipleAudioAvailable, int accountType, int uid,
+ int disconnectCause, int simultaneousType, int duration) {
post(() -> {
CallStatsKey key = new CallStatsKey(direction, isExternal, isEmergency,
- isMultipleAudioAvailable, accountType, uid);
+ isMultipleAudioAvailable, accountType, uid, disconnectCause, simultaneousType);
CallStatsData data = mCallStatsMap.computeIfAbsent(key, k -> new CallStatsData(0, 0));
data.add(duration);
onAggregate();
@@ -171,7 +182,8 @@
}
log(direction, call.isExternalCall(), call.isEmergencyCall(), hasMultipleAudioDevices,
- accountType, uid, duration);
+ accountType, uid, call.getDisconnectCause().getCode(),
+ call.getSimultaneousType(), duration);
});
}
@@ -236,15 +248,26 @@
final boolean mIsMultipleAudioAvailable;
final int mAccountType;
final int mUid;
+ final int mCause;
+ final int mSimultaneousType;
CallStatsKey(int direction, boolean isExternal, boolean isEmergency,
- boolean isMultipleAudioAvailable, int accountType, int uid) {
+ boolean isMultipleAudioAvailable, int accountType, int uid) {
+ this(direction, isExternal, isEmergency, isMultipleAudioAvailable, accountType, uid,
+ 0, 0);
+ }
+
+ CallStatsKey(int direction, boolean isExternal, boolean isEmergency,
+ boolean isMultipleAudioAvailable, int accountType, int uid,
+ int cause, int simultaneousType) {
mDirection = direction;
mIsExternal = isExternal;
mIsEmergency = isEmergency;
mIsMultipleAudioAvailable = isMultipleAudioAvailable;
mAccountType = accountType;
mUid = uid;
+ mCause = cause;
+ mSimultaneousType = simultaneousType;
}
@Override
@@ -258,13 +281,14 @@
return this.mDirection == obj.mDirection && this.mIsExternal == obj.mIsExternal
&& this.mIsEmergency == obj.mIsEmergency
&& this.mIsMultipleAudioAvailable == obj.mIsMultipleAudioAvailable
- && this.mAccountType == obj.mAccountType && this.mUid == obj.mUid;
+ && this.mAccountType == obj.mAccountType && this.mUid == obj.mUid
+ && this.mCause == obj.mCause && this.mSimultaneousType == obj.mSimultaneousType;
}
@Override
public int hashCode() {
return Objects.hash(mDirection, mIsExternal, mIsEmergency, mIsMultipleAudioAvailable,
- mAccountType, mUid);
+ mAccountType, mUid, mCause, mSimultaneousType);
}
@Override
@@ -272,7 +296,7 @@
return "[CallStatsKey: mDirection=" + mDirection + ", mIsExternal=" + mIsExternal
+ ", mIsEmergency=" + mIsEmergency + ", mIsMultipleAudioAvailable="
+ mIsMultipleAudioAvailable + ", mAccountType=" + mAccountType + ", mUid="
- + mUid + "]";
+ + mUid + ", mCause=" + mCause + ", mScType=" + mSimultaneousType + "]";
}
}
diff --git a/src/com/android/server/telecom/metrics/EventStats.java b/src/com/android/server/telecom/metrics/EventStats.java
new file mode 100644
index 0000000..18e68fb
--- /dev/null
+++ b/src/com/android/server/telecom/metrics/EventStats.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2025 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.telecom.metrics;
+
+import static com.android.server.telecom.TelecomStatsLog.TELECOM_EVENT_STATS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.Looper;
+import android.telecom.CallException;
+import android.telecom.Log;
+import android.util.StatsEvent;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.server.telecom.TelecomStatsLog;
+import com.android.server.telecom.metrics.ApiStats.ApiEvent;
+import com.android.server.telecom.nano.PulledAtomsClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class EventStats extends TelecomPulledAtom {
+ public static final int ID_UNKNOWN = TelecomStatsLog.TELECOM_EVENT_STATS__EVENT__EVENT_UNKNOWN;
+ public static final int ID_INIT = TelecomStatsLog.TELECOM_EVENT_STATS__EVENT__EVENT_INIT;
+ public static final int ID_DEFAULT_DIALER_CHANGED = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT__EVENT_DEFAULT_DIALER_CHANGED;
+ public static final int ID_ADD_CALL = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT__EVENT_ADD_CALL;
+
+ public static final int CAUSE_UNKNOWN = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT_CAUSE__CAUSE_UNKNOWN;
+ public static final int CAUSE_GENERIC_SUCCESS = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT_CAUSE__CAUSE_GENERIC_SUCCESS;
+ public static final int CAUSE_GENERIC_FAILURE = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT_CAUSE__CAUSE_GENERIC_FAILURE;
+ public static final int CAUSE_CALL_TRANSACTION_SUCCESS = TelecomStatsLog
+ .TELECOM_EVENT_STATS__EVENT_CAUSE__CALL_TRANSACTION_SUCCESS;
+ public static final int CAUSE_CALL_TRANSACTION_BASE = CAUSE_CALL_TRANSACTION_SUCCESS;
+ public static final int CAUSE_CALL_TRANSACTION_ERROR_UNKNOWN =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_ERROR_UNKNOWN;
+ public static final int CAUSE_CALL_TRANSACTION_CANNOT_HOLD_CURRENT_ACTIVE_CALL =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL;
+ public static final int CAUSE_CALL_TRANSACTION_CALL_IS_NOT_BEING_TRACKED =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_CALL_IS_NOT_BEING_TRACKED;
+ public static final int CAUSE_CALL_TRANSACTION_CALL_CANNOT_BE_SET_TO_ACTIVE =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE;
+ public static final int CAUSE_CALL_TRANSACTION_CALL_NOT_PERMITTED_AT_PRESENT_TIME =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
+ public static final int CAUSE_CALL_TRANSACTION_OPERATION_TIMED_OUT =
+ CAUSE_CALL_TRANSACTION_BASE + CallException.CODE_OPERATION_TIMED_OUT;
+ private static final String TAG = EventStats.class.getSimpleName();
+ private static final String FILE_NAME = "event_stats";
+ private Map<CriticalEvent, Integer> mEventStatsMap;
+
+ public EventStats(@NonNull Context context, @NonNull Looper looper,
+ boolean isTestMode) {
+ super(context, looper, isTestMode);
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @Override
+ public int getTag() {
+ return TELECOM_EVENT_STATS;
+ }
+
+ @Override
+ protected String getFileName() {
+ return FILE_NAME;
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @Override
+ public synchronized int onPull(final List<StatsEvent> data) {
+ if (mPulledAtoms.telecomEventStats.length != 0) {
+ Arrays.stream(mPulledAtoms.telecomEventStats).forEach(v -> data.add(
+ TelecomStatsLog.buildStatsEvent(getTag(),
+ v.getEvent(), v.getUid(), v.getEventCause(), v.getCount())));
+ mEventStatsMap.clear();
+ onAggregate();
+ return StatsManager.PULL_SUCCESS;
+ } else {
+ return StatsManager.PULL_SKIP;
+ }
+ }
+
+ @Override
+ protected synchronized void onLoad() {
+ if (mPulledAtoms.telecomEventStats != null) {
+ mEventStatsMap = new HashMap<>();
+ for (PulledAtomsClass.TelecomEventStats v : mPulledAtoms.telecomEventStats) {
+ mEventStatsMap.put(new CriticalEvent(v.getEvent(), v.getUid(),
+ v.getEventCause()), v.getCount());
+ }
+ mLastPulledTimestamps = mPulledAtoms.getTelecomEventStatsPullTimestampMillis();
+ }
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @Override
+ public synchronized void onAggregate() {
+ Log.d(TAG, "onAggregate: %s", mEventStatsMap);
+ clearAtoms();
+ if (mEventStatsMap.isEmpty()) {
+ return;
+ }
+ mPulledAtoms.setTelecomEventStatsPullTimestampMillis(mLastPulledTimestamps);
+ mPulledAtoms.telecomEventStats =
+ new PulledAtomsClass.TelecomEventStats[mEventStatsMap.size()];
+ int[] index = new int[1];
+ mEventStatsMap.forEach((k, v) -> {
+ mPulledAtoms.telecomEventStats[index[0]] = new PulledAtomsClass.TelecomEventStats();
+ mPulledAtoms.telecomEventStats[index[0]].setEvent(k.mId);
+ mPulledAtoms.telecomEventStats[index[0]].setUid(k.mUid);
+ mPulledAtoms.telecomEventStats[index[0]].setEventCause(k.mCause);
+ mPulledAtoms.telecomEventStats[index[0]].setCount(v);
+ index[0]++;
+ });
+ save(DELAY_FOR_PERSISTENT_MILLIS);
+ }
+
+ public void log(@NonNull CriticalEvent event) {
+ post(() -> {
+ mEventStatsMap.put(event, mEventStatsMap.getOrDefault(event, 0) + 1);
+ onAggregate();
+ });
+ }
+
+ @IntDef(prefix = "ID_", value = {
+ ID_UNKNOWN,
+ ID_INIT,
+ ID_DEFAULT_DIALER_CHANGED,
+ ID_ADD_CALL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventId {
+ }
+
+ @IntDef(prefix = "CAUSE_", value = {
+ CAUSE_UNKNOWN,
+ CAUSE_GENERIC_SUCCESS,
+ CAUSE_GENERIC_FAILURE,
+ CAUSE_CALL_TRANSACTION_SUCCESS,
+ CAUSE_CALL_TRANSACTION_ERROR_UNKNOWN,
+ CAUSE_CALL_TRANSACTION_CANNOT_HOLD_CURRENT_ACTIVE_CALL,
+ CAUSE_CALL_TRANSACTION_CALL_IS_NOT_BEING_TRACKED,
+ CAUSE_CALL_TRANSACTION_CALL_CANNOT_BE_SET_TO_ACTIVE,
+ CAUSE_CALL_TRANSACTION_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+ CAUSE_CALL_TRANSACTION_OPERATION_TIMED_OUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CauseId {
+ }
+
+ public static class CriticalEvent {
+
+ @EventId
+ int mId;
+ int mUid;
+ @CauseId
+ int mCause;
+
+ public CriticalEvent(@EventId int id, int uid, @CauseId int cause) {
+ mId = id;
+ mUid = uid;
+ mCause = cause;
+ }
+
+ public void setUid(int uid) {
+ this.mUid = uid;
+ }
+
+ public void setResult(@CauseId int result) {
+ this.mCause = result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof ApiEvent obj)) {
+ return false;
+ }
+ return this.mId == obj.mId && this.mUid == obj.mCallerUid
+ && this.mCause == obj.mResult;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mUid, mCause);
+ }
+
+ @Override
+ public String toString() {
+ return "[CriticalEvent: mId=" + mId + ", m"
+ + "Uid=" + mUid
+ + ", mResult=" + mCause + "]";
+ }
+ }
+
+
+}
diff --git a/src/com/android/server/telecom/metrics/TelecomMetricsController.java b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
index 23673ca..980c180 100644
--- a/src/com/android/server/telecom/metrics/TelecomMetricsController.java
+++ b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
@@ -20,6 +20,7 @@
import static com.android.server.telecom.TelecomStatsLog.CALL_STATS;
import static com.android.server.telecom.TelecomStatsLog.TELECOM_API_STATS;
import static com.android.server.telecom.TelecomStatsLog.TELECOM_ERROR_STATS;
+import static com.android.server.telecom.TelecomStatsLog.TELECOM_EVENT_STATS;
import android.annotation.NonNull;
import android.app.StatsManager;
@@ -117,6 +118,16 @@
return stats;
}
+ @NonNull
+ public EventStats getEventStats() {
+ EventStats stats = (EventStats) mStats.get(TELECOM_EVENT_STATS);
+ if (stats == null) {
+ stats = new EventStats(mContext, mHandlerThread.getLooper(), isTestMode());
+ registerAtom(stats.getTag(), stats);
+ }
+ return stats;
+ }
+
@Override
public int onPullAtom(final int atomTag, final List<StatsEvent> data) {
if (mStats.containsKey(atomTag)) {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
index 3836a41..3716b4d 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
@@ -19,6 +19,7 @@
import static com.android.server.telecom.TelecomStatsLog.CALL_STATS;
import static com.android.server.telecom.TelecomStatsLog.TELECOM_API_STATS;
import static com.android.server.telecom.TelecomStatsLog.TELECOM_ERROR_STATS;
+import static com.android.server.telecom.TelecomStatsLog.TELECOM_EVENT_STATS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject;
@@ -38,6 +39,7 @@
import com.android.server.telecom.metrics.AudioRouteStats;
import com.android.server.telecom.metrics.CallStats;
import com.android.server.telecom.metrics.ErrorStats;
+import com.android.server.telecom.metrics.EventStats;
import com.android.server.telecom.metrics.TelecomMetricsController;
import org.junit.After;
@@ -61,6 +63,8 @@
CallStats mCallStats;
@Mock
ErrorStats mErrorStats;
+ @Mock
+ EventStats mEventStats;
HandlerThread mHandlerThread;
@@ -114,6 +118,13 @@
}
@Test
+ public void testGetEventStatsReturnsSameInstance() {
+ EventStats stats1 = mTelecomMetricsController.getEventStats();
+ EventStats stats2 = mTelecomMetricsController.getEventStats();
+ assertThat(stats1).isSameInstanceAs(stats2);
+ }
+
+ @Test
public void testOnPullAtomReturnsPullSkipIfAtomNotRegistered() {
mTelecomMetricsController.getStats().clear();
@@ -143,6 +154,7 @@
verify(statsManager, times(1)).clearPullAtomCallback(eq(CALL_STATS));
verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_API_STATS));
verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_ERROR_STATS));
+ verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_EVENT_STATS));
assertThat(mTelecomMetricsController.getStats()).isEmpty();
}
@@ -195,5 +207,6 @@
mTelecomMetricsController.getStats().put(CALL_STATS, mCallStats);
mTelecomMetricsController.getStats().put(TELECOM_API_STATS, mApiStats);
mTelecomMetricsController.getStats().put(TELECOM_ERROR_STATS, mErrorStats);
+ mTelecomMetricsController.getStats().put(TELECOM_EVENT_STATS, mEventStats);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
index 4f7569e..875617f 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
@@ -42,6 +42,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Looper;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.util.StatsEvent;
@@ -55,6 +56,7 @@
import com.android.server.telecom.metrics.AudioRouteStats;
import com.android.server.telecom.metrics.CallStats;
import com.android.server.telecom.metrics.ErrorStats;
+import com.android.server.telecom.metrics.EventStats;
import com.android.server.telecom.nano.PulledAtomsClass;
import org.junit.After;
@@ -104,6 +106,10 @@
private static final int VALUE_ERROR_ID = 1;
private static final int VALUE_ERROR_COUNT = 1;
+ private static final int VALUE_EVENT_ID = 1;
+ private static final int VALUE_CAUSE_ID = 1;
+ private static final int VALUE_EVENT_COUNT = 1;
+
@Rule
public TemporaryFolder mTempFolder = new TemporaryFolder();
@Mock
@@ -187,6 +193,11 @@
ErrorStats errorStats = new ErrorStats(mSpyContext, mLooper, false);
verifyTestDataForErrorStats(errorStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
+
+ createTestFileForEventStats(DEFAULT_TIMESTAMPS_MILLIS);
+ EventStats eventStats = new EventStats(mSpyContext, mLooper, false);
+
+ verifyTestDataForEventStats(eventStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
}
@Test
@@ -681,6 +692,8 @@
doReturn(cn).when(handle).getComponentName();
Call call = mock(Call.class);
doReturn(true).when(call).isIncoming();
+ doReturn(new DisconnectCause(0)).when(call).getDisconnectCause();
+ doReturn(0).when(call).getSimultaneousType();
doReturn(account).when(call).getPhoneAccountFromHandle();
doReturn((long) duration).when(call).getAgeMillis();
doReturn(false).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SELF_MANAGED));
@@ -698,7 +711,7 @@
verify(callStats, times(1)).log(eq(CALL_STATS__CALL_DIRECTION__DIR_INCOMING),
eq(false), eq(false), eq(false), eq(CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM),
- eq(fakeUid), eq(duration));
+ eq(fakeUid), eq(0), eq(0), eq(duration));
}
@Test
@@ -719,6 +732,8 @@
doReturn(cn).when(handle).getComponentName();
Call call = mock(Call.class);
doReturn(true).when(call).isIncoming();
+ doReturn(new DisconnectCause(0)).when(call).getDisconnectCause();
+ doReturn(0).when(call).getSimultaneousType();
doReturn(account).when(call).getPhoneAccountFromHandle();
doReturn((long) duration).when(call).getAgeMillis();
doReturn(false).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SELF_MANAGED));
@@ -739,7 +754,7 @@
verify(callStats, times(1)).log(eq(CALL_STATS__CALL_DIRECTION__DIR_INCOMING),
eq(false), eq(false), eq(true), eq(CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM),
- eq(fakeUid), eq(duration));
+ eq(fakeUid), eq(0), eq(0), eq(duration));
}
@Test
@@ -871,6 +886,94 @@
verify(mSpyContext, never()).openFileOutput(anyString(), anyInt());
}
+ @Test
+ public void testPullEventStatsLessThanMinPullIntervalShouldSkip() throws Exception {
+ createTestFileForEventStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS / 2);
+ EventStats eventStats = spy(new EventStats(mSpyContext, mLooper, false));
+ final List<StatsEvent> data = new ArrayList<>();
+
+ int result = eventStats.pull(data);
+
+ assertEquals(StatsManager.PULL_SKIP, result);
+ verify(eventStats, never()).onPull(any());
+ assertEquals(data.size(), 0);
+ }
+
+ @Test
+ public void testPullEventStatsGreaterThanMinPullIntervalShouldNotSkip() throws Exception {
+ createTestFileForEventStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
+ EventStats eventStats = spy(new EventStats(mSpyContext, mLooper, false));
+ final List<StatsEvent> data = new ArrayList<>();
+ int sizePulled = eventStats.mPulledAtoms.telecomEventStats.length;
+
+ int result = eventStats.pull(data);
+
+ assertEquals(StatsManager.PULL_SUCCESS, result);
+ verify(eventStats).onPull(eq(data));
+ assertEquals(data.size(), sizePulled);
+ assertEquals(eventStats.mPulledAtoms.telecomEventStats.length, 0);
+ }
+
+ @Test
+ public void testEventStatsLogCount() throws Exception {
+ EventStats eventStats = spy(new EventStats(mSpyContext, mLooper, false));
+ EventStats.CriticalEvent event = new EventStats.CriticalEvent(
+ VALUE_EVENT_ID, VALUE_UID, VALUE_CAUSE_ID);
+
+ for (int i = 0; i < 10; i++) {
+ eventStats.log(event);
+ waitForHandlerAction(eventStats, TEST_TIMEOUT);
+
+ verify(eventStats, times(i + 1)).onAggregate();
+ verify(eventStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(eventStats.mPulledAtoms.telecomEventStats.length, 1);
+ verifyMessageForEventStats(eventStats.mPulledAtoms.telecomEventStats[0],
+ VALUE_EVENT_ID, VALUE_UID, VALUE_CAUSE_ID, i + 1);
+ }
+ }
+
+ @Test
+ public void testEventStatsLogEvent() throws Exception {
+ EventStats eventStats = spy(new EventStats(mSpyContext, mLooper, false));
+ int[] events = {
+ EventStats.ID_UNKNOWN,
+ EventStats.ID_INIT,
+ EventStats.ID_DEFAULT_DIALER_CHANGED,
+ EventStats.ID_ADD_CALL,
+ };
+ int[] causes = {
+ EventStats.CAUSE_UNKNOWN,
+ EventStats.CAUSE_GENERIC_SUCCESS,
+ EventStats.CAUSE_GENERIC_FAILURE,
+ EventStats.CAUSE_CALL_TRANSACTION_SUCCESS,
+ EventStats.CAUSE_CALL_TRANSACTION_ERROR_UNKNOWN,
+ EventStats.CAUSE_CALL_TRANSACTION_CALL_CANNOT_BE_SET_TO_ACTIVE,
+ EventStats.CAUSE_CALL_TRANSACTION_CALL_IS_NOT_BEING_TRACKED,
+ EventStats.CAUSE_CALL_TRANSACTION_CANNOT_HOLD_CURRENT_ACTIVE_CALL,
+ EventStats.CAUSE_CALL_TRANSACTION_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+ EventStats.CAUSE_CALL_TRANSACTION_OPERATION_TIMED_OUT,
+ };
+ Random rand = new Random();
+ Map<EventStats.CriticalEvent, Integer> eventMap = new HashMap<>();
+
+ for (int i = 0; i < 10; i++) {
+ int e = events[rand.nextInt(events.length)];
+ int uid = rand.nextInt(65535);
+ int cause = causes[rand.nextInt(causes.length)];
+ EventStats.CriticalEvent ce = new EventStats.CriticalEvent(e, uid, cause);
+ eventMap.put(ce, eventMap.getOrDefault(ce, 0) + 1);
+
+ eventStats.log(ce);
+ waitForHandlerAction(eventStats, TEST_TIMEOUT);
+
+ verify(eventStats, times(i + 1)).onAggregate();
+ verify(eventStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(eventStats.mPulledAtoms.telecomEventStats.length, eventMap.size());
+ assertTrue(hasMessageForEventStats(eventStats.mPulledAtoms.telecomEventStats,
+ e, uid, cause, eventMap.get(ce)));
+ }
+ }
+
private void createTestFileForApiStats(long timestamps) throws IOException {
PulledAtomsClass.PulledAtoms atom = new PulledAtomsClass.PulledAtoms();
atom.telecomApiStats =
@@ -1037,8 +1140,8 @@
assertEquals(atom.telecomErrorStats.length, VALUE_ATOM_COUNT);
for (int i = 0; i < VALUE_ATOM_COUNT; i++) {
assertNotNull(atom.telecomErrorStats[i]);
- verifyMessageForErrorStats(atom.telecomErrorStats[i], VALUE_MODULE_ID, VALUE_ERROR_ID
- , VALUE_ERROR_COUNT);
+ verifyMessageForErrorStats(atom.telecomErrorStats[i], VALUE_MODULE_ID,
+ VALUE_ERROR_ID, VALUE_ERROR_COUNT);
}
}
@@ -1059,4 +1162,53 @@
}
return false;
}
+
+ private void createTestFileForEventStats(long timestamps) throws IOException {
+ PulledAtomsClass.PulledAtoms atom = new PulledAtomsClass.PulledAtoms();
+ atom.telecomEventStats =
+ new PulledAtomsClass.TelecomEventStats[VALUE_ATOM_COUNT];
+ for (int i = 0; i < VALUE_ATOM_COUNT; i++) {
+ atom.telecomEventStats[i] = new PulledAtomsClass.TelecomEventStats();
+ atom.telecomEventStats[i].setEvent(VALUE_EVENT_ID + i);
+ atom.telecomEventStats[i].setUid(VALUE_UID);
+ atom.telecomEventStats[i].setEventCause(VALUE_CAUSE_ID);
+ atom.telecomEventStats[i].setCount(VALUE_EVENT_COUNT);
+ }
+ atom.setTelecomEventStatsPullTimestampMillis(timestamps);
+ FileOutputStream stream = new FileOutputStream(mTempFile);
+ stream.write(PulledAtomsClass.PulledAtoms.toByteArray(atom));
+ stream.close();
+ }
+
+ private void verifyTestDataForEventStats(
+ final PulledAtomsClass.PulledAtoms atom, long timestamps) {
+ assertNotNull(atom);
+ assertEquals(atom.getTelecomEventStatsPullTimestampMillis(), timestamps);
+ assertNotNull(atom.telecomEventStats);
+ assertEquals(atom.telecomEventStats.length, VALUE_ATOM_COUNT);
+ for (int i = 0; i < VALUE_ATOM_COUNT; i++) {
+ assertNotNull(atom.telecomEventStats[i]);
+ verifyMessageForEventStats(atom.telecomEventStats[i], VALUE_EVENT_ID + i,
+ VALUE_UID, VALUE_CAUSE_ID, VALUE_EVENT_COUNT);
+ }
+ }
+
+ private void verifyMessageForEventStats(final PulledAtomsClass.TelecomEventStats msg,
+ int eventId, int uid, int causeId, int count) {
+ assertEquals(msg.getEvent(), eventId);
+ assertEquals(msg.getUid(), uid);
+ assertEquals(msg.getEventCause(), causeId);
+ assertEquals(msg.getCount(), count);
+ }
+
+ private boolean hasMessageForEventStats(final PulledAtomsClass.TelecomEventStats[] msgs,
+ int eventId, int uid, int causeId, int count) {
+ for (PulledAtomsClass.TelecomEventStats msg : msgs) {
+ if (msg.getEvent() == eventId && msg.getUid() == uid
+ && msg.getEventCause() == causeId && msg.getCount() == count) {
+ return true;
+ }
+ }
+ return false;
+ }
}