add anomaly reports for every new CallException occurrence

Adding these anomaly reports will help surface the frequency of each
CalException.  A total of 8 new anomaly reports have been added in this
CL.

Flag: com.android.server.telecom.flags.enable_call_exception_anom_reports
Fixes: 390654979
Test: build
Change-Id: Ided325ade6c4610e902942b7c57539459ff96930
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index 5d42b86..bc248c8 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -27,3 +27,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=tjstuart TARGET=25Q2
+flag {
+  name: "enable_call_exception_anom_reports"
+  namespace: "telecom"
+  description: "When a new CallException is created, generate an anomaly report for metrics"
+  bug: "308932906"
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 25992fc..0a38720 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -329,6 +329,10 @@
             UUID.fromString("0a86157c-50ca-11ee-be56-0242ac120002");
     public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
             "Telephony has a default MO acct but Telecom prompted user for MO";
+    public static final UUID CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID =
+            UUID.fromString("1b6a9b88-5049-4ffa-a52a-134d7c3a40e6");
+    public static final UUID FAILED_TO_SWITCH_FOCUS_ERROR_UUID =
+            UUID.fromString("a1b2c3d4-e5f6-7890-1234-567890abcdef");
 
     public static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -4089,11 +4093,14 @@
             if (activeCall.isLocallyDisconnecting()) {
                 callback.onResult(true);
             } else {
-                Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: active call could "
-                        + "not be held or disconnected");
+                String msg = "active call could not be held or disconnected";
+                Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: " + msg);
                 callback.onError(
-                        new CallException("activeCall could not be held or disconnected",
+                        new CallException(msg,
                                 CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+                }
             }
         }
     }
@@ -4109,19 +4116,25 @@
         // early
         if (!canHold(activeCall) &&
                 !(supportsHold(activeCall) && areFromSameSource(activeCall, newCall))) {
-            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
-                    + "conditions show the call cannot be held.");
-            callback.onError(new CallException("call does not support hold",
+            String msg = "call does not support hold";
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: " + msg);
+            callback.onError(new CallException(msg,
                     CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+            }
             return;
         }
 
         // attempt to hold the active call
         if (!holdActiveCallForNewCall(newCall)) {
-            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
-                    + "attempted to hold call but failed.");
-            callback.onError(new CallException("cannot hold active call failed",
+            String msg = "cannot hold active call failed";
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: " + msg);
+            callback.onError(new CallException(msg,
                     CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+            }
             return;
         }
 
@@ -6834,8 +6847,12 @@
                 if (mTargetCallFocus.getState() != mPreviousCallState) {
                     mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
                 }
-                mCallback.onError(new CallException("failed to switch focus to requested call",
+                String msg = "failed to switch focus to requested call";
+                mCallback.onError(new CallException(msg,
                         CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(FAILED_TO_SWITCH_FOCUS_ERROR_UUID, msg);
+                }
                 return;
             }
             // at this point, we know the FocusManager is able to update successfully
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index af094b7..b9841ba 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -27,8 +27,6 @@
 import static android.Manifest.permission.READ_SMS;
 import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.telecom.CallAttributes.DIRECTION_INCOMING;
-import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
 import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
 import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
 
@@ -52,8 +50,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -84,15 +80,12 @@
 import com.android.internal.telecom.ICallEventCallback;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
 import com.android.server.telecom.callsequencing.voip.VoipCallMonitor;
 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.TelecomMetricsController;
 import com.android.server.telecom.settings.BlockedNumbersActivity;
-import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
-import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
 import com.android.server.telecom.callsequencing.TransactionManager;
 import com.android.server.telecom.callsequencing.CallTransaction;
 import com.android.server.telecom.callsequencing.CallTransactionResult;
@@ -147,6 +140,13 @@
             UUID.fromString("4edf6c8d-1e43-4c94-b0fc-a40c8d80cfe8");
     public static final String PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG =
             "Security exception thrown while placing an outgoing call.";
+    public static final UUID CALL_IS_NULL_OR_ID_MISMATCH_UUID =
+            UUID.fromString("b11f3251-474c-4f90-96d6-a256aebc3c19");
+    public static final String CALL_IS_NULL_OR_ID_MISMATCH_MSG =
+            "call is null or id mismatch";
+    public static final UUID ADD_CALL_ON_ERROR_UUID =
+            UUID.fromString("f8e7d6c5-b4a3-9210-8765-432109abcdef");
+
     private static final String TAG = "TelecomServiceImpl";
     private static final String TIME_LINE_ARG = "timeline";
     private static final int DEFAULT_VIDEO_STATE = -1;
@@ -239,6 +239,11 @@
                                     onAddCallControl(callId, callEventCallback, null,
                                             new CallException(ADD_CALL_ERR_MSG,
                                                     CODE_ERROR_UNKNOWN));
+                                    if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                                        mAnomalyReporter.reportAnomaly(
+                                                CALL_IS_NULL_OR_ID_MISMATCH_UUID,
+                                                CALL_IS_NULL_OR_ID_MISMATCH_MSG);
+                                    }
                                     return;
                                 }
 
@@ -268,6 +273,11 @@
                             public void onError(@NonNull CallException exception) {
                                 Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
                                 onAddCallControl(callId, callEventCallback, null, exception);
+                                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                                    mAnomalyReporter.reportAnomaly(
+                                            ADD_CALL_ON_ERROR_UUID,
+                                            exception.getMessage());
+                                }
                             }
                         });
                     }
@@ -3024,7 +3034,10 @@
         });
 
         mTransactionManager = TransactionManager.getInstance();
-        mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags);
+        mTransactionManager.setFeatureFlag(mFeatureFlags);
+        mTransactionManager.setAnomalyReporter(mAnomalyReporter);
+        mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags,
+                mAnomalyReporter);
         mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
                 ? mContext.getSystemService(BlockedNumbersManager.class)
                 : null;
diff --git a/src/com/android/server/telecom/TransactionalServiceRepository.java b/src/com/android/server/telecom/TransactionalServiceRepository.java
index 5ae459e..954307a 100644
--- a/src/com/android/server/telecom/TransactionalServiceRepository.java
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -35,9 +35,13 @@
     private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
             new HashMap<>();
     private final FeatureFlags mFlags;
+    private final AnomalyReporterAdapter mAnomalyReporter;
 
-    public TransactionalServiceRepository(FeatureFlags flags) {
+    public TransactionalServiceRepository(
+            FeatureFlags flags,
+            AnomalyReporterAdapter anomalyReporter) {
         mFlags = flags;
+        mAnomalyReporter = anomalyReporter;
     }
 
     public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
@@ -50,7 +54,8 @@
             Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
             service = new TransactionalServiceWrapper(callEventCallback,
                     callsManager, phoneAccountHandle, call, this,
-                    TransactionManager.getInstance(), mFlags.enableCallSequencing());
+                    TransactionManager.getInstance(), mFlags.enableCallSequencing(),
+                    mFlags, mAnomalyReporter);
         } else {
             Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
             service = getTransactionalServiceWrapper(phoneAccountHandle);
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index d63a0bd..cc0d547 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -47,9 +47,11 @@
 import com.android.server.telecom.callsequencing.TransactionManager;
 import com.android.server.telecom.callsequencing.CallTransaction;
 import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Locale;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -92,7 +94,12 @@
     private TransactionManager mTransactionManager;
     private CallStreamingController mStreamingController;
     private final TransactionalCallSequencingAdapter mCallSequencingAdapter;
-
+    private final FeatureFlags mFeatureFlags;
+    private final AnomalyReporterAdapter mAnomalyReporter;
+    public static final UUID CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID =
+            UUID.fromString("8187cd59-97a7-4e9f-a772-638dda4b69bb");
+    public static final String CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG =
+            "A call update was attempted for a call no longer being tracked";
 
     // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
     // any calls in the event the application crashes or is force stopped.
@@ -108,7 +115,8 @@
     public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
             CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
             TransactionalServiceRepository repo, TransactionManager transactionManager,
-            boolean isCallSequencingEnabled) {
+            boolean isCallSequencingEnabled, FeatureFlags featureFlags,
+            AnomalyReporterAdapter anomalyReporterAdapter) {
         // passed args
         mICallEventCallback = callEventCallback;
         mCallsManager = callsManager;
@@ -123,6 +131,8 @@
         mCallSequencingAdapter = new TransactionalCallSequencingAdapter(mTransactionManager,
                 mCallsManager, isCallSequencingEnabled);
         setDeathRecipient(callEventCallback);
+        mFeatureFlags = featureFlags;
+        mAnomalyReporter = anomalyReporterAdapter;
     }
 
     public TransactionManager getTransactionManager() {
@@ -307,6 +317,11 @@
                                 + " via TelecomManager#addCall", action, callId),
                                 CODE_CALL_IS_NOT_BEING_TRACKED));
                 callback.send(CODE_CALL_IS_NOT_BEING_TRACKED, exceptionBundle);
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(
+                            CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID,
+                            CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG);
+                }
             }
         }
 
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
index 67ef358..034f02a 100644
--- a/src/com/android/server/telecom/callsequencing/CallSequencingController.java
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -42,7 +42,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
-import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.AnomalyReporterAdapter;
@@ -62,6 +61,7 @@
 import com.android.server.telecom.stats.CallFailureCause;
 
 import java.util.Objects;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -81,6 +81,10 @@
     private final Context mContext;
     private final FeatureFlags mFeatureFlags;
     private static String TAG = CallSequencingController.class.getSimpleName();
+    public static final UUID SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID =
+            UUID.fromString("ea094d77-6ea9-4e40-891e-14bff5d485d7");
+    public static final String SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG =
+            "Cannot hold active call";
 
     public CallSequencingController(CallsManager callsManager, Context context,
             ClockProxy clockProxy, AnomalyReporterAdapter anomalyReporter,
@@ -217,6 +221,12 @@
                         callback.onError(
                                 new CallException("activeCall could not be held or disconnected",
                                 CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                        if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                            mAnomalyReporter.reportAnomaly(
+                                    SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID,
+                                    SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG
+                            );
+                        }
                     }
                     return CompletableFuture.completedFuture(result);
                 }, new LoggedHandlerExecutor(mHandler, "CM.mCAA", mCallsManager.getLock()));
diff --git a/src/com/android/server/telecom/callsequencing/TransactionManager.java b/src/com/android/server/telecom/callsequencing/TransactionManager.java
index 2a6431b..98d54da 100644
--- a/src/com/android/server/telecom/callsequencing/TransactionManager.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionManager.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.AnomalyReporterAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.flags.Flags;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -32,6 +34,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Queue;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 
 public class TransactionManager {
@@ -43,6 +46,12 @@
     private final Deque<CallTransaction> mCompletedTransactions;
     private CallTransaction mCurrentTransaction;
     private boolean mProcessingCallSequencing;
+    private AnomalyReporterAdapter mAnomalyReporter;
+    private FeatureFlags mFeatureFlags;
+    public static final UUID TRANSACTION_MANAGER_TIMEOUT_UUID =
+            UUID.fromString("9ccce52e-6694-4357-9e5e-516a9531b062");
+    public static final String TRANSACTION_MANAGER_TIMEOUT_MSG =
+            "TransactionManager hit a timeout while processing a transaction";
 
     public interface TransactionCompleteListener {
         void onTransactionCompleted(CallTransactionResult result, String transactionName);
@@ -67,6 +76,14 @@
         return INSTANCE;
     }
 
+    public void setFeatureFlag(FeatureFlags flag){
+       mFeatureFlags = flag;
+    }
+
+    public void setAnomalyReporter(AnomalyReporterAdapter callAnomalyReporter){
+        mAnomalyReporter = callAnomalyReporter;
+    }
+
     @VisibleForTesting
     public static TransactionManager getTestInstance() {
         return new TransactionManager();
@@ -109,6 +126,12 @@
                     receiver.onError(new CallException(transactionName + " timeout",
                             CODE_OPERATION_TIMED_OUT));
                     transactionCompleteFuture.complete(false);
+                    if (mFeatureFlags != null && mAnomalyReporter != null &&
+                            mFeatureFlags.enableCallExceptionAnomReports()) {
+                        mAnomalyReporter.reportAnomaly(
+                                TRANSACTION_MANAGER_TIMEOUT_UUID,
+                                TRANSACTION_MANAGER_TIMEOUT_MSG);
+                    }
                 } catch (Exception e) {
                     Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
                             + " %s resulted in an Exception.", transactionName), e);
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
index fea6135..16b6e44 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.telecom.ICallControl;
 import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.TelecomSystem;
@@ -70,6 +71,7 @@
     @Mock private TransactionManager mTransactionManager;
     @Mock private ICallEventCallback mCallEventCallback;
     @Mock private TransactionalServiceRepository mRepository;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
     @Mock private IBinder mIBinder;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
 
@@ -84,7 +86,7 @@
         Mockito.when(mCallEventCallback.asBinder()).thenReturn(mIBinder);
         mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
                 mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                false /*call sequencing*/);
+                false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
     }
 
     @Override
@@ -98,7 +100,7 @@
         TransactionalServiceWrapper service =
                 new TransactionalServiceWrapper(mCallEventCallback,
                         mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                        false /*call sequencing*/);
+                        false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
 
         assertEquals(SERVICE_HANDLE, service.getPhoneAccountHandle());
         assertEquals(1, service.getNumberOfTrackedCalls());
@@ -109,7 +111,7 @@
         TransactionalServiceWrapper service =
                 new TransactionalServiceWrapper(mCallEventCallback,
                         mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                        false /*call sequencing*/);
+                        false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
 
         assertEquals(1, service.getNumberOfTrackedCalls());
         service.trackCall(mMockCall2);