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.