Merge "Make InCallController the first call listener to trigger" into main
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 21c8d7c..f0923d5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -128,7 +128,7 @@
     <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ማዳመጫ"</string>
     <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ብሉቱዝ"</string>
     <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ባለገመድ ማዳመጫ"</string>
-    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ድምጽ ማውጫ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ድምፅ ማውጫ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ውጫዊ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ያልታወቀ"</string>
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ኦዲዮን ወደ ሌላ መሣሪያ በመልቀቅ ላይ"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index e68f3c8..5fbe1d3 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -65,7 +65,7 @@
     <string name="blocked_numbers" msgid="8322134197039865180">"Números bloqueados"</string>
     <string name="blocked_numbers_msg" msgid="2797422132329662697">"Não irá receber chamadas ou mensagens de texto de números bloqueados."</string>
     <string name="block_number" msgid="3784343046852802722">"Adicionar um número"</string>
-    <string name="unblock_dialog_body" msgid="2723393535797217261">"Pretende desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_dialog_body" msgid="2723393535797217261">"Quer desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
     <string name="unblock_button" msgid="8732021675729981781">"Desbloquear"</string>
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Bloquear chamadas e mensagens de texto de"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"Número de telefone"</string>
@@ -102,7 +102,7 @@
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Apps Telefone com falhas"</string>
     <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming de chamadas"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ao efetuar esta chamada, irá terminar a chamada na app <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
-    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como pretende efetuar esta chamada"</string>
+    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como quer efetuar esta chamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecionar chamada através de <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Ligar com o meu número de telefone"</string>
     <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Não é possível efetuar uma chamada através da app <xliff:g id="OTHER_APP">%1$s</xliff:g>. Experimente utilizar uma app de redirecionamento de chamadas diferente ou contactar o programador para obter ajuda."</string>
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index a817302..2c70d45 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1241,7 +1241,13 @@
                     // Expected, since we just transitioned here
                     return HANDLED;
                 case SPEAKER_OFF:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    // Check if we already requested to connect to other devices and just waiting
+                    // for their response. In some cases, this SPEAKER_OFF message may come in
+                    // before the response, we can just ignore the message here to not re-evaluate
+                    // the baseline route incorrectly
+                    if (!mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
+                        sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    }
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 3005656..72aecac 100644
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -19,9 +19,12 @@
 import static android.provider.CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
 import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.location.Country;
 import android.location.CountryDetector;
 import android.location.Location;
@@ -30,6 +33,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.PersistableBundle;
+import android.os.UserManager;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.Connection;
@@ -42,6 +46,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
@@ -49,6 +54,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.UUID;
 import java.util.stream.Stream;
 
 /**
@@ -68,16 +74,19 @@
      */
     private static class AddCallArgs {
         public AddCallArgs(Context context, CallLog.AddCallParams params,
-                @Nullable LogCallCompletedListener logCallCompletedListener) {
+                @Nullable LogCallCompletedListener logCallCompletedListener,
+                @NonNull String callId) {
             this.context = context;
             this.params = params;
             this.logCallCompletedListener = logCallCompletedListener;
+            this.callId = callId;
 
         }
         // Since the members are accessed directly, we don't use the
         // mXxxx notation.
         public final Context context;
         public final CallLog.AddCallParams params;
+        public final String callId;
         @Nullable
         public final LogCallCompletedListener logCallCompletedListener;
     }
@@ -88,15 +97,20 @@
     // TODO: come up with a better way to indicate in a android.telecom.DisconnectCause that
     // a conference was merged successfully
     private static final String REASON_IMS_MERGED_SUCCESSFULLY = "IMS_MERGED_SUCCESSFULLY";
+    private static final UUID LOG_CALL_FAILED_ANOMALY_ID =
+            UUID.fromString("1c4c15f3-ab4f-459c-b9ef-43d2988bae82");
+    private static final String LOG_CALL_FAILED_ANOMALY_DESC =
+            "Failed to record a call to the call log.";
 
     private final Context mContext;
     private final CarrierConfigManager mCarrierConfigManager;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
+    private AnomalyReporterAdapter mAnomalyReporterAdapter;
     private static final String ACTION_CALLS_TABLE_ADD_ENTRY =
-                "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
+            "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
     private static final String PERMISSION_PROCESS_CALLLOG_INFO =
-                "android.permission.PROCESS_CALLLOG_INFO";
+            "android.permission.PROCESS_CALLLOG_INFO";
     private static final String CALL_TYPE = "callType";
     private static final String CALL_DURATION = "duration";
 
@@ -104,12 +118,13 @@
     private String mCurrentCountryIso;
 
     public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
-            MissedCallNotifier missedCallNotifier) {
+            MissedCallNotifier missedCallNotifier, AnomalyReporterAdapter anomalyReporterAdapter) {
         mContext = context;
         mCarrierConfigManager = (CarrierConfigManager) mContext
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
+        mAnomalyReporterAdapter = anomalyReporterAdapter;
         mLock = new Object();
     }
 
@@ -263,7 +278,7 @@
      *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
      */
     void logCall(Call call, int callLogType,
-        @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
+            @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
 
         CallLog.AddCallParams.AddCallParametersBuilder paramBuilder =
                 new CallLog.AddCallParams.AddCallParametersBuilder();
@@ -385,7 +400,7 @@
                 okayToLogCall(accountHandle, logNumber, call.isEmergencyCall());
         if (okayToLog) {
             AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
-                    logCallCompletedListener);
+                    logCallCompletedListener, call.getId());
             Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
                     + ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
                     + call.getHandlePresentation());
@@ -517,7 +532,26 @@
                 mListeners[i] = c.logCallCompletedListener;
                 try {
                     // May block.
+                    ContentResolver resolver = c.context.getContentResolver();
+                    Pair<Integer, Integer> startStats = getCallLogStats(resolver);
+                    Log.i(TAG, "LogCall; about to log callId=%s, "
+                                    + "startCount=%d, startMaxId=%d",
+                            c.callId, startStats.first, startStats.second);
+
                     result[i] = Calls.addCall(c.context, c.params);
+                    Pair<Integer, Integer> endStats = getCallLogStats(resolver);
+                    Log.i(TAG, "LogCall; logged callId=%s, uri=%s, "
+                                    + "endCount=%d, endMaxId=%s",
+                            c.callId, result, endStats.first, endStats.second);
+                    if ((endStats.second - startStats.second) <= 0) {
+                        // No call was added or even worse we lost a call in the log.  Trigger an
+                        // anomaly report.  Note: it technically possible that an app modified the
+                        // call log while we were writing to it here; that is pretty unlikely, and
+                        // the goal here is to try and identify potential anomalous conditions with
+                        // logging calls.
+                        mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+                                LOG_CALL_FAILED_ANOMALY_DESC);
+                    }
                 } catch (Exception e) {
                     // This is very rare but may happen in legitimate cases.
                     // E.g. If the phone is encrypted and thus write request fails, it may cause
@@ -526,8 +560,10 @@
                     //
                     // We don't want to crash the whole process just because of that, so just log
                     // it instead.
-                    Log.e(TAG, e, "Exception raised during adding CallLog entry.");
+                    Log.e(TAG, e, "LogCall: Exception raised adding callId=%s", c.callId);
                     result[i] = null;
+                    mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+                            LOG_CALL_FAILED_ANOMALY_DESC);
                 }
             }
             return result;
@@ -602,4 +638,56 @@
             return mCurrentCountryIso;
         }
     }
+
+
+    /**
+     * Returns a pair containing the number of rows in the call log, as well as the maximum call log
+     * ID.  There is a limit of 500 entries in the call log for a phone account, so once we hit 500
+     * we can reasonably expect that number to not change before and after logging a call.
+     * We determine the maximum ID in the call log since this is a way we can objectively check if
+     * the provider did record a call log entry or not.  Ideally there should me more call log
+     * entries after logging than before, and certainly not less.
+     * @param resolver content resolver
+     * @return pair with number of rows in the call log and max id.
+     */
+    private Pair<Integer, Integer> getCallLogStats(@NonNull ContentResolver resolver) {
+        try {
+            final UserManager userManager = mContext.getSystemService(UserManager.class);
+            final int currentUserId = userManager.getProcessUserId();
+
+            // Use shadow provider based on current user unlock state.
+            Uri providerUri;
+            if (userManager.isUserUnlocked(currentUserId)) {
+                providerUri = Calls.CONTENT_URI;
+            } else {
+                providerUri = Calls.SHADOW_CONTENT_URI;
+            }
+            int maxCallId = -1;
+            int numFound;
+            Cursor countCursor = resolver.query(providerUri,
+                    new String[]{Calls._ID},
+                    null,
+                    null,
+                    Calls._ID + " DESC");
+            try {
+                numFound = countCursor.getCount();
+                if (numFound > 0) {
+                    countCursor.moveToFirst();
+                    maxCallId = countCursor.getInt(0);
+                }
+            } finally {
+                countCursor.close();
+            }
+            return new Pair<>(numFound, maxCallId);
+        } catch (Exception e) {
+            // Oh jeepers, we crashed getting the call count.
+            Log.e(TAG, e, "getCountOfCallLogRows: failed");
+            return new Pair<>(-1, -1);
+        }
+    }
+
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+        mAnomalyReporterAdapter = anomalyReporterAdapter;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index b405acd..abf3478 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -657,7 +657,8 @@
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
-        mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier);
+        mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier,
+                mAnomalyReporter);
         mConnectionServiceRepository =
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
@@ -1383,8 +1384,11 @@
     }
 
     @VisibleForTesting
-    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
-        mAnomalyReporter = mAnomalyReporterAdapter;
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+        mAnomalyReporter = anomalyReporterAdapter;
+        if (mCallLogManager != null) {
+            mCallLogManager.setAnomalyReporterAdapter(anomalyReporterAdapter);
+        }
     }
 
     void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 598664e..166ebd9 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -2885,4 +2885,25 @@
             return userFromCall;
         }
     }
+
+    /**
+     * Useful for debugging purposes and called on the command line via
+     * an "adb shell telecom command".
+     *
+     * @return true if a particular non-ui InCallService package is bound in a call.
+     */
+    public boolean isNonUiInCallServiceBound(String packageName) {
+        for (NonUIInCallServiceConnectionCollection ics : mNonUIInCallServiceConnections.values()) {
+            for (InCallServiceBindingConnection connection : ics.getSubConnections()) {
+                InCallServiceInfo serviceInfo = connection.mInCallServiceInfo;
+                Log.i(this, "isNonUiInCallServiceBound: found serviceInfo=[%s]", serviceInfo);
+                if (serviceInfo != null &&
+                        serviceInfo.mComponentName.getPackageName().contains(packageName)) {
+                    Log.i(this, "isNonUiInCallServiceBound: found target package");
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 7d3eeb6..9b48bc2 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -2212,6 +2212,39 @@
         }
 
         /**
+         * A method intended for use in testing to query whether a particular non-ui inCallService
+         * is bound in a call.
+         * @param packageName of the service to query.
+         * @return whether it is bound or not.
+         */
+        @Override
+        public boolean isNonUiInCallServiceBound(String packageName) {
+            Log.startSession("TCI.iNUICSB");
+            try {
+                synchronized (mLock) {
+                    enforceShellOnly(Binder.getCallingUid(), "isNonUiInCallServiceBound");
+                    if (!(mContext.checkCallingOrSelfPermission(READ_PHONE_STATE)
+                            == PackageManager.PERMISSION_GRANTED) ||
+                            !(mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+                                    == PackageManager.PERMISSION_GRANTED)) {
+                        throw new SecurityException("isNonUiInCallServiceBound requires the"
+                                + " READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE permission");
+                    }
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mCallsManager
+                                .getInCallController()
+                                .isNonUiInCallServiceBound(packageName);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * A method intended for use in testing to reset car mode at all priorities.
          *
          * Runs during setup to avoid cascading failures from failing car mode CTS.
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index fc55d27..cc9c769 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -61,7 +61,8 @@
             public void onGroupStatusChanged(int groupId, int groupStatus) {}
             @Override
             public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
-                Log.i(this, device.getAddress() + " group added " + groupId);
+                Log.i(this, (device == null ? "device is null" : device.getAddress())
+                        + " group added " + groupId);
                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
                     Log.w(this, "invalid parameter");
                     return;
@@ -73,6 +74,8 @@
             }
             @Override
             public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+                Log.i(this, (device == null ? "device is null" : device.getAddress())
+                        + " group removed " + groupId);
                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
                     Log.w(this, "invalid parameter");
                     return;
@@ -88,7 +91,7 @@
             new BluetoothProfile.ServiceListener() {
                 @Override
                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    Log.startSession("BMSL.oSC");
+                    Log.startSession("BPSL.oSC");
                     try {
                         synchronized (mLock) {
                             String logString;
@@ -104,9 +107,13 @@
                                 logString = "Got BluetoothLeAudio: "
                                         + mBluetoothLeAudioService;
                                 if (!mLeAudioCallbackRegistered) {
-                                    mBluetoothLeAudioService.registerCallback(
-                                                mExecutor, mLeAudioCallbacks);
-                                    mLeAudioCallbackRegistered = true;
+                                    try {
+                                        mBluetoothLeAudioService.registerCallback(
+                                                    mExecutor, mLeAudioCallbacks);
+                                        mLeAudioCallbackRegistered = true;
+                                    } catch (IllegalStateException e) {
+                                        logString += ", but Bluetooth is down";
+                                    }
                                 }
                             } else {
                                 logString = "Connected to non-requested bluetooth service." +
@@ -122,7 +129,7 @@
 
                 @Override
                 public void onServiceDisconnected(int profile) {
-                    Log.startSession("BMSL.oSD");
+                    Log.startSession("BPSL.oSD");
                     try {
                         synchronized (mLock) {
                             LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
index 36f2077..64060c8 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -126,6 +126,7 @@
                     .setShouldReject(true)
                     .setShouldAddToCallLog(true)
                     .setShouldShowNotification(false)
+                    .setShouldSilence(true)
                     .setCallBlockReason(getBlockReason(blockStatus))
                     .setCallScreeningAppName(null)
                     .setCallScreeningComponentName(null)
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 963e923..05e73d5 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -175,6 +175,20 @@
                     Log.endSession();
                 }
             }
+
+            @Override
+            public void onBindingDied(ComponentName componentName) {
+                // Make sure we unbind the service if binding died to avoid background stating
+                // activity leaks
+                Log.startSession("CRSC.oBD");
+                try {
+                    synchronized (mTelecomLock) {
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
         }
 
         private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
diff --git a/testapps/transactionalVoipApp/res/values-am/strings.xml b/testapps/transactionalVoipApp/res/values-am/strings.xml
index 120a9b9..d71c287 100644
--- a/testapps/transactionalVoipApp/res/values-am/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-am/strings.xml
@@ -29,7 +29,7 @@
     <string name="set_call_inactive" msgid="7106775211368705195">"ወደ ገቢር ያልሆነ ተቀናብሯል"</string>
     <string name="disconnect_call" msgid="1349412380315371385">"ግንኙነትን ያቋርጡ"</string>
     <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ማዳመጫ"</string>
-    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ድምጽ ማውጫ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ድምፅ ማውጫ"</string>
     <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ብሉቱዝ"</string>
     <string name="start_stream" msgid="3567634786280097431">"ዥረት ይጀምሩ"</string>
     <string name="crash_app" msgid="2548690390730057704">"ለየት ያለ ነገርን ይጣሉ"</string>
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
index 7df4f29..a98c1ee 100644
--- a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -72,6 +72,7 @@
     private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult.Builder()
             .setShouldAllowCall(false)
             .setShouldReject(true)
+            .setShouldSilence(true)
             .setShouldAddToCallLog(true)
             .setShouldShowNotification(false)
             .setCallBlockReason(CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER)
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index d93cb2c..a4dd1fe 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -63,6 +63,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -931,6 +932,48 @@
         assertEquals(initState, stateMachine.getCurrentCallAudioState());
     }
 
+    @SmallTest
+    @Test
+    public void testIgnoreSpeakerOffMessage() {
+        when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(true);
+        when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                .thenReturn(bluetoothDevice1);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        doAnswer(
+                (address) -> {
+                    stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SPEAKER_OFF);
+                    stateMachine.sendMessageDelayed(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED,
+                            5000L);
+                    return null;
+        }).when(mockBluetoothRouteManager).connectBluetoothAudio(anyString());
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
+
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_EARPIECE);
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
     private void initializationTestHelper(CallAudioState expectedState,
             int earpieceControl) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
diff --git a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
index b81479b..9101a19 100644
--- a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
@@ -229,17 +229,26 @@
         // verify the initial state of the test
         assertEquals(2, btEndpoints.size());
         assertEquals(CallEndpoint.TYPE_BLUETOOTH, currentEndpoint.getEndpointType());
-        assertEquals(currentEndpoint, btEndpoints.get(0));
-        assertNotEquals(currentEndpoint, btEndpoints.get(1));
+
+        CallEndpoint otherBluetoothEndpoint = null;
+        for (CallEndpoint e : btEndpoints) {
+            if (!e.equals(currentEndpoint)) {
+                otherBluetoothEndpoint = e;
+            }
+        }
+
+        assertNotNull(otherBluetoothEndpoint);
+        assertNotEquals(currentEndpoint, otherBluetoothEndpoint);
 
         // request an endpoint change from BT D1 --> BT D2
         doReturn(audioState2).when(mCallAudioManager).getCallAudioState();
-        mCallEndpointController.requestCallEndpointChange(btEndpoints.get(1), mResultReceiver);
+        mCallEndpointController.requestCallEndpointChange(otherBluetoothEndpoint, mResultReceiver);
 
         // verify the transaction was successful and CallAudioManager#setAudioRoute was called
         verify(mResultReceiver, never()).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
-        verify(mCallAudioManager, times(1)).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
-                eq(bluetoothDevice2.getAddress()));
+        verify(mCallAudioManager, times(1))
+                .setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+                        eq(bluetoothDevice2.getAddress()));
     }
 
     @Test
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 9466220..c68cbbf 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -65,6 +65,7 @@
 import androidx.test.filters.FlakyTest;
 
 import com.android.server.telecom.Analytics;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallLogManager;
 import com.android.server.telecom.CallState;
@@ -123,6 +124,8 @@
     PhoneAccountRegistrar mMockPhoneAccountRegistrar;
     @Mock
     MissedCallNotifier mMissedCallNotifier;
+    @Mock
+    AnomalyReporterAdapter mAnomalyReporterAdapter;
 
     @Override
     @Before
@@ -130,7 +133,7 @@
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mCallLogManager = new CallLogManager(mContext, mMockPhoneAccountRegistrar,
-                mMissedCallNotifier);
+                mMissedCallNotifier, mAnomalyReporterAdapter);
         mDefaultAccountHandle = new PhoneAccountHandle(
                 new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
                 TEST_PHONE_ACCOUNT_ID,