resolve merge conflicts of e4653aa85 to stage-aosp-master

Change-Id: I9e7b395d1c12fe65f52f58df44fc670aee2ec80c
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b59c29c..ef4025b 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -31,7 +31,7 @@
     <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"U gužvi sam. O čemu se radi?"</string>
     <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Pozvaću te uskoro."</string>
     <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Pozvaću te kasnije."</string>
-    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"U gužvi sam. Da se čujemo kasnije?"</string>
+    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"U gužvi sam. Zoveš me kasnije?"</string>
     <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Brzi odgovori"</string>
     <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Izmena brzih odgovora"</string>
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
@@ -67,4 +67,20 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"Nije moguće blokirati broj hitne službe."</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> je već blokiran."</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"Korišćenje brojčanika iz ličnog profila za upućivanje poziva"</string>
+    <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g> poziv od <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_VIA">%1$s</xliff:g> video poziv od <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="answering_ends_other_call" msgid="8282145910153766401">"Ako odgovorite, završićete <xliff:g id="CALL_VIA">%1$s</xliff:g> poziv"</string>
+    <string name="answering_ends_other_calls" msgid="1198589551399049197">"Ako odgovorite, završićete <xliff:g id="CALL_VIA">%1$s</xliff:g> pozive"</string>
+    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"Ako odgovorite, završićete <xliff:g id="CALL_VIA">%1$s</xliff:g> video poziv"</string>
+    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"Ako odgovorite, završićete poziv koji je u toku"</string>
+    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"Ako odgovorite, završićete pozive koji su u toku"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"Ako odgovorite, završićete video poziv koji je u toku"</string>
+    <string name="answer_incoming_call" msgid="4140530013111794587">"Odgovori"</string>
+    <string name="decline_incoming_call" msgid="806026168661598368">"Odbij"</string>
+    <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"Ne možete da uputite poziv zbog <xliff:g id="OTHER_CALL">%1$s</xliff:g> poziva."</string>
+    <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"Ne možete da uputite poziv zbog <xliff:g id="OTHER_CALL">%1$s</xliff:g> poziva."</string>
+    <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Ne možete da uputite poziv zbog poziva u drugoj aplikaciji."</string>
+    <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Dolazni pozivi"</string>
+    <string name="notification_channel_missed_call" msgid="8727062678632713146">"Propušteni pozivi"</string>
+    <string name="alert_outgoing_call" msgid="982908156825958001">"Ako uputite ovaj poziv, završićete <xliff:g id="OTHER_APP">%1$s</xliff:g> poziv."</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 5d44ed9..9950e14 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="telecommAppLabel" product="default" msgid="382363169988504520">"Кіраванне выклікамі"</string>
-    <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Патэлефанаваць"</string>
+    <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Тэлефон"</string>
     <string name="unknown" msgid="6878797917991465859">"Невядомы"</string>
     <string name="notification_missedCallTitle" msgid="7554385905572364535">"Прапушчаны выклік"</string>
     <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Прапушчаны выклік па працы"</string>
@@ -50,7 +50,7 @@
     <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"Скасаваць"</string>
     <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> зможа вызначаць і кантраляваць усе аспекты выклікаў. Стандартнымі тэлефоннымі праграмамі павінны прызначацца толькі праграмы, якім вы давяраеце."</string>
     <string name="blocked_numbers" msgid="2751843139572970579">"Заблакіраваныя нумары"</string>
-    <string name="blocked_numbers_msg" msgid="1045015186124965643">"Вы не будзеце атрымліваць выклікі ці SMS з заблакаваных нумароў."</string>
+    <string name="blocked_numbers_msg" msgid="1045015186124965643">"Вы не будзеце атрымліваць выклікі ці SMS з заблакіраваных нумароў."</string>
     <string name="block_number" msgid="1101252256321306179">"Дадаць нумар"</string>
     <string name="unblock_dialog_body" msgid="1614238499771862793">"Разблакіраваць <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
     <string name="unblock_button" msgid="3078048901972674170">"Разблакiраваць"</string>
@@ -67,4 +67,20 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"Немагчыма заблакіраваць нумар экстранай службы."</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ужо заблакіраваны."</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"Выкарыстанне асабістага набіральніка нумара для выканання выкліку"</string>
+    <string name="notification_incoming_call" msgid="7713197997773986670">"Выклік <xliff:g id="CALL_VIA">%1$s</xliff:g> ад <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="notification_incoming_video_call" msgid="6638486071698373893">"Відэавыклік <xliff:g id="CALL_VIA">%1$s</xliff:g> ад <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="answering_ends_other_call" msgid="8282145910153766401">"Адказ на гэты выклік завершыць ваш выклік <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_calls" msgid="1198589551399049197">"Адказ на гэты выклік завершыць вашы выклікі <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"Адказ на гэты выклік завершыць ваш бягучы відэавыклік <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"Адказ на гэты выклік завершыць ваш бягучы выклік"</string>
+    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"Адказ на гэты выклік завершыць вашы бягучыя выклікі"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"Адказ на гэты выклік завершыць ваш бягучы відэавыклік"</string>
+    <string name="answer_incoming_call" msgid="4140530013111794587">"Адказаць"</string>
+    <string name="decline_incoming_call" msgid="806026168661598368">"Адхіліць"</string>
+    <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"Выклік немагчыма выканаць, бо ідзе выклік <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
+    <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"Выклік немагчыма выканаць, бо ідуць выклікі <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
+    <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Выклік немагчыма выканаць, бо ідзе выклік у іншай праграме."</string>
+    <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Уваходныя выклікі"</string>
+    <string name="notification_channel_missed_call" msgid="8727062678632713146">"Прапушчаныя выклікі"</string>
+    <string name="alert_outgoing_call" msgid="982908156825958001">"Калі зрабіць гэты выклік, ваш выклік праз праграму <xliff:g id="OTHER_APP">%1$s</xliff:g> скончыцца."</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index e1571b3..63bccaa 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -28,10 +28,10 @@
     <string name="notification_missedCall_message" msgid="3049928912736917988">"Poruka"</string>
     <string name="accessibility_call_muted" msgid="2776111226185342220">"Zvuk poziva je isključen."</string>
     <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Zvučnik je omogućen."</string>
-    <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Ne mogu sada pričati. Šta ima?"</string>
+    <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Ne mogu sada pričati. O čemu se radi?"</string>
     <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Nazvat ću te uskoro."</string>
     <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Nazvat ću te kasnije."</string>
-    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ne mogu pričati. Nazovi kasnije?"</string>
+    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ne mogu pričati. Nazovi me kasnije."</string>
     <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Brzi odgovori"</string>
     <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Uredi brze odgovore"</string>
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
@@ -54,7 +54,7 @@
     <string name="block_number" msgid="1101252256321306179">"Dodaj broj"</string>
     <string name="unblock_dialog_body" msgid="1614238499771862793">"Deblokirati <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
     <string name="unblock_button" msgid="3078048901972674170">"Deblokiraj"</string>
-    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokiraj pozive i tekstualne poruke od"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokiraj pozive i poruke od"</string>
     <string name="add_blocked_number_hint" msgid="6847675097085433553">"Broj telefona"</string>
     <string name="block_button" msgid="8822290682524373357">"Blokiraj"</string>
     <string name="non_primary_user" msgid="5180129233352533459">"Samo vlasnik uređaja može pregledati i upravljati blokiranim brojevima."</string>
@@ -67,4 +67,20 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"Nije moguće blokirati broj za hitne slučajeve."</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> je već blokiran."</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"Za upućivanje poziva koristi se lična brojčana tastatura"</string>
+    <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g> poziv od osobe <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_VIA">%1$s</xliff:g> videopoziv od osobe <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+    <string name="answering_ends_other_call" msgid="8282145910153766401">"Odgovaranje će prekinuti poziv: <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_calls" msgid="1198589551399049197">"Odgovaranje će prekinuti pozive: <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"Odgovaranje će prekinuti videopoziv: <xliff:g id="CALL_VIA">%1$s</xliff:g>"</string>
+    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"Odgovaranje će prekinuti poziv koji je u toku"</string>
+    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"Odgovaranje će prekinuti pozive koji su u toku"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"Odgovaranje će prekinuti videopoziv koji je u toku"</string>
+    <string name="answer_incoming_call" msgid="4140530013111794587">"Odgovori"</string>
+    <string name="decline_incoming_call" msgid="806026168661598368">"Odbij"</string>
+    <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"Pozivanje nije moguće zbog poziva: <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
+    <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"Pozivanje nije moguće zbog poziva: <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
+    <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Pozivanje nije moguće zbog poziva u drugoj aplikaciji."</string>
+    <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Dolazni pozivi"</string>
+    <string name="notification_channel_missed_call" msgid="8727062678632713146">"Propušteni pozivi"</string>
+    <string name="alert_outgoing_call" msgid="982908156825958001">"Upućivanje ovog poziva će prekinuti poziv: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 26d0c42..218db6a 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -38,7 +38,7 @@
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Gyors válasz"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Üzenet elküldve ide: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Telefonos fiókok"</string>
-    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Csak segélyhívás engedélyezett."</string>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Csak vészhívás engedélyezett."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Az alkalmazásból nem lehet kimenő hívást kezdeményezni a Telefon (Phone) engedély nélkül."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Hívásindításhoz adjon meg egy érvényes számot."</string>
     <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Jelenleg nem lehet videohívást hozzáadni."</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 714ddc9..d789e31 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -50,7 +50,7 @@
     <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"Bekor qilish"</string>
     <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> ilovasi qo‘ng‘iroq qilishga yodam beradi va qo‘ng‘iroqlarga tegishli boshqa barcha parametrlarni boshqaradi. Qo‘ng‘iroqlar uchun faqat ishonarli ilovani asosiy ilova qilib o‘rnatish lozim."</string>
     <string name="blocked_numbers" msgid="2751843139572970579">"Bloklangan raqamlar"</string>
-    <string name="blocked_numbers_msg" msgid="1045015186124965643">"Bloklangan telefon raqamlaridan chaqiruv yoki matnli xabarlar qabul qilib bo‘lmaydi."</string>
+    <string name="blocked_numbers_msg" msgid="1045015186124965643">"Bloklangan raqamlardan keladigan chaqiruv yoki SMS xabarlar qabul qilinmaydi."</string>
     <string name="block_number" msgid="1101252256321306179">"Biror raqamni bloklash"</string>
     <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> raqami blokdan chiqarilsinmi?"</string>
     <string name="unblock_button" msgid="3078048901972674170">"Blokdan chiqarish"</string>
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
index 0ece427..723b041 100644
--- a/scripts/telecom_testing.sh
+++ b/scripts/telecom_testing.sh
@@ -10,6 +10,7 @@
                           running tests (mmma).
   -e                    Run code coverage. Coverage will be output into the coverage/
                           directory in the repo root.
+  -g                    Run build commands with USE_GOMA=true
   -h                    This help message.
   "
 
@@ -20,8 +21,9 @@
   local installwdep=false
   local debug=false
   local coverage=false
+  local goma=false
 
-  while getopts "c:p:hadie" opt; do
+  while getopts "c:p:hadieg" opt; do
     case "$opt" in
       h)
         echo "$usage"
@@ -40,6 +42,8 @@
         installwdep=true;;
       e)
         coverage=true;;
+      g)
+        goma=true;;
       p)
         project=$OPTARG;;
     esac
@@ -67,6 +71,8 @@
   if [ $install = true ] ; then
     local olddir=$(pwd)
     local emma_opt=
+    local goma_opt=
+
     cd $T
     # Build and exit script early if build fails
 
@@ -76,10 +82,14 @@
       emma_opt="EMMA_INSTRUMENT=false"
     fi
 
+    if [ $goma = true ] ; then
+        goma_opt="USE_GOMA=true"
+    fi
+
     if [ $installwdep = true ] ; then
-      (export ${emma_opt}; mmma -j40 "$build_dir")
+      (export ${emma_opt}; mmma ${goma_opt} -j40 "$build_dir")
     else
-      (export ${emma_opt}; mmm "$build_dir")
+      (export ${emma_opt}; mmm ${goma_opt} "$build_dir")
     fi
     if [ $? -ne 0 ] ; then
       echo "Make failed! try using -a instead of -i if building with coverage"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 62f5eaa..90c1fe8 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -25,12 +25,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.CallAudioState;
 import android.telecom.Conference;
+import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
 import android.telecom.Connection;
 import android.telecom.GatewayInfo;
@@ -128,6 +130,8 @@
         void onExternalCallChanged(Call call, boolean isExternalCall);
         void onRttInitiationFailure(Call call, int reason);
         void onRemoteRttRequest(Call call, int requestId);
+        void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
+                                 Bundle extras);
     }
 
     public abstract static class ListenerBase implements Listener {
@@ -199,6 +203,9 @@
         public void onRttInitiationFailure(Call call, int reason) {}
         @Override
         public void onRemoteRttRequest(Call call, int requestId) {}
+        @Override
+        public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
+                                        Bundle extras) {}
     }
 
     private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -469,6 +476,23 @@
     private int mPendingRttRequestId = INVALID_RTT_REQUEST_ID;
 
     /**
+     * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
+     * int, Bundle)}, contains the call which this call is being handed over to.
+     */
+    private Call mHandoverDestinationCall = null;
+
+    /**
+     * When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
+     * int, Bundle)}, contains the call which this call is being handed over from.
+     */
+    private Call mHandoverSourceCall = null;
+
+    /**
+     * Indicates the current state of this call if it is in the process of a handover.
+     */
+    private int mHandoverState = HandoverState.HANDOVER_NONE;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      *  @param context The context.
      * @param repository The connection service repository.
@@ -663,6 +687,23 @@
 
         s.append("\n\tTo address: ");
         s.append(Log.piiHandle(getHandle()));
+        s.append(" Presentation: ");
+        switch (getHandlePresentation()) {
+            case TelecomManager.PRESENTATION_ALLOWED:
+                s.append("Allowed");
+                break;
+            case TelecomManager.PRESENTATION_PAYPHONE:
+                s.append("Payphone");
+                break;
+            case TelecomManager.PRESENTATION_RESTRICTED:
+                s.append("Restricted");
+                break;
+            case TelecomManager.PRESENTATION_UNKNOWN:
+                s.append("Unknown");
+                break;
+            default:
+                s.append("<undefined>");
+        }
         s.append("\n");
         return s.toString();
     }
@@ -766,6 +807,8 @@
                 return;
             }
 
+            updateVideoHistoryViaState(mState, newState);
+
             mState = newState;
             maybeLoadCannedSmsResponses();
 
@@ -779,12 +822,6 @@
                     mAnalytics.setCallStartTime(mConnectTimeMillis);
                 }
 
-                // Video state changes are normally tracked against history when a call is active.
-                // When the call goes active we need to be sure we track the history in case the
-                // state never changes during the duration of the call -- we want to ensure we
-                // always know the state at the start of the call.
-                mVideoStateHistory = mVideoStateHistory | mVideoState;
-
                 // We're clearly not disconnected, so reset the disconnected time.
                 mDisconnectTimeMillis = 0;
                 mDisconnectElapsedTimeMillis = 0;
@@ -795,11 +832,6 @@
                 setLocallyDisconnecting(false);
                 fixParentAfterDisconnect();
             }
-            if (mState == CallState.DISCONNECTED &&
-                    mDisconnectCause.getCode() == DisconnectCause.MISSED) {
-                // Ensure when an incoming call is missed that the video state history is updated.
-                mVideoStateHistory |= mVideoState;
-            }
 
             // Log the state transition event
             String event = null;
@@ -1042,6 +1074,30 @@
         return phoneAccount.getLabel();
     }
 
+    /**
+     * Determines if this Call should be written to the call log.
+     * @return {@code true} for managed calls or for self-managed calls which have the
+     * {@link PhoneAccount#EXTRA_LOG_SELF_MANAGED_CALLS} extra set.
+     */
+    public boolean isLoggedSelfManaged() {
+        if (!isSelfManaged()) {
+            // Managed calls are always logged.
+            return true;
+        }
+        if (getTargetPhoneAccount() == null) {
+            return false;
+        }
+        PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
+                .getPhoneAccountUnchecked(getTargetPhoneAccount());
+
+        if (phoneAccount == null) {
+            return false;
+        }
+
+        return phoneAccount.getExtras() != null && phoneAccount.getExtras().getBoolean(
+                PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, false);
+    }
+
     @VisibleForTesting
     public boolean isIncoming() {
         return mCallDirection == CALL_DIRECTION_INCOMING;
@@ -1071,6 +1127,58 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public void markFinishedHandoverStateAndCleanup(int handoverState) {
+        if (mHandoverSourceCall != null) {
+            mHandoverSourceCall.setHandoverState(handoverState);
+        } else if (mHandoverDestinationCall != null) {
+            mHandoverDestinationCall.setHandoverState(handoverState);
+        }
+        setHandoverState(handoverState);
+        maybeCleanupHandover();
+    }
+
+    public void maybeCleanupHandover() {
+        if (mHandoverSourceCall != null) {
+            mHandoverSourceCall.setHandoverSourceCall(null);
+            mHandoverSourceCall.setHandoverDestinationCall(null);
+            mHandoverSourceCall = null;
+        } else if (mHandoverDestinationCall != null) {
+            mHandoverDestinationCall.setHandoverSourceCall(null);
+            mHandoverDestinationCall.setHandoverDestinationCall(null);
+            mHandoverDestinationCall = null;
+        }
+    }
+
+    public boolean isHandoverInProgress() {
+        return mHandoverSourceCall != null || mHandoverDestinationCall != null;
+    }
+
+    public Call getHandoverDestinationCall() {
+        return mHandoverDestinationCall;
+    }
+
+    public void setHandoverDestinationCall(Call call) {
+        mHandoverDestinationCall = call;
+    }
+
+    public Call getHandoverSourceCall() {
+        return mHandoverSourceCall;
+    }
+
+    public void setHandoverSourceCall(Call call) {
+        mHandoverSourceCall = call;
+    }
+
+    public void setHandoverState(int handoverState) {
+        Log.d(this, "setHandoverState: callId=%s, handoverState=%s", getId(),
+                HandoverState.stateToString(handoverState));
+        mHandoverState = handoverState;
+    }
+
+    public int getHandoverState() {
+        return mHandoverState;
+    }
+
     private void configureIsWorkCall() {
         PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
         boolean isWorkCall = false;
@@ -1401,7 +1509,6 @@
         setVideoProvider(connection.getVideoProvider());
         setVideoState(connection.getVideoState());
         setRingbackRequested(connection.isRingbackRequested());
-        setIsVoipAudioMode(connection.getIsVoipAudioMode());
         setStatusHints(connection.getStatusHints());
         putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());
 
@@ -1895,7 +2002,38 @@
      */
     public void sendCallEvent(String event, Bundle extras) {
         if (mConnectionService != null) {
-            mConnectionService.sendCallEvent(this, event, extras);
+            if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
+                // Handover requests are targeted at Telecom, not the ConnectionService.
+                if (extras == null) {
+                    Log.w(this, "sendCallEvent: %s event received with null extras.",
+                            android.telecom.Call.EVENT_REQUEST_HANDOVER);
+                    mConnectionService.sendCallEvent(this,
+                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    return;
+                }
+                Parcelable parcelable = extras.getParcelable(
+                        android.telecom.Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE);
+                if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
+                    Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
+                            android.telecom.Call.EVENT_REQUEST_HANDOVER);
+                    mConnectionService.sendCallEvent(this,
+                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    return;
+                }
+                PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
+                int videoState = extras.getInt(android.telecom.Call.EXTRA_HANDOVER_VIDEO_STATE,
+                        VideoProfile.STATE_AUDIO_ONLY);
+                Parcelable handoverExtras = extras.getParcelable(
+                        android.telecom.Call.EXTRA_HANDOVER_EXTRAS);
+                Bundle handoverExtrasBundle = null;
+                if (handoverExtras instanceof Bundle) {
+                    handoverExtrasBundle = (Bundle) handoverExtras;
+                }
+                requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle);
+            } else {
+                Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
+                mConnectionService.sendCallEvent(this, event, extras);
+            }
         } else {
             Log.e(this, new NullPointerException(),
                     "sendCallEvent failed due to null CS callId=%s", getId());
@@ -2371,13 +2509,14 @@
             videoState = VideoProfile.STATE_AUDIO_ONLY;
         }
 
-        // Track which video states were applicable over the duration of the call.
-        // Only track the call state when the call is active or disconnected.  This ensures we do
-        // not include the video state when:
+        // Track Video State history during the duration of the call.
+        // Only update the history when the call is active or disconnected. This ensures we do
+        // not include the video state history when:
         // - Call is incoming (but not answered).
         // - Call it outgoing (but not answered).
         // We include the video state when disconnected to ensure that rejected calls reflect the
         // appropriate video state.
+        // For all other times we add to the video state history, see #setState.
         if (isActive() || getState() == CallState.DISCONNECTED) {
             mVideoStateHistory = mVideoStateHistory | videoState;
         }
@@ -2610,4 +2749,35 @@
         return capabilities & ~(Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL |
                 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
     }
+
+    /**
+     * Initiates a handover of this {@link Call} to another {@link PhoneAccount}.
+     * @param handoverToHandle The {@link PhoneAccountHandle} to handover to.
+     * @param videoState The video state of the call when handed over.
+     * @param extras Optional extras {@link Bundle} provided by the initiating
+     *      {@link android.telecom.InCallService}.
+     */
+    private void requestHandover(PhoneAccountHandle handoverToHandle, int videoState,
+                                 Bundle extras) {
+        for (Listener l : mListeners) {
+            l.onHandoverRequested(this, handoverToHandle, videoState, extras);
+        }
+    }
+
+    /**
+     * Sets the video history based on the state and state transitions of the call. Always add the
+     * current video state to the video state history during a call transition except for the
+     * transitions DIALING->ACTIVE and RINGING->ACTIVE. In these cases, clear the history. If a
+     * call starts dialing/ringing as a VT call and gets downgraded to audio, we need to record
+     * the history as an audio call.
+     */
+    private void updateVideoHistoryViaState(int oldState, int newState) {
+        if ((oldState == CallState.DIALING || oldState == CallState.RINGING)
+                && newState == CallState.ACTIVE) {
+            mVideoStateHistory = mVideoState;
+        }
+
+        mVideoStateHistory |= mVideoState;
+    }
+
 }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index ee5478f..705d5a8 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -232,8 +232,10 @@
             }
         }
 
-        // Turn off mute when a new incoming call is answered.
-        mute(false /* shouldMute */);
+        // Turn off mute when a new incoming call is answered iff it's not a handover.
+        if (!call.isHandoverInProgress()) {
+            mute(false /* shouldMute */);
+        }
 
         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
     }
@@ -638,6 +640,13 @@
     }
 
     private void playToneForDisconnectedCall(Call call) {
+        // If this call is being disconnected as a result of being handed over to another call,
+        // we will not play a disconnect tone.
+        if (call.isHandoverInProgress()) {
+            Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
+            return;
+        }
+
         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
                     " and there is another call.");
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 12625bb..c695244 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -18,11 +18,7 @@
 
 
 import android.app.ActivityManager;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -164,49 +160,6 @@
         put(RUN_RUNNABLE, "RUN_RUNNABLE");
     }};
 
-    /**
-     * BroadcastReceiver used to track changes in the notification interruption filter.  This
-     * ensures changes to the notification interruption filter made by the user during a call are
-     * respected when restoring the notification interruption filter state.
-     */
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Log.startSession("CARSM.oR");
-            try {
-                String action = intent.getAction();
-
-                if (action.equals(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)) {
-                    // We get an this broadcast any time the notification filter is changed, even if
-                    // we are the initiator of the change.
-                    // So, we'll look at who the initiator of the manual zen rule is in the
-                    // notification manager.  If its us, then we can just exit now.
-                    String initiator =
-                            mInterruptionFilterProxy.getInterruptionModeInitiator();
-
-                    if (TELECOM_PACKAGE.equals(initiator)) {
-                        // We are the initiator of this change, so ignore it.
-                        Log.i(this, "interruptionFilterChanged - ignoring own change");
-                        return;
-                    }
-
-                    if (mAreNotificationSuppressed) {
-                        // If we've already set the interruption filter, and the user changes it to
-                        // something other than INTERRUPTION_FILTER_ALARMS, assume we will no longer
-                        // try to change it back if the audio route changes.
-                        mAreNotificationSuppressed =
-                                mInterruptionFilterProxy.getCurrentInterruptionFilter()
-                                        == NotificationManager.INTERRUPTION_FILTER_ALARMS;
-                        Log.i(this, "interruptionFilterChanged - changing to %b",
-                                mAreNotificationSuppressed);
-                    }
-                }
-            } finally {
-                Log.endSession();
-            }
-        }
-    };
-
     private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
     private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
     private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
@@ -328,9 +281,6 @@
             super.enter();
             setSpeakerphoneOn(false);
             setBluetoothOn(false);
-            if (mAudioFocusType == ACTIVE_FOCUS) {
-                setNotificationsSuppressed(true);
-            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes);
             setSystemAudioState(newState, true);
@@ -338,12 +288,6 @@
         }
 
         @Override
-        public void exit() {
-            super.exit();
-            setNotificationsSuppressed(false);
-        }
-
-        @Override
         public void updateSystemAudioState() {
             updateInternalCallAudioState();
             setSystemAudioState(mCurrentCallAudioState);
@@ -381,10 +325,6 @@
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == ACTIVE_FOCUS) {
-                        setNotificationsSuppressed(true);
-                    }
-
                     if (msg.arg1 == NO_FOCUS) {
                         reinitialize();
                     }
@@ -1184,7 +1124,6 @@
     private int mAudioFocusType;
     private boolean mWasOnSpeaker;
     private boolean mIsMuted;
-    private boolean mAreNotificationSuppressed = false;
 
     private final Context mContext;
     private final CallsManager mCallsManager;
@@ -1193,7 +1132,6 @@
     private final WiredHeadsetManager mWiredHeadsetManager;
     private final StatusBarNotifier mStatusBarNotifier;
     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
-    private final InterruptionFilterProxy mInterruptionFilterProxy;
     private final boolean mDoesDeviceSupportEarpieceRoute;
     private final TelecomSystem.SyncRoot mLock;
     private boolean mHasUserExplicitlyLeftBluetooth = false;
@@ -1213,7 +1151,6 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            InterruptionFilterProxy interruptionFilterProxy,
             boolean doesDeviceSupportEarpieceRoute) {
         super(NAME);
         addState(mActiveEarpieceRoute);
@@ -1233,11 +1170,6 @@
         mWiredHeadsetManager = wiredHeadsetManager;
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
-        mInterruptionFilterProxy = interruptionFilterProxy;
-        // Register for misc other intent broadcasts.
-        IntentFilter intentFilter =
-                new IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
-        context.registerReceiver(mReceiver, intentFilter);
         mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute;
         mLock = callsManager.getLock();
 
@@ -1360,49 +1292,6 @@
         return mBluetoothRouteManager.isBluetoothAvailable();
     }
 
-    /**
-     * Sets whether notifications should be suppressed or not.  Used when in a call to ensure the
-     * device will not vibrate due to notifications.
-     * Alarm-only filtering is activated when
-     *
-     * @param on {@code true} when notification suppression should be activated, {@code false} when
-     *                       it should be deactivated.
-     */
-    private void setNotificationsSuppressed(boolean on) {
-        if (mInterruptionFilterProxy == null) {
-            return;
-        }
-
-        Log.i(this, "setNotificationsSuppressed: on=%s; suppressed=%s", (on ? "yes" : "no"),
-                (mAreNotificationSuppressed ? "yes" : "no"));
-        if (on) {
-            if (!mAreNotificationSuppressed) {
-                // Enabling suppression of notifications.
-                int interruptionFilter = mInterruptionFilterProxy.getCurrentInterruptionFilter();
-                if (interruptionFilter == NotificationManager.INTERRUPTION_FILTER_ALL) {
-                    // No interruption filter is specified, so suppress notifications by setting the
-                    // current filter to alarms-only.
-                    mAreNotificationSuppressed = true;
-                    mInterruptionFilterProxy.setInterruptionFilter(
-                            NotificationManager.INTERRUPTION_FILTER_ALARMS);
-                } else {
-                    // Interruption filter is already chosen by the user, so do not attempt to change
-                    // it.
-                    mAreNotificationSuppressed = false;
-                }
-            }
-        } else {
-            // Disabling suppression of notifications.
-            if (mAreNotificationSuppressed) {
-                // We have implemented the alarms-only policy and the user has not changed it since
-                // we originally set it, so reset the notification filter.
-                mInterruptionFilterProxy.setInterruptionFilter(
-                        NotificationManager.INTERRUPTION_FILTER_ALL);
-            }
-            mAreNotificationSuppressed = false;
-        }
-    }
-
     private void setSpeakerphoneOn(boolean on) {
         if (mAudioManager.isSpeakerphoneOn() != on) {
             Log.i(this, "turning speaker phone %s", on);
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index c9569d7..ba03e5c 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -21,7 +21,6 @@
 import android.content.Intent;
 import android.location.Country;
 import android.location.CountryDetector;
-import android.location.CountryListener;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Looper;
@@ -146,13 +145,17 @@
         // 2) It is a conference call
         // 3) Call was not explicitly canceled
         // 4) Call is not an external call
-        // 5) Call is not a self-managed call
+        // 5) Call is not a self-managed call OR call is a self-managed call which has indicated it
+        //    should be logged in its PhoneAccount
         if (isNewlyDisconnected &&
                 (oldState != CallState.SELECT_PHONE_ACCOUNT &&
-                 !call.isConference() &&
-                 !isCallCanceled) &&
+                        !call.isConference() &&
+                        !isCallCanceled) &&
                 !call.isExternalCall() &&
-                !call.isSelfManaged()) {
+                (!call.isSelfManaged() ||
+                        (call.isLoggedSelfManaged() &&
+                                (call.getHandoverState() == HandoverState.HANDOVER_NONE ||
+                                call.getHandoverState() == HandoverState.HANDOVER_COMPLETE)))) {
             int type;
             if (!call.isIncoming()) {
                 type = Calls.OUTGOING_TYPE;
@@ -165,7 +168,10 @@
             } else {
                 type = Calls.INCOMING_TYPE;
             }
-            logCall(call, type, true /*showNotificationForMissedCall*/);
+            // Always show the notification for managed calls. For self-managed calls, it is up to
+            // the app to show the notification, so suppress the notification when logging the call.
+            boolean showNotification = !call.isSelfManaged();
+            logCall(call, type, showNotification);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 4e7216b..b473ccc 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,7 +17,6 @@
 package com.android.server.telecom;
 
 import android.app.ActivityManager;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.Intent;
@@ -258,7 +257,6 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
-    private final NotificationManager mNotificationManager;
     private final ClockProxy mClockProxy;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
@@ -308,8 +306,8 @@
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            InterruptionFilterProxy interruptionFilterProxy,
             EmergencyCallHelper emergencyCallHelper,
+            InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy) {
         mContext = context;
         mLock = lock;
@@ -331,8 +329,6 @@
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        mNotificationManager = (NotificationManager) context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
         CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
                 context,
                 this,
@@ -340,7 +336,6 @@
                 wiredHeadsetManager,
                 statusBarNotifier,
                 audioServiceFactory,
-                interruptionFilterProxy,
                 CallAudioRouteStateMachine.doesDeviceSupportEarpieceRoute()
         );
         callAudioRouteStateMachine.initialize();
@@ -353,7 +348,7 @@
                         mDockManager);
 
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
-                callAudioRoutePeripheralAdapter, lock);
+                callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory);
 
         SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
@@ -721,6 +716,20 @@
         }
     }
 
+    /**
+     * A {@link Call} managed by the {@link CallsManager} has requested a handover to another
+     * {@link PhoneAccount}.
+     * @param call The call.
+     * @param handoverTo The {@link PhoneAccountHandle} to handover the call to.
+     * @param videoState The desired video state of the call after handover.
+     * @param extras
+     */
+    @Override
+    public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
+                                    Bundle extras) {
+        requestHandover(call, handoverTo, videoState, extras);
+    }
+
     @VisibleForTesting
     public Call getForegroundCall() {
         if (mCallAudioManager == null) {
@@ -806,6 +815,7 @@
      */
     void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
         Log.d(this, "processIncomingCallIntent");
+        boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
         Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
         if (handle == null) {
             // Required for backwards compatibility
@@ -858,6 +868,12 @@
                     call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
                 }
             }
+
+            if (extras.getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+                Log.d(this, "processIncomingCallIntent: defaulting to voip mode for call %s",
+                        call.getId());
+                call.setIsVoipAudioMode(true);
+            }
         }
         if (extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
             if (phoneAccount != null &&
@@ -867,10 +883,12 @@
         }
         // If the extras specifies a video state, set it on the call if the PhoneAccount supports
         // video.
+        int videoState = VideoProfile.STATE_AUDIO_ONLY;
         if (extras.containsKey(TelecomManager.EXTRA_INCOMING_VIDEO_STATE) &&
                 phoneAccount != null && phoneAccount.hasCapabilities(
                         PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
-            call.setVideoState(extras.getInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE));
+            videoState = extras.getInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE);
+            call.setVideoState(videoState);
         }
 
         call.initAnalytics();
@@ -878,12 +896,54 @@
             getForegroundCall().getAnalytics().setCallIsInterrupted(true);
             call.getAnalytics().setCallIsAdditional(true);
         }
-
         setIntentExtrasAndStartTime(call, extras);
         // TODO: Move this to be a part of addCall()
         call.addListener(this);
 
-        if (call.isSelfManaged() && !isIncomingCallPermitted(call, call.getTargetPhoneAccount())) {
+        boolean isHandoverAllowed = true;
+        if (isHandover) {
+            if (!isHandoverInProgress() &&
+                    isHandoverToPhoneAccountSupported(phoneAccountHandle)) {
+                final String handleScheme = handle.getSchemeSpecificPart();
+                Call fromCall = mCalls.stream()
+                        .filter((c) -> mPhoneNumberUtilsAdapter.isSamePhoneNumber(
+                                c.getHandle().getSchemeSpecificPart(), handleScheme))
+                        .findFirst()
+                        .orElse(null);
+                if (fromCall != null) {
+                    if (!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount())) {
+                        Log.w(this, "processIncomingCallIntent: From account doesn't support " +
+                                "handover.");
+                        isHandoverAllowed = false;
+                    }
+                } else {
+                    Log.w(this, "processIncomingCallIntent: handover fail; can't find from call.");
+                    isHandoverAllowed = false;
+                }
+
+                if (isHandoverAllowed) {
+                    // Link the calls so we know we're handing over.
+                    fromCall.setHandoverDestinationCall(call);
+                    call.setHandoverSourceCall(fromCall);
+                    call.setHandoverState(HandoverState.HANDOVER_TO_STARTED);
+                    fromCall.setHandoverState(HandoverState.HANDOVER_FROM_STARTED);
+                    Log.addEvent(fromCall, LogUtils.Events.START_HANDOVER,
+                            "handOverFrom=%s, handOverTo=%s", fromCall.getId(), call.getId());
+                    Log.addEvent(call, LogUtils.Events.START_HANDOVER,
+                            "handOverFrom=%s, handOverTo=%s", fromCall.getId(), call.getId());
+                    if (isSpeakerEnabledForVideoCalls() && VideoProfile.isVideo(videoState)) {
+                        // Ensure when the call goes active that it will go to speakerphone if the
+                        // handover to call is a video call.
+                        call.setStartWithSpeakerphoneOn(true);
+                    }
+                }
+            } else {
+                Log.w(this, "processIncomingCallIntent: To account doesn't support handover.");
+            }
+        }
+
+        if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
+                call.getTargetPhoneAccount()))) {
             notifyCreateConnectionFailed(phoneAccountHandle, call);
         } else {
             call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1109,11 +1169,20 @@
             extras = new Bundle(extras);
             extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts);
         } else {
+            PhoneAccount accountToUse =
+                    mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+            if (accountToUse != null && accountToUse.getExtras() != null) {
+                if (accountToUse.getExtras()
+                        .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+                    Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
+                            call.getId());
+                    call.setIsVoipAudioMode(true);
+                }
+            }
+
             call.setState(
                     CallState.CONNECTING,
                     phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
-            PhoneAccount accountToUse =
-                    mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
             if (extras != null
                     && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
                 if (accountToUse != null
@@ -1601,11 +1670,16 @@
             Log.i(this, "Attempted to add account to unknown call %s", call);
         } else {
             call.setTargetPhoneAccount(account);
-
+            PhoneAccount realPhoneAccount =
+                    mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
+            if (realPhoneAccount != null && realPhoneAccount.getExtras() != null
+                    && realPhoneAccount.getExtras()
+                    .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+                Log.d("phoneAccountSelected: default to voip mode for call %s", call.getId());
+                call.setIsVoipAudioMode(true);
+            }
             if (call.getIntentExtras()
                     .getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
-                PhoneAccount realPhoneAccount =
-                        mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
                 if (realPhoneAccount != null
                         && realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
                     call.setRttStreams(true);
@@ -1679,6 +1753,7 @@
      * Removes an existing disconnected call, and notifies the in-call app.
      */
     void markCallAsRemoved(Call call) {
+        call.maybeCleanupHandover();
         removeCall(call);
         Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
         if (mLocallyDisconnectingCalls.contains(call)) {
@@ -2138,6 +2213,9 @@
             maybeShowErrorDialogOnDisconnect(call);
 
             Trace.beginSection("onCallStateChanged");
+
+            maybeHandleHandover(call, newState);
+
             // Only broadcast state change for calls that are being tracked.
             if (mCalls.contains(call)) {
                 updateCanAddCall();
@@ -2155,6 +2233,109 @@
         }
     }
 
+    /**
+     * Identifies call state transitions for a call which trigger handover events.
+     * - If this call has a handover to it which just started and this call goes active, treat
+     * this as if the user accepted the handover.
+     * - If this call has a handover to it which just started and this call is disconnected, treat
+     * this as if the user rejected the handover.
+     * - If this call has a handover from it which just started and this call is disconnected, do
+     * nothing as the call prematurely disconnected before the user accepted the handover.
+     * - If this call has a handover from it which was already accepted by the user and this call is
+     * disconnected, mark the handover as complete.
+     *
+     * @param call A call whose state is changing.
+     * @param newState The new state of the call.
+     */
+    private void maybeHandleHandover(Call call, int newState) {
+        if (call.getHandoverSourceCall() != null) {
+            // We are handing over another call to this one.
+            if (call.getHandoverState() == HandoverState.HANDOVER_TO_STARTED) {
+                // A handover to this call has just been initiated.
+                if (newState == CallState.ACTIVE) {
+                    // This call went active, so the user has accepted the handover.
+                    Log.i(this, "setCallState: handover to accepted");
+                    acceptHandoverTo(call);
+                } else if (newState == CallState.DISCONNECTED) {
+                    // The call was disconnected, so the user has rejected the handover.
+                    Log.i(this, "setCallState: handover to rejected");
+                    rejectHandoverTo(call);
+                }
+            }
+        // If this call was disconnected because it was handed over TO another call, report the
+        // handover as complete.
+        } else if (call.getHandoverDestinationCall() != null
+                && newState == CallState.DISCONNECTED) {
+            int handoverState = call.getHandoverState();
+            if (handoverState == HandoverState.HANDOVER_FROM_STARTED) {
+                // Disconnect before handover was accepted.
+                Log.i(this, "setCallState: disconnect before handover accepted");
+                // Let the handover destination know that the source has disconnected prior to
+                // completion of the handover.
+                call.getHandoverDestinationCall().sendCallEvent(
+                        android.telecom.Call.EVENT_HANDOVER_SOURCE_DISCONNECTED, null);
+            } else if (handoverState == HandoverState.HANDOVER_ACCEPTED) {
+                Log.i(this, "setCallState: handover from complete");
+                completeHandoverFrom(call);
+            }
+        }
+    }
+
+    private void completeHandoverFrom(Call call) {
+        Call handoverTo = call.getHandoverDestinationCall();
+        Log.addEvent(handoverTo, LogUtils.Events.HANDOVER_COMPLETE, "from=%s, to=%s",
+                call.getId(), handoverTo.getId());
+        Log.addEvent(call, LogUtils.Events.HANDOVER_COMPLETE, "from=%s, to=%s",
+                call.getId(), handoverTo.getId());
+
+        // Inform the "from" Call (ie the source call) that the handover from it has
+        // completed; this allows the InCallService to be notified that a handover it
+        // initiated completed.
+        call.onConnectionEvent(Connection.EVENT_HANDOVER_COMPLETE, null);
+        // Inform the "to" ConnectionService that handover to it has completed.
+        handoverTo.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_COMPLETE, null);
+        answerCall(handoverTo, handoverTo.getVideoState());
+        call.markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
+    }
+
+    private void rejectHandoverTo(Call handoverTo) {
+        Call handoverFrom = handoverTo.getHandoverSourceCall();
+        Log.i(this, "rejectHandoverTo: from=%s, to=%s", handoverFrom.getId(), handoverTo.getId());
+        Log.addEvent(handoverFrom, LogUtils.Events.HANDOVER_FAILED, "from=%s, to=%s",
+                handoverTo.getId(), handoverFrom.getId());
+        Log.addEvent(handoverTo, LogUtils.Events.HANDOVER_FAILED, "from=%s, to=%s",
+                handoverTo.getId(), handoverFrom.getId());
+
+        // Inform the "from" Call (ie the source call) that the handover from it has
+        // failed; this allows the InCallService to be notified that a handover it
+        // initiated failed.
+        handoverFrom.onConnectionEvent(Connection.EVENT_HANDOVER_FAILED, null);
+        // Inform the "to" ConnectionService that handover to it has failed.  This
+        // allows the ConnectionService the call was being handed over
+        if (handoverTo.getConnectionService() != null) {
+            // Only attempt if the call has a bound ConnectionService if handover failed
+            // early on in the handover process, the CS will be unbound and we won't be
+            // able to send the call event.
+            handoverTo.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+        }
+        handoverTo.markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_FAILED);
+    }
+
+    private void acceptHandoverTo(Call handoverTo) {
+        Call handoverFrom = handoverTo.getHandoverSourceCall();
+        Log.i(this, "acceptHandoverTo: from=%s, to=%s", handoverFrom.getId(), handoverTo.getId());
+        handoverTo.setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+        handoverFrom.setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+
+        Log.addEvent(handoverTo, LogUtils.Events.ACCEPT_HANDOVER, "from=%s, to=%s",
+                handoverFrom.getId(), handoverTo.getId());
+        Log.addEvent(handoverFrom, LogUtils.Events.ACCEPT_HANDOVER, "from=%s, to=%s",
+                handoverFrom.getId(), handoverTo.getId());
+
+        // Disconnect the call we handed over from.
+        disconnectCall(handoverFrom);
+    }
+
     private void updateCanAddCall() {
         boolean newCanAddCall = canAddCall();
         if (newCanAddCall != mCanAddCall) {
@@ -2360,7 +2541,8 @@
      */
     public boolean shouldShowSystemIncomingCallUi(Call incomingCall) {
         return incomingCall.isIncoming() && incomingCall.isSelfManaged() &&
-                hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount());
+                hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount()) &&
+                incomingCall.getHandoverSourceCall() == null;
     }
 
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
@@ -2484,6 +2666,11 @@
      * Checks to see if the call should be on speakerphone and if so, set it.
      */
     private void maybeMoveToSpeakerPhone(Call call) {
+        if (call.isHandoverInProgress() && call.getState() == CallState.DIALING) {
+            // When a new outgoing call is initiated for the purpose of handing over, do not engage
+            // speaker automatically until the call goes active.
+            return;
+        }
         if (call.getStartWithSpeakerphoneOn()) {
             setAudioRoute(CallAudioState.ROUTE_SPEAKER);
             call.setStartWithSpeakerphoneOn(false);
@@ -2527,6 +2714,7 @@
                 "existing connection");
         call.setConnectionCapabilities(connection.getConnectionCapabilities());
         call.setConnectionProperties(connection.getConnectionProperties());
+        call.setHandle(connection.getHandle(), connection.getHandlePresentation());
         call.setCallerDisplayName(connection.getCallerDisplayName(),
                 connection.getCallerDisplayNamePresentation());
         call.addListener(this);
@@ -2691,10 +2879,11 @@
         } else {
             // Only permit outgoing calls if there is no ongoing emergency calls and all other calls
             // are associated with the current PhoneAccountHandle.
-            return !hasEmergencyCall() &&
-                    !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
-                    !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
-                    !hasManagedCalls();
+            return !hasEmergencyCall() && (
+                    excludeCall.getHandoverSourceCall() != null ||
+                            (!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
+                            !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
+                            !hasManagedCalls()));
         }
     }
 
@@ -2922,6 +3111,107 @@
         }
     }
 
+    /**
+     * Called in response to a {@link Call} receiving a {@link Call#sendCallEvent(String, Bundle)}
+     * of type {@link android.telecom.Call#EVENT_REQUEST_HANDOVER} indicating the
+     * {@link android.telecom.InCallService} has requested a handover to another
+     * {@link android.telecom.ConnectionService}.
+     *
+     * We will explicitly disallow a handover when there is an emergency call present.
+     *
+     * @param handoverFromCall The {@link Call} to be handed over.
+     * @param handoverToHandle The {@link PhoneAccountHandle} to hand over the call to.
+     * @param videoState The desired video state of {@link Call} after handover.
+     * @param initiatingExtras Extras associated with the handover, to be passed to the handover
+     *               {@link android.telecom.ConnectionService}.
+     */
+    private void requestHandover(Call handoverFromCall, PhoneAccountHandle handoverToHandle,
+                                 int videoState, Bundle initiatingExtras) {
+
+        boolean isHandoverFromSupported = isHandoverFromPhoneAccountSupported(
+                handoverFromCall.getTargetPhoneAccount());
+        boolean isHandoverToSupported = isHandoverToPhoneAccountSupported(handoverToHandle);
+
+        if (!isHandoverFromSupported || !isHandoverToSupported || hasEmergencyCall()) {
+            handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+            return;
+        }
+
+        Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, handoverToHandle);
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
+        extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
+                handoverFromCall.getTargetPhoneAccount());
+        extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+        if (initiatingExtras != null) {
+            extras.putAll(initiatingExtras);
+        }
+        extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
+                mCallAudioManager.getCallAudioState());
+        Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
+                extras, getCurrentUserHandle(), null /* originalIntent */);
+        Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
+                "handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), handoverToCall.getId());
+        handoverFromCall.setHandoverDestinationCall(handoverToCall);
+        handoverFromCall.setHandoverState(HandoverState.HANDOVER_FROM_STARTED);
+        handoverToCall.setHandoverState(HandoverState.HANDOVER_TO_STARTED);
+        handoverToCall.setHandoverSourceCall(handoverFromCall);
+        handoverToCall.setNewOutgoingCallIntentBroadcastIsDone();
+        placeOutgoingCall(handoverToCall, handoverToCall.getHandle(), null /* gatewayInfo */,
+                false /* startwithSpeaker */,
+                videoState);
+    }
+
+    /**
+     * Determines if handover from the specified {@link PhoneAccountHandle} is supported.
+     *
+     * @param from The {@link PhoneAccountHandle} the handover originates from.
+     * @return {@code true} if handover is currently allowed, {@code false} otherwise.
+     */
+    private boolean isHandoverFromPhoneAccountSupported(PhoneAccountHandle from) {
+        return getBooleanPhoneAccountExtra(from, PhoneAccount.EXTRA_SUPPORTS_HANDOVER_FROM);
+    }
+
+    /**
+     * Determines if handover to the specified {@link PhoneAccountHandle} is supported.
+     *
+     * @param to The {@link PhoneAccountHandle} the handover it to.
+     * @return {@code true} if handover is currently allowed, {@code false} otherwise.
+     */
+    private boolean isHandoverToPhoneAccountSupported(PhoneAccountHandle to) {
+        return getBooleanPhoneAccountExtra(to, PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
+    }
+
+    /**
+     * Retrieves a boolean phone account extra.
+     * @param handle the {@link PhoneAccountHandle} to retrieve the extra for.
+     * @param key The extras key.
+     * @return {@code true} if the extra {@link PhoneAccount} extra is true, {@code false}
+     *      otherwise.
+     */
+    private boolean getBooleanPhoneAccountExtra(PhoneAccountHandle handle, String key) {
+        PhoneAccount phoneAccount = getPhoneAccountRegistrar().getPhoneAccountUnchecked(handle);
+        if (phoneAccount == null) {
+            return false;
+        }
+
+        Bundle fromExtras = phoneAccount.getExtras();
+        if (fromExtras == null) {
+            return false;
+        }
+        return fromExtras.getBoolean(key);
+    }
+
+    /**
+     * Determines if there is an existing handover in process.
+     * @return {@code true} if a call in the process of handover exists, {@code false} otherwise.
+     */
+    private boolean isHandoverInProgress() {
+        return mCalls.stream().filter(c -> c.getHandoverSourceCall() != null ||
+                c.getHandoverDestinationCall() != null).count() > 0;
+    }
+
     private void broadcastUnregisterIntent(PhoneAccountHandle accountHandle) {
         Intent intent =
                 new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index b395adc..8f54cec 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -928,6 +928,14 @@
                       mCallsManager.getEmergencyCallHelper().getLastEmergencyCallTimeMillis());
                 }
 
+                // Call is incoming and added because we're handing over from another; tell CS
+                // that its expected to handover.
+                if (call.isIncoming() && call.getHandoverSourceCall() != null) {
+                    extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
+                    extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
+                            call.getHandoverSourceCall().getTargetPhoneAccount());
+                }
+
                 Log.addEvent(call, LogUtils.Events.START_CONNECTION,
                         Log.piiHandle(call.getHandle()));
 
diff --git a/src/com/android/server/telecom/HandoverState.java b/src/com/android/server/telecom/HandoverState.java
new file mode 100644
index 0000000..c68f0d3
--- /dev/null
+++ b/src/com/android/server/telecom/HandoverState.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Defines handover state constants for calls undergoing handover.
+ */
+public class HandoverState {
+    private HandoverState() {
+        // Can't instantiate.
+    }
+
+    public static final int HANDOVER_NONE = 1;
+    public static final int HANDOVER_TO_STARTED = 2;
+    public static final int HANDOVER_FROM_STARTED = 3;
+    public static final int HANDOVER_ACCEPTED = 4;
+    public static final int HANDOVER_COMPLETE = 5;
+    public static final int HANDOVER_FAILED = 6;
+
+    private static final String HANDOVER_NONE_STR = "NONE";
+    private static final String HANDOVER_TO_STARTED_STR = "HANDOVER_TO_STARTED";
+    private static final String HANDOVER_FROM_STARTED_STR = "HANDOVER_FROM_STARTED";
+    private static final String HANDOVER_ACCEPTED_STR = "HANDOVER_ACCEPTED";
+    private static final String HANDOVER_COMPLETE_STR = "HANDOVER_COMPLETE";
+    private static final String HANDOVER_FAILED_STR = "HANDOVER_FAILED";
+
+    public static String stateToString(int state) {
+        switch (state) {
+            case HANDOVER_NONE:
+                return HANDOVER_NONE_STR;
+            case HANDOVER_TO_STARTED:
+                return HANDOVER_TO_STARTED_STR;
+            case HANDOVER_FROM_STARTED:
+                return HANDOVER_FROM_STARTED_STR;
+            case HANDOVER_ACCEPTED:
+                return HANDOVER_ACCEPTED_STR;
+            case HANDOVER_COMPLETE:
+                return HANDOVER_COMPLETE_STR;
+            case HANDOVER_FAILED:
+                return HANDOVER_FAILED_STR;
+        }
+        return "";
+    }
+}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 62c692b..a258aee 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -40,11 +40,13 @@
         private CallAudioManager mCallAudioManager;
         private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
         private final TelecomSystem.SyncRoot mLock;
+        private final ToneGeneratorFactory mToneGeneratorFactory;
 
         Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
-                TelecomSystem.SyncRoot lock) {
+                TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) {
             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
+            mToneGeneratorFactory = toneGeneratorFactory;
         }
 
         public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -53,10 +55,14 @@
 
         public InCallTonePlayer createPlayer(int tone) {
             return new InCallTonePlayer(tone, mCallAudioManager,
-                    mCallAudioRoutePeripheralAdapter, mLock);
+                    mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory);
         }
     }
 
+    public interface ToneGeneratorFactory {
+        ToneGenerator get (int streamType, int volume);
+    }
+
     // The possible tones that we can play.
     public static final int TONE_INVALID = 0;
     public static final int TONE_BUSY = 1;
@@ -111,6 +117,8 @@
     private Session mSession;
     private final Object mSessionLock = new Object();
 
+    private final ToneGeneratorFactory mToneGenerator;
+
     /**
      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
      *
@@ -120,12 +128,14 @@
             int toneId,
             CallAudioManager callAudioManager,
             CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
-            TelecomSystem.SyncRoot lock) {
+            TelecomSystem.SyncRoot lock,
+            ToneGeneratorFactory toneGeneratorFactory) {
         mState = STATE_OFF;
         mToneId = toneId;
         mCallAudioManager = callAudioManager;
         mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
         mLock = lock;
+        mToneGenerator = toneGeneratorFactory;
     }
 
     /** {@inheritDoc} */
@@ -227,7 +237,7 @@
             // signal, and is not as important.
             try {
                 Log.v(this, "Creating generator");
-                toneGenerator = new ToneGenerator(stream, toneVolume);
+                toneGenerator = mToneGenerator.get(stream, toneVolume);
             } catch (RuntimeException e) {
                 Log.w(this, "Failed to create ToneGenerator.", e);
                 return;
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 06811bd..0411355 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -126,6 +126,12 @@
         public static final String PROPERTY_CHANGE = "PROPERTY_CHANGE";
         public static final String CAPABILITY_CHANGE = "CAPABILITY_CHANGE";
         public static final String CONNECTION_EVENT = "CONNECTION_EVENT";
+        public static final String CALL_EVENT = "CALL_EVENT";
+        public static final String HANDOVER_REQUEST = "HANDOVER_REQUEST";
+        public static final String START_HANDOVER = "START_HANDOVER";
+        public static final String ACCEPT_HANDOVER = "ACCEPT_HANDOVER";
+        public static final String HANDOVER_COMPLETE = "HANDOVER_COMPLETE";
+        public static final String HANDOVER_FAILED = "HANDOVER_FAILED";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
index 8e59a64..aa568a9 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -27,6 +27,7 @@
     boolean isLocalEmergencyNumber(Context context, String number);
     boolean isPotentialLocalEmergencyNumber(Context context, String number);
     boolean isUriNumber(String number);
+    boolean isSamePhoneNumber(String number1, String number2);
     String getNumberFromIntent(Intent intent, Context context);
     String convertKeypadLettersToDigits(String number);
     String stripSeparators(String number);
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
index fa316a5..7ff854e 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -37,6 +37,11 @@
     }
 
     @Override
+    public boolean isSamePhoneNumber(String number1, String number2) {
+        return PhoneNumberUtils.compare(number1, number2);
+    }
+
+    @Override
     public String getNumberFromIntent(Intent intent, Context context) {
         return PhoneNumberUtils.getNumberFromIntent(intent, context);
     }
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index d955227..36bb4ed 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.os.VibrationEffect;
 import android.telecom.Log;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
@@ -33,20 +34,30 @@
  */
 @VisibleForTesting
 public class Ringer {
-    private static final long[] VIBRATION_PATTERN = new long[] {
-        0, // No delay before starting
-        1000, // How long to vibrate
-        1000, // How long to wait before vibrating again
-    };
+    VibrationEffect mVibrationEffect;
+
+    private static final long[] PULSE_PATTERN = {0,12,250,12,500, // priming  + interval
+            50,50,50,50,50,50,50,50,50,50,50,50,50,50, // ease-in
+            300, // Peak
+            1000}; // pause before repetition
+
+    private static final int[] PULSE_AMPLITUDE = {0,255,0,255,0, // priming  + interval
+            77,77,78,79,81,84,87,93,101,114,133,162,205,255, // ease-in (min amplitude = 30%)
+            255, // Peak
+            0}; // pause before repetition
+
+    /**
+     * Indicates that vibration should be repeated at element 5 in the {@link #PULSE_AMPLITUDE} and
+     * {@link #PULSE_PATTERN} arrays.  This means repetition will happen for the main ease-in/peak
+     * pattern, but the priming + interval part will not be repeated.
+     */
+    private static final int REPEAT_VIBRATION_AT = 5;
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .build();
 
-    /** Indicate that we want the pattern to repeat at the step which turns on vibration. */
-    private static final int VIBRATION_PATTERN_REPEAT = 1;
-
     /**
      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
      * calls and explicit ordering is useful for maintaining the proper state of the ringer.
@@ -95,6 +106,9 @@
         mRingtonePlayer = asyncRingtonePlayer;
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
+
+        mVibrationEffect = VibrationEffect.createWaveform(PULSE_PATTERN, PULSE_AMPLITUDE,
+                REPEAT_VIBRATION_AT);
     }
 
     public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
@@ -151,8 +165,7 @@
 
         if (shouldVibrate(mContext, foregroundCall) && !mIsVibrating && shouldRingForContact) {
             mVibratingCall = foregroundCall;
-            mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
-                    VIBRATION_ATTRIBUTES);
+            mVibrator.vibrate(mVibrationEffect, VIBRATION_ATTRIBUTES);
             mIsVibrating = true;
         } else if (mIsVibrating) {
             Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index cbcd41d..3a09aa8 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1083,6 +1083,10 @@
                 if (extras != null) {
                     phoneAccountHandle = extras.getParcelable(
                             TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+                    if (extras.containsKey(TelecomManager.EXTRA_IS_HANDOVER)) {
+                        // This extra is for Telecom use only so should never be passed in.
+                        extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
+                    }
                 }
                 boolean isSelfManaged = phoneAccountHandle != null &&
                         isSelfManagedConnectionService(phoneAccountHandle);
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 59b194f..31afbba 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -188,8 +188,8 @@
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            InterruptionFilterProxy interruptionFilterProxy,
             IncomingCallNotifier incomingCallNotifier,
+            InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
@@ -254,8 +254,8 @@
                 timeoutsAdapter,
                 asyncRingtonePlayer,
                 phoneNumberUtilsAdapter,
-                interruptionFilterProxy,
                 emergencyCallHelper,
+                toneGeneratorFactory,
                 clockProxy);
 
         mIncomingCallNotifier = incomingCallNotifier;
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index a5e4404..120d1ce 100644
--- a/src/com/android/server/telecom/WiredHeadsetManager.java
+++ b/src/com/android/server/telecom/WiredHeadsetManager.java
@@ -60,16 +60,7 @@
         }
 
         private void updateHeadsetStatus() {
-            AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-            boolean isPluggedIn = false;
-            for (AudioDeviceInfo device : devices) {
-                switch (device.getType()) {
-                    case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
-                    case AudioDeviceInfo.TYPE_WIRED_HEADSET:
-                    case AudioDeviceInfo.TYPE_USB_DEVICE:
-                        isPluggedIn = true;
-                }
-            }
+            final boolean isPluggedIn = isWiredHeadsetPluggedIn();
 
             Log.i(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b, ",
                     isPluggedIn);
@@ -89,7 +80,7 @@
 
     public WiredHeadsetManager(Context context) {
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mIsPluggedIn = mAudioManager.isWiredHeadsetOn();
+        mIsPluggedIn = isWiredHeadsetPluggedIn();
 
         mAudioManager.registerAudioDeviceCallback(new WiredHeadsetCallback(), null);
     }
@@ -110,6 +101,24 @@
         return mIsPluggedIn;
     }
 
+    private boolean isWiredHeadsetPluggedIn() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        boolean isPluggedIn = false;
+        for (AudioDeviceInfo device : devices) {
+            switch (device.getType()) {
+                case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+                case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+                case AudioDeviceInfo.TYPE_USB_HEADSET:
+                case AudioDeviceInfo.TYPE_USB_DEVICE:
+                    isPluggedIn = true;
+            }
+            if (isPluggedIn) {
+                break;
+            }
+        }
+        return isPluggedIn;
+    }
+
     private void onHeadsetPluggedInChanged(boolean isPluggedIn) {
         if (mIsPluggedIn != isPluggedIn) {
             Log.v(this, "onHeadsetPluggedInChanged, mIsPluggedIn: %b -> %b", mIsPluggedIn,
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index e525701..19dd404 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -16,17 +16,16 @@
 
 package com.android.server.telecom.components;
 
-import android.app.NotificationManager;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.Intent;
 import android.media.IAudioService;
+import android.media.ToneGenerator;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.service.notification.ZenModeConfig;
 import android.telecom.Log;
 
 import com.android.internal.telephony.CallerInfoAsyncQuery;
@@ -39,9 +38,9 @@
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
+import com.android.server.telecom.InCallTonePlayer;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
 import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.InterruptionFilterProxy;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManagerFactory;
@@ -80,9 +79,6 @@
      */
     static void initializeTelecomSystem(Context context) {
         if (TelecomSystem.getInstance() == null) {
-            final NotificationManager notificationManager =
-                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-
             NotificationChannelManager notificationChannelManager =
                     new NotificationChannelManager();
             notificationChannelManager.createChannels(context);
@@ -137,7 +133,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                                                       CallsManager callsManager) {
+                                        CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -166,27 +162,8 @@
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(),
                             new PhoneNumberUtilsAdapterImpl(),
-                            new InterruptionFilterProxy() {
-                                @Override
-                                public void setInterruptionFilter(int interruptionFilter) {
-                                    notificationManager.setInterruptionFilter(interruptionFilter);
-                                }
-
-                                @Override
-                                public int getCurrentInterruptionFilter() {
-                                    return notificationManager.getCurrentInterruptionFilter();
-                                }
-
-                                @Override
-                                public String getInterruptionModeInitiator() {
-                                    ZenModeConfig config = notificationManager.getZenModeConfig();
-                                    if (config.manualRule != null) {
-                                        return config.manualRule.enabler;
-                                    }
-                                    return null;
-                                }
-                            },
                             new IncomingCallNotifier(context),
+                            ToneGenerator::new,
                             new ClockProxy() {
                                 @Override
                                 public long currentTimeMillis() {
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
index 6ed98a4..0c90e06 100644
--- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.telecom.Log;
-import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
@@ -34,10 +33,10 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.CallerInfo;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.HandoverState;
 import com.android.server.telecom.R;
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
@@ -133,7 +132,8 @@
     private void updateIncomingCall() {
         Optional<Call> incomingCallOp = mCalls.stream()
                 .filter(call -> call.isSelfManaged() && call.isIncoming() &&
-                        call.getState() == CallState.RINGING)
+                        call.getState() == CallState.RINGING &&
+                        call.getHandoverState() == HandoverState.HANDOVER_NONE)
                 .findFirst();
         Call incomingCall = incomingCallOp.orElse(null);
         if (incomingCall != null && mCallsManagerProxy != null &&
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 75d467a..83f021f 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -208,6 +208,14 @@
           </intent-filter>
         </activity>
 
+        <activity android:name="com.android.server.telecom.testapps.HandoverActivity"
+                  android:label="@string/selfManagedCallingActivityLabel"
+                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+          <intent-filter>
+              <action android:name="android.intent.action.MAIN" />
+          </intent-filter>
+        </activity>
+
         <service android:name="com.android.server.telecom.testapps.SelfManagedConnectionService"
                  android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index 502bdf4..36ffb27 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -27,7 +27,7 @@
             android:dividerHeight="4px">
     </ListView>
     <GridLayout
-        android:columnCount="4"
+        android:columnCount="3"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="horizontal">
@@ -66,5 +66,10 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/acceptRttButton"/>
+        <Button
+            android:id="@+id/request_handover_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/handoverButton"/>
     </GridLayout>
 </LinearLayout>
diff --git a/testapps/res/layout/self_managed_handover.xml b/testapps/res/layout/self_managed_handover.xml
new file mode 100644
index 0000000..4524370
--- /dev/null
+++ b/testapps/res/layout/self_managed_handover.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:text="Do you want to handover your call?"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/incomingCallText" />
+
+    <Button
+        android:text="No Way"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/rejectUpgradeButton" />
+
+    <Button
+        android:text="Yes Definitely"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/acceptUpgradeButton" />
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index e30ef42..e55de33 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -92,6 +92,11 @@
             android:layout_height="wrap_content"
             android:text="Incoming Call"
             android:id="@+id/placeIncomingCallButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Handover From"
+            android:id="@+id/handoverFrom" />
     </LinearLayout>
 
     <ListView
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 73575aa..a0485d0 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -56,6 +56,8 @@
 
     <string name="holdButton">Hold</string>
 
+    <string name="handoverButton">Handover</string>
+
     <string name="inCallUiAppLabel">Test InCall UI</string>
 
     <string name="UssdUiAppLabel">Test Ussd UI</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
new file mode 100644
index 0000000..f33022c
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.testapps;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+import android.telecom.TelecomManager;
+import android.telephony.DisconnectCause;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Displays a UX to the user confirming whether they want to handover a call to the self-managed CS.
+ */
+public class HandoverActivity extends Activity {
+    public static final String EXTRA_CALL_ID = "com.android.server.telecom.testapps.extra.CALL_ID";
+
+    private Button mAcceptHandoverButton;
+    private Button mRejectHandoverButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent launchingIntent = getIntent();
+        int callId = launchingIntent.getIntExtra(EXTRA_CALL_ID, 0);
+        Log.i(this, "showing fullscreen upgrade ux for call id %d", callId);
+
+        setContentView(R.layout.self_managed_handover);
+        final SelfManagedConnection connection = SelfManagedCallList.getInstance()
+                .getConnectionById(callId);
+        mAcceptHandoverButton = (Button) findViewById(R.id.acceptUpgradeButton);
+        mAcceptHandoverButton.setOnClickListener((View v) -> {
+            if (connection != null) {
+                connection.setConnectionActive();
+                Intent intent = new Intent(Intent.ACTION_MAIN, null);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION |
+                        Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                intent.setClass(this, SelfManagedCallingActivity.class);
+                startActivity(intent);
+            }
+            finish();
+        });
+        mRejectHandoverButton = (Button) findViewById(R.id.rejectUpgradeButton);
+        mRejectHandoverButton.setOnClickListener((View v) -> {
+            if (connection != null) {
+                connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
+                connection.destroy();
+                TelecomManager tm = TelecomManager.from(this);
+                tm.showInCallScreen(false);
+            }
+            finish();
+        });
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 4275079..f9bce35 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Bundle;
 import android.telecom.ConnectionRequest;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
@@ -92,14 +93,20 @@
 
     public void registerPhoneAccounts(Context context) {
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1,
-                SELF_MANAGED_NAME_1);
+                SELF_MANAGED_NAME_1, true /* areCallsLogged */);
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
-                SELF_MANAGED_NAME_2);
+                SELF_MANAGED_NAME_2, false /* areCallsLogged */);
     }
 
-    public void registerPhoneAccount(Context context, String id, Uri address, String name) {
+    public void registerPhoneAccount(Context context, String id, Uri address, String name,
+                                     boolean areCallsLogged) {
         PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
         mPhoneAccounts.put(id, handle);
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
+        if (areCallsLogged) {
+            extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
+        }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
@@ -107,6 +114,7 @@
                 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
                         PhoneAccount.CAPABILITY_VIDEO_CALLING |
                         PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+                .setExtras(extras)
                 .setShortDescription(name);
 
         TelecomManager.from(context).registerPhoneAccount(builder.build());
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 4dfa012..6139e33 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -47,6 +47,7 @@
     private CheckBox mCheckIfPermittedBeforeCalling;
     private Button mPlaceOutgoingCallButton;
     private Button mPlaceIncomingCallButton;
+    private Button mHandoverFrom;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
     private RadioButton mVideoCallButton;
@@ -100,9 +101,13 @@
         mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                placeIncomingCall();
+                placeIncomingCall(false /* isHandoverFrom */);
             }
         });
+        mHandoverFrom = (Button) findViewById(R.id.handoverFrom);
+        mHandoverFrom.setOnClickListener((v -> {
+            placeIncomingCall(true /* isHandoverFrom */);
+        }));
 
         mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
         mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
@@ -148,7 +153,7 @@
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
-    private void placeIncomingCall() {
+    private void placeIncomingCall(boolean isHandoverFrom) {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
 
@@ -166,6 +171,9 @@
             extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
                     VideoProfile.STATE_BIDIRECTIONAL);
         }
+        if (isHandoverFrom) {
+            extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
+        }
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
 }
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 766efa5..82967c4 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -23,6 +23,8 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
@@ -55,6 +57,7 @@
     private final boolean mIsIncomingCall;
     private boolean mIsIncomingCallUiShowing;
     private Listener mListener;
+    private boolean mIsHandover;
 
     SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
         mCallList = callList;
@@ -79,6 +82,9 @@
 
     @Override
     public void onShowIncomingCallUi() {
+        if (isHandover()) {
+            return;
+        }
         // Create the fullscreen intent used to show the fullscreen incoming call UX.
         Intent intent = new Intent(Intent.ACTION_MAIN, null);
         intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -201,6 +207,14 @@
         return mCallId;
     }
 
+    public void setIsHandover(boolean isHandover) {
+        mIsHandover = isHandover;
+    }
+
+    public boolean isHandover() {
+        return mIsHandover;
+    }
+
     private MediaPlayer createMediaPlayer(Context context) {
         int audioToPlay = (Math.random() > 0.5f) ? R.raw.sample_audio : R.raw.sample_audio2;
         MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 1d52a3b..bb34530 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.testapps;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -79,6 +80,23 @@
         if (isIncoming) {
             connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
         }
+        Bundle requestExtras = request.getExtras();
+        if (requestExtras != null) {
+            Log.i(this, "createConnection: isHandover=%b, handoverFrom=%s",
+                    requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER),
+                    requestExtras.getString(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT));
+            connection.setIsHandover(requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER,
+                    false));
+            if (!isIncoming && connection.isHandover()) {
+                Intent intent = new Intent(Intent.ACTION_MAIN, null);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.setClass(this, HandoverActivity.class);
+                intent.putExtra(HandoverActivity.EXTRA_CALL_ID, connection.getCallId());
+                startActivity(intent);
+            } else {
+                Log.i(this, "Handover incoming call created.");
+            }
+        }
 
         // Track the phone account handle which created this connection so we can distinguish them
         // in the sample call list later.
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index 1b32690..322c94c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -151,7 +151,9 @@
         call.unregisterCallback(this);
 
         for (Listener l : mListeners) {
-            l.onCallRemoved(call);
+            if (l != null) {
+                l.onCallRemoved(call);
+            }
         }
     }
 
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 2920ca7..d99798c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -20,6 +20,9 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.util.Log;
 import android.view.View;
@@ -27,6 +30,9 @@
 import android.widget.ListView;
 import android.widget.Toast;
 
+import java.util.List;
+import java.util.Optional;
+
 public class TestInCallUI extends Activity {
 
     private ListView mListView;
@@ -83,6 +89,7 @@
         View answerButton = findViewById(R.id.answer_button);
         View startRttButton = findViewById(R.id.start_rtt_button);
         View acceptRttButton = findViewById(R.id.accept_rtt_button);
+        View handoverButton = findViewById(R.id.request_handover_button);
 
         endCallButton.setOnClickListener(new OnClickListener() {
             @Override
@@ -145,6 +152,15 @@
                 call.respondToRttRequest(mCallList.getLastRttRequestId(), true);
             }
         });
+
+        handoverButton.setOnClickListener((v) -> {
+            Call call = mCallList.getCall(0);
+            Bundle extras = new Bundle();
+            extras.putParcelable(Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE,
+                    getHandoverToPhoneAccountHandle());
+            extras.putInt(Call.EXTRA_HANDOVER_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL);
+            call.sendCallEvent(Call.EVENT_REQUEST_HANDOVER, extras);
+        });
     }
 
     /** ${inheritDoc} */
@@ -167,4 +183,19 @@
     protected void onResume() {
         super.onResume();
     }
+
+    private PhoneAccountHandle getHandoverToPhoneAccountHandle() {
+        TelecomManager tm = TelecomManager.from(this);
+
+        List<PhoneAccountHandle> handles = tm.getAllPhoneAccountHandles();
+        Optional<PhoneAccountHandle> found = handles.stream().filter(h -> {
+            PhoneAccount account = tm.getPhoneAccount(h);
+            Bundle extras = account.getExtras();
+            return extras != null && extras.getBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
+        }).findFirst();
+        PhoneAccountHandle foundHandle = found.orElse(null);
+        Log.i(TestInCallUI.class.getSimpleName(), "getHandoverToPhoneAccountHandle() = " +
+            foundHandle);
+        return foundHandle;
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java b/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
index b78a48a..9bb6977 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestRttActivity.java
@@ -93,12 +93,7 @@
                     }
                     // inner read loop
                     while (true) {
-                        String receivedText;
-                        try {
-                            receivedText = rttCall.read();
-                        } catch (IOException e) {
-                            break;
-                        }
+                        String receivedText = rttCall.read();
                         if (receivedText == null) {
                             if (Thread.currentThread().isInterrupted()) {
                                 break begin;
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 8d5184d..291152f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -33,7 +33,6 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ConnectionServiceWrapper;
 import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.InterruptionFilterProxy;
 import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.WiredHeadsetManager;
@@ -73,39 +72,30 @@
     static class RoutingTestParameters extends TestParameters {
         public String name;
         public int initialRoute;
-        public int initialNotificationFilter;
         public int availableRoutes; // may excl. speakerphone, because that's always available
         public int speakerInteraction; // one of NONE, ON, or OFF
         public int bluetoothInteraction; // one of NONE, ON, or OFF
         public int action;
         public int expectedRoute;
         public int expectedAvailableRoutes; // also may exclude the speakerphone.
-        public int expectedNotificationFilter; // expected end notification filter.
-        public boolean isNotificationChangeExpected; // indicates whether we expect the notification
-                                                     // filter to change during the process of the
-                                                     // test.
         public boolean doesDeviceSupportEarpiece; // set to false in the case of Wear devices
         public boolean shouldRunWithFocus;
 
         public int callSupportedRoutes = CallAudioState.ROUTE_ALL;
 
         public RoutingTestParameters(String name, int initialRoute,
-                int initialNotificationFilter, int availableRoutes, int speakerInteraction,
+                int availableRoutes, int speakerInteraction,
                 int bluetoothInteraction, int action, int expectedRoute,
-                int expectedAvailableRoutes, int expectedNotificationFilter,
-                boolean isNotificationChangeExpected, boolean doesDeviceSupportEarpiece,
+                int expectedAvailableRoutes, boolean doesDeviceSupportEarpiece,
                 boolean shouldRunWithFocus) {
             this.name = name;
             this.initialRoute = initialRoute;
-            this.initialNotificationFilter = initialNotificationFilter;
             this.availableRoutes = availableRoutes;
             this.speakerInteraction = speakerInteraction;
             this.bluetoothInteraction = bluetoothInteraction;
             this.action = action;
             this.expectedRoute = expectedRoute;
             this.expectedAvailableRoutes = expectedAvailableRoutes;
-            this.expectedNotificationFilter = expectedNotificationFilter;
-            this.isNotificationChangeExpected = isNotificationChangeExpected;
             this.doesDeviceSupportEarpiece = doesDeviceSupportEarpiece;
             this.shouldRunWithFocus = shouldRunWithFocus;
         }
@@ -120,15 +110,12 @@
             return "RoutingTestParameters{" +
                     "name='" + name + '\'' +
                     ", initialRoute=" + initialRoute +
-                    ", initialNotificationFilter=" + initialNotificationFilter +
                     ", availableRoutes=" + availableRoutes +
                     ", speakerInteraction=" + speakerInteraction +
                     ", bluetoothInteraction=" + bluetoothInteraction +
                     ", action=" + action +
                     ", expectedRoute=" + expectedRoute +
                     ", expectedAvailableRoutes=" + expectedAvailableRoutes +
-                    ", expectedNotificationFilter= " + expectedNotificationFilter +
-                    ", isNotificationChangeExpected=" + isNotificationChangeExpected +
                     ", doesDeviceSupportEarpiece=" + doesDeviceSupportEarpiece +
                     ", shouldRunWithFocus=" + shouldRunWithFocus +
                     '}';
@@ -142,7 +129,6 @@
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
-    @Mock InterruptionFilterProxy mMockInterruptionFilterProxy;
 
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
@@ -169,29 +155,11 @@
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
-        setupInterruptionFilterMocks();
 
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
     }
 
-    private void setupInterruptionFilterMocks() {
-        // These mock implementations keep track of when the caller sets the current notification
-        // filter, and ensures the same value is returned via getCurrentInterruptionFilter.
-        final int objId = Objects.hashCode(mMockInterruptionFilterProxy);
-        when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
-                NotificationManager.INTERRUPTION_FILTER_ALL);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock i) {
-                int requestedFilter = (int) i.getArguments()[0];
-                when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
-                        requestedFilter);
-                return null;
-            }
-        }).when(mMockInterruptionFilterProxy).setInterruptionFilter(anyInt());
-    }
-
     @LargeTest
     public void testStateMachineTransitionsWithFocus() throws Throwable {
         List<RoutingTestParameters> paramList = generateTransitionTests(true);
@@ -213,7 +181,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 true);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -256,7 +223,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 true);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -277,9 +243,6 @@
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
         verifyNewSystemCallAudioState(initState, expectedEndState);
-        // Expecting to end up in earpiece, so we expect notifications to be filtered.
-        assertEquals(NotificationManager.INTERRUPTION_FILTER_ALARMS,
-                mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
         resetMocks(false);
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
@@ -299,7 +262,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 true);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -315,9 +277,6 @@
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
-        // Shouldn't change interruption filter when in bluetooth route.
-        assertEquals(NotificationManager.INTERRUPTION_FILTER_ALL,
-                mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
@@ -334,7 +293,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 true);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -436,7 +394,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 doesDeviceSupportEarpiece);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
@@ -447,15 +404,12 @@
         params.add(new RoutingTestParameters(
                 "Connect headset during earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -463,15 +417,12 @@
         params.add(new RoutingTestParameters(
                 "Connect headset during bluetooth", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -479,15 +430,12 @@
         params.add(new RoutingTestParameters(
                 "Connect headset during speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -495,15 +443,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect headset during headset", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -511,15 +456,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect headset during headset with bluetooth available", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -527,15 +469,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect headset during bluetooth", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -543,15 +482,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect headset during speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -559,15 +495,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect headset during speakerphone with bluetooth available", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -575,15 +508,12 @@
         params.add(new RoutingTestParameters(
                 "Connect bluetooth during earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 NONE, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -591,15 +521,12 @@
         params.add(new RoutingTestParameters(
                 "Connect bluetooth during wired headset", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 NONE, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -607,15 +534,12 @@
         params.add(new RoutingTestParameters(
                 "Connect bluetooth during speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -623,15 +547,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during bluetooth without headset in", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -639,15 +560,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during bluetooth without headset in, priority mode ", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_PRIORITY, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_PRIORITY, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -655,15 +573,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during bluetooth with headset in", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -671,15 +586,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -687,15 +599,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -703,15 +612,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to speakerphone from earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 ON, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -719,15 +625,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to speakerphone from headset", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 ON, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -735,15 +638,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to speakerphone from bluetooth", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 ON, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -751,15 +651,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to earpiece from bluetooth", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -767,15 +664,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to earpiece from speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -783,15 +677,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to earpiece from speakerphone, priority notifications", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_PRIORITY, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_PRIORITY, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -799,15 +690,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to earpiece from speakerphone, silent mode", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_NONE, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_NONE, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -815,15 +703,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to bluetooth from speakerphone", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OFF, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -831,15 +716,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to bluetooth from earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 NONE, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                true, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -847,15 +729,12 @@
         params.add(new RoutingTestParameters(
                 "Switch to bluetooth from wired headset", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 NONE, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -863,15 +742,12 @@
         params.add(new RoutingTestParameters(
                 "Switch from bluetooth to wired/earpiece when neither are available", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 ON, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 false, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -879,15 +755,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect wired headset when device does not support earpiece", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 ON, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 false, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ));
@@ -895,15 +768,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect wired headset when call doesn't support earpiece", // name
                 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 ON, // speakerInteraction
                 NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
@@ -911,15 +781,12 @@
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth when call does not support earpiece", // name
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
                 CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
                 ON, // speakerInteraction
                 OFF, // bluetoothInteraction
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
-                false, // isNotificationChangeExpected
                 true, // doesDeviceSupportEarpiece
                 shouldRunWithFocus
         ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
@@ -940,8 +807,6 @@
     private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params)
             throws Throwable {
         resetMocks(true);
-        when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
-                params.initialNotificationFilter);
 
         // Construct a fresh state machine on every case
         final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
@@ -951,7 +816,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 params.doesDeviceSupportEarpiece);
 
         setupMocksForParams(params);
@@ -973,22 +837,6 @@
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
 
-        // Capture the changes made to the interruption filter and verify that the last change
-        // made to it matches the expected interruption filter.
-        if (params.isNotificationChangeExpected) {
-            ArgumentCaptor<Integer> interruptionCaptor = ArgumentCaptor.forClass(Integer.class);
-            verify(mMockInterruptionFilterProxy, timeout(TEST_TIMEOUT).atLeastOnce())
-                    .setInterruptionFilter(interruptionCaptor.capture());
-            List<Integer> interruptionFilterValues = interruptionCaptor.getAllValues();
-
-            int lastChange = interruptionFilterValues.get(interruptionFilterValues.size() - 1)
-                    .intValue();
-            assertEquals(params.expectedNotificationFilter, lastChange);
-        } else {
-            Thread.sleep(TEST_TIMEOUT);
-            verify(mMockInterruptionFilterProxy, never()).setInterruptionFilter(anyInt());
-        }
-
         Handler h = stateMachine.getHandler();
         waitForHandlerAction(h, TEST_TIMEOUT);
         stateMachine.quitStateMachine();
@@ -1048,7 +896,6 @@
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
-                mMockInterruptionFilterProxy,
                 params.doesDeviceSupportEarpiece);
 
         // Set up bluetooth and speakerphone state
@@ -1068,6 +915,8 @@
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioModeStateMachine.RUN_RUNNABLE);
 
+        Handler h = stateMachine.getHandler();
+        waitForHandlerAction(h, TEST_TIMEOUT);
         stateMachine.quitStateMachine();
 
         // Verify that no substantive interactions have taken place with the
@@ -1111,11 +960,6 @@
     private void resetMocks(boolean resetNotificationFilter) {
         reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
                 mockConnectionServiceWrapper, fakeCall);
-        if (resetNotificationFilter) {
-            reset(mMockInterruptionFilterProxy);
-            mMockInterruptionFilterProxy = mock(InterruptionFilterProxy.class);
-            setupInterruptionFilterMocks();
-        }
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 255af74..1c74bfa 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -439,7 +439,7 @@
         mResourceConfiguration.setLocale(Locale.TAIWAN);
 
         // TODO: Move into actual tests
-        when(mAudioManager.isWiredHeadsetOn()).thenReturn(false);
+        doReturn(false).when(mAudioManager).isWiredHeadsetOn();
 
         doAnswer(new Answer<List<ResolveInfo>>() {
             @Override
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
index 6f2d008..1909259 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -16,20 +16,18 @@
 
 package com.android.server.telecom.tests;
 
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
 import android.os.Build;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
+import com.android.server.telecom.HandoverState;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import static org.mockito.Matchers.any;
@@ -73,6 +71,7 @@
         when(mRingingCall.getState()).thenReturn(CallState.RINGING);
         when(mRingingCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mRingingCall.getTargetPhoneAccountLabel()).thenReturn("Foo");
+        when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_NONE);
     }
 
     /**
@@ -128,4 +127,40 @@
         verify(mNotificationManager).cancel(eq(IncomingCallNotifier.NOTIFICATION_TAG),
                 eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL));
     }
+
+    /**
+     * Ensure notification doesn't show during handover.
+     */
+    @SmallTest
+    public void testDontShowDuringHandover1() {
+        when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(true);
+        when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(1);
+        when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+        when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_FROM_STARTED);
+
+        mIncomingCallNotifier.onCallAdded(mAudioCall);
+        mIncomingCallNotifier.onCallAdded(mRingingCall);
+
+        // Incoming call is in the middle of a handover, don't expect to be notified.
+        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+    }
+
+    /**
+     * Ensure notification doesn't show during handover.
+     */
+    @SmallTest
+    public void testDontShowDuringHandover2() {
+        when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(true);
+        when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(1);
+        when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+        when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_COMPLETE);
+
+        mIncomingCallNotifier.onCallAdded(mAudioCall);
+        mIncomingCallNotifier.onCallAdded(mRingingCall);
+
+        // Incoming call is done a handover, don't expect to be notified.
+        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 0832e7a..e51a9c7 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -44,6 +44,7 @@
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.IAudioService;
+import android.media.ToneGenerator;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -73,7 +74,6 @@
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
-import com.android.server.telecom.InterruptionFilterProxy;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -179,31 +179,28 @@
             return mIsEmergencyCall;
         }
     }
-    PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
 
-    public static class MockInterruptionFilterProxy implements InterruptionFilterProxy {
-        private int mInterruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
-        @Override
-        public void setInterruptionFilter(int interruptionFilter) {
-            mInterruptionFilter = interruptionFilter;
+    private class IncomingCallAddedListener extends CallsManagerListenerBase {
+
+        private final CountDownLatch mCountDownLatch;
+
+        public IncomingCallAddedListener(CountDownLatch latch) {
+            mCountDownLatch = latch;
         }
 
         @Override
-        public int getCurrentInterruptionFilter() {
-            return mInterruptionFilter;
-        }
-
-        @Override
-        public String getInterruptionModeInitiator() {
-            return "com.android.server.telecom";
+        public void onCallAdded(com.android.server.telecom.Call call) {
+            mCountDownLatch.countDown();
         }
     }
+
+    PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
+
     @Mock HeadsetMediaButton mHeadsetMediaButton;
     @Mock ProximitySensorManager mProximitySensorManager;
     @Mock InCallWakeLockController mInCallWakeLockController;
     @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
     @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
-    @Mock InterruptionFilterProxy mInterruptionFilterProxy;
     @Mock IncomingCallNotifier mIncomingCallNotifier;
     @Mock ClockProxy mClockProxy;
 
@@ -348,6 +345,8 @@
         // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
         // now-running TelecomSystem
         setupConnectionServices();
+
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
     }
 
     @Override
@@ -437,8 +436,8 @@
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
                 mPhoneNumberUtilsAdapter,
-                mInterruptionFilterProxy,
                 mIncomingCallNotifier,
+                (streamType, volume) -> mock(ToneGenerator.class),
                 mClockProxy);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
@@ -781,6 +780,10 @@
         final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
         connectionServiceFixture.mConnectionServiceDelegate.mVideoState = videoState;
+        CountDownLatch incomingCallAddedLatch = new CountDownLatch(1);
+        IncomingCallAddedListener callAddedListener =
+                new IncomingCallAddedListener(incomingCallAddedLatch);
+        mTelecomSystem.getCallsManager().addListener(callAddedListener);
 
         Bundle extras = new Bundle();
         extras.putParcelable(
@@ -805,6 +808,9 @@
         mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
                 CallerInfoAsyncQueryFactoryFixture.Request::reply);
 
+        //Wait for/Verify call blocking happened asynchronously
+        incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
         IContentProvider blockedNumberProvider =
                 mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY);
         verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index b909a54..b735df9 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -37,6 +37,7 @@
         mMockitoHelper.setUp(getContext(), getClass());
         mComponentContextFixture = new ComponentContextFixture();
         Log.setSessionContext(mComponentContextFixture.getTestDouble().getApplicationContext());
+        Log.getSessionManager().mCleanStaleSessions = null;
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 4764e92..c91f86e 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -18,9 +18,12 @@
 
 import org.mockito.ArgumentCaptor;
 
+import android.os.Process;
 import android.os.RemoteException;
 import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
 import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.server.telecom.CallAudioModeStateMachine;
@@ -127,6 +130,93 @@
     }
 
     /**
+     * Ensure that when an incoming video call is missed, the video state history still includes
+     * video calling. This is important for the call log.
+     */
+    @LargeTest
+    public void testIncomingVideoCallMissedCheckVideoHistory() throws Exception {
+        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
+                .iterator().next();
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.MISSED);
+
+        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
+    }
+
+    /**
+     * Ensure that when an incoming video call is rejected, the video state history still includes
+     * video calling. This is important for the call log.
+     */
+    @LargeTest
+    public void testIncomingVideoCallRejectedCheckVideoHistory() throws Exception {
+        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
+                .iterator().next();
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.REJECTED);
+
+        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
+    }
+
+
+    /**
+     * Ensure that when an outgoing video call is canceled, the video state history still includes
+     * video calling. This is important for the call log.
+     */
+    @LargeTest
+    public void testOutgoingVideoCallCanceledCheckVideoHistory() throws Exception {
+        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA, Process.myUserHandle(),
+                VideoProfile.STATE_BIDIRECTIONAL);
+        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
+                .iterator().next();
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+
+        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
+    }
+
+    /**
+     * Ensure that when an outgoing video call is rejected, the video state history still includes
+     * video calling. This is important for the call log.
+     */
+    @LargeTest
+    public void testOutgoingVideoCallRejectedCheckVideoHistory() throws Exception {
+        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA, Process.myUserHandle(),
+                VideoProfile.STATE_BIDIRECTIONAL);
+        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
+                .iterator().next();
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.REMOTE);
+
+        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
+    }
+
+    /**
+     * Ensure that when an outgoing video call is answered as audio only, the video state history
+     * shows that the call was audio only. This is important for the call log.
+     */
+    @LargeTest
+    public void testOutgoingVideoCallAnsweredAsAudio() throws Exception {
+        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA, Process.myUserHandle(),
+                VideoProfile.STATE_BIDIRECTIONAL);
+        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
+                .iterator().next();
+
+        mConnectionServiceFixtureA.mConnectionById.get(ids.mConnectionId).videoState
+                = VideoProfile.STATE_AUDIO_ONLY;
+        mConnectionServiceFixtureA.sendSetVideoState(ids.mConnectionId);
+        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+
+        assertFalse(VideoProfile.isVideo(call.getVideoStateHistory()));
+    }
+
+    /**
      * Verifies that the
      * {@link android.telecom.InCallService#onCallAudioStateChanged(CallAudioState)} change is
      * called with an expected route and number of changes.