Merge "remove setCallIsSuppressedByDoNotDisturb in Ringer#shouldRingForContact" into udc-dev
diff --git a/Android.bp b/Android.bp
index c5141ca..501b438 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.core_core",
     ],
     libs: [
         "services",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d42dcff..ab067d9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -63,6 +63,8 @@
     <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"/>
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
     <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
diff --git a/res/drawable/gm_phonelink.xml b/res/drawable/gm_phonelink.xml
new file mode 100644
index 0000000..2ffba0e
--- /dev/null
+++ b/res/drawable/gm_phonelink.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M5,6h16L21,4L5,4c-1.1,0 -2,0.9 -2,2v11L1,17v3h11v-3L5,17L5,6zM21,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM20,17h-4v-7h4v7z"/>
+</vector>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 4af6351..883ce52 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -25,7 +25,7 @@
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ચૂકી ગયેલા કૉલ"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> નો કૉલ ચૂકી ગયાં"</string>
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"કૉલ બેક"</string>
-    <string name="notification_missedCall_message" msgid="4054698824390076431">"સંદેશ"</string>
+    <string name="notification_missedCall_message" msgid="4054698824390076431">"મેસેજ"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"ડિસ્કનેક્ટ કરેલો કૉલ"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ઇમર્જન્સી કૉલને કારણે <xliff:g id="CALLER">%s</xliff:g>નો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ઇમર્જન્સી કૉલને કારણે તમારો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 8109de2..68d8078 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -53,7 +53,7 @@
     <string name="no_vm_number" msgid="2179959110602180844">"ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯು ಕಾಣೆಯಾಗಿದೆ"</string>
     <string name="no_vm_number_msg" msgid="1339245731058529388">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ನಲ್ಲಿ ಯಾವುದೇ ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯನ್ನು ಸಂಗ್ರಹಿಸಿಲ್ಲ."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಮಾಡುವುದೇ?"</string>
+    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಆ್ಯಪ್ ಆಗಿ ಮಾಡಬೇಕೆ?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ಡಿಫಾಲ್ಟ್ ಹೊಂದಿಸಿ"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ರದ್ದುಮಾಡಿ"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> ಗೆ ನಿಮ್ಮ ಕರೆಗಳ ಎಲ್ಲಾ ಅಂಶಗಳನ್ನು ನಿಯಂತ್ರಿಸಲು ಮತ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ನೀವು ವಿಶ್ವಾಸವಿರಿಸಿರುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮಾತ್ರ ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಹೊಂದಿಸಬೇಕು."</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d67df4b..ec278f0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -321,6 +321,10 @@
     <string name="notification_channel_disconnected_calls">Disconnected calls</string>
     <!-- Notification channel name for a channel containing crashed phone apps service notifications. -->
     <string name="notification_channel_in_call_service_crash">Crashed phone apps</string>
+    <!-- Notification channel name for a channel containing notifications related to call streaming.
+         Call streaming is a feature where an app can use another device like a tablet to see and
+         control a call taking place on their phone. -->
+    <string name="notification_channel_call_streaming">Call streaming</string>
 
     <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
          ongoing call in the app "other_app". -->
@@ -395,4 +399,20 @@
     <string name="callendpoint_name_streaming">External</string>
     <!-- The user-visible name of the unknown new type CallEndpoint -->
     <string name="callendpoint_name_unknown">Unknown</string>
+
+    <!-- The content of a notification shown when a call is being streamed to another device.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_body">Streaming audio to other device</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will hang up the call.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_hang_up">Hang up</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will move the call back to the phone from the device it is being
+         streamed to.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_switch_here">Switch here</string>
 </resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6ae8834..42f02fb 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -999,6 +999,9 @@
         s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
         s.append("]");
         s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
+        s.append("(User=");
+        s.append(getInitiatingUser());
+        s.append(")");
         s.append("\n\t");
 
         PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
@@ -4116,6 +4119,15 @@
      * @param extras The extras.
      */
     public void onConnectionEvent(String event, Bundle extras) {
+        if (mIsTransactionalCall) {
+            // send the Event directly to the ICS via the InCallController listener
+            for (Listener l : mListeners) {
+                l.onConnectionEvent(this, event, extras);
+            }
+            // Don't run the below block since it applies to Calls that are attached to a
+            // ConnectionService
+            return;
+        }
         // Don't log call quality reports; they're quite frequent and will clog the log.
         if (!Connection.EVENT_CALL_QUALITY_REPORT.equals(event)) {
             Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 6276a7d..d90524d 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -87,6 +87,14 @@
             mStreamingCall = null;
             mTransactionalServiceWrapper = null;
             if (mConnection != null) {
+                // Notify service streaming stopped and then unbind.
+                try {
+                    mService.onCallStreamingStopped();
+                } catch (RemoteException e) {
+                    // Could not notify stop streaming; we're about to just unbind so this is
+                    // unfortunate but not the end of the world.
+                    Log.e(this, e, "resetController: failed to notify stop streaming.");
+                }
                 mContext.unbindService(mConnection);
                 mConnection = null;
             }
@@ -140,7 +148,7 @@
 
         @Override
         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(this, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             if (mEnterInterception) {
@@ -178,9 +186,8 @@
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(this, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
             RoleManager roleManager = mContext.getSystemService(RoleManager.class);
             PackageManager packageManager = mContext.getPackageManager();
             if (roleManager == null || packageManager == null) {
@@ -198,7 +205,7 @@
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
             }
-
+            Log.i(this, "processTransaction: servicePackage=%s", holders.get(0));
             Intent serviceIntent = new Intent(CallStreamingService.SERVICE_INTERFACE);
             serviceIntent.setPackage(holders.get(0));
             List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
@@ -223,7 +230,7 @@
             Intent intent = new Intent(CallStreamingService.SERVICE_INTERFACE);
             intent.setComponent(serviceInfo.getComponentName());
 
-            mConnection =  new CallStreamingServiceConnection(mCall, mWrapper, future);
+            mConnection = new CallStreamingServiceConnection(mCall, mWrapper, future);
             if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
                     | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
@@ -232,7 +239,6 @@
                         VoipCallTransactionResult.RESULT_FAILED,
                         "STREAMING_FAILED_SENDER_BINDING_ERROR"));
             }
-
             return future;
         }
     }
@@ -249,7 +255,7 @@
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(this, "processTransaction");
+            Log.i(this, "processTransaction (unbindStreaming");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             resetController();
@@ -280,11 +286,13 @@
                 case CallState.ON_HOLD:
                     transaction = new CallStreamingStateChangeTransaction(
                             StreamingCall.STATE_HOLDING);
+                    break;
                 case CallState.DISCONNECTING:
                 case CallState.DISCONNECTED:
                     Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
                     transaction = new CallStreamingStateChangeTransaction(
                             StreamingCall.STATE_DISCONNECTED);
+                    break;
                 default:
                     // ignore
             }
@@ -374,13 +382,6 @@
         }
 
         private void clearBinding() {
-            try {
-                if (mService != null) {
-                    mService.onCallStreamingStopped();
-                }
-            } catch (RemoteException e) {
-                Log.e(this, e, "Exception when stop call streaming");
-            }
             resetController();
             if (!mFuture.isDone()) {
                 mFuture.complete(new VoipCallTransactionResult(
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index d457cc8..13a965c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -41,6 +41,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
@@ -132,6 +133,7 @@
 import com.android.server.telecom.stats.CallFailureCause;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
+import com.android.server.telecom.ui.CallStreamingNotification;
 import com.android.server.telecom.ui.ConfirmCallDialogActivity;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
@@ -448,6 +450,7 @@
     private final BlockedNumbersAdapter mBlockedNumbersAdapter;
     private final TransactionManager mTransactionManager;
     private final UserManager mUserManager;
+    private final CallStreamingNotification mCallStreamingNotification;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -559,7 +562,8 @@
             Executor asyncTaskExecutor,
             BlockedNumbersAdapter blockedNumbersAdapter,
             TransactionManager transactionManager,
-            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
+            CallStreamingNotification callStreamingNotification) {
 
         mContext = context;
         mLock = lock;
@@ -648,6 +652,7 @@
         mBlockedNumbersAdapter = blockedNumbersAdapter;
         mCallStreamingController = new CallStreamingController(mContext, mLock);
         mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
+        mCallStreamingNotification = callStreamingNotification;
 
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
@@ -669,6 +674,7 @@
         // this needs to be after the mCallAudioManager
         mListeners.add(mPhoneStateBroadcaster);
         mListeners.add(mVoipCallMonitor);
+        mListeners.add(mCallStreamingNotification);
 
         mVoipCallMonitor.startMonitor();
 
@@ -1414,6 +1420,15 @@
                     extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
                             CallAttributes.SUPPORTS_SET_INACTIVE), true);
             call.setTargetPhoneAccount(phoneAccountHandle);
+            if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                CharSequence displayName = extras.getCharSequence(CallAttributes.DISPLAY_NAME_KEY);
+                if (!TextUtils.isEmpty(displayName)) {
+                    call.setCallerDisplayName(displayName.toString(),
+                            TelecomManager.PRESENTATION_ALLOWED);
+                }
+            }
+            // Incoming address was set via EXTRA_INCOMING_CALL_ADDRESS above.
+            call.setInitiatingUser(phoneAccountHandle.getUserHandle());
         }
 
         // Ensure new calls related to self-managed calls/connections are set as such. This will
@@ -1698,7 +1713,6 @@
         boolean isReusedCall;
         Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
         Call call = reuseOutgoingCall(handle);
-
         PhoneAccount account =
                 mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
         Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
@@ -1739,6 +1753,14 @@
                 call.setConnectionCapabilities(
                         extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
                                 CallAttributes.SUPPORTS_SET_INACTIVE), true);
+                if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                    CharSequence displayName = extras.getCharSequence(
+                            CallAttributes.DISPLAY_NAME_KEY);
+                    if (!TextUtils.isEmpty(displayName)) {
+                        call.setCallerDisplayName(displayName.toString(),
+                                TelecomManager.PRESENTATION_ALLOWED);
+                    }
+                }
                 call.setTargetPhoneAccount(requestedAccountHandle);
             }
 
@@ -6365,4 +6387,30 @@
     public CallStreamingController getCallStreamingController() {
         return mCallStreamingController;
     }
+
+    /**
+     * Given a call identified by call id, get the instance from the list of calls.
+     * @param callId the call id.
+     * @return the call, or null if not found.
+     */
+    public @Nullable Call getCall(@NonNull String callId) {
+        Optional<Call> foundCall = mCalls.stream().filter(
+                c -> c.getId().equals(callId)).findFirst();
+        if (foundCall.isPresent()) {
+            return foundCall.get();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Triggers stopping of call streaming for a call by launching a stop streaming transaction.
+     * @param call the call.
+     */
+    public void stopCallStreaming(@NonNull Call call) {
+        if (call.getTransactionServiceWrapper() == null) {
+            return;
+        }
+        call.getTransactionServiceWrapper().stopCallStreaming(call);
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 0be90e0..523b841 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -101,6 +101,10 @@
     public static final String ACTION_CANCEL_REDIRECTED_CALL =
             "com.android.server.telecom.CANCEL_REDIRECTED_CALL";
 
+    public static final String ACTION_HANGUP_CALL = "com.android.server.telecom.HANGUP_CALL";
+    public static final String ACTION_STOP_STREAMING =
+            "com.android.server.telecom.ACTION_STOP_STREAMING";
+
     public static final String EXTRA_USERHANDLE = "userhandle";
     public static final String EXTRA_REDIRECTION_OUTGOING_CALL_ID =
             "android.telecom.extra.REDIRECTION_OUTGOING_CALL_ID";
@@ -242,6 +246,26 @@
             } finally {
                 Log.endSession();
             }
+        } else if (ACTION_HANGUP_CALL.equals(action)) {
+            Log.startSession("TBIP.aHC", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.disconnectCall(call);
+                }
+            } finally {
+                Log.endSession();
+            }
+        } else if (ACTION_STOP_STREAMING.equals(action)) {
+            Log.startSession("TBIP.aSS", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.stopCallStreaming(call);
+                }
+            } finally {
+                Log.endSession();
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 8477d49..d3ca0b7 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -48,6 +48,7 @@
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.CallStreamingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -350,6 +351,12 @@
                     mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
 
             TransactionManager transactionManager = TransactionManager.getInstance();
+
+            CallStreamingNotification callStreamingNotification =
+                    new CallStreamingNotification(mContext,
+                            packageName -> AppLabelProxy.Util.getAppLabel(
+                                    mContext.getPackageManager(), packageName), asyncTaskExecutor);
+
             mCallsManager = new CallsManager(
                     mContext,
                     mLock,
@@ -386,7 +393,8 @@
                     asyncTaskExecutor,
                     blockedNumbersAdapter,
                     transactionManager,
-                    emergencyCallDiagnosticLogger);
+                    emergencyCallDiagnosticLogger,
+                    callStreamingNotification);
 
             mIncomingCallNotifier = incomingCallNotifier;
             incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index d83e551..ec95f39 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -682,6 +682,7 @@
 
 
     public void stopCallStreaming(Call call) {
+        Log.i(this, "stopCallStreaming; callid=%s", call.getId());
         if (call != null && call.isStreaming()) {
             VoipCallTransaction stopStreamingTransaction = createStopStreamingTransaction(call);
             addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
diff --git a/src/com/android/server/telecom/ui/CallStreamingNotification.java b/src/com/android/server/telecom/ui/CallStreamingNotification.java
new file mode 100644
index 0000000..752d8c8
--- /dev/null
+++ b/src/com/android/server/telecom/ui/CallStreamingNotification.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2023 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.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.telecom.AppLabelProxy;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Class responsible for tracking if there is a call which is being streamed and posting a
+ * notification which informs the user that a call is streaming.  The user has two possible actions:
+ * disconnect the call, bring the call back to the current device (stop streaming).
+ */
+public class CallStreamingNotification extends CallsManagerListenerBase implements Call.Listener {
+    // URI scheme used for data related to the notification actions.
+    public static final String CALL_ID_SCHEME = "callid";
+    // The default streaming notification ID.
+    private static final int STREAMING_NOTIFICATION_ID = 90210;
+    // Tag for streaming notification.
+    private static final String NOTIFICATION_TAG =
+            CallStreamingNotification.class.getSimpleName();
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    // Used to get the app name for the notification.
+    private final AppLabelProxy mAppLabelProxy;
+    // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+    private final Executor mAsyncTaskExecutor;
+    // The call which is treaming.
+    private Call mStreamingCall;
+    // Lock for notification post/remove -- these happen outside the Telecom sync lock.
+    private final Object mNotificationLock = new Object();
+
+    // Whether the notification is showing.
+    @GuardedBy("mNotificationLock")
+    private boolean mIsNotificationShowing = false;
+    @GuardedBy("mNotificationLock")
+    private UserHandle mNotificationUserHandle;
+
+    public CallStreamingNotification(@NonNull Context context,
+            @NonNull AppLabelProxy appLabelProxy,
+            @NonNull Executor asyncTaskExecutor) {
+        mContext = context;
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+        mAppLabelProxy = appLabelProxy;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (call.isStreaming()) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call == mStreamingCall) {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Handles streaming state changes for a call.
+     * @param call the call
+     * @param isStreaming whether it is streaming or not
+     */
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        Log.i(this, "onCallStreamingStateChanged: call=%s, isStreaming=%b", call.getId(),
+                isStreaming);
+
+        if (isStreaming) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        } else {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Handles changes to the caller info for a call.  Used to ensure we can update the photo uri
+     * if one was found.
+     * @param call the call which the caller info changed on.
+     */
+    @Override
+    public void onCallerInfoChanged(Call call) {
+        if (call == mStreamingCall) {
+            Log.i(this, "onCallerInfoChanged: call=%s, photoUri=%b", call.getId(),
+                    call.getContactPhotoUri());
+            enqueueStreamingNotification(call);
+        }
+    }
+
+    /**
+     * Change the streaming call we are tracking.
+     * @param call the call.
+     */
+    private void trackStreamingCall(Call call) {
+        if (mStreamingCall != null) {
+            mStreamingCall.removeListener(this);
+        }
+        mStreamingCall = call;
+        if (mStreamingCall != null) {
+            mStreamingCall.addListener(this);
+        }
+    }
+
+    /**
+     * Enqueue an async task to post/repost the streaming notification.
+     * Note: This happens INSIDE the telecom lock.
+     * @param call the call to post notification for.
+     */
+    private void enqueueStreamingNotification(Call call) {
+        final Bitmap contactPhotoBitmap = call.getPhotoIcon();
+        mAsyncTaskExecutor.execute(() -> {
+            Icon contactPhotoIcon = null;
+            try {
+                if (contactPhotoBitmap != null) {
+                    // Make the icon rounded... because there has to be hoops to jump through.
+                    RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
+                            mContext.getResources(), contactPhotoBitmap);
+                    roundedDrawable.setCornerRadius(Math.max(contactPhotoBitmap.getWidth(),
+                            contactPhotoBitmap.getHeight()) / 2.0f);
+                    contactPhotoIcon = Icon.createWithBitmap(drawableToBitmap(roundedDrawable,
+                            contactPhotoBitmap.getWidth(), contactPhotoBitmap.getHeight()));
+                }
+            } catch (Exception e) {
+                // All loads of things can do wrong when working with bitmaps and images, so to
+                // ensure Telecom doesn't crash, lets try/catch to be sure.
+                Log.e(this, e, "enqueueStreamingNotification: Couldn't build rounded icon");
+            }
+            showStreamingNotification(call.getId(),
+                    call.getUserHandleFromTargetPhoneAccount(), call.getCallerDisplayName(),
+                    call.getHandle(), contactPhotoIcon,
+                    call.getTargetPhoneAccount().getComponentName().getPackageName(),
+                    call.getConnectTimeMillis());
+        });
+    }
+
+    /**
+     * Dequeues the call streaming notification.
+     * Note: This is yo be called within the Telecom sync lock to launch the task to remove the call
+     * streaming notification.
+     */
+    private void dequeueStreamingNotification() {
+        mAsyncTaskExecutor.execute(() -> hideStreamingNotification());
+    }
+
+    /**
+     * Show the call streaming notification.  This is intended to run outside the Telecom sync lock.
+     *
+     * @param callId the call ID we're streaming.
+     * @param userHandle the userhandle for the call.
+     * @param callerName the name of the caller/callee associated with the call
+     * @param callerAddress the address associated with the caller/callee
+     * @param photoIcon the contact photo icon if available
+     * @param appPackageName the package name for the app to post the notification for
+     * @param connectTimeMillis when the call connected (for chronometer in the notification)
+     */
+    private void showStreamingNotification(final String callId, final UserHandle userHandle,
+            String callerName, Uri callerAddress, Icon photoIcon, String appPackageName,
+            long connectTimeMillis) {
+        Log.i(this, "showStreamingNotification; callid=%s, hasPhoto=%b", callId, photoIcon != null);
+
+        // Use the caller name for the label if available, default to app name if none.
+        if (TextUtils.isEmpty(callerName)) {
+            // App did not provide a caller name, so default to app's name.
+            callerName = mAppLabelProxy.getAppLabel(appPackageName).toString();
+        }
+
+        // Action to hangup; this can use the default hangup action from the call style
+        // notification.
+        Intent hangupIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_HANGUP_CALL,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent hangupPendingIntent = PendingIntent.getBroadcast(mContext, 0, hangupIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Action to switch here.
+        Intent switchHereIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_STOP_STREAMING,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent switchHerePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                switchHereIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Apply a span to the string to colorize it using the "answer" color.
+        Spannable spannable = new SpannableString(
+                mContext.getString(R.string.call_streaming_notification_action_switch_here));
+        spannable.setSpan(new ForegroundColorSpan(
+                com.android.internal.R.color.call_notification_answer_color), 0, spannable.length(),
+                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // Use the "phone link" icon per mock.
+        Icon switchHereIcon = Icon.createWithResource(mContext, R.drawable.gm_phonelink);
+        Notification.Action.Builder switchHereBuilder = new Notification.Action.Builder(
+                switchHereIcon,
+                spannable,
+                switchHerePendingIntent);
+        Notification.Action switchHereAction = switchHereBuilder.build();
+
+        // Notifications use a "person" entity to identify caller/callee.
+        Person.Builder personBuilder = new Person.Builder()
+                .setName(callerName);
+
+        // Some apps use phone numbers to identify; these are something the notification framework
+        // can lookup in contacts to provide more data
+        if (callerAddress != null && PhoneAccount.SCHEME_TEL.equals(callerAddress)) {
+            personBuilder.setUri(callerAddress.toString());
+        }
+        if (photoIcon != null) {
+            personBuilder.setIcon(photoIcon);
+        }
+        Person person = personBuilder.build();
+
+        // Call Style notification requires a full screen intent, so we'll just link in a null
+        // pending intent
+        Intent nullIntent = new Intent();
+        PendingIntent nullPendingIntent = PendingIntent.getBroadcast(mContext, 0, nullIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Builder builder = new Notification.Builder(mContext,
+                NotificationChannelManager.CHANNEL_ID_CALL_STREAMING)
+                // Use call style to get the general look and feel for the notification; it provides
+                // a hangup action with the right action already so we can leverage that.  The
+                // "switch here" action will be a custom action defined later.
+                .setStyle(Notification.CallStyle.forOngoingCall(person, hangupPendingIntent))
+                .setSmallIcon(R.drawable.ic_phone)
+                .setContentText(mContext.getString(
+                        R.string.call_streaming_notification_body))
+                // Report call time
+                .setWhen(connectTimeMillis)
+                .setShowWhen(true)
+                .setUsesChronometer(true)
+                // Set the full screen intent; this is just tricking notification manager into
+                // letting us use this style.  Sssh.
+                .setFullScreenIntent(nullPendingIntent, true)
+                .setColorized(true)
+                .addAction(switchHereAction);
+        Notification notification = builder.build();
+
+        synchronized(mNotificationLock) {
+            mIsNotificationShowing = true;
+            mNotificationUserHandle = userHandle;
+            try {
+                mNotificationManager.notifyAsUser(NOTIFICATION_TAG, STREAMING_NOTIFICATION_ID,
+                        notification, userHandle);
+            } catch (Exception e) {
+                // We don't want to crash Telecom if something changes with the requirements for the
+                // notification.
+                Log.e(this, e, "Notification post failed.");
+            }
+        }
+    }
+
+    /**
+     * Removes the posted streaming notification.  Intended to run outside the telecom lock.
+     */
+    private void hideStreamingNotification() {
+        Log.i(this, "hideStreamingNotification");
+        synchronized(mNotificationLock) {
+            if (mIsNotificationShowing) {
+                mIsNotificationShowing = false;
+                mNotificationManager.cancelAsUser(NOTIFICATION_TAG,
+                        STREAMING_NOTIFICATION_ID, mNotificationUserHandle);
+            }
+        }
+    }
+
+    public static Bitmap drawableToBitmap(@Nullable Drawable drawable, int width, int height) {
+        if (drawable == null) {
+            return null;
+        }
+
+        Bitmap bitmap;
+        if (drawable instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            if (width > 0 || height > 0) {
+                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            } else if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+                // Needed for drawables that are just a colour.
+                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            } else {
+                bitmap =
+                        Bitmap.createBitmap(
+                                drawable.getIntrinsicWidth(),
+                                drawable.getIntrinsicHeight(),
+                                Bitmap.Config.ARGB_8888);
+            }
+
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 58794a6..a0baa03 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -40,6 +40,7 @@
     public static final String CHANNEL_ID_AUDIO_PROCESSING = "TelecomBackgroundAudioProcessing";
     public static final String CHANNEL_ID_DISCONNECTED_CALLS = "TelecomDisconnectedCalls";
     public static final String CHANNEL_ID_IN_CALL_SERVICE_CRASH = "TelecomInCallServiceCrash";
+    public static final String CHANNEL_ID_CALL_STREAMING = "TelecomCallStreaming";
 
     private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
@@ -63,6 +64,7 @@
         createOrUpdateChannel(context, CHANNEL_ID_AUDIO_PROCESSING);
         createOrUpdateChannel(context, CHANNEL_ID_DISCONNECTED_CALLS);
         createOrUpdateChannel(context, CHANNEL_ID_IN_CALL_SERVICE_CRASH);
+        createOrUpdateChannel(context, CHANNEL_ID_CALL_STREAMING);
     }
 
     private void createOrUpdateChannel(Context context, String channelId) {
@@ -127,6 +129,14 @@
                 lights = true;
                 vibration = true;
                 sound = null;
+            case CHANNEL_ID_CALL_STREAMING:
+                name = context.getText(R.string.notification_channel_call_streaming);
+                importance = NotificationManager.IMPORTANCE_DEFAULT;
+                canShowBadge = false;
+                lights = false;
+                vibration = false;
+                sound = null;
+                break;
         }
 
         NotificationChannel channel = new NotificationChannel(channelId, name, importance);
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
index c0bb93d..d35030c 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.voip;
 
 import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
 
 import android.os.Bundle;
 import android.telecom.CallAttributes;
@@ -80,6 +81,9 @@
         mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
         mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
         mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        mExtras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                callAttributes.getAddress());
         return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 0b17da2..b2625e6 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.CALL_PRIVILEGED;
 import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
 import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
 
 import android.content.Context;
@@ -126,6 +127,7 @@
         mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
         mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 callAttributes.getCallType());
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
         return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/VoipCallTransaction.java b/src/com/android/server/telecom/voip/VoipCallTransaction.java
index a1cc13c..38f1d54 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransaction.java
+++ b/src/com/android/server/telecom/voip/VoipCallTransaction.java
@@ -18,6 +18,7 @@
 
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.telecom.Log;
 
 import com.android.server.telecom.LoggedHandlerExecutor;
 import com.android.server.telecom.TelecomSystem;
@@ -80,7 +81,11 @@
                             }
                             finish();
                             return null;
-                        });
+                        })
+                .exceptionally((throwable) -> {
+                    Log.e(this, throwable, "Error while executing transaction.");
+                    return null;
+                });;
     }
 
     public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
diff --git a/testapps/streamingtest/Android.bp b/testapps/streamingtest/Android.bp
new file mode 100644
index 0000000..bd0a582
--- /dev/null
+++ b/testapps/streamingtest/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "streamingTestApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/testapps/streamingtest/AndroidManifest.xml b/testapps/streamingtest/AndroidManifest.xml
new file mode 100644
index 0000000..47e4abc
--- /dev/null
+++ b/testapps/streamingtest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          coreApp="true"
+          package="com.android.server.telecom.streamingtest">
+
+    <uses-sdk android:minSdkVersion="28"
+              android:targetSdkVersion="33"/>
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"/>
+
+    <application android:label="Streaming Test App">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name="com.android.server.telecom.streamingtest.StreamingService"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_CALL_STREAMING_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallStreamingService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
new file mode 100644
index 0000000..c76b349
--- /dev/null
+++ b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.streamingtest;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.telecom.CallStreamingService;
+import android.telecom.StreamingCall;
+import android.telecom.Log;
+
+public class StreamingService extends CallStreamingService {
+    @Override
+    public void onCallStreamingStarted(@NonNull StreamingCall call) {
+        Log.i(this, "onCallStreamingStarted: call %s", call);
+    }
+
+    @Override
+    public void onCallStreamingStopped() {
+        Log.i(this, "onCallStreamingStopped");
+    }
+
+    @Override
+    public void onCallStreamingStateChanged(int state) {
+        Log.i(this, "onCallStreamingStateChanged; state=%d", state);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.i(this, "onUnbind");
+        return false;
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
index b868b70..3e53800 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -57,6 +57,11 @@
         setContentView(R.layout.in_call_activity);
 
         Bundle extras = getIntent().getExtras();
+        // Copy the extras with properties like call direction into the extras so the below
+        // code can access them.
+        if (extras != null && extras.containsKey(Utils.sEXTRAS_KEY)) {
+            extras.putAll(extras.getBundle(Utils.sEXTRAS_KEY));
+        }
         if (extras != null) {
             mCallDirection = extras.getInt(Utils.sCALL_DIRECTION_KEY, DIRECTION_INCOMING);
         }
@@ -211,7 +216,7 @@
                         Utils.PHONE_ACCOUNT_HANDLE,
                         mCallDirection,
                         "Alan Turing",
-                        Uri.parse("tel:6506959001")).build();
+                        Uri.parse("tel:+16506959001")).build();
 
         mTelecomManager.addCall(callAttributes, Runnable::run,
                 new OutcomeReceiver<CallControl, CallException>() {
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 22a850f..a9e5fac 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -125,6 +125,7 @@
 import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.CallStreamingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
 import com.android.server.telecom.voip.TransactionManager;
@@ -257,6 +258,7 @@
     @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
     @Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
     @Mock private PhoneCapability mPhoneCapability;
+    @Mock private CallStreamingNotification mCallStreamingNotification;
 
     private CallsManager mCallsManager;
 
@@ -327,7 +329,8 @@
                 command -> command.run(),
                 mBlockedNumbersAdapter,
                 TransactionManager.getTestInstance(),
-                mEmergencyCallDiagnosticLogger);
+                mEmergencyCallDiagnosticLogger,
+                mCallStreamingNotification);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);