diff options
174 files changed, 3448 insertions, 2824 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 327d0a2c5..45e315121 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -47,6 +47,7 @@ <uses-permission android:name="android.permission.READ_CALL_LOG"/> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/> <uses-permission android:name="android.permission.VIBRATE"/> @@ -165,6 +166,7 @@ <activity-alias android:name="PrivilegedCallActivity" android:targetActivity=".components.UserCallActivity" android:permission="android.permission.CALL_PRIVILEGED" + android:exported="true" android:process=":ui"> <intent-filter android:priority="1000"> <action android:name="android.intent.action.CALL_PRIVILEGED"/> @@ -200,6 +202,7 @@ <activity-alias android:name="EmergencyCallActivity" android:targetActivity=".components.UserCallActivity" android:permission="android.permission.CALL_PRIVILEGED" + android:exported="true" android:process=":ui"> <intent-filter android:priority="1000"> <action android:name="android.intent.action.CALL_EMERGENCY"/> diff --git a/res/values-af/config.xml b/res/values-af/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-af/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-am/config.xml b/res/values-am/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-am/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ar/config.xml b/res/values-ar/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ar/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-as/config.xml b/res/values-as/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-as/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-az/config.xml b/res/values-az/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-az/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-b+sr+Latn/config.xml b/res/values-b+sr+Latn/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-b+sr+Latn/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-be/config.xml b/res/values-be/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-be/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-bg/config.xml b/res/values-bg/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-bg/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-bn/config.xml b/res/values-bn/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-bn/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-bs/config.xml b/res/values-bs/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-bs/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ca/config.xml b/res/values-ca/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ca/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-cs/config.xml b/res/values-cs/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-cs/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-da/config.xml b/res/values-da/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-da/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-de/config.xml b/res/values-de/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-de/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-el/config.xml b/res/values-el/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-el/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-en-rAU/config.xml b/res/values-en-rAU/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-en-rAU/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-en-rCA/config.xml b/res/values-en-rCA/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-en-rCA/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-en-rGB/config.xml b/res/values-en-rGB/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-en-rGB/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-en-rIN/config.xml b/res/values-en-rIN/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-en-rIN/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-en-rXC/config.xml b/res/values-en-rXC/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-en-rXC/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-es-rUS/config.xml b/res/values-es-rUS/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-es-rUS/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-es/config.xml b/res/values-es/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-es/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index b7b9cff0d..e124de414 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -99,7 +99,7 @@ <string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueo de llamadas"</string> <string name="notification_channel_background_calls" msgid="7785659903711350506">"Llamadas en segundo plano"</string> <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Llamadas interrumpidas"</string> - <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicaciones para teléfonos con fallos"</string> + <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicaciones para teléfonos con bloqueos"</string> <string name="alert_outgoing_call" msgid="5319895109298927431">"Si haces esta llamada, se finalizará la de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string> <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Elige cómo quieres hacer esta llamada"</string> <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirigir llamada con <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string> diff --git a/res/values-et/config.xml b/res/values-et/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-et/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-eu/config.xml b/res/values-eu/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-eu/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-fa/config.xml b/res/values-fa/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-fa/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-fi/config.xml b/res/values-fi/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-fi/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-fr-rCA/config.xml b/res/values-fr-rCA/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-fr-rCA/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 5061eda1f..74faab9d4 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -104,7 +104,7 @@ <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choisissez comment passer cet appel"</string> <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Rediriger l\'appel en utilisant <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string> <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Appeler en utilisant mon numéro de téléphone"</string> - <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Impossible de passer l\'appel au moyen de l\'application <xliff:g id="OTHER_APP">%1$s</xliff:g>. Essayez d\'utiliser une autre application de redirection d\'appels ou de communiquer avec le développeur de l\'application pour obtenir de l\'aide."</string> + <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Impossible de passer l\'appel au moyen de l\'application <xliff:g id="OTHER_APP">%1$s</xliff:g>. Essayez d\'utiliser une autre application de redirection d\'appels ou de communiquer avec le concepteur de l\'application pour obtenir de l\'aide."</string> <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"Blocage des appels"</string> <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"Numéros non répertoriés dans les contacts"</string> <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Bloquer les numéros non répertoriés dans vos contacts"</string> diff --git a/res/values-fr/config.xml b/res/values-fr/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-fr/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-gl/config.xml b/res/values-gl/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-gl/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-gu/config.xml b/res/values-gu/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-gu/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-hi/config.xml b/res/values-hi/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-hi/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-hr/config.xml b/res/values-hr/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-hr/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-hu/config.xml b/res/values-hu/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-hu/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-hy/config.xml b/res/values-hy/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-hy/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-in/config.xml b/res/values-in/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-in/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index f4793402e..23ebb7380 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -65,13 +65,13 @@ <string name="blocked_numbers" msgid="8322134197039865180">"Nomor yang diblokir"</string> <string name="blocked_numbers_msg" msgid="2797422132329662697">"Anda tidak akan menerima telepon atau SMS dari nomor yang diblokir."</string> <string name="block_number" msgid="3784343046852802722">"Tambahkan nomor"</string> - <string name="unblock_dialog_body" msgid="2723393535797217261">"Berhenti memblokir <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string> - <string name="unblock_button" msgid="8732021675729981781">"Berhenti memblokir"</string> + <string name="unblock_dialog_body" msgid="2723393535797217261">"Batalkan pemblokiran <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string> + <string name="unblock_button" msgid="8732021675729981781">"Batalkan pemblokiran"</string> <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Blokir panggilan telepon dan SMS dari"</string> <string name="add_blocked_number_hint" msgid="8769422085658041097">"Nomor telepon"</string> <string name="block_button" msgid="485080149164258770">"Blokir"</string> <string name="non_primary_user" msgid="315564589279622098">"Hanya pemilik perangkat yang dapat melihat dan mengelola nomor yang diblokir."</string> - <string name="delete_icon_description" msgid="5335959254954774373">"Berhenti memblokir"</string> + <string name="delete_icon_description" msgid="5335959254954774373">"Bebaskan"</string> <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Blokir dinonaktifkan sementara"</string> <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Setelah Anda menelepon atau mengirim SMS ke nomor gawat darurat, blokir akan dinonaktifkan agar layanan gawat darurat tersebut dapat menghubungi Anda."</string> <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Aktifkan lagi sekarang"</string> diff --git a/res/values-is/config.xml b/res/values-is/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-is/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-it/config.xml b/res/values-it/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-it/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-iw/config.xml b/res/values-iw/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-iw/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 17e4a1b65..30a909b7b 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -24,7 +24,7 @@ <string name="notification_missedCallsTitle" msgid="3910479625507893809">"שיחות שלא נענו"</string> <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> שיחות שלא נענו"</string> <string name="notification_missedCallTicker" msgid="6731461957487087769">"שיחה שלא נענתה מאת <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string> - <string name="notification_missedCall_call_back" msgid="7900333283939789732">"התקשרות בחזרה"</string> + <string name="notification_missedCall_call_back" msgid="7900333283939789732">"התקשר חזרה"</string> <string name="notification_missedCall_message" msgid="4054698824390076431">"שליחת הודעה"</string> <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"השיחה נותקה"</string> <string name="notification_disconnectedCall_body" msgid="600491714584417536">"השיחה עם <xliff:g id="CALLER">%s</xliff:g> נותקה בגלל שיחת חירום."</string> @@ -37,7 +37,7 @@ <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"רמקול מופעל."</string> <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"לא נוח לי עכשיו. מה קורה?"</string> <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"תיכף אחזור אליך."</string> - <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"אתקשר אליך יותר מאוחר."</string> + <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"אני אתקשר אליך יותר מאוחר."</string> <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"לא נוח לי עכשיו. נדבר אחר כך?"</string> <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"תגובות מהירות"</string> <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"עריכת תגובות מהירות"</string> @@ -48,15 +48,15 @@ <string name="enable_account_preference_title" msgid="6949224486748457976">"חשבונות לביצוע שיחות"</string> <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"ניתן לבצע רק שיחות חירום."</string> <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"לא ניתן להוציא שיחות באמצעות האפליקציה הזו ללא ההרשאה \'טלפון\'."</string> - <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"כדי להתקשר, יש להזין מספר טלפון חוקי."</string> + <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"כדי להתקשר, הזן מספר טלפון חוקי."</string> <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"לא ניתן כעת להוסיף את השיחה."</string> <string name="no_vm_number" msgid="2179959110602180844">"חסר מספר של דואר קולי"</string> <string name="no_vm_number_msg" msgid="1339245731058529388">"בכרטיס ה-SIM לא מאוחסן מספר של דואר קולי."</string> - <string name="add_vm_number_str" msgid="5179510133063168998">"הוספת מספר"</string> + <string name="add_vm_number_str" msgid="5179510133063168998">"הוסף מספר"</string> <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"האם להפוך את <xliff:g id="NEW_APP">%s</xliff:g> לברירת המחדל לאפליקציית \'טלפון\'?"</string> - <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"הגדרה כברירת מחדל"</string> + <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"הגדר כברירת מחדל"</string> <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ביטול"</string> - <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> תוכל להתקשר ולשלוט בכל ההיבטים של השיחות. מומלץ לבחור רק אפליקציות שסומכים עליהן כברירת המחדל לאפליקציית \'טלפון\'."</string> + <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> תוכל להתקשר ולשלוט בכל ההיבטים של השיחות. מומלץ לבחור רק אפליקציות שאתה סומך עליהן כברירת המחדל לאפליקציית \'טלפון\'."</string> <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"רוצה להפוך את <xliff:g id="NEW_APP">%s</xliff:g> לאפליקציית ברירת המחדל שלך לסינון שיחות?"</string> <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"אפליקציית <xliff:g id="OLD_APP">%s</xliff:g> לא תוכל לסנן שיחות עוד."</string> <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"אפליקציית <xliff:g id="NEW_APP">%s</xliff:g> תוכל לראות מידע על מתקשרים שאינם באנשי הקשר שלך ולחסום שיחות מהם. מומלץ לבחור רק אפליקציות שסומכים עליהן כברירת המחדל לסינון שיחות."</string> @@ -74,12 +74,12 @@ <string name="delete_icon_description" msgid="5335959254954774373">"ביטול חסימה"</string> <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"החסימה הושבתה זמנית"</string> <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"לאחר חיוג או שליחת הודעה למספר חירום, החסימה תושבת כדי ששירותי החירום יוכלו ליצור איתך קשר."</string> - <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"הפעלה מחדש עכשיו"</string> + <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"הפעל מחדש עכשיו"</string> <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> נחסם"</string> <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"החסימה של <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> בוטלה"</string> <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"אי אפשר לחסום מספרי חירום."</string> <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"המספר <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> כבר חסום."</string> - <string name="toast_personal_call_msg" msgid="5817631570381795610">"נעשה שימוש בחייגן האישי כדי להתקשר"</string> + <string name="toast_personal_call_msg" msgid="5817631570381795610">"משתמש בחייגן האישי כדי להתקשר"</string> <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_FROM">%2$s</xliff:g> מתקשר/ת אליך ב-<xliff:g id="CALL_VIA">%1$s</xliff:g>"</string> <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_FROM">%2$s</xliff:g> מתקשר/ת אליך בשיחת וידאו ב-<xliff:g id="CALL_VIA">%1$s</xliff:g>"</string> <string name="answering_ends_other_call" msgid="8653544281903986641">"מענה יסיים את השיחה ב-<xliff:g id="CALL_VIA">%1$s</xliff:g>"</string> diff --git a/res/values-ja/config.xml b/res/values-ja/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ja/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ka/config.xml b/res/values-ka/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ka/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-kk/config.xml b/res/values-kk/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-kk/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-km/config.xml b/res/values-km/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-km/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-kn/config.xml b/res/values-kn/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-kn/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ko/config.xml b/res/values-ko/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ko/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ky/config.xml b/res/values-ky/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ky/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-lo/config.xml b/res/values-lo/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-lo/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-lt/config.xml b/res/values-lt/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-lt/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-lv/config.xml b/res/values-lv/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-lv/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-mk/config.xml b/res/values-mk/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-mk/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ml/config.xml b/res/values-ml/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ml/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-mn/config.xml b/res/values-mn/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-mn/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-mr/config.xml b/res/values-mr/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-mr/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index c235aab8e..331cf5cd3 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -94,7 +94,7 @@ <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"आपल्या <xliff:g id="OTHER_CALL">%1$s</xliff:g> कॉलमुळे कॉल केला जाऊ शकत नाही."</string> <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"आपल्या <xliff:g id="OTHER_CALL">%1$s</xliff:g> कॉलमुळे कॉल केला जाऊ शकत नाही."</string> <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"दुसर्या ॲपमधील कॉलमुळे कॉल केला जाऊ शकत नाही."</string> - <string name="notification_channel_incoming_call" msgid="5245550964701715662">"इनकमिंग कॉल"</string> + <string name="notification_channel_incoming_call" msgid="5245550964701715662">"येणारे कॉल"</string> <string name="notification_channel_missed_call" msgid="7168893015283909012">"मिस्ड कॉल"</string> <string name="notification_channel_call_blocking" msgid="2028807677868598710">"कॉल ब्लॉक करणे"</string> <string name="notification_channel_background_calls" msgid="7785659903711350506">"बॅकग्राउंड कॉल"</string> @@ -111,9 +111,9 @@ <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"खाजगी"</string> <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"अशा कॉलरना ब्लॉक करा, जे त्यांचे क्रमांक उघड करत नाहीत"</string> <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"पे फोन"</string> - <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"पे फोनवरून इनकमिंग कॉल ब्लॉक करा"</string> + <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"पे फोनवरून येणारे कॉल ब्लॉक करा"</string> <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"अज्ञात"</string> - <string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"अनोळखी कॉलरकडून इनकमिंग कॉल ब्लॉक करा"</string> + <string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"अनोळखी कॉलरकडून येणारे कॉल ब्लॉक करा"</string> <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"कॉल ब्लॉक करणे"</string> <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"कॉल ब्लॉक करणे बंद केले"</string> <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"आणीबाणी कॉल केला"</string> diff --git a/res/values-ms/config.xml b/res/values-ms/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ms/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-my/config.xml b/res/values-my/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-my/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-nb/config.xml b/res/values-nb/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-nb/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ne/config.xml b/res/values-ne/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ne/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 5ef8bc005..5d7eef0fe 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -93,7 +93,7 @@ <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"यस प्रकारका कलहरूलाई समर्थन गर्ने कुनै पनि कल गर्ने खाता नभएकाले कल गर्न सकिँदैन।"</string> <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"तपाईंको <xliff:g id="OTHER_CALL">%1$s</xliff:g> कलका कारण कल गर्न सकिँदैन।"</string> <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"तपाईंका <xliff:g id="OTHER_CALL">%1$s</xliff:g> कलहरूका कारण कल गर्न सकिँदैन।"</string> - <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"अर्को एपमा जारी कलका कारण कल गर्न सकिँदैन।"</string> + <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"अर्को अनुप्रयोगमा जारी कलका कारण कल गर्न सकिँदैन।"</string> <string name="notification_channel_incoming_call" msgid="5245550964701715662">"आगमन कलहरू"</string> <string name="notification_channel_missed_call" msgid="7168893015283909012">"मिस कलहरू"</string> <string name="notification_channel_call_blocking" msgid="2028807677868598710">"कलमाथि रोक लगाउने सुविधा"</string> diff --git a/res/values-nl/config.xml b/res/values-nl/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-nl/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-or/config.xml b/res/values-or/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-or/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-pa/config.xml b/res/values-pa/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-pa/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-pl/config.xml b/res/values-pl/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-pl/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-pt-rPT/config.xml b/res/values-pt-rPT/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-pt-rPT/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-pt/config.xml b/res/values-pt/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-pt/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ro/config.xml b/res/values-ro/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ro/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ru/config.xml b/res/values-ru/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ru/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-si/config.xml b/res/values-si/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-si/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sk/config.xml b/res/values-sk/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sk/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sl/config.xml b/res/values-sl/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sl/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sq/config.xml b/res/values-sq/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sq/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sr/config.xml b/res/values-sr/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sr/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sv/config.xml b/res/values-sv/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sv/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-sw/config.xml b/res/values-sw/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-sw/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ta/config.xml b/res/values-ta/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ta/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-te/config.xml b/res/values-te/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-te/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 0dece3491..e6deb9c8e 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -47,7 +47,7 @@ <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశాన్ని పంపడం విఫలమైంది."</string> <string name="enable_account_preference_title" msgid="6949224486748457976">"కాలింగ్ ఖాతాలు"</string> <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"కేవలం అత్యవసర కాల్లు మాత్రమే అనుమతించబడతాయి."</string> - <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"ఈ యాప్ ఫోన్ అనుమతి లేకుండా అవుట్గోయింగ్ కాల్లను చేయలేదు."</string> + <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"ఈ అనువర్తనం ఫోన్ అనుమతి లేకుండా అవుట్గోయింగ్ కాల్లను చేయలేదు."</string> <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్ను నమోదు చేయండి."</string> <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్ను జోడించడం సాధ్యపడదు."</string> <string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string> diff --git a/res/values-th/config.xml b/res/values-th/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-th/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-tl/config.xml b/res/values-tl/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-tl/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-tr/config.xml b/res/values-tr/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-tr/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-uk/config.xml b/res/values-uk/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-uk/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-ur/config.xml b/res/values-ur/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-ur/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-uz/config.xml b/res/values-uz/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-uz/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-vi/config.xml b/res/values-vi/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-vi/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-zh-rCN/config.xml b/res/values-zh-rCN/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-zh-rCN/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-zh-rHK/config.xml b/res/values-zh-rHK/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-zh-rHK/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-zh-rTW/config.xml b/res/values-zh-rTW/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-zh-rTW/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values-zu/config.xml b/res/values-zu/config.xml deleted file mode 100644 index 5b355dfbb..000000000 --- a/res/values-zu/config.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2014 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. - --> - -<!-- Telecomm resources that may need to be customized for different hardware or product - builds. --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="call_diagnostic_service_package_name" msgid="2953253648590374717"></string> -</resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 9b49515c7..8d6e084d4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -17,8 +17,10 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Official label of the Telecomm/Phone app, as seen in "Manage Applications" and other settings UIs. This is the "app name" used in notification, recents, - and app info screens. --> - <string name="telecommAppLabel" product="default">Call Management</string> + and app info screens. The term "phone calls" is used since an end user would more commonly + think of the stuff the Telecom framework deals with as having to do with "phone calls", + not "call management" (even though that is technically more accurate). --> + <string name="telecommAppLabel" product="default">Phone Calls</string> <!-- Title used for the activity for placing a call. This name appears in activity disambig dialogs --> diff --git a/src/com/android/server/telecom/BluetoothAdapterProxy.java b/src/com/android/server/telecom/BluetoothAdapterProxy.java deleted file mode 100644 index ee9cde3f0..000000000 --- a/src/com/android/server/telecom/BluetoothAdapterProxy.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.Context; - -/** - * Proxy class used so that BluetoothAdapter can be mocked for testing. - */ -public class BluetoothAdapterProxy { - private BluetoothAdapter mBluetoothAdapter; - - public BluetoothAdapterProxy() { - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - } - - public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, - int profile) { - if (mBluetoothAdapter == null) { - return false; - } - return mBluetoothAdapter.getProfileProxy(context, listener, profile); - } - - public boolean setActiveDevice(BluetoothDevice device, int profiles) { - if (mBluetoothAdapter == null) { - return false; - } - if (device != null) { - return mBluetoothAdapter.setActiveDevice(device, profiles); - } else { - return mBluetoothAdapter.removeActiveDevice(profiles); - } - } -} diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java deleted file mode 100644 index e4eed8707..000000000 --- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; - -import java.util.List; - -/** - * A proxy class that facilitates testing of the BluetoothPhoneServiceImpl class. - * - * This is necessary due to the "final" attribute of the BluetoothHeadset class. In order to - * test the correct functioning of the BluetoothPhoneServiceImpl class, the final class must be put - * into a container that can be mocked correctly. - */ -public class BluetoothHeadsetProxy { - - private BluetoothHeadset mBluetoothHeadset; - - public BluetoothHeadsetProxy(BluetoothHeadset headset) { - mBluetoothHeadset = headset; - } - - public List<BluetoothDevice> getConnectedDevices() { - return mBluetoothHeadset.getConnectedDevices(); - } - - public int getConnectionState(BluetoothDevice device) { - return mBluetoothHeadset.getConnectionState(device); - } - - public int getAudioState(BluetoothDevice device) { - return mBluetoothHeadset.getAudioState(device); - } - - public boolean connectAudio() { - return mBluetoothHeadset.connectAudio(); - } - - public boolean setActiveDevice(BluetoothDevice device) { - return mBluetoothHeadset.setActiveDevice(device); - } - - public BluetoothDevice getActiveDevice() { - return mBluetoothHeadset.getActiveDevice(); - } - - public boolean isAudioOn() { - return mBluetoothHeadset.isAudioOn(); - } - - public boolean disconnectAudio() { - return mBluetoothHeadset.disconnectAudio(); - } - - public boolean isInbandRingingEnabled() { - return mBluetoothHeadset.isInbandRingingEnabled(); - } -} diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java index ae0719b88..ed6d223ca 100644 --- a/src/com/android/server/telecom/Call.java +++ b/src/com/android/server/telecom/Call.java @@ -39,14 +39,14 @@ import android.provider.CallLog; import android.provider.ContactsContract.Contacts; import android.telecom.BluetoothCallQualityReport; import android.telecom.CallAudioState; +import android.telecom.CallDiagnosticService; +import android.telecom.CallDiagnostics; import android.telecom.CallerInfo; import android.telecom.Conference; import android.telecom.Connection; import android.telecom.ConnectionService; -import android.telecom.DiagnosticCall; import android.telecom.DisconnectCause; import android.telecom.GatewayInfo; -import android.telecom.InCallService; import android.telecom.Log; import android.telecom.Logging.EventManager; import android.telecom.ParcelableConference; @@ -57,11 +57,12 @@ import android.telecom.Response; import android.telecom.StatusHints; import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.CallQuality; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; -import android.util.ArrayMap; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; @@ -81,7 +82,9 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * Encapsulates all aspects of a given phone call throughout its lifecycle, starting @@ -160,6 +163,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, void onHandoverComplete(Call call); void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report); void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue); + void onReceivedCallQualityReport(Call call, CallQuality callQuality); + void onCallerNumberVerificationStatusChanged(Call call, int callerNumberVerificationStatus); } public abstract static class ListenerBase implements Listener { @@ -252,6 +257,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {} @Override public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {} + @Override + public void onReceivedCallQualityReport(Call call, CallQuality callQuality) {} + @Override + public void onCallerNumberVerificationStatusChanged(Call call, + int callerNumberVerificationStatus) {} } private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener = @@ -662,6 +672,22 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, private boolean mIsSimCall; /** + * Set to {@code true} if we received a valid response ({@code null} or otherwise) from + * the {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or + * {@link CallDiagnostics#onCallDisconnected(int, int)} calls. This is used to detect a timeout + * when awaiting a response from the call diagnostic service. + */ + private boolean mReceivedCallDiagnosticPostCallResponse = false; + + /** + * {@link CompletableFuture} used to delay posting disconnection and removal to a call until + * after a {@link CallDiagnosticService} is able to handle the disconnection and provide a + * disconnect message via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or + * {@link CallDiagnostics#onCallDisconnected(int, int)}. + */ + private CompletableFuture<Boolean> mDisconnectFuture; + + /** * Persists the specified parameters and initializes the new instance. * @param context The context. * @param repository The connection service repository. @@ -1092,8 +1118,29 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, } } + /** + * Handles an incoming overridden disconnect message for this call. + * + * We only care if the disconnect is handled via a future. + * @param message the overridden disconnect message. + */ public void handleOverrideDisconnectMessage(@Nullable CharSequence message) { + Log.i(this, "handleOverrideDisconnectMessage; callid=%s, msg=%s", getId(), message); + + if (isDisconnectHandledViaFuture()) { + mReceivedCallDiagnosticPostCallResponse = true; + if (message != null) { + Log.addEvent(this, LogUtils.Events.OVERRIDE_DISCONNECT_MESSAGE, message); + // Replace the existing disconnect cause in this call + setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, message, + message, null)); + } + mDisconnectFuture.complete(true); + } else { + Log.w(this, "handleOverrideDisconnectMessage; callid=%s - got override when unbound", + getId()); + } } /** @@ -1280,6 +1327,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public void setCallerNumberVerificationStatus( @Connection.VerificationStatus int callerNumberVerificationStatus) { mCallerNumberVerificationStatus = callerNumberVerificationStatus; + mListeners.forEach(l -> l.onCallerNumberVerificationStatusChanged(this, + callerNumberVerificationStatus)); } public @Connection.VerificationStatus int getCallerNumberVerificationStatus() { @@ -1316,6 +1365,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, } catch (IllegalStateException ise) { Log.e(this, ise, "setHandle: can't determine if number is emergency"); mIsEmergencyCall = false; + } catch (RuntimeException r) { + Log.e(this, r, "setHandle: can't determine if number is emergency"); + mIsEmergencyCall = false; } mAnalytics.setCallIsEmergency(mIsEmergencyCall); } @@ -1340,6 +1392,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, number.equals(eNumber.getNumber())); } catch (IllegalStateException ise) { return false; + } catch (RuntimeException r) { + return false; } } @@ -2665,7 +2719,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, return mState == CallState.ACTIVE; } - Bundle getExtras() { + @VisibleForTesting + public Bundle getExtras() { return mExtras; } @@ -2705,6 +2760,16 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID)); } + if (extras.containsKey(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS) + && source == SOURCE_CONNECTION_SERVICE) { + int callerNumberVerificationStatus = + extras.getInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS); + if (mCallerNumberVerificationStatus != callerNumberVerificationStatus) { + Log.addEvent(this, LogUtils.Events.VERSTAT_CHANGED, callerNumberVerificationStatus); + setCallerNumberVerificationStatus(callerNumberVerificationStatus); + } + } + // The remote connection service API can track the phone account which was originally // requested to create a connection via the remote connection service API; we store that so // we have some visibility into how a call was actually placed. @@ -3726,7 +3791,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, * @param extras The extras. */ public void onConnectionEvent(String event, Bundle extras) { - Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event); + // Don't log call quality reports; they're quite frequent and will clog the log. + if (!Connection.EVENT_CALL_QUALITY_REPORT.equals(event)) { + Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event); + } if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) { mIsRemotelyHeld = true; Log.addEvent(this, LogUtils.Events.REMOTELY_HELD); @@ -3760,6 +3828,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, for (Listener l : mListeners) { l.onReceivedDeviceToDeviceMessage(this, messageType, messageValue); } + } else if (Connection.EVENT_CALL_QUALITY_REPORT.equals(event) + && extras != null && extras.containsKey(Connection.EXTRA_CALL_QUALITY_REPORT)) { + CallQuality callQuality = extras.getParcelable(Connection.EXTRA_CALL_QUALITY_REPORT); + for (Listener l : mListeners) { + l.onReceivedCallQualityReport(this, callQuality); + } } else { for (Listener l : mListeners) { l.onConnectionEvent(this, event, extras); @@ -3925,6 +3999,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, mIsUsingCallFiltering = isUsingCallFiltering; } + public boolean isUsingCallFiltering() { + return mIsUsingCallFiltering; + } + /** * Returns whether or not Volte call was used. * @@ -3965,7 +4043,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, * @param message the message type to send. * @param value the value for the message. */ - public void sendDeviceToDeviceMessage(@DiagnosticCall.MessageType int message, int value) { + public void sendDeviceToDeviceMessage(@CallDiagnostics.MessageType int message, int value) { Log.i(this, "sendDeviceToDeviceMessage; callId=%s, msg=%d/%d", getId(), message, value); Bundle extras = new Bundle(); extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, message); @@ -4093,4 +4171,69 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public boolean isSimCall() { return mIsSimCall; } + + /** + * Sets whether this is a sim call or not. + * @param isSimCall {@code true} if this is a SIM call, {@code false} otherwise. + */ + public void setIsSimCall(boolean isSimCall) { + mIsSimCall = isSimCall; + } + + /** + * Initializes a disconnect future which is used to chain up pending operations which take + * place when the {@link CallDiagnosticService} returns the result of the + * {@link CallDiagnostics#onCallDisconnected(int, int)} or + * {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} invocation via + * {@link CallDiagnosticServiceAdapter}. If no {@link CallDiagnosticService} is in use, we + * would not try to make a disconnect future. + * @param timeoutMillis Timeout we use for waiting for the response. + * @return the {@link CompletableFuture}. + */ + public CompletableFuture<Boolean> initializeDisconnectFuture(long timeoutMillis) { + if (mDisconnectFuture == null) { + mDisconnectFuture = new CompletableFuture<Boolean>() + .completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS); + // After all the chained stuff we will report where the CDS timed out. + mDisconnectFuture.thenRunAsync(() -> { + if (!mReceivedCallDiagnosticPostCallResponse) { + Log.addEvent(this, LogUtils.Events.CALL_DIAGNOSTIC_SERVICE_TIMEOUT); + } + // Clear the future as a final step. + mDisconnectFuture = null; + }, + new LoggedHandlerExecutor(mHandler, "C.iDF", mLock)) + .exceptionally((throwable) -> { + Log.e(this, throwable, "Error while executing disconnect future"); + return null; + }); + } + return mDisconnectFuture; + } + + /** + * @return the disconnect future, if initialized. Used for chaining operations after creation. + */ + public CompletableFuture<Boolean> getDisconnectFuture() { + return mDisconnectFuture; + } + + /** + * @return {@code true} if disconnection and removal is handled via a future, or {@code false} + * if this is handled immediately. + */ + public boolean isDisconnectHandledViaFuture() { + return mDisconnectFuture != null; + } + + /** + * Perform any cleanup on this call as a result of a {@link TelecomServiceImpl} + * {@code cleanupStuckCalls} request. + */ + public void cleanup() { + if (mDisconnectFuture != null) { + mDisconnectFuture.complete(false); + mDisconnectFuture = null; + } + } } diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java index 7bf94ed57..e5a6eccfd 100644 --- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java +++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java @@ -1347,6 +1347,15 @@ public class CallAudioRouteStateMachine extends StateMachine { } else { sendInternalMessage(MUTE_EXTERNALLY_CHANGED); } + } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) { + int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + boolean isStreamMuted = intent.getBooleanExtra( + AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); + + if (streamType == AudioManager.STREAM_RING && !isStreamMuted) { + Log.i(this, "Ring stream was un-muted."); + mCallAudioManager.onRingerModeChange(); + } } else { Log.w(this, "Received non-mute-change intent"); } @@ -1530,6 +1539,8 @@ public class CallAudioRouteStateMachine extends StateMachine { mWasOnSpeaker = false; mContext.registerReceiver(mMuteChangeReceiver, new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)); + mContext.registerReceiver(mMuteChangeReceiver, + new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION)); mContext.registerReceiver(mSpeakerPhoneChangeReceiver, new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)); diff --git a/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java index 79a94d3bd..862386648 100644 --- a/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java +++ b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java @@ -20,11 +20,10 @@ import android.annotation.NonNull; import android.os.Binder; import android.os.RemoteException; import android.telecom.CallDiagnosticService; -import android.telecom.DiagnosticCall; +import android.telecom.CallDiagnostics; import android.telecom.Log; import com.android.internal.telecom.ICallDiagnosticServiceAdapter; -import com.android.internal.telecom.IInCallAdapter; /** * Adapter class used to provide a path for messages FROM a {@link CallDiagnosticService} back to @@ -34,7 +33,7 @@ public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter. public interface TelecomAdapter { void displayDiagnosticMessage(String callId, int messageId, CharSequence message); void clearDiagnosticMessage(String callId, int messageId); - void sendDeviceToDeviceMessage(String callId, @DiagnosticCall.MessageType int message, + void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message, int value); void overrideDisconnectMessage(String callId, CharSequence message); } @@ -91,7 +90,7 @@ public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter. } @Override - public void sendDeviceToDeviceMessage(String callId, @DiagnosticCall.MessageType int message, + public void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message, int value) throws RemoteException { try { diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java index 943a176d0..d8ee475e7 100644 --- a/src/com/android/server/telecom/CallDiagnosticServiceController.java +++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java @@ -35,17 +35,18 @@ import android.telecom.BluetoothCallQualityReport; import android.telecom.CallAudioState; import android.telecom.CallDiagnosticService; import android.telecom.ConnectionService; -import android.telecom.DiagnosticCall; +import android.telecom.CallDiagnostics; +import android.telecom.DisconnectCause; import android.telecom.InCallService; import android.telecom.Log; import android.telecom.ParcelableCall; +import android.telephony.CallQuality; import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; import com.android.internal.telecom.ICallDiagnosticService; import com.android.internal.util.IndentingPrintWriter; -import java.util.ArrayList; import java.util.List; /** @@ -154,6 +155,16 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) { handleReceivedDeviceToDeviceMessage(call, messageType, messageValue); } + + /** + * Handles an incoming {@link CallQuality} report from a {@link android.telecom.Connection}. + * @param call The call. + * @param callQualityReport The call quality report. + */ + @Override + public void onReceivedCallQualityReport(Call call, CallQuality callQualityReport) { + handleCallQualityReport(call, callQualityReport); + } }; /** @@ -218,6 +229,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { private final String mPackageName; private final ContextProxy mContextProxy; + private InCallTonePlayer.Factory mPlayerFactory; private String mTestPackageName; private CallDiagnosticServiceConnection mConnection; private CallDiagnosticServiceAdapter mAdapter; @@ -233,6 +245,14 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Sets the current {@link InCallTonePlayer.Factory} for this instance. + * @param factory the factory. + */ + public void setInCallTonePlayerFactory(InCallTonePlayer.Factory factory) { + mPlayerFactory = factory; + } + + /** * Handles Telecom adding new calls. Will bind to the call diagnostic service if needed and * send the calls, or send to an already bound service. * @param call The call to add. @@ -255,6 +275,32 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Handles a newly disconnected call signalled from {@link CallsManager}. + * @param call The call + * @param disconnectCause The disconnect cause + * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false} + * if the call was not applicable to the CDS or if there was an issue sending it. + */ + public boolean onCallDisconnected(@NonNull Call call, + @NonNull DisconnectCause disconnectCause) { + if (!call.isSimCall() || call.isExternalCall()) { + Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.", + call.getId()); + return false; + } + String callId = mCallIdMapper.getCallId(call); + try { + if (isConnected()) { + mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause); + return true; + } + } catch (RemoteException e) { + Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e); + } + return false; + } + + /** * Handles Telecom removal of calls; will remove the call from the bound service and if the * number of tracked calls falls to zero, unbind from the service. * @param call The call to remove from the bound CDS. @@ -428,7 +474,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { @Override public void sendDeviceToDeviceMessage(String callId, - @DiagnosticCall.MessageType int message, int value) { + @CallDiagnostics.MessageType int message, int value) { handleSendD2DMessage(callId, message, value); } @@ -471,8 +517,13 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { callId, messageId, message); return; } - Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call", + Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s", callId, messageId, message); + if (mPlayerFactory != null) { + // Play that tone! + mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION) + .startTone(); + } call.displayDiagnosticMessage(messageId, message); } @@ -501,7 +552,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { * @param value The message value. */ private void handleSendD2DMessage(@NonNull String callId, - @DiagnosticCall.MessageType int message, int value) { + @CallDiagnostics.MessageType int message, int value) { Call call = mCallIdMapper.getCall(callId); if (call == null) { Log.w(this, "handleSendD2DMessage: callId=%s; msg=%d/%d; invalid call", callId, @@ -515,7 +566,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { /** * Handles a request from a {@link CallDiagnosticService} to override the disconnect message * for a call. This is the response path from a previous call into the - * {@link CallDiagnosticService} via {@link DiagnosticCall#onCallDisconnected(ImsReasonInfo)}. + * {@link CallDiagnosticService} via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)}. * @param callId The telecom call ID the disconnect override is pending for. * @param message The new disconnect message, or {@code null} if no override. */ @@ -569,7 +620,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { /** * @return {@code true} if the call diagnostic service is bound/connected. */ - private boolean isConnected() { + public boolean isConnected() { return mCallDiagnosticService != null; } @@ -625,6 +676,23 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Handles a reported {@link CallQuality} report from a {@link android.telecom.Connection}. + * @param call The call the report originated from. + * @param callQualityReport The {@link CallQuality} report. + */ + private void handleCallQualityReport(@NonNull Call call, + @NonNull CallQuality callQualityReport) { + try { + if (isConnected()) { + mCallDiagnosticService.callQualityChanged(call.getId(), callQualityReport); + } + } catch (RemoteException e) { + Log.w(this, "handleCallQualityReport: callId=%s, exception=%s", + call.getId(), e); + } + } + + /** * Get a parcelled representation of a call for transport to the service. * @param call The call. * @return The parcelled call. diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java index 3cec6187b..0ec236274 100755 --- a/src/com/android/server/telecom/CallLogManager.java +++ b/src/com/android/server/telecom/CallLogManager.java @@ -16,7 +16,7 @@ package com.android.server.telecom; -import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED; +import static android.provider.CallLog.Calls.BLOCK_REASON_NOT_BLOCKED; import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL; import android.annotation.Nullable; @@ -24,25 +24,26 @@ import android.content.Context; import android.content.Intent; import android.location.Country; import android.location.CountryDetector; +import android.location.Location; import android.net.Uri; import android.os.AsyncTask; import android.os.Looper; import android.os.UserHandle; import android.os.PersistableBundle; +import android.provider.CallLog; import android.provider.CallLog.Calls; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionManager; -// TODO: Needed for move to system service: import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import android.telecom.CallerInfo; import com.android.server.telecom.callfiltering.CallFilteringResult; import java.util.Arrays; @@ -66,71 +67,19 @@ public final class CallLogManager extends CallsManagerListenerBase { * Parameter object to hold the arguments to add a call in the call log DB. */ private static class AddCallArgs { - /** - * @param callerInfo Caller details. - * @param number The phone number to be logged. - * @param presentation Number presentation of the phone number to be logged. - * @param callType The type of call (e.g INCOMING_TYPE). @see - * {@link android.provider.CallLog} for the list of values. - * @param features The features of the call (e.g. FEATURES_VIDEO). @see - * {@link android.provider.CallLog} for the list of values. - * @param creationDate Time when the call was created (milliseconds since epoch). - * @param durationInMillis Duration of the call (milliseconds). - * @param dataUsage Data usage in bytes, or null if not applicable. - * @param isRead Indicates if the entry has been read or not. - * @param logCallCompletedListener optional callback called after the call is logged. - */ - public AddCallArgs(Context context, CallerInfo callerInfo, String number, - String postDialDigits, String viaNumber, int presentation, int callType, - int features, PhoneAccountHandle accountHandle, long creationDate, - long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead, - @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason, - CharSequence callScreeningAppName, String callScreeningComponentName, - long missedReason) { + public AddCallArgs(Context context, CallLog.AddCallParams params, + @Nullable LogCallCompletedListener logCallCompletedListener) { this.context = context; - this.callerInfo = callerInfo; - this.number = number; - this.postDialDigits = postDialDigits; - this.viaNumber = viaNumber; - this.presentation = presentation; - this.callType = callType; - this.features = features; - this.accountHandle = accountHandle; - this.timestamp = creationDate; - this.durationInSec = (int)(durationInMillis / 1000); - this.dataUsage = dataUsage; - this.initiatingUser = initiatingUser; - this.isRead = isRead; + this.params = params; this.logCallCompletedListener = logCallCompletedListener; - this.callBockReason = callBlockReason; - this.callScreeningAppName = callScreeningAppName; - this.callScreeningComponentName = callScreeningComponentName; - this.missedReason = missedReason; + } // Since the members are accessed directly, we don't use the // mXxxx notation. public final Context context; - public final CallerInfo callerInfo; - public final String number; - public final String postDialDigits; - public final String viaNumber; - public final int presentation; - public final int callType; - public final int features; - public final PhoneAccountHandle accountHandle; - public final long timestamp; - public final int durationInSec; - public final Long dataUsage; - public final UserHandle initiatingUser; - public final boolean isRead; - + public final CallLog.AddCallParams params; @Nullable public final LogCallCompletedListener logCallCompletedListener; - - public final int callBockReason; - public final CharSequence callScreeningAppName; - public final String callScreeningComponentName; - public final long missedReason; } private static final String TAG = CallLogManager.class.getSimpleName(); @@ -315,47 +264,50 @@ public final class CallLogManager extends CallsManagerListenerBase { */ void logCall(Call call, int callLogType, @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) { - long creationTime; + CallLog.AddCallParams.AddCallParametersBuilder paramBuilder = + new CallLog.AddCallParams.AddCallParametersBuilder(); if (call.getConnectTimeMillis() != 0 && call.getConnectTimeMillis() < call.getCreationTimeMillis()) { // If connected time is available, use connected time. The connected time might be // earlier than created time since it might come from carrier sent special SMS to // notifier user earlier missed call. - creationTime = call.getConnectTimeMillis(); + paramBuilder.setStart(call.getConnectTimeMillis()); } else { - creationTime = call.getCreationTimeMillis(); + paramBuilder.setStart(call.getCreationTimeMillis()); } - final long age = call.getAgeMillis(); + paramBuilder.setDuration((int) (call.getAgeMillis() / 1000)); - final String logNumber = getLogNumber(call); + String logNumber = getLogNumber(call); + paramBuilder.setNumber(logNumber); Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber)); - final PhoneAccountHandle emergencyAccountHandle = - TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle(); - String formattedViaNumber = PhoneNumberUtils.formatNumber(call.getViaNumber(), getCountryIso()); formattedViaNumber = (formattedViaNumber != null) ? formattedViaNumber : call.getViaNumber(); + paramBuilder.setViaNumber(formattedViaNumber); + final PhoneAccountHandle emergencyAccountHandle = + TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle(); PhoneAccountHandle accountHandle = call.getTargetPhoneAccount(); if (emergencyAccountHandle.equals(accountHandle)) { accountHandle = null; } + paramBuilder.setAccountHandle(accountHandle); - Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null : - call.getCallDataUsage(); + paramBuilder.setDataUsage(call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET + ? Long.MIN_VALUE : call.getCallDataUsage()); - int callFeatures = getCallFeatures(call.getVideoStateHistory(), + paramBuilder.setFeatures(getCallFeatures(call.getVideoStateHistory(), call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED, call.wasHighDefAudio(), call.wasWifi(), (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING) == Connection.PROPERTY_ASSISTED_DIALING, call.wasEverRttCall(), - call.wasVolte()); + call.wasVolte())); if (result == null) { result = new CallFilteringResult.Builder() @@ -363,67 +315,82 @@ public final class CallLogManager extends CallsManagerListenerBase { .setCallScreeningComponentName(call.getCallScreeningComponentName()) .build(); } - if (callLogType == Calls.BLOCKED_TYPE || callLogType == Calls.MISSED_TYPE) { - logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber, - call.getHandlePresentation(), callLogType, callFeatures, accountHandle, - creationTime, age, callDataUsage, call.isEmergencyCall(), - call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener, - result.mCallBlockReason, result.mCallScreeningAppName, - result.mCallScreeningComponentName, call.getMissedReason()); + paramBuilder.setCallBlockReason(result.mCallBlockReason); + paramBuilder.setCallScreeningComponentName(result.mCallScreeningComponentName); + paramBuilder.setCallScreeningAppName(result.mCallScreeningAppName); } else { - logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber, - call.getHandlePresentation(), callLogType, callFeatures, accountHandle, - creationTime, age, callDataUsage, call.isEmergencyCall(), - call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener, - Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/, - null /*callScreeningComponentName*/, call.getMissedReason()); + paramBuilder.setCallBlockReason(BLOCK_REASON_NOT_BLOCKED); } - } - /** - * Inserts a call into the call log, based on the parameters passed in. - * - * @param callerInfo Caller details. - * @param number The number the call was made to or from. - * @param postDialDigits The post-dial digits that were dialed after the number, - * if it was an outgoing call. Otherwise ''. - * @param presentation - * @param callType The type of call. - * @param features The features of the call. - * @param start The start time of the call, in milliseconds. - * @param duration The duration of the call, in milliseconds. - * @param dataUsage The data usage for the call, null if not applicable. - * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. - * @param logCallCompletedListener optional callback called after the call is logged. - * @param initiatingUser The user the call was initiated under. - * @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise. - * @param callBlockReason The reason why the call is blocked. - * @param callScreeningAppName The call screening application name which block the call. - * @param callScreeningComponentName The call screening component name which block the call. - * @param missedReason The encoded information about reasons for missed call. - */ - private void logCall( - CallerInfo callerInfo, - String number, - String postDialDigits, - String viaNumber, - int presentation, - int callType, - int features, - PhoneAccountHandle accountHandle, - long start, - long duration, - Long dataUsage, - boolean isEmergency, - UserHandle initiatingUser, - boolean isSelfManaged, - @Nullable LogCallCompletedListener logCallCompletedListener, - int callBlockReason, - CharSequence callScreeningAppName, - String callScreeningComponentName, - long missedReason) { + PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(accountHandle); + UserHandle initiatingUser = call.getInitiatingUser(); + if (phoneAccount != null && + phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { + if (initiatingUser != null && + UserUtil.isManagedProfile(mContext, initiatingUser)) { + paramBuilder.setUserToBeInsertedTo(initiatingUser); + paramBuilder.setAddForAllUsers(false); + } else { + paramBuilder.setAddForAllUsers(true); + } + } else { + if (accountHandle == null) { + paramBuilder.setAddForAllUsers(true); + } else { + paramBuilder.setUserToBeInsertedTo(accountHandle.getUserHandle()); + paramBuilder.setAddForAllUsers(accountHandle.getUserHandle() == null); + } + } + if (call.getIntentExtras() != null) { + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PRIORITY)) { + paramBuilder.setPriority(call.getIntentExtras() + .getInt(TelecomManager.EXTRA_PRIORITY)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) { + paramBuilder.setSubject(call.getIntentExtras() + .getString(TelecomManager.EXTRA_CALL_SUBJECT)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) { + paramBuilder.setPictureUri(call.getIntentExtras() + .getParcelable(TelecomManager.EXTRA_PICTURE_URI)); + } + // The picture uri can end up either in extras or in intent extras due to how these + // two bundles are set. For incoming calls they're in extras, but for outgoing calls + // they're in intentExtras. + if (call.getExtras() != null + && call.getExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) { + paramBuilder.setPictureUri(call.getExtras() + .getParcelable(TelecomManager.EXTRA_PICTURE_URI)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_LOCATION)) { + Location l = call.getIntentExtras().getParcelable(TelecomManager.EXTRA_LOCATION); + if (l != null) { + paramBuilder.setLatitude(l.getLatitude()); + paramBuilder.setLongitude(l.getLongitude()); + } + } + } + + paramBuilder.setCallerInfo(call.getCallerInfo()); + paramBuilder.setPostDialDigits(call.getPostDialDigits()); + paramBuilder.setPresentation(call.getHandlePresentation()); + paramBuilder.setCallType(callLogType); + paramBuilder.setIsRead(call.isSelfManaged()); + paramBuilder.setMissedReason(call.getMissedReason()); + + sendAddCallBroadcast(callLogType, call.getAgeMillis()); + boolean okayToLog = + okayToLogCall(accountHandle, logNumber, call.isEmergencyCall()); + if (okayToLog) { + AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(), + logCallCompletedListener); + logCallAsync(args); + } + } + + boolean okayToLogCall(PhoneAccountHandle accountHandle, String number, boolean isEmergency) { // On some devices, to avoid accidental redialing of emergency numbers, we *never* log // emergency calls to the Call Log. (This behavior is set on a per-product basis, based // on carrier requirements.) @@ -438,29 +405,8 @@ public final class CallLogManager extends CallsManagerListenerBase { } // Don't log emergency numbers if the device doesn't allow it. - final boolean isOkToLogThisCall = (!isEmergency || okToLogEmergencyNumber) + return (!isEmergency || okToLogEmergencyNumber) && !isUnloggableNumber(number, configBundle); - - sendAddCallBroadcast(callType, duration); - - if (isOkToLogThisCall) { - Log.d(TAG, "Logging Call log entry: " + callerInfo + ", " - + Log.pii(number) + "," + presentation + ", " + callType - + ", " + start + ", " + duration); - boolean isRead = false; - if (isSelfManaged) { - // Mark self-managed calls are read since they're being handled by their own app. - // Their inclusion in the call log is informational only. - isRead = true; - } - AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits, - viaNumber, presentation, callType, features, accountHandle, start, duration, - dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason, - callScreeningAppName, callScreeningComponentName, missedReason); - logCallAsync(args); - } else { - Log.d(TAG, "Not adding emergency call to call log."); - } } private boolean isUnloggableNumber(String callNumber, PersistableBundle carrierConfig) { @@ -566,7 +512,7 @@ public final class CallLogManager extends CallsManagerListenerBase { mListeners[i] = c.logCallCompletedListener; try { // May block. - result[i] = addCall(c); + result[i] = Calls.addCall(c.context, c.params); } catch (Exception e) { // This is very rare but may happen in legitimate cases. // E.g. If the phone is encrypted and thus write request fails, it may cause @@ -582,37 +528,6 @@ public final class CallLogManager extends CallsManagerListenerBase { return result; } - private Uri addCall(AddCallArgs c) { - PhoneAccount phoneAccount = mPhoneAccountRegistrar - .getPhoneAccountUnchecked(c.accountHandle); - if (phoneAccount != null && - phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { - if (c.initiatingUser != null && - UserUtil.isManagedProfile(mContext, c.initiatingUser)) { - return addCall(c, c.initiatingUser); - } else { - return addCall(c, null); - } - } else { - return addCall(c, c.accountHandle == null ? null : c.accountHandle.getUserHandle()); - } - } - - /** - * Insert the call to a specific user or all users except managed profile. - * @param c context - * @param userToBeInserted user handle of user that the call going be inserted to. null - * if insert to all users except managed profile. - */ - private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) { - return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber, - c.presentation, c.callType, c.features, c.accountHandle, c.timestamp, - c.durationInSec, c.dataUsage, userToBeInserted == null, - userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName, - c.callScreeningComponentName, c.missedReason); - } - - @Override protected void onPostExecute(Uri[] result) { for (int i = 0; i < result.length; i++) { diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java index f02b9240c..94352506c 100644 --- a/src/com/android/server/telecom/CallScreeningServiceHelper.java +++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java @@ -55,23 +55,8 @@ public class CallScreeningServiceHelper { } @Override - public void allowCall(String s) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void silenceCall(String s) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void screenCallFurther(String callId) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void disallowCall(String s, boolean b, boolean b1, boolean b2, - ComponentName componentName) throws RemoteException { + public void onScreeningResponse(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse callResponse) { unbindCallScreeningService(); } diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java index 898b6a877..e4922b038 100755 --- a/src/com/android/server/telecom/CallsManager.java +++ b/src/com/android/server/telecom/CallsManager.java @@ -72,6 +72,7 @@ import android.provider.CallLog.Calls; import android.provider.Settings; import android.sysprop.TelephonyProperties; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.CallerInfo; import android.telecom.Conference; import android.telecom.Connection; @@ -546,6 +547,7 @@ public class CallsManager extends Call.ListenerBase systemStateHelper, defaultDialerCache, mTimeoutsAdapter, emergencyCallHelper); mCallDiagnosticServiceController = callDiagnosticServiceController; + mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory); mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer, ringtoneFactory, systemVibrator, new Ringer.VibrationEffectProxy(), mInCallController); @@ -767,6 +769,42 @@ public class CallsManager extends Call.ListenerBase return; } + // Inform our connection service that call filtering is done (if it was performed at all). + if (incomingCall.isUsingCallFiltering()) { + boolean isInContacts = incomingCall.getCallerInfo() != null + && incomingCall.getCallerInfo().contactExists; + Connection.CallFilteringCompletionInfo completionInfo = + new Connection.CallFilteringCompletionInfo(!result.shouldAllowCall, + isInContacts, + result.mCallScreeningResponse == null + ? null : result.mCallScreeningResponse.toCallResponse(), + result.mCallScreeningComponentName == null ? null + : ComponentName.unflattenFromString( + result.mCallScreeningComponentName)); + incomingCall.getConnectionService().onCallFilteringCompleted(incomingCall, + completionInfo); + } + + // Get rid of the call composer attachments that aren't wanted + if (result.mIsResponseFromSystemDialer && result.mCallScreeningResponse != null + && result.mCallScreeningResponse.getCallComposerAttachmentsToShow() >= 0) { + int attachmentMask = result.mCallScreeningResponse.getCallComposerAttachmentsToShow(); + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_LOCATION) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_LOCATION); + } + + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_CALL_SUBJECT); + } + + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_PRIORITY); + } + } + if (result.shouldAllowCall) { incomingCall.setPostCallPackageName( getRoleManagerAdapter().getDefaultCallScreeningApp()); @@ -1250,8 +1288,8 @@ public class CallsManager extends Call.ListenerBase if (call.isSelfManaged()) { // Self managed calls will always be voip audio mode. call.setIsVoipAudioMode(true); - call.setVisibleToInCallService(phoneAccountExtras != null - && phoneAccountExtras.getBoolean( + call.setVisibleToInCallService(phoneAccountExtras == null + || phoneAccountExtras.getBoolean( PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true)); } else { // Incoming call is managed, the active call is self-managed and can't be held. @@ -1526,8 +1564,8 @@ public class CallsManager extends Call.ListenerBase if (isSelfManaged) { // Self-managed calls will ALWAYS use voip audio mode. call.setIsVoipAudioMode(true); - call.setVisibleToInCallService(phoneAccountExtra != null - && phoneAccountExtra.getBoolean( + call.setVisibleToInCallService(phoneAccountExtra == null + || phoneAccountExtra.getBoolean( PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true)); } call.setInitiatingUser(initiatingUser); @@ -1648,6 +1686,21 @@ public class CallsManager extends Call.ListenerBase } } + if (!finalCall.isEmergencyCall() && isInEmergencyCall()) { + Log.i(CallsManager.this, "Aborting call since there's an" + + " ongoing emergency call"); + // If the ongoing call is a managed call, we will prevent the outgoing + // call from dialing. + if (isConference) { + notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(), + finalCall); + } else { + notifyCreateConnectionFailed( + finalCall.getTargetPhoneAccount(), finalCall); + } + return CompletableFuture.completedFuture(null); + } + // If we can not supportany more active calls, our options are to move a call // to hold, disconnect a call, or cancel this call altogether. boolean isRoomForCall = finalCall.isEmergencyCall() ? @@ -2041,6 +2094,8 @@ public class CallsManager extends Call.ListenerBase handle.getSchemeSpecificPart()); } catch (IllegalStateException ise) { isPotentialEmergencyNumber = false; + } catch (RuntimeException r) { + isPotentialEmergencyNumber = false; } if (shouldCancelCall) { @@ -2218,8 +2273,16 @@ public class CallsManager extends Call.ListenerBase public void processRedirectedOutgoingCallAfterUserInteraction(String callId, String action) { Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for Call ID %s, action=%s", callId, action); - if (mPendingRedirectedOutgoingCall != null && mPendingRedirectedOutgoingCall.getId() - .equals(callId)) { + if (mPendingRedirectedOutgoingCall != null) { + String pendingCallId = mPendingRedirectedOutgoingCall.getId(); + if (!pendingCallId.equals(callId)) { + Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for new Call ID %s, " + + "cancel the previous pending Call with ID %s", callId, pendingCallId); + mPendingRedirectedOutgoingCall.disconnect("Another call redirection requested"); + mPendingRedirectedOutgoingCallInfo.remove(pendingCallId); + mPendingUnredirectedOutgoingCallInfo.remove(pendingCallId); + } + if (action.equals(TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL)) { mHandler.post(mPendingRedirectedOutgoingCallInfo.get(callId).prepare()); } else if (action.equals( @@ -2892,8 +2955,8 @@ public class CallsManager extends Call.ListenerBase } private boolean isRttSettingOn(PhoneAccountHandle handle) { - boolean isRttModeSettingOn = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.RTT_CALLING_MODE, 0) != 0; + boolean isRttModeSettingOn = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.RTT_CALLING_MODE, 0, mContext.getUserId()) != 0; // If the carrier config says that we should ignore the RTT mode setting from the user, // assume that it's off (i.e. only make an RTT call if it's requested through the extra). boolean shouldIgnoreRttModeSetting = getCarrierConfigForPhoneAccount(handle) @@ -2971,6 +3034,7 @@ public class CallsManager extends Call.ListenerBase */ boolean holdActiveCallForNewCall(Call call) { Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall(); + Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall); if (activeCall != null && activeCall != call) { if (canHold(activeCall)) { activeCall.hold(); @@ -3026,6 +3090,7 @@ public class CallsManager extends Call.ListenerBase @VisibleForTesting public void markCallAsActive(Call call) { + Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged()); if (call.isSelfManaged()) { // backward compatibility, the self-managed connection service will set the call state // to active directly. We should hold or disconnect the current active call based on the @@ -3077,27 +3142,83 @@ public class CallsManager extends Call.ListenerBase // be marked as missed. call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED)); } - call.setDisconnectCause(disconnectCause); - setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); - if(oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) { + // If a call diagnostic service is in use, we will log the original telephony-provided + // disconnect cause, inform the CDS of the disconnection, and then chain the update of the + // call state until AFTER the CDS reports it's result back. + if ((oldState == CallState.ACTIVE || oldState == CallState.DIALING) + && disconnectCause.getCode() != DisconnectCause.MISSED + && mCallDiagnosticServiceController.isConnected() + && mCallDiagnosticServiceController.onCallDisconnected(call, disconnectCause)) { + Log.i(this, "markCallAsDisconnected; callid=%s, postingToFuture.", call.getId()); + + // Log the original disconnect reason prior to calling into the + // CallDiagnosticService. + Log.addEvent(call, LogUtils.Events.SET_DISCONNECTED_ORIG, disconnectCause); + + // Setup the future with a timeout so that the CDS is time boxed. + CompletableFuture<Boolean> future = call.initializeDisconnectFuture( + mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis( + mContext.getContentResolver())); + + // Post the disconnection updates to the future for completion once the CDS returns + // with it's overridden disconnect message. + future.thenRunAsync(() -> { + call.setDisconnectCause(disconnectCause); + setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); + }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock)) + .exceptionally((throwable) -> { + Log.e(TAG, throwable, "Error while executing disconnect future."); + return null; + }); + } else { + // No CallDiagnosticService, or it doesn't handle this call, so just do this + // synchronously as always. + call.setDisconnectCause(disconnectCause); + setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); + } + + if (oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) { Log.i(this, "markCallAsDisconnected: logging missed call "); mCallLogManager.logCall(call, Calls.MISSED_TYPE, true, null); } - } /** * Removes an existing disconnected call, and notifies the in-call app. */ void markCallAsRemoved(Call call) { + if (call.isDisconnectHandledViaFuture()) { + Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId()); + // A future is being used due to a CallDiagnosticService handling the call. We will + // chain the removal operation to the end of any outstanding disconnect work. + call.getDisconnectFuture().thenRunAsync(() -> { + performRemoval(call); + }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock)) + .exceptionally((throwable) -> { + Log.e(TAG, throwable, "Error while executing disconnect future"); + return null; + }); + + } else { + Log.i(this, "markCallAsRemoved; callid=%s, immediate.", call.getId()); + performRemoval(call); + } + } + + /** + * Work which is completed when a call is to be removed. Can either be be run synchronously or + * posted to a {@link Call#getDisconnectFuture()}. + * @param call The call. + */ + private void performRemoval(Call call) { mInCallController.getBindingFuture().thenRunAsync(() -> { call.maybeCleanupHandover(); removeCall(call); Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall(); if (mLocallyDisconnectingCalls.contains(call)) { boolean isDisconnectingChildCall = call.isDisconnectingChildCall(); - Log.v(this, "markCallAsRemoved: isDisconnectingChildCall = " + Log.v(this, "performRemoval: isDisconnectingChildCall = " + isDisconnectingChildCall + "call -> %s", call); mLocallyDisconnectingCalls.remove(call); // Auto-unhold the foreground call due to a locally disconnected call, except if the @@ -3114,10 +3235,11 @@ public class CallsManager extends Call.ListenerBase // The new foreground call is on hold, however the carrier does not display the hold // button in the UI. Therefore, we need to auto unhold the held call since the user // has no means of unholding it themselves. - Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)"); + Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't " + + "support hold)"); foregroundCall.unhold(); } - }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock)) + }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock)) .exceptionally((throwable) -> { Log.e(TAG, throwable, "Error while executing call removal"); return null; @@ -4195,7 +4317,8 @@ public class CallsManager extends Call.ListenerBase return false; } - private boolean makeRoomForOutgoingCall(Call call) { + @VisibleForTesting + public boolean makeRoomForOutgoingCall(Call call) { // Already room! if (!hasMaximumLiveCalls(call)) return true; @@ -4212,6 +4335,13 @@ public class CallsManager extends Call.ListenerBase return true; } + // If the live call is stuck in a connecting state, then we should disconnect it in favor + // of the new outgoing call. + if (liveCall.getState() == CallState.CONNECTING) { + liveCall.disconnect("Force disconnect CONNECTING call."); + return true; + } + if (hasMaximumOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { @@ -5447,4 +5577,10 @@ public class CallsManager extends Call.ListenerBase public void addToPendingCallsToDisconnect(Call call) { mPendingCallsToDisconnect.add(call); } + + @VisibleForTesting + public void addConnectionServiceRepositoryCache(ComponentName componentName, + UserHandle userHandle, ConnectionServiceWrapper service) { + mConnectionServiceRepository.setService(componentName, userHandle, service); + } } diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java index e64ef5de8..737ce5a82 100644 --- a/src/com/android/server/telecom/CarModeTracker.java +++ b/src/com/android/server/telecom/CarModeTracker.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.PriorityQueue; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -40,30 +41,40 @@ public class CarModeTracker { * Data class holding information about apps which have requested to enter car mode. */ private class CarModeApp { - private @IntRange(from = 0) int mPriority; + private final boolean mAutomotiveProjection; + private final @IntRange(from = 0) int mPriority; private @NonNull String mPackageName; + public CarModeApp(@NonNull String packageName) { + this(true, 0, packageName); + } + public CarModeApp(int priority, @NonNull String packageName) { + this(false, priority, packageName); + } + + private CarModeApp(boolean automotiveProjection, int priority, @NonNull String packageName) { + mAutomotiveProjection = automotiveProjection; mPriority = priority; mPackageName = Objects.requireNonNull(packageName); } + public boolean hasSetAutomotiveProjection() { + return mAutomotiveProjection; + } + /** * The priority at which the app requested to enter car mode. * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)} - * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid. + * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specified. * @return The priority. */ public int getPriority() { return mPriority; } - public void setPriority(int priority) { - mPriority = priority; - } - /** - * @return The package name of the app which requested to enter car mode. + * @return The package name of the app which requested to enter car mode/set projection. */ public String getPackageName() { return mPackageName; @@ -72,26 +83,24 @@ public class CarModeTracker { public void setPackageName(String packageName) { mPackageName = packageName; } - } - /** - * Comparator used to maintain the car mode priority queue ordering. - */ - private class CarModeAppComparator implements Comparator<CarModeApp> { - @Override - public int compare(CarModeApp o1, CarModeApp o2) { - // highest priority takes precedence. - return Integer.compare(o2.getPriority(), o1.getPriority()); + public String toString() { + return String.format("[%s, %s]", + mAutomotiveProjection ? "PROJECTION SET" : mPriority, + mPackageName); } } /** - * Priority list of apps which have entered or exited car mode, ordered with the highest - * priority app at the top of the queue. Where items have the same priority, they are ordered - * by insertion time. + * Priority list of apps which have entered or exited car mode, ordered first by whether the app + * has set automotive projection, and then by highest priority. Where items have the same + * priority, order is arbitrary, but we only allow one item in the queue per priority. */ private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2, - new CarModeAppComparator()); + // Natural ordering of booleans is False, True. Natural ordering of ints is increasing. + Comparator.comparing(CarModeApp::hasSetAutomotiveProjection) + .thenComparing(CarModeApp::getPriority) + .reversed()); private final LocalLog mCarModeChangeLog = new LocalLog(20); @@ -144,6 +153,47 @@ public class CarModeTracker { mCarModeApps.removeIf(c -> c.getPriority() == priority); } + public void handleSetAutomotiveProjection(@NonNull String packageName) { + Optional<CarModeApp> projectingApp = mCarModeApps.stream() + .filter(CarModeApp::hasSetAutomotiveProjection) + .findAny(); + // No app with automotive projection? Easy peasy, just add it. + if (!projectingApp.isPresent()) { + Log.i(this, "handleSetAutomotiveProjection: %s", packageName); + mCarModeChangeLog.log("setAutomotiveProjection: packageName=" + packageName); + mCarModeApps.add(new CarModeApp(packageName)); + return; + } + // Otherwise an app already has automotive projection set. Is it the same app? + if (packageName.equals(projectingApp.get().getPackageName())) { + Log.w(this, "handleSetAutomotiveProjection: %s already the automotive projection app", + packageName); + return; + } + // We have a new app for automotive projection. As a shortcut just reuse the same object by + // overwriting the package name. + Log.i(this, "handleSetAutomotiveProjection: %s replacing %s as automotive projection app", + packageName, projectingApp.get().getPackageName()); + mCarModeChangeLog.log("setAutomotiveProjection: " + packageName + " replaces " + + projectingApp.get().getPackageName()); + projectingApp.get().setPackageName(packageName); + } + + public void handleReleaseAutomotiveProjection() { + Optional<String> projectingPackage = mCarModeApps.stream() + .filter(CarModeApp::hasSetAutomotiveProjection) + .map(CarModeApp::getPackageName) + .findAny(); + if (!projectingPackage.isPresent()) { + Log.w(this, "handleReleaseAutomotiveProjection: no current automotive projection app"); + return; + } + Log.i(this, "handleReleaseAutomotiveProjection: %s", projectingPackage.get()); + mCarModeChangeLog.log("releaseAutomotiveProjection: packageName=" + + projectingPackage.get()); + mCarModeApps.removeIf(CarModeApp::hasSetAutomotiveProjection); + } + /** * Force-removes a package from the car mode tracking list, no matter at which priority. * @@ -151,19 +201,21 @@ public class CarModeTracker { * from the tracking list so they don't cause a leak. * @param packageName Package name of the app to force-remove */ - public void forceExitCarMode(@NonNull String packageName) { - Optional<CarModeApp> forcedApp = mCarModeApps.stream() + public void forceRemove(@NonNull String packageName) { + // We must account for the possibility that the app has set both car mode AND projection. + List<CarModeApp> forcedApp = mCarModeApps.stream() .filter(c -> c.getPackageName().equals(packageName)) - .findAny(); - if (forcedApp.isPresent()) { - String logString = String.format("forceExitCarMode: packageName=%s, was at priority=%s", - packageName, forcedApp.get().getPriority()); + .collect(Collectors.toList()); + if (forcedApp.isEmpty()) { + Log.i(this, "Package %s is not tracked.", packageName); + return; + } + for (CarModeApp app : forcedApp) { + String logString = "forceRemove: " + app; Log.i(this, logString); mCarModeChangeLog.log(logString); - mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName)); - } else { - Log.i(this, "Package %s is not tracked as requesting car mode", packageName); } + mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName)); } /** @@ -175,7 +227,7 @@ public class CarModeTracker { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) - .map(cma -> cma.getPackageName()) + .map(CarModeApp::getPackageName) .collect(Collectors.toList()); } @@ -183,7 +235,7 @@ public class CarModeTracker { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) - .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]") + .map(CarModeApp::toString) .collect(Collectors.joining(", ")); } @@ -216,7 +268,7 @@ public class CarModeTracker { pw.increaseIndent(); for (CarModeApp app : mCarModeApps) { pw.print("["); - pw.print(app.getPriority()); + pw.print(app.hasSetAutomotiveProjection() ? "PROJECTION SET" : app.getPriority()); pw.print("] "); pw.println(app.getPackageName()); } diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java index fbb23f4a8..aa0a64f69 100644 --- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java +++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java @@ -391,7 +391,7 @@ public class ConnectionServiceFocusManager { } private void handleRequestFocus(FocusRequest focusRequest) { - Log.d(this, "handleRequestFocus req = %s", focusRequest); + Log.i(this, "handleRequestFocus req = %s", focusRequest); if (mCurrentFocus == null || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) { updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java index d34ea3c07..3991ed51f 100644 --- a/src/com/android/server/telecom/ConnectionServiceRepository.java +++ b/src/com/android/server/telecom/ConnectionServiceRepository.java @@ -79,6 +79,13 @@ public class ConnectionServiceRepository { return service; } + @VisibleForTesting + public void setService(ComponentName componentName, UserHandle userHandle, + ConnectionServiceWrapper service) { + Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle); + mServiceCache.put(cacheKey, service); + } + /** * Dumps the state of the {@link ConnectionServiceRepository}. * diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java index 1fc9a3bd4..21c684499 100755 --- a/src/com/android/server/telecom/ConnectionServiceWrapper.java +++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java @@ -18,6 +18,7 @@ package com.android.server.telecom; import static android.Manifest.permission.MODIFY_PHONE_STATE; +import android.Manifest; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; @@ -30,6 +31,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.ConnectionService; @@ -350,7 +352,7 @@ public class ConnectionServiceWrapper extends ServiceBinder implements logIncoming("removeCall %s", callId); Call call = mCallIdMapper.getCall(callId); if (call != null) { - if (call.isAlive()) { + if (call.isAlive() && !call.isDisconnectHandledViaFuture()) { mCallsManager.markCallAsDisconnected( call, new DisconnectCause(DisconnectCause.REMOTE)); } else { @@ -1360,7 +1362,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements * create a connection has been denied or failed. * @param call The call. */ - void createConnectionFailed(final Call call) { + @VisibleForTesting + public void createConnectionFailed(final Call call) { Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName()); BindCallback callback = new BindCallback() { @Override @@ -1871,6 +1874,28 @@ public class ConnectionServiceWrapper extends ServiceBinder implements } } + void onCallFilteringCompleted(Call call, + Connection.CallFilteringCompletionInfo completionInfo) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("onCallFilteringCompleted")) { + try { + logOutgoing("onCallFilteringCompleted %s", completionInfo); + int contactsPermission = mContext.getPackageManager() + .checkPermission(Manifest.permission.READ_CONTACTS, + getComponentName().getPackageName()); + if (contactsPermission == PackageManager.PERMISSION_GRANTED) { + mServiceInterface.onCallFilteringCompleted(callId, completionInfo, + Log.getExternalSession(TELECOM_ABBREVIATION)); + } else { + logOutgoing("Skipping call filtering complete message for %s due" + + " to lack of READ_CONTACTS", getComponentName().getPackageName()); + } + } catch (RemoteException e) { + Log.e(this, e, "Remote exception calling onCallFilteringCompleted"); + } + } + } + void onExtrasChanged(Call call, Bundle extras) { final String callId = mCallIdMapper.getCallId(call); if (callId != null && isServiceValid("onExtrasChanged")) { diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java index 304a698d8..5869008b3 100644 --- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java +++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java @@ -193,8 +193,9 @@ public class DtmfLocalTonePlayer { final Context context = call.getContext(); final boolean areLocalTonesEnabled; if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) { - areLocalTonesEnabled = Settings.System.getInt( - context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; + areLocalTonesEnabled = Settings.System.getIntForUser( + context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1, + context.getUserId()) == 1; } else { areLocalTonesEnabled = false; } diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java index b138eaed6..ca76456f6 100644 --- a/src/com/android/server/telecom/InCallController.java +++ b/src/com/android/server/telecom/InCallController.java @@ -16,6 +16,7 @@ package com.android.server.telecom; +import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.os.Process.myUid; import android.Manifest; @@ -23,6 +24,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; +import android.content.AttributionSource; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -33,13 +35,17 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.hardware.SensorPrivacyManager; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PackageTagsList; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.telecom.CallAudioState; import android.telecom.ConnectionService; import android.telecom.InCallService; @@ -54,6 +60,7 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; // TODO: Needed for move to system service: import com.android.internal.R; import com.android.internal.telecom.IInCallService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.SystemStateHelper.SystemStateListener; import com.android.server.telecom.ui.NotificationChannelManager; @@ -75,9 +82,10 @@ import java.util.stream.Collectors; * can send updates to the in-call app. This class is created and owned by CallsManager and retains * a binding to the {@link IInCallService} (implemented by the in-call app). */ -public class InCallController extends CallsManagerListenerBase { - public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3; +public class InCallController extends CallsManagerListenerBase implements + AppOpsManager.OnOpActiveChangedListener { public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName(); + public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3; public class InCallServiceConnection { /** @@ -274,7 +282,16 @@ public class InCallController extends CallsManagerListenerBase { @Override public int connect(Call call) { if (mIsConnected) { - Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request."); + Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request: " + + mInCallServiceInfo); + if (call != null) { + // Track the call if we don't already know about it. + addCall(call); + + // Notify this new added call + sendCallToService(call, mInCallServiceInfo, + mInCallServices.get(mInCallServiceInfo)); + } return CONNECTION_SUCCEEDED; } @@ -289,7 +306,7 @@ public class InCallController extends CallsManagerListenerBase { Intent intent = new Intent(InCallService.SERVICE_INTERFACE); intent.setComponent(mInCallServiceInfo.getComponentName()); - if (call != null && !call.isIncoming() && !call.isExternalCall()){ + if (call != null && !call.isIncoming() && !call.isExternalCall()) { intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call.getIntentExtras()); intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, @@ -300,9 +317,9 @@ public class InCallController extends CallsManagerListenerBase { mIsConnected = true; mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime()); if (!mContext.bindServiceAsUser(intent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, - UserHandle.CURRENT)) { + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, + UserHandle.CURRENT)) { Log.w(this, "Failed to connect."); mIsConnected = false; } @@ -442,15 +459,15 @@ public class InCallController extends CallsManagerListenerBase { } mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call, - mCallsManager.getCurrentUserHandle()); + mCallsManager.getCurrentUserHandle()); if (call != null && call.isIncoming() - && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) { - // Add the last emergency call time to the call - Bundle extras = new Bundle(); - extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, - mEmergencyCallHelper.getLastEmergencyCallTimeMillis()); - call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras); + && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) { + // Add the last emergency call time to the call + Bundle extras = new Bundle(); + extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, + mEmergencyCallHelper.getLastEmergencyCallTimeMillis()); + call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras); } // If we are here, we didn't or could not connect to child. So lets connect ourselves. @@ -484,6 +501,7 @@ public class InCallController extends CallsManagerListenerBase { return super.getInfo(); } } + @Override protected void onDisconnected() { // Save this here because super.onDisconnected() could force us to explicitly @@ -547,6 +565,7 @@ public class InCallController extends CallsManagerListenerBase { /** * Called when we move to a state where calls are present on the device. Chooses the * {@link InCallService} to which we should connect. + * * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise. */ public synchronized void chooseInitialInCallService(boolean isCarMode) { @@ -585,6 +604,7 @@ public class InCallController extends CallsManagerListenerBase { /** * Changes the active {@link InCallService} to a car mode app. Called whenever the device * changes to car mode or the currently active car mode app changes. + * * @param packageName The package name of the car mode app. */ public synchronized void changeCarModeApp(String packageName) { @@ -593,7 +613,8 @@ public class InCallController extends CallsManagerListenerBase { InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null : mCurrentConnection.getInfo(); InCallServiceInfo carModeConnectionInfo = - getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + getInCallServiceComponent(packageName, + IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */); if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) { Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => " @@ -608,7 +629,7 @@ public class InCallController extends CallsManagerListenerBase { new InCallServiceBindingConnection(carModeConnectionInfo); mIsCarMode = true; } else { - // Invalid car mode app; don't expect this but should handle it gracefully. + // The app is not enabled. Using the default dialer connection instead mCarModeConnection = null; mIsCarMode = false; mCurrentConnection = mDialerConnection; @@ -622,6 +643,10 @@ public class InCallController extends CallsManagerListenerBase { } } + public boolean isCarMode() { + return mIsCarMode; + } + @Override public int connect(Call call) { if (mIsConnected) { @@ -878,6 +903,12 @@ public class InCallController extends CallsManagerListenerBase { public void onRemoteRttRequest(Call call, int requestId) { notifyRemoteRttRequest(call, requestId); } + + @Override + public void onCallerNumberVerificationStatusChanged(Call call, + int callerNumberVerificationStatus) { + updateCall(call); + } }; private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { @@ -904,6 +935,10 @@ public class InCallController extends CallsManagerListenerBase { if (mNonUIInCallServiceConnections != null) { mNonUIInCallServiceConnections.addConnections(componentsToBind); } + + // If the current car mode app become enabled from disabled, update + // the connection to binding + updateCarModeForConnections(); } } } finally { @@ -919,8 +954,18 @@ public class InCallController extends CallsManagerListenerBase { } @Override + public void onAutomotiveProjectionStateSet(String automotiveProjectionPackage) { + InCallController.this.handleSetAutomotiveProjection(automotiveProjectionPackage); + } + + @Override + public void onAutomotiveProjectionStateReleased() { + InCallController.this.handleReleaseAutomotiveProjection(); + } + + @Override public void onPackageUninstalled(String packageName) { - mCarModeTracker.forceExitCarMode(packageName); + mCarModeTracker.forceRemove(packageName); updateCarModeForConnections(); } }; @@ -932,6 +977,9 @@ public class InCallController extends CallsManagerListenerBase { private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5; + private static final int[] LIVE_CALL_STATES = { CallState.ACTIVE, CallState.PULLING, + CallState.DISCONNECTING }; + /** The in-call app implementations, see {@link IInCallService}. */ private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); @@ -939,6 +987,7 @@ public class InCallController extends CallsManagerListenerBase { private final Context mContext; private final AppOpsManager mAppOpsManager; + private final SensorPrivacyManager mSensorPrivacyManager; private final TelecomSystem.SyncRoot mLock; private final CallsManager mCallsManager; private final SystemStateHelper mSystemStateHelper; @@ -949,6 +998,7 @@ public class InCallController extends CallsManagerListenerBase { private CarSwappingInCallServiceConnection mInCallServiceConnection; private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; private final ClockProxy mClockProxy; + private final IBinder mToken = new Binder(); // A set of known non-UI in call services on the device, including those that are disabled. // We track this so that we can efficiently bind to them when we're notified that a new @@ -968,16 +1018,33 @@ public class InCallController extends CallsManagerListenerBase { /** * {@code true} if InCallController is tracking a managed, not external call which is using the - * microphone, {@code false} otherwise. + * microphone, and is not muted {@code false} otherwise. */ private boolean mIsCallUsingMicrophone = false; + /** + * {@code true} if InCallController is tracking a managed, not external call which is using the + * microphone, {@code false} otherwise. + */ + private boolean mIsTrackingManagedAliveCall = false; + + private boolean mIsStartCallDelayScheduled = false; + + /** + * A list of call IDs which are currently using the camera. + */ + private ArrayList<String> mCallsUsingCamera = new ArrayList<>(); + + private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>(); + private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>(); + public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker, ClockProxy clockProxy) { mContext = context; mAppOpsManager = context.getSystemService(AppOpsManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mLock = lock; mCallsManager = callsManager; mSystemStateHelper = systemStateHelper; @@ -987,6 +1054,76 @@ public class InCallController extends CallsManagerListenerBase { mCarModeTracker = carModeTracker; mSystemStateHelper.addListener(mSystemStateListener); mClockProxy = clockProxy; + restrictPhoneCallOps(); + } + + private void restrictPhoneCallOps() { + PackageTagsList packageRestriction = new PackageTagsList.Builder() + .add(mContext.getPackageName()) + .build(); + mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, + mToken, packageRestriction, UserHandle.USER_ALL); + mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, + mToken, packageRestriction, UserHandle.USER_ALL); + } + + @Override + public void onOpActiveChanged(@androidx.annotation.NonNull String op, int uid, + @androidx.annotation.NonNull String packageName, boolean active) { + synchronized (mLock) { + if (!mAllCarrierPrivilegedApps.contains(packageName)) { + return; + } + + if (active) { + mActiveCarrierPrivilegedApps.add(packageName); + } else { + mActiveCarrierPrivilegedApps.remove(packageName); + } + maybeTrackMicrophoneUse(isMuted()); + } + } + + private void updateAllCarrierPrivilegedUsingMic() { + mActiveCarrierPrivilegedApps.clear(); + UserManager userManager = mContext.getSystemService(UserManager.class); + PackageManager pkgManager = mContext.getPackageManager(); + for (String pkg : mAllCarrierPrivilegedApps) { + boolean isActive = mActiveCarrierPrivilegedApps.contains(pkg); + List<UserHandle> users = userManager.getUserHandles(true); + for (UserHandle user : users) { + if (isActive) { + break; + } + + int uid; + try { + uid = pkgManager.getPackageUidAsUser(pkg, user.getIdentifier()); + } catch (PackageManager.NameNotFoundException e) { + continue; + } + List<AppOpsManager.PackageOps> pkgOps = mAppOpsManager.getOpsForPackage( + uid, pkg, OPSTR_RECORD_AUDIO); + for (int j = 0; j < pkgOps.size(); j++) { + List<AppOpsManager.OpEntry> opEntries = pkgOps.get(j).getOps(); + for (int k = 0; k < opEntries.size(); k++) { + AppOpsManager.OpEntry entry = opEntries.get(k); + if (entry.isRunning()) { + mActiveCarrierPrivilegedApps.add(pkg); + break; + } + } + } + } + } + } + + private void updateAllCarrierPrivileged() { + mAllCarrierPrivilegedApps.clear(); + for (Call call : mCallIdMapper.getCalls()) { + mAllCarrierPrivilegedApps.add(call.getConnectionManagerPhoneAccount() + .getComponentName().getPackageName()); + } } @Override @@ -1036,7 +1173,7 @@ public class InCallController extends CallsManagerListenerBase { true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported(), includeRttCall, info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); try { inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); updateCallTracking(call, info, true /* isAdd */); @@ -1065,11 +1202,16 @@ public class InCallController extends CallsManagerListenerBase { } } }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay( - mContext.getContentResolver())); + mContext.getContentResolver())); } call.removeListener(mCallListener); mCallIdMapper.removeCall(call); + if (mCallIdMapper.getCalls().isEmpty()) { + mActiveCarrierPrivilegedApps.clear(); + mAppOpsManager.stopWatchingActive(this); + } maybeTrackMicrophoneUse(isMuted()); + onSetCamera(call, null); } @Override @@ -1103,8 +1245,8 @@ public class InCallController extends CallsManagerListenerBase { ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported(), includeRttCall, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI + || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); try { inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); updateCallTracking(call, info, true /* isAdd */); @@ -1135,9 +1277,9 @@ public class InCallController extends CallsManagerListenerBase { false /* supportsExternalCalls */, android.telecom.Call.STATE_DISCONNECTED /* overrideState */, false /* includeRttCall */, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI - ); + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI + || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI + ); try { inCallService.updateCall( @@ -1221,6 +1363,7 @@ public class InCallController extends CallsManagerListenerBase { public void onIsVoipAudioModeChanged(Call call) { Log.d(this, "onIsVoipAudioModeChanged %s", call); updateCall(call); + maybeTrackMicrophoneUse(isMuted()); } @Override @@ -1237,18 +1380,35 @@ public class InCallController extends CallsManagerListenerBase { /** * Track changes to camera usage for a call. - * @param call The call. + * + * @param call The call. * @param cameraId The id of the camera to use, or {@code null} if camera is off. */ @Override public void onSetCamera(Call call, String cameraId) { + if (call == null) { + return; + } + Log.i(this, "onSetCamera callId=%s, cameraId=%s", call.getId(), cameraId); if (cameraId != null) { - mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), - mContext.getOpPackageName(), false, null, null); + boolean shouldStart = mCallsUsingCamera.isEmpty(); + if (!mCallsUsingCamera.contains(call.getId())) { + mCallsUsingCamera.add(call.getId()); + } + + if (shouldStart) { + mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), + mContext.getOpPackageName(), false, null, null); + mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA); + } } else { - mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), - mContext.getOpPackageName(), null); + boolean hadCall = !mCallsUsingCamera.isEmpty(); + mCallsUsingCamera.remove(call.getId()); + if (hadCall && mCallsUsingCamera.isEmpty()) { + mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), + mContext.getOpPackageName(), null); + } } } @@ -1281,8 +1441,8 @@ public class InCallController extends CallsManagerListenerBase { for (IInCallService inCallService : mInCallServices.values()) { try { Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}", - (call != null ? call.toString() :"null"), - (event != null ? event : "null") , + (call != null ? call.toString() : "null"), + (event != null ? event : "null"), (extras != null ? extras.toString() : "null")); inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); } catch (RemoteException ignored) { @@ -1293,7 +1453,7 @@ public class InCallController extends CallsManagerListenerBase { private void notifyRttInitiationFailure(Call call, int reason) { if (!mInCallServices.isEmpty()) { - mInCallServices.entrySet().stream() + mInCallServices.entrySet().stream() .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo())) .forEach((entry) -> { try { @@ -1420,6 +1580,10 @@ public class InCallController extends CallsManagerListenerBase { } else { Log.i(this, "bindToServices: current UI doesn't support call; not binding."); } + + IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED); + packageChangedFilter.addDataScheme("package"); + mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter); } private void updateNonUiInCallServices() { @@ -1434,7 +1598,7 @@ public class InCallController extends CallsManagerListenerBase { if (callCompanionApps != null && !callCompanionApps.isEmpty()) { for (String pkg : callCompanionApps) { InCallServiceInfo info = getInCallServiceComponent(pkg, - IN_CALL_SERVICE_TYPE_COMPANION); + IN_CALL_SERVICE_TYPE_COMPANION, true /* ignoreDisabled */); if (info != null) { nonUIInCalls.add(new InCallServiceBindingConnection(info)); } @@ -1449,10 +1613,6 @@ public class InCallController extends CallsManagerListenerBase { updateNonUiInCallServices(); } mNonUIInCallServiceConnections.connect(call); - - IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED); - packageChangedFilter.addDataScheme("package"); - mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter); } private InCallServiceInfo getDefaultDialerComponent() { @@ -1463,8 +1623,10 @@ public class InCallController extends CallsManagerListenerBase { InCallServiceInfo defaultDialerComponent = (systemPackageName != null && systemPackageName.equals(packageName)) - ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI) - : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI); + ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI, + true /* ignoreDisabled */) + : getInCallServiceComponent(packageName, + IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */); /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role. if (packageName != null && defaultDialerComponent == null) { // The in call service of default phone app is disabled, send notification. @@ -1476,7 +1638,7 @@ public class InCallController extends CallsManagerListenerBase { private InCallServiceInfo getCurrentCarModeComponent() { return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(), - IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabled */); } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { @@ -1486,13 +1648,15 @@ public class InCallController extends CallsManagerListenerBase { } else { // Last Resort: Try to bind to the ComponentName given directly. Log.e(this, new Exception(), "Package Manager could not find ComponentName: " - + componentName +". Trying to bind anyway."); + + componentName + ". Trying to bind anyway."); return new InCallServiceInfo(componentName, false, false, type); } } - private InCallServiceInfo getInCallServiceComponent(String packageName, int type) { - List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type); + private InCallServiceInfo getInCallServiceComponent(String packageName, int type, + boolean ignoreDisabled) { + List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type, + ignoreDisabled); if (list != null && !list.isEmpty()) { return list.get(0); } @@ -1503,8 +1667,9 @@ public class InCallController extends CallsManagerListenerBase { return getInCallServiceComponents(null, null, type); } - private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) { - return getInCallServiceComponents(packageName, null, type); + private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type, + boolean ignoreDisabled) { + return getInCallServiceComponents(packageName, null, type, ignoreDisabled); } private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName, @@ -1514,6 +1679,12 @@ public class InCallController extends CallsManagerListenerBase { private List<InCallServiceInfo> getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType) { + return getInCallServiceComponents(packageName, componentName, requestedType, + true /* ignoreDisabled */); + } + + private List<InCallServiceInfo> getInCallServiceComponents(String packageName, + ComponentName componentName, int requestedType, boolean ignoreDisabled) { List<InCallServiceInfo> retval = new LinkedList<>(); Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); @@ -1546,13 +1717,16 @@ public class InCallController extends CallsManagerListenerBase { mKnownNonUiInCallServices.add(foundComponentName); } + boolean isEnabled = isServiceEnabled(foundComponentName, + serviceInfo, packageManager); boolean isRequestedType; if (requestedType == IN_CALL_SERVICE_TYPE_INVALID) { isRequestedType = true; } else { isRequestedType = requestedType == currentType; } - if (serviceInfo.enabled && isRequestedType) { + + if ((!ignoreDisabled || isEnabled) && isRequestedType) { retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported, isSelfManageCallsSupported, requestedType)); } @@ -1561,6 +1735,21 @@ public class InCallController extends CallsManagerListenerBase { return retval; } + private boolean isServiceEnabled(ComponentName componentName, + ServiceInfo serviceInfo, PackageManager packageManager) { + int componentEnabledState = packageManager.getComponentEnabledSetting(componentName); + + if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + return true; + } + + if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + return serviceInfo.isEnabled(); + } + + return false; + } + private boolean shouldUseCarModeUI() { return mCarModeTracker.isInCarMode(); } @@ -1687,34 +1876,7 @@ public class InCallController extends CallsManagerListenerBase { "calls", calls.size(), info.getComponentName()); int numCallsSent = 0; for (Call call : calls) { - try { - if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported() - || !call.visibleToInCallService())) || - (call.isExternalCall() && !info.isExternalCallsSupported())) { - continue; - } - - // Only send the RTT call if it's a UI in-call service - boolean includeRttCall = false; - if (mInCallServiceConnection != null) { - includeRttCall = info.equals(mInCallServiceConnection.getInfo()); - } - - // Track the call if we don't already know about it. - addCall(call); - numCallsSent += 1; - ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( - call, - true /* includeVideoProvider */, - mCallsManager.getPhoneAccountRegistrar(), - info.isExternalCallsSupported(), - includeRttCall, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); - inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); - updateCallTracking(call, info, true /* isAdd */); - } catch (RemoteException ignored) { - } + numCallsSent += sendCallToService(call, info, inCallService); } try { inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); @@ -1722,7 +1884,7 @@ public class InCallController extends CallsManagerListenerBase { } catch (RemoteException ignored) { } // Don't complete the binding future for non-ui incalls - if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI) { + if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI && !mBindingFuture.isDone()) { mBindingFuture.complete(true); } @@ -1730,6 +1892,39 @@ public class InCallController extends CallsManagerListenerBase { return true; } + private int sendCallToService(Call call, InCallServiceInfo info, + IInCallService inCallService) { + try { + if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported() + || !call.visibleToInCallService())) || + (call.isExternalCall() && !info.isExternalCallsSupported())) { + return 0; + } + + // Only send the RTT call if it's a UI in-call service + boolean includeRttCall = false; + if (mInCallServiceConnection != null) { + includeRttCall = info.equals(mInCallServiceConnection.getInfo()); + } + + // Track the call if we don't already know about it. + addCall(call); + ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( + call, + true /* includeVideoProvider */, + mCallsManager.getPhoneAccountRegistrar(), + info.isExternalCallsSupported(), + includeRttCall, + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || + info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); + updateCallTracking(call, info, true /* isAdd */); + return 1; + } catch (RemoteException ignored) { + } + return 0; + } + /** * Cleans up an instance of in-call app after the service has been unbound. * @@ -1805,10 +2000,18 @@ public class InCallController extends CallsManagerListenerBase { * @param call The call to add. */ private void addCall(Call call) { + if (mCallIdMapper.getCalls().size() == 0) { + mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO }, + java.lang.Runnable::run, this); + updateAllCarrierPrivileged(); + updateAllCarrierPrivilegedUsingMic(); + } + if (mCallIdMapper.getCallId(call) == null) { mCallIdMapper.addCall(call); call.addListener(mCallListener); } + maybeTrackMicrophoneUse(isMuted()); } @@ -1952,8 +2155,11 @@ public class InCallController extends CallsManagerListenerBase { * {@code false} otherwise. */ private boolean isCarModeInCallService(@NonNull String packageName) { + // Disabled InCallService should also be considered as a valid InCallService here so that + // it can be added to the CarModeTracker, in case it will be enabled in future. InCallServiceInfo info = - getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI, + false /* ignoreDisabled */); return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } @@ -1976,15 +2182,39 @@ public class InCallController extends CallsManagerListenerBase { updateCarModeForConnections(); } + public void handleSetAutomotiveProjection(@NonNull String packageName) { + Log.i(this, "handleSetAutomotiveProjection: packageName=%s", packageName); + if (!isCarModeInCallService(packageName)) { + Log.i(this, "handleSetAutomotiveProjection: not a valid InCallService: packageName=%s", + packageName); + return; + } + mCarModeTracker.handleSetAutomotiveProjection(packageName); + + updateCarModeForConnections(); + } + + public void handleReleaseAutomotiveProjection() { + Log.i(this, "handleReleaseAutomotiveProjection"); + mCarModeTracker.handleReleaseAutomotiveProjection(); + + updateCarModeForConnections(); + } + public void updateCarModeForConnections() { Log.i(this, "updateCarModeForConnections: car mode apps: %s", mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", "))); if (mInCallServiceConnection != null) { if (shouldUseCarModeUI()) { + Log.i(this, "updateCarModeForConnections: potentially update car mode app."); mInCallServiceConnection.changeCarModeApp( mCarModeTracker.getCurrentCarModePackage()); } else { - mInCallServiceConnection.disableCarMode(); + if (mInCallServiceConnection.isCarMode()) { + Log.i(this, "updateCarModeForConnections: car mode no longer " + + "applicable; disabling"); + mInCallServiceConnection.disableCarMode(); + } } } } @@ -2013,18 +2243,42 @@ public class InCallController extends CallsManagerListenerBase { Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName); } + private void maybeTrackMicrophoneUse(boolean isMuted) { + maybeTrackMicrophoneUse(isMuted, false); + } + /** * As calls are added, removed and change between external and non-external status, track * whether the current active calling UX is using the microphone. We assume if there is a * managed call present and the mic is not muted that the microphone is in use. */ - private void maybeTrackMicrophoneUse(boolean isMuted) { - boolean wasTrackingManagedCall = mIsCallUsingMicrophone; - mIsCallUsingMicrophone = isTrackingManagedAliveCall() && !isMuted; - if (wasTrackingManagedCall != mIsCallUsingMicrophone) { + private void maybeTrackMicrophoneUse(boolean isMuted, boolean isScheduledDelay) { + if (mIsStartCallDelayScheduled && !isScheduledDelay) { + return; + } + + mIsStartCallDelayScheduled = false; + boolean wasUsingMicrophone = mIsCallUsingMicrophone; + boolean wasTrackingCall = mIsTrackingManagedAliveCall; + mIsTrackingManagedAliveCall = isTrackingManagedAliveCall(); + if (!wasTrackingCall && mIsTrackingManagedAliveCall) { + mIsStartCallDelayScheduled = true; + mHandler.postDelayed(new Runnable("ICC.mTMU", mLock) { + @Override + public void loggedRun() { + maybeTrackMicrophoneUse(isMuted(), true); + } + }.prepare(), mTimeoutsAdapter.getCallStartAppOpDebounceIntervalMillis()); + return; + } + + mIsCallUsingMicrophone = mIsTrackingManagedAliveCall && !isMuted + && !isCarrierPrivilegedUsingMicDuringVoipCall(); + if (wasUsingMicrophone != mIsCallUsingMicrophone) { if (mIsCallUsingMicrophone) { mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(), mContext.getOpPackageName(), false, null, null); + mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.MICROPHONE); } else { mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(), mContext.getOpPackageName(), null); @@ -2038,8 +2292,13 @@ public class InCallController extends CallsManagerListenerBase { */ private boolean isTrackingManagedAliveCall() { return mCallIdMapper.getCalls().stream().anyMatch(c -> !c.isExternalCall() - && !c.isSelfManaged() && c.isAlive() && c.getState() != CallState.ON_HOLD - && c.getState() != CallState.AUDIO_PROCESSING); + && !c.isSelfManaged() && c.isAlive() && ArrayUtils.contains(LIVE_CALL_STATES, + c.getState())); + } + + private boolean isCarrierPrivilegedUsingMicDuringVoipCall() { + return !mActiveCarrierPrivilegedApps.isEmpty() && + mCallIdMapper.getCalls().stream().anyMatch(Call::getIsVoipAudioMode); } /** @@ -2053,9 +2312,13 @@ public class InCallController extends CallsManagerListenerBase { } private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) { - return PermissionChecker.checkPermissionForPreflight(mContext, - Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, uid, - callingPackage) == PermissionChecker.PERMISSION_GRANTED; + return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext, + Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, + new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(uid, callingPackage, + /*attributionTag*/ null)), "Checking whether the app has" + + " MANAGE_ONGOING_CALLS permission") + == PermissionChecker.PERMISSION_GRANTED; } private void sendCrashedInCallServiceNotification(String packageName) { diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java index a9bf18ce0..138e4414b 100644 --- a/src/com/android/server/telecom/LogUtils.java +++ b/src/com/android/server/telecom/LogUtils.java @@ -197,6 +197,11 @@ public class LogUtils { public static final String REDIRECTION_USER_CONFIRMED = "REDIRECTION_USER_CONFIRMED"; public static final String REDIRECTION_USER_CANCELLED = "REDIRECTION_USER_CANCELLED"; public static final String BT_QUALITY_REPORT = "BT_QUALITY_REPORT"; + public static final String SET_DISCONNECTED_ORIG = "SET_DISCONNECTED_ORIG"; + public static final String OVERRIDE_DISCONNECT_MESSAGE = "OVERRIDE_DISCONNECT_MSG"; + public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT = + "CALL_DIAGNOSTIC_SERVICE_TIMEOUT"; + public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED"; public static class Timings { public static final String ACCEPT_TIMING = "accept"; diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java index 8c46e10d6..0becaef30 100644 --- a/src/com/android/server/telecom/ParcelableCallUtils.java +++ b/src/com/android/server/telecom/ParcelableCallUtils.java @@ -27,6 +27,7 @@ import android.telecom.DisconnectCause; import android.telecom.ParcelableCall; import android.telecom.ParcelableRttCall; import android.telecom.TelecomManager; +import android.telephony.ims.ImsCallProfile; import android.text.TextUtils; import java.util.ArrayList; @@ -61,6 +62,7 @@ public class ParcelableCallUtils { static { RESTRICTED_CALL_SCREENING_EXTRA_KEYS = new ArrayList<>(); RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(android.telecom.Connection.EXTRA_SIP_INVITE); + RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(ImsCallProfile.EXTRA_IS_BUSINESS_CALL); } public static class Converter { @@ -558,7 +560,10 @@ public class ParcelableCallUtils { android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL, Connection.PROPERTY_IS_ADHOC_CONFERENCE, - android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE + android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE, + + Connection.PROPERTY_CROSS_SIM, + android.telecom.Call.Details.PROPERTY_CROSS_SIM }; private static int convertConnectionToCallProperties(int connectionProperties) { diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java index 02f0dabfa..bce6e4302 100644 --- a/src/com/android/server/telecom/PhoneAccountRegistrar.java +++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java @@ -2001,8 +2001,8 @@ public class PhoneAccountRegistrar { * @return {@code True} if SIP should be used for all calls. */ private boolean useSipForPstnCalls(Context context) { - String option = Settings.System.getString(context.getContentResolver(), - Settings.System.SIP_CALL_OPTIONS); + String option = Settings.System.getStringForUser(context.getContentResolver(), + Settings.System.SIP_CALL_OPTIONS, context.getUserId()); option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY; return option.equals(Settings.System.SIP_ALWAYS); } diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java index f02f7e8ac..490db8547 100644 --- a/src/com/android/server/telecom/PhoneStateBroadcaster.java +++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java @@ -130,10 +130,17 @@ final class PhoneStateBroadcaster extends CallsManagerListenerBase { TelephonyManager tm = mCallsManager.getContext().getSystemService(TelephonyManager.class); String strippedNumber = PhoneNumberUtils.stripSeparators(call.getHandle().getSchemeSpecificPart()); - Optional<EmergencyNumber> emergencyNumber = tm.getEmergencyNumberList().values().stream() - .flatMap(List::stream) - .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber)) - .findFirst(); + Optional<EmergencyNumber> emergencyNumber; + try { + emergencyNumber = tm.getEmergencyNumberList().values().stream() + .flatMap(List::stream) + .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber)) + .findFirst(); + } catch (IllegalStateException ie) { + emergencyNumber = Optional.empty(); + } catch (RuntimeException r) { + emergencyNumber = Optional.empty(); + } int subscriptionId = tm.getSubscriptionId(call.getTargetPhoneAccount()); SubscriptionManager subscriptionManager = diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java index a769a9496..91276ded8 100644 --- a/src/com/android/server/telecom/Ringer.java +++ b/src/com/android/server/telecom/Ringer.java @@ -20,26 +20,27 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.Person; import android.content.Context; -import android.os.VibrationEffect; -import android.telecom.Log; -import android.telecom.TelecomManager; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.Ringtone; import android.media.VolumeShaper; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.VibrationEffect; import android.os.Vibrator; +import android.telecom.Log; +import android.telecom.TelecomManager; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.LogUtils.EventTimer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Controls the ringtone player. @@ -114,6 +115,8 @@ public class Ringer { private static final int REPEAT_SIMPLE_VIBRATION_AT = 1; + private static final long RINGER_ATTRIBUTES_TIMEOUT = 5000; // 5 seconds + private static final float EPSILON = 1e-6f; private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() @@ -147,6 +150,7 @@ public class Ringer { private InCallTonePlayer mCallWaitingPlayer; private RingtoneFactory mRingtoneFactory; + private AudioManager mAudioManager; /** * Call objects that are ringing, vibrating or call-waiting. These are used only for logging @@ -161,6 +165,8 @@ public class Ringer { */ private boolean mIsVibrating = false; + private Handler mHandler = null; + /** Initializes the Ringer. */ @VisibleForTesting public Ringer( @@ -183,6 +189,7 @@ public class Ringer { mRingtoneFactory = ringtoneFactory; mInCallController = inCallController; mVibrationEffectProxy = vibrationEffectProxy; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) { mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN, @@ -216,58 +223,39 @@ public class Ringer { return false; } - AudioManager audioManager = - (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - LogUtils.EventTimer timer = new EventTimer(); - boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0; - timer.record("isVolumeOverZero"); - boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri()); - timer.record("shouldRingForContact"); - boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null); - timer.record("getRingtone"); - boolean isSelfManaged = foregroundCall.isSelfManaged(); - timer.record("isSelfManaged"); - boolean isSilentRingingRequested = foregroundCall.isSilentRingingRequested(); - timer.record("isSilentRingRequested"); - - boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent; - timer.record("isRingerAudible"); - boolean hasExternalRinger = hasExternalRinger(foregroundCall); - timer.record("hasExternalRinger"); - // Don't do call waiting operations or vibration unless these are false. - boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext); - timer.record("isTheaterModeOn"); - boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(); - timer.record("letDialerHandleRinging"); - - Log.i(this, "startRinging timings: " + timer); - boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged || - hasExternalRinger || isSilentRingingRequested; + // Use completable future to establish a timeout, not intent to make these work outside the + // main thread asynchronously + // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking. + CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture + .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached), + new LoggedHandlerExecutor(getHandler(), "R.sR", null)); + + RingerAttributes attributes = null; + try { + attributes = ringerAttributesFuture.get( + RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + // Keep attributs as null + Log.i(this, "getAttributes error: " + e); + } - // Acquire audio focus under any of the following conditions: - // 1. Should ring for contact and there's an HFP device attached - // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone - // present. - // 3. The call is self-managed. - boolean shouldAcquireAudioFocus = - isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged; + if (attributes == null) { + Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); + return false; + } - if (endEarly) { - if (letDialerHandleRinging) { + if (attributes.isEndEarly()) { + if (attributes.letDialerHandleRinging()) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles"); } - if (isSilentRingingRequested) { + if (attributes.isSilentRingingRequested()) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing " + "requested"); } - Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " + - "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", - isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, - isSilentRingingRequested); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } - return shouldAcquireAudioFocus; + return attributes.shouldAcquireAudioFocus(); } stopCallWaiting(); @@ -276,7 +264,7 @@ public class Ringer { CompletableFuture<Boolean> hapticsFuture = null; // Determine if the settings and DND mode indicate that the vibrator can be used right now. boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall); - if (isRingerAudible) { + if (attributes.isRingerAudible()) { mRingingCall = foregroundCall; Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER); // Because we wait until a contact info query to complete before processing a @@ -309,15 +297,14 @@ public class Ringer { effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall); } } else { - String reason = String.format( - "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s", - isVolumeOverZero, shouldRingForContact, isRingtonePresent); - Log.i(this, "startRinging: skipping because ringer would not be audible. " + reason); - Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + reason); + Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + + attributes.getInaudibleReason()); effect = mDefaultVibrationEffect; } if (hapticsFuture != null) { + final boolean shouldRingForContact = attributes.shouldRingForContact(); + final boolean isRingerAudible = attributes.isRingerAudible(); mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> { if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) { Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b", @@ -342,11 +329,11 @@ public class Ringer { mBlockOnRingingFuture.complete(null); } Log.w(this, "startRinging: No haptics future; fallback to default behavior"); - maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, - isRingerAudible); + maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect, + isVibratorEnabled, attributes.isRingerAudible()); } - return shouldAcquireAudioFocus; + return attributes.shouldAcquireAudioFocus(); } private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact, @@ -517,4 +504,75 @@ public class Ringer { return mSystemSettingsUtil.canVibrateWhenRinging(context) || mSystemSettingsUtil.applyRampingRinger(context); } + + private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) { + RingerAttributes.Builder builder = new RingerAttributes.Builder(); + + LogUtils.EventTimer timer = new EventTimer(); + + boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0; + timer.record("isVolumeOverZero"); + boolean shouldRingForContact = shouldRingForContact(call.getHandle()); + timer.record("shouldRingForContact"); + boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null); + timer.record("getRingtone"); + boolean isSelfManaged = call.isSelfManaged(); + timer.record("isSelfManaged"); + boolean isSilentRingingRequested = call.isSilentRingingRequested(); + timer.record("isSilentRingRequested"); + + boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent; + timer.record("isRingerAudible"); + String inaudibleReason = ""; + if (!isRingerAudible) { + inaudibleReason = String.format( + "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s", + isVolumeOverZero, shouldRingForContact, isRingtonePresent); + } + + boolean hasExternalRinger = hasExternalRinger(call); + timer.record("hasExternalRinger"); + // Don't do call waiting operations or vibration unless these are false. + boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext); + timer.record("isTheaterModeOn"); + boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(); + timer.record("letDialerHandleRinging"); + + Log.i(this, "startRinging timings: " + timer); + boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged || + hasExternalRinger || isSilentRingingRequested; + + if (endEarly) { + Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " + + "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", + isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, + isSilentRingingRequested); + } + + // Acquire audio focus under any of the following conditions: + // 1. Should ring for contact and there's an HFP device attached + // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone + // present. + // 3. The call is self-managed. + boolean shouldAcquireAudioFocus = + isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged; + + return builder.setEndEarly(endEarly) + .setLetDialerHandleRinging(letDialerHandleRinging) + .setAcquireAudioFocus(shouldAcquireAudioFocus) + .setRingerAudible(isRingerAudible) + .setInaudibleReason(inaudibleReason) + .setShouldRingForContact(shouldRingForContact) + .setSilentRingingRequested(isSilentRingingRequested) + .build(); + } + + private Handler getHandler() { + if (mHandler == null) { + HandlerThread handlerThread = new HandlerThread("Ringer"); + handlerThread.start(); + mHandler = handlerThread.getThreadHandler(); + } + return mHandler; + } } diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java new file mode 100644 index 000000000..840d815d1 --- /dev/null +++ b/src/com/android/server/telecom/RingerAttributes.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 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; + +public class RingerAttributes { + public static class Builder { + private boolean mEndEarly; + private boolean mLetDialerHandleRinging; + private boolean mAcquireAudioFocus; + private boolean mRingerAudible; + private String mInaudibleReason; + private boolean mShouldRingForContact; + private boolean mSilentRingingRequested; + + public RingerAttributes.Builder setEndEarly(boolean endEarly) { + mEndEarly = endEarly; + return this; + } + + public RingerAttributes.Builder setLetDialerHandleRinging(boolean letDialerHandleRinging) { + mLetDialerHandleRinging = letDialerHandleRinging; + return this; + } + + public RingerAttributes.Builder setAcquireAudioFocus(boolean acquireAudioFocus) { + mAcquireAudioFocus = acquireAudioFocus; + return this; + } + + public RingerAttributes.Builder setRingerAudible(boolean ringerAudible) { + mRingerAudible = ringerAudible; + return this; + } + + public RingerAttributes.Builder setInaudibleReason(String inaudibleReason) { + mInaudibleReason = inaudibleReason; + return this; + } + + public RingerAttributes.Builder setShouldRingForContact(boolean shouldRingForContact) { + mShouldRingForContact = shouldRingForContact; + return this; + } + + public RingerAttributes.Builder setSilentRingingRequested(boolean silentRingingRequested) { + mSilentRingingRequested = silentRingingRequested; + return this; + } + + public RingerAttributes build() { + return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus, + mRingerAudible, mInaudibleReason, mShouldRingForContact, + mSilentRingingRequested); + } + } + + private boolean mEndEarly; + private boolean mLetDialerHandleRinging; + private boolean mAcquireAudioFocus; + private boolean mRingerAudible; + private String mInaudibleReason; + private boolean mShouldRingForContact; + private boolean mSilentRingingRequested; + + private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging, + boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason, + boolean shouldRingForContact, boolean silentRingingRequested) { + mEndEarly = endEarly; + mLetDialerHandleRinging = letDialerHandleRinging; + mAcquireAudioFocus = acquireAudioFocus; + mRingerAudible = ringerAudible; + mInaudibleReason = inaudibleReason; + mShouldRingForContact = shouldRingForContact; + mSilentRingingRequested = silentRingingRequested; + } + + public boolean isEndEarly() { + return mEndEarly; + } + + public boolean letDialerHandleRinging() { + return mLetDialerHandleRinging; + } + + public boolean shouldAcquireAudioFocus() { + return mAcquireAudioFocus; + } + + public boolean isRingerAudible() { + return mRingerAudible; + } + + public String getInaudibleReason() { + return mInaudibleReason; + } + + public boolean shouldRingForContact() { + return mShouldRingForContact; + } + + public boolean isSilentRingingRequested() { + return mSilentRingingRequested; + } +} diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java index d416376bc..6a121f7c2 100644 --- a/src/com/android/server/telecom/RingtoneFactory.java +++ b/src/com/android/server/telecom/RingtoneFactory.java @@ -75,7 +75,12 @@ public class RingtoneFactory { if(ringtoneUri != null && userContext != null) { // Ringtone URI is explicitly specified. First, try to create a Ringtone with that. - ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri, volumeShaperConfig); + try { + ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri, + volumeShaperConfig); + } catch (NullPointerException npe) { + Log.e(this, npe, "getRingtone: NPE while getting ringtone."); + } } if(ringtone == null) { // Contact didn't specify ringtone or custom Ringtone creation failed. Get default @@ -97,8 +102,12 @@ public class RingtoneFactory { if (defaultRingtoneUri == null) { return null; } - ringtone = RingtoneManager.getRingtone( - contextToUse, defaultRingtoneUri, volumeShaperConfig); + try { + ringtone = RingtoneManager.getRingtone( + contextToUse, defaultRingtoneUri, volumeShaperConfig); + } catch (NullPointerException npe) { + Log.e(this, npe, "getRingtone: NPE while getting ringtone."); + } } if (ringtone != null) { ringtone.setAudioAttributes(new AudioAttributes.Builder() diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java index f104f2720..7baae0579 100644 --- a/src/com/android/server/telecom/SystemSettingsUtil.java +++ b/src/com/android/server/telecom/SystemSettingsUtil.java @@ -40,18 +40,19 @@ public class SystemSettingsUtil { } public boolean canVibrateWhenRinging(Context context) { - return Settings.System.getInt(context.getContentResolver(), - Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.VIBRATE_WHEN_RINGING, 0, context.getUserId()) != 0; } public boolean isEnhancedCallBlockingEnabled(Context context) { - return Settings.System.getInt(context.getContentResolver(), - Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0) != 0; + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0, context.getUserId()) != 0; } public boolean setEnhancedCallBlockingEnabled(Context context, boolean enabled) { - return Settings.System.putInt(context.getContentResolver(), - Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0); + return Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0, + context.getUserId()); } public boolean applyRampingRinger(Context context) { diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java index f44eb395e..dd978c23b 100644 --- a/src/com/android/server/telecom/SystemStateHelper.java +++ b/src/com/android/server/telecom/SystemStateHelper.java @@ -16,6 +16,7 @@ package com.android.server.telecom; +import android.annotation.NonNull; import android.app.UiModeManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -38,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Provides various system states to the rest of the telecom codebase. */ -public class SystemStateHelper { +public class SystemStateHelper implements UiModeManager.OnProjectionStateChangedListener { public interface SystemStateListener { /** * Listener method to inform interested parties when a package name requests to enter or @@ -51,6 +52,19 @@ public class SystemStateHelper { void onCarModeChanged(int priority, String packageName, boolean isCarMode); /** + * Listener method to inform interested parties when a package has set automotive projection + * state. + * @param automotiveProjectionPackage the package that set automotive projection. + */ + void onAutomotiveProjectionStateSet(String automotiveProjectionPackage); + + /** + * Listener method to inform interested parties when automotive projection state has been + * cleared. + */ + void onAutomotiveProjectionStateReleased(); + + /** * Notifies when a package has been uninstalled. * @param packageName the package name of the uninstalled package */ @@ -61,7 +75,7 @@ public class SystemStateHelper { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Log.startSession("SSP.oR"); + Log.startSession("SSH.oR"); try { synchronized (mLock) { String action = intent.getAction(); @@ -103,8 +117,26 @@ public class SystemStateHelper { } }; + @Override + public void onProjectionStateChanged(int activeProjectionTypes, + @NonNull Set<String> projectingPackages) { + Log.startSession("SSH.oPSC"); + try { + synchronized (mLock) { + if (projectingPackages.isEmpty()) { + onReleaseAutomotiveProjection(); + } else { + onSetAutomotiveProjection(projectingPackages.iterator().next()); + } + } + } finally { + Log.endSession(); + } + + } + private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>(); - private boolean mIsCarMode; + private boolean mIsCarModeOrProjectionActive; private final TelecomSystem.SyncRoot mLock; public SystemStateHelper(Context context, TelecomSystem.SyncRoot lock) { @@ -122,7 +154,9 @@ public class SystemStateHelper { Log.i(this, "Registering broadcast receiver: %s", intentFilter1); Log.i(this, "Registering broadcast receiver: %s", intentFilter2); - mIsCarMode = getSystemCarMode(); + mContext.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener( + UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, mContext.getMainExecutor(), this); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); } public void addListener(SystemStateListener listener) { @@ -135,8 +169,8 @@ public class SystemStateHelper { return mListeners.remove(listener); } - public boolean isCarMode() { - return mIsCarMode; + public boolean isCarModeOrProjectionActive() { + return mIsCarModeOrProjectionActive; } public boolean isDeviceAtEar() { @@ -221,7 +255,7 @@ public class SystemStateHelper { private void onEnterCarMode(int priority, String packageName) { Log.i(this, "Entering carmode"); - mIsCarMode = getSystemCarMode(); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); for (SystemStateListener listener : mListeners) { listener.onCarModeChanged(priority, packageName, true /* isCarMode */); } @@ -229,25 +263,44 @@ public class SystemStateHelper { private void onExitCarMode(int priority, String packageName) { Log.i(this, "Exiting carmode"); - mIsCarMode = getSystemCarMode(); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); for (SystemStateListener listener : mListeners) { listener.onCarModeChanged(priority, packageName, false /* isCarMode */); } } + private void onSetAutomotiveProjection(String packageName) { + Log.i(this, "Automotive projection set."); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); + for (SystemStateListener listener : mListeners) { + listener.onAutomotiveProjectionStateSet(packageName); + } + + } + + private void onReleaseAutomotiveProjection() { + Log.i(this, "Automotive projection released."); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); + for (SystemStateListener listener : mListeners) { + listener.onAutomotiveProjectionStateReleased(); + } + } + /** - * Checks the system for the current car mode. + * Checks the system for the current car projection state. * - * @return True if in car mode, false otherwise. + * @return True if projection is active, false otherwise. */ - private boolean getSystemCarMode() { - UiModeManager uiModeManager = - (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); + private boolean getSystemCarModeOrProjectionState() { + UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); if (uiModeManager != null) { - return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR; + return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR + || (uiModeManager.getActiveProjectionTypes() + & UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0; } + Log.w(this, "Got null UiModeManager, returning false."); return false; } } diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java index 0a284847e..e9b760a4e 100644 --- a/src/com/android/server/telecom/TelecomServiceImpl.java +++ b/src/com/android/server/telecom/TelecomServiceImpl.java @@ -30,6 +30,9 @@ import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.UiModeManager; +import android.app.compat.CompatChanges; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -58,7 +61,6 @@ import android.text.TextUtils; import android.util.EventLog; import com.android.internal.telecom.ITelecomService; -import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.components.UserCallIntentProcessorFactory; import com.android.server.telecom.settings.BlockedNumbersActivity; @@ -315,9 +317,21 @@ public class TelecomServiceImpl { } @Override - public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) { + public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle, + String callingPackage) { synchronized (mLock) { final UserHandle callingUserHandle = Binder.getCallingUserHandle(); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION, + callingPackage, Binder.getCallingUserHandle())) { + if (Binder.getCallingUid() != Process.SHELL_UID && + !canGetPhoneAccount(callingPackage, accountHandle)) { + SecurityException e = new SecurityException("getPhoneAccount API requires" + + "READ_PHONE_NUMBERS"); + Log.e(this, e, "getPhoneAccount %s", accountHandle); + throw e; + } + } long token = Binder.clearCallingIdentity(); try { Log.startSession("TSI.gPA"); @@ -327,7 +341,7 @@ public class TelecomServiceImpl { // profile's phone account handle. return mPhoneAccountRegistrar .getPhoneAccount(accountHandle, callingUserHandle, - /* acrossProfiles */ true); + /* acrossProfiles */ true); } catch (Exception e) { Log.e(this, e, "getPhoneAccount %s", accountHandle); throw e; @@ -457,11 +471,11 @@ public class TelecomServiceImpl { try { Log.startSession("TSI.gSCMFU"); final int callingUid = Binder.getCallingUid(); + if (user != ActivityManager.getCurrentUser()) { + enforceCrossUserPermission(callingUid); + } long token = Binder.clearCallingIdentity(); try { - if (user != ActivityManager.getCurrentUser()) { - enforceCrossUserPermission(callingUid); - } return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user)); } finally { Binder.restoreCallingIdentity(token); @@ -838,10 +852,14 @@ public class TelecomServiceImpl { public boolean hasManageOngoingCallsPermission(String callingPackage) { try { Log.startSession("TSI.hMOCP"); - return PermissionChecker.checkPermissionForPreflight(mContext, - Manifest.permission.MANAGE_ONGOING_CALLS, - PermissionChecker.PID_UNKNOWN, Binder.getCallingUid(), - callingPackage) == PermissionChecker.PERMISSION_GRANTED; + return PermissionChecker.checkPermissionForDataDeliveryFromDataSource( + mContext, Manifest.permission.MANAGE_ONGOING_CALLS, + Binder.getCallingPid(), + new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(Binder.getCallingUid(), + callingPackage, /*attributionTag*/ null)), + "Checking whether the caller has MANAGE_ONGOING_CALLS permission") + == PermissionChecker.PERMISSION_GRANTED; } finally { Log.endSession(); } @@ -898,12 +916,48 @@ public class TelecomServiceImpl { } /** - * @see TelecomManager#getCallState + * @see TelecomManager#getCallState() + * @deprecated this is only being kept due to an @UnsupportedAppUsage tag. Apps targeting + * API 31+ must use {@link #getCallStateUsingPackage(String, String)} below. */ + @Deprecated @Override public int getCallState() { try { - Log.startSession("TSI.getCallState"); + Log.startSession("TSI.getCallState(DEPRECATED)"); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, + Binder.getCallingUid())) { + // Do not allow this API to be called on API version 31+, it should only be + // called on old apps using this Binder call directly. + throw new SecurityException("This method can only be used for applications " + + "targeting API version 30 or less."); + } + synchronized (mLock) { + return mCallsManager.getCallState(); + } + } finally { + Log.endSession(); + } + } + + /** + * @see TelecomManager#getCallState() + */ + @Override + public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) { + try { + Log.startSession("TSI.getCallStateUsingPackage"); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage, + Binder.getCallingUserHandle())) { + // Bypass canReadPhoneState check if this is being called from SHELL UID + if (Binder.getCallingUid() != Process.SHELL_UID && !canReadPhoneState( + callingPackage, callingFeatureId, "getCallState")) { + throw new SecurityException("getCallState API requires READ_PHONE_STATE" + + " for API version 31+"); + } + } synchronized (mLock) { return mCallsManager.getCallState(); } @@ -948,7 +1002,7 @@ public class TelecomServiceImpl { long token = Binder.clearCallingIdentity(); try { - acceptRingingCallInternal(DEFAULT_VIDEO_STATE); + acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName); } finally { Binder.restoreCallingIdentity(token); } @@ -971,7 +1025,7 @@ public class TelecomServiceImpl { long token = Binder.clearCallingIdentity(); try { - acceptRingingCallInternal(videoState); + acceptRingingCallInternal(videoState, packageName); } finally { Binder.restoreCallingIdentity(token); } @@ -1776,6 +1830,8 @@ public class TelecomServiceImpl { * {@link CallState#DISCONNECTED} or {@link CallState#DISCONNECTING} states. Stuck calls * during CTS cause cascading failures, so if the CTS test detects such a state, it should * call this method via a shell command to clean up before moving on to the next test. + * Also cleans up any pending futures related to + * {@link android.telecom.CallDiagnosticService}s. */ @Override public void cleanupStuckCalls() { @@ -1785,6 +1841,7 @@ public class TelecomServiceImpl { enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls"); Binder.withCleanCallingIdentity(() -> { for (Call call : mCallsManager.getCalls()) { + call.cleanup(); if (call.getState() == CallState.DISCONNECTED || call.getState() == CallState.DISCONNECTING) { mCallsManager.markCallAsRemoved(call); @@ -1798,6 +1855,28 @@ public class TelecomServiceImpl { } } + /** + * A method intended for use in testing to reset car mode at all priorities. + * + * Runs during setup to avoid cascading failures from failing car mode CTS. + */ + @Override + public void resetCarMode() { + Log.startSession("TCI.rCM"); + try { + synchronized (mLock) { + enforceShellOnly(Binder.getCallingUid(), "resetCarMode"); + Binder.withCleanCallingIdentity(() -> { + UiModeManager uiModeManager = + mContext.getSystemService(UiModeManager.class); + uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES); + }); + } + } finally { + Log.endSession(); + } + } + @Override public void setTestDefaultCallRedirectionApp(String packageName) { try { @@ -2053,9 +2132,16 @@ public class TelecomServiceImpl { return false; } - private void acceptRingingCallInternal(int videoState) { - Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, CallState.SIMULATED_RINGING); + private void acceptRingingCallInternal(int videoState, String packageName) { + Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, + CallState.SIMULATED_RINGING); if (call != null) { + if (call.isSelfManaged()) { + Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT, + "self-mgd accept ignored from " + packageName); + return; + } + if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) { videoState = call.getVideoState(); } @@ -2083,6 +2169,12 @@ public class TelecomServiceImpl { return false; } + if (call.isSelfManaged()) { + Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT, + "self-mgd disconnect ignored from " + callingPackage); + return false; + } + if (call.getState() == CallState.RINGING || call.getState() == CallState.SIMULATED_RINGING) { mCallsManager.rejectCall(call, false /* rejectWithMessage */, null); @@ -2342,6 +2434,28 @@ public class TelecomServiceImpl { == AppOpsManager.MODE_ALLOWED; } + private boolean canGetPhoneAccount(String callingPackage, PhoneAccountHandle accountHandle) { + // Allow default dialer, system dialer and sim call manager to be able to do this without + // extra permission + try { + if (isPrivilegedDialerCalling(callingPackage) || isCallerSimCallManager( + accountHandle)) { + return true; + } + } catch (SecurityException e) { + // ignore + } + + try { + mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null); + return true; + } catch (SecurityException e) { + // Accessing phone state is gated by a special permission. + mContext.enforceCallingOrSelfPermission(READ_PHONE_NUMBERS, null); + return true; + } + } + private boolean isCallerSimCallManager(PhoneAccountHandle targetPhoneAccount) { long token = Binder.clearCallingIdentity(); PhoneAccountHandle accountHandle = null; diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java index a9f18d68b..a7442a9a4 100644 --- a/src/com/android/server/telecom/TelecomSystem.java +++ b/src/com/android/server/telecom/TelecomSystem.java @@ -31,6 +31,7 @@ import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter import com.android.server.telecom.ui.ToastFactory; import android.app.ActivityManager; +import android.bluetooth.BluetoothManager; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; @@ -217,6 +218,7 @@ public class TelecomSystem { defaultDialerAdapter, roleManagerAdapter, mLock); Log.startSession("TS.init"); + // Wrap this in a try block to ensure session cleanup occurs in the case of error. try { mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache, packageName -> AppLabelProxy.Util.getAppLabel( @@ -231,7 +233,7 @@ public class TelecomSystem { } }); BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext, - new BluetoothAdapterProxy()); + new BluetoothManager(mContext).getAdapter()); BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock, bluetoothDeviceManager, new Timeouts.Adapter()); BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver( @@ -243,7 +245,8 @@ public class TelecomSystem { mMissedCallNotifier = missedCallNotifierImplFactory .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, - defaultDialerCache, deviceIdleControllerAdapter); + defaultDialerCache, + deviceIdleControllerAdapter); DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory = new DisconnectedCallNotifier.Default(); @@ -306,7 +309,8 @@ public class TelecomSystem { @Override public Toast makeText(Context context, int resId, int duration) { return Toast.makeText(context, context.getMainLooper(), - context.getString(resId), duration); + context.getString(resId), + duration); } @Override diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java index 230959641..36caa2598 100644 --- a/src/com/android/server/telecom/Timeouts.java +++ b/src/com/android/server/telecom/Timeouts.java @@ -17,8 +17,13 @@ package com.android.server.telecom; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; +import android.telecom.CallDiagnosticService; import android.telecom.CallRedirectionService; +import android.telecom.CallDiagnostics; +import android.telephony.ims.ImsReasonInfo; + import java.util.concurrent.TimeUnit; /** @@ -67,6 +72,14 @@ public final class Timeouts { public long getCallRecordingToneRepeatIntervalMillis(ContentResolver cr) { return Timeouts.getCallRecordingToneRepeatIntervalMillis(cr); } + + public long getCallDiagnosticServiceTimeoutMillis(ContentResolver cr) { + return Timeouts.getCallDiagnosticServiceTimeoutMillis(cr); + } + + public long getCallStartAppOpDebounceIntervalMillis() { + return Timeouts.getCallStartAppOpDebounceIntervalMillis(); + } } /** A prefix to use for all keys so to not clobber the global namespace. */ @@ -85,7 +98,8 @@ public final class Timeouts { * @return The timeout value from Settings or the default value if it hasn't been changed. */ private static long get(ContentResolver contentResolver, String key, long defaultValue) { - return Settings.Secure.getLong(contentResolver, PREFIX + key, defaultValue); + return Settings.Secure.getLongForUser(contentResolver, PREFIX + key, defaultValue, + contentResolver.getUserId()); } /** @@ -189,7 +203,7 @@ public final class Timeouts { /** * Returns the amount of time for an user-defined {@link CallRedirectionService}. * - * @param contentResolver The content resolved. + * @param contentResolver The content resolver. */ public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) { return get(contentResolver, "user_defined_call_redirection_timeout", @@ -199,7 +213,7 @@ public final class Timeouts { /** * Returns the amount of time for a carrier {@link CallRedirectionService}. * - * @param contentResolver The content resolved. + * @param contentResolver The content resolver. */ public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) { return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */); @@ -213,6 +227,21 @@ public final class Timeouts { } /** + * Returns the maximum amount of time a {@link CallDiagnosticService} is permitted to take to + * return back from {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} and + * {@link CallDiagnostics#onCallDisconnected(int, int)}. + * @param contentResolver The resolver for the config option. + * @return The timeout in millis. + */ + public static long getCallDiagnosticServiceTimeoutMillis(ContentResolver contentResolver) { + return get(contentResolver, "call_diagnostic_service_timeout", 2000L /* 2 sec */); + } + + public static long getCallStartAppOpDebounceIntervalMillis() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, "app_op_debounce_time", 250L); + } + + /** * Returns the number of milliseconds for which the system should exempt the default dialer from * power save restrictions due to the dialer needing to handle a missed call notification * (update call log, check VVM, etc...). diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java index dfddb8f0b..457ba363f 100644 --- a/src/com/android/server/telecom/TtyManager.java +++ b/src/com/android/server/telecom/TtyManager.java @@ -42,10 +42,11 @@ final class TtyManager implements WiredHeadsetManager.Listener { mWiredHeadsetManager = wiredHeadsetManager; mWiredHeadsetManager.addListener(this); - mPreferredTtyMode = Settings.Secure.getInt( + mPreferredTtyMode = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.PREFERRED_TTY_MODE, - TelecomManager.TTY_MODE_OFF); + TelecomManager.TTY_MODE_OFF, + mContext.getUserId()); IntentFilter intentFilter = new IntentFilter( TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java index 7fd600c59..8e93a7519 100644 --- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java +++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java @@ -26,8 +26,6 @@ import android.telecom.Log; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.telecom.BluetoothAdapterProxy; -import com.android.server.telecom.BluetoothHeadsetProxy; import java.util.ArrayList; import java.util.Collection; @@ -48,13 +46,12 @@ public class BluetoothDeviceManager { synchronized (mLock) { String logString; if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadsetService = - new BluetoothHeadsetProxy((BluetoothHeadset) proxy); - logString = "Got BluetoothHeadset: " + mBluetoothHeadsetService; + mBluetoothHeadset = (BluetoothHeadset) proxy; + logString = "Got BluetoothHeadset: " + mBluetoothHeadset; } else if (profile == BluetoothProfile.HEARING_AID) { - mBluetoothHearingAidService = (BluetoothHearingAid) proxy; + mBluetoothHearingAid = (BluetoothHearingAid) proxy; logString = "Got BluetoothHearingAid: " - + mBluetoothHearingAidService; + + mBluetoothHearingAid; } else { logString = "Connected to non-requested bluetooth service." + " Not changing bluetooth headset."; @@ -75,13 +72,13 @@ public class BluetoothDeviceManager { LinkedHashMap<String, BluetoothDevice> lostServiceDevices; String logString; if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadsetService = null; + mBluetoothHeadset = null; lostServiceDevices = mHfpDevicesByAddress; mBluetoothRouteManager.onActiveDeviceChanged(null, false); logString = "Lost BluetoothHeadset service. " + "Removing all tracked devices"; } else if (profile == BluetoothProfile.HEARING_AID) { - mBluetoothHearingAidService = null; + mBluetoothHearingAid = null; logString = "Lost BluetoothHearingAid service. " + "Removing all tracked devices."; lostServiceDevices = mHearingAidDevicesByAddress; @@ -117,14 +114,14 @@ public class BluetoothDeviceManager { private final Object mLock = new Object(); private BluetoothRouteManager mBluetoothRouteManager; - private BluetoothHeadsetProxy mBluetoothHeadsetService; - private BluetoothHearingAid mBluetoothHearingAidService; + private BluetoothHeadset mBluetoothHeadset; + private BluetoothHearingAid mBluetoothHearingAid; private BluetoothDevice mBluetoothHearingAidActiveDeviceCache; - private BluetoothAdapterProxy mBluetoothAdapterProxy; + private BluetoothAdapter mBluetoothAdapter; - public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) { + public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) { if (bluetoothAdapter != null) { - mBluetoothAdapterProxy = bluetoothAdapter; + mBluetoothAdapter = bluetoothAdapter; bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, @@ -160,13 +157,12 @@ public class BluetoothDeviceManager { Set<Long> seenHiSyncIds = new LinkedHashSet<>(); // Add the left-most active device to the seen list so that we match up with the list // generated in BluetoothRouteManager. - if (mBluetoothHearingAidService != null) { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - result.add(device); - seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); - break; - } + for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + result.add(device); + seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); + break; } } synchronized (mLock) { @@ -182,20 +178,24 @@ public class BluetoothDeviceManager { return Collections.unmodifiableCollection(result); } - public BluetoothHeadsetProxy getHeadsetService() { - return mBluetoothHeadsetService; + public BluetoothHeadset getBluetoothHeadset() { + return mBluetoothHeadset; + } + + public BluetoothAdapter getBluetoothAdapter() { + return mBluetoothAdapter; } - public BluetoothHearingAid getHearingAidService() { - return mBluetoothHearingAidService; + public BluetoothHearingAid getBluetoothHearingAid() { + return mBluetoothHearingAid; } - public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) { - mBluetoothHeadsetService = bluetoothHeadset; + public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) { + mBluetoothHeadset = bluetoothHeadset; } public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) { - mBluetoothHearingAidService = bluetoothHearingAid; + mBluetoothHearingAid = bluetoothHearingAid; } void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) { @@ -204,15 +204,15 @@ public class BluetoothDeviceManager { synchronized (mLock) { LinkedHashMap<String, BluetoothDevice> targetDeviceMap; if (isHearingAid) { - if (mBluetoothHearingAidService == null) { + if (mBluetoothHearingAid == null) { Log.w(this, "Hearing aid service null when receiving device added broadcast"); return; } - long hiSyncId = mBluetoothHearingAidService.getHiSyncId(device); + long hiSyncId = mBluetoothHearingAid.getHiSyncId(device); mHearingAidDeviceSyncIds.put(device, hiSyncId); targetDeviceMap = mHearingAidDevicesByAddress; } else { - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Headset service null when receiving device added broadcast"); return; } @@ -244,24 +244,20 @@ public class BluetoothDeviceManager { } public void disconnectAudio() { - if (mBluetoothHearingAidService == null) { - Log.w(this, "Trying to disconnect audio but no hearing aid service exists"); - } else { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - mBluetoothAdapterProxy.setActiveDevice(null, - BluetoothAdapter.ACTIVE_DEVICE_ALL); - } + for (BluetoothDevice device: mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + mBluetoothAdapter.setActiveDevice(null, BluetoothAdapter.ACTIVE_DEVICE_ALL); } } disconnectSco(); } public void disconnectSco() { - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Trying to disconnect audio but no headset service exists."); } else { - mBluetoothHeadsetService.disconnectAudio(); + mBluetoothHeadset.disconnectAudio(); } } @@ -269,27 +265,27 @@ public class BluetoothDeviceManager { // or a HFP device, and using the proper BT API. public boolean connectAudio(String address) { if (mHearingAidDevicesByAddress.containsKey(address)) { - if (mBluetoothHearingAidService == null) { + if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } - return mBluetoothAdapterProxy.setActiveDevice( + return mBluetoothAdapter.setActiveDevice( mHearingAidDevicesByAddress.get(address), BluetoothAdapter.ACTIVE_DEVICE_ALL); } else if (mHfpDevicesByAddress.containsKey(address)) { BluetoothDevice device = mHfpDevicesByAddress.get(address); - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } - boolean success = mBluetoothAdapterProxy.setActiveDevice(device, + boolean success = mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); if (!success) { Log.w(this, "Couldn't set active device to %s", address); return false; } - if (!mBluetoothHeadsetService.isAudioOn()) { - return mBluetoothHeadsetService.connectAudio(); + if (!mBluetoothHeadset.isAudioOn()) { + return mBluetoothHeadset.connectAudio(); } return true; } else { @@ -299,20 +295,18 @@ public class BluetoothDeviceManager { } public void cacheHearingAidDevice() { - if (mBluetoothHearingAidService != null) { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - mBluetoothHearingAidActiveDeviceCache = device; - } - } + for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + mBluetoothHearingAidActiveDeviceCache = device; + } } } public void restoreHearingAidDevice() { - if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) { - mBluetoothAdapterProxy.setActiveDevice( - mBluetoothHearingAidActiveDeviceCache, - BluetoothAdapter.ACTIVE_DEVICE_ALL); + if (mBluetoothHearingAidActiveDeviceCache != null) { + mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache, + BluetoothAdapter.ACTIVE_DEVICE_ALL); mBluetoothHearingAidActiveDeviceCache = null; } } diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java index 721199030..81f4ab6f3 100644 --- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java +++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java @@ -16,9 +16,11 @@ package com.android.server.telecom.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Message; import android.telecom.Log; @@ -30,7 +32,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; @@ -701,19 +702,28 @@ public class BluetoothRouteManager extends StateMachine { */ @VisibleForTesting public BluetoothDevice getBluetoothAudioConnectedDevice() { - BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService(); - BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getHearingAidService(); + BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter(); + BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset(); + BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getBluetoothHearingAid(); BluetoothDevice hfpAudioOnDevice = null; BluetoothDevice hearingAidActiveDevice = null; + if (bluetoothAdapter == null) { + Log.i(this, "getBluetoothAudioConnectedDevice: no adapter available."); + return null; + } if (bluetoothHeadset == null && bluetoothHearingAid == null) { Log.i(this, "getBluetoothAudioConnectedDevice: no service available."); return null; } if (bluetoothHeadset != null) { - hfpAudioOnDevice = bluetoothHeadset.getActiveDevice(); + for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( + BluetoothProfile.HEADSET)) { + hfpAudioOnDevice = device; + break; + } if (bluetoothHeadset.getAudioState(hfpAudioOnDevice) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { @@ -722,7 +732,8 @@ public class BluetoothRouteManager extends StateMachine { } if (bluetoothHearingAid != null) { - for (BluetoothDevice device : bluetoothHearingAid.getActiveDevices()) { + for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { if (device != null) { hearingAidActiveDevice = device; break; @@ -751,7 +762,7 @@ public class BluetoothRouteManager extends StateMachine { */ @VisibleForTesting public boolean isInbandRingingEnabled() { - BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService(); + BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset(); if (bluetoothHeadset == null) { Log.i(this, "isInbandRingingEnabled: no headset service available."); return false; diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java index d95d578f4..84ce4d4e2 100644 --- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java +++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java @@ -18,6 +18,7 @@ package com.android.server.telecom.callfiltering; import android.provider.CallLog; import android.provider.CallLog.Calls; +import android.telecom.CallScreeningService; import android.text.TextUtils; import java.util.Objects; @@ -34,6 +35,8 @@ public class CallFilteringResult { private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED; private CharSequence mCallScreeningAppName = null; private String mCallScreeningComponentName = null; + private CallScreeningService.ParcelableCallResponse mCallScreeningResponse = null; + private boolean mIsResponseFromSystemDialer = false; public Builder setShouldAllowCall(boolean shouldAllowCall) { mShouldAllowCall = shouldAllowCall; @@ -80,6 +83,13 @@ public class CallFilteringResult { return this; } + public Builder setCallScreeningResponse( + CallScreeningService.ParcelableCallResponse response, boolean isFromSystemDialer) { + mCallScreeningResponse = response; + mIsResponseFromSystemDialer = isFromSystemDialer; + return this; + } + public Builder setContactExists(boolean contactExists) { mContactExists = contactExists; return this; @@ -96,14 +106,16 @@ public class CallFilteringResult { .setShouldScreenViaAudio(result.shouldScreenViaAudio) .setCallScreeningAppName(result.mCallScreeningAppName) .setCallScreeningComponentName(result.mCallScreeningComponentName) + .setCallScreeningResponse(result.mCallScreeningResponse, + result.mIsResponseFromSystemDialer) .setContactExists(result.contactExists); } public CallFilteringResult build() { return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence, mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason, - mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio, - mContactExists); + mCallScreeningAppName, mCallScreeningComponentName, mCallScreeningResponse, + mIsResponseFromSystemDialer, mShouldScreenViaAudio, mContactExists); } } @@ -116,11 +128,15 @@ public class CallFilteringResult { public int mCallBlockReason; public CharSequence mCallScreeningAppName; public String mCallScreeningComponentName; + public CallScreeningService.ParcelableCallResponse mCallScreeningResponse; + public boolean mIsResponseFromSystemDialer; public boolean contactExists; private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, boolean shouldScreenViaAudio, boolean contactExists) { this.shouldAllowCall = shouldAllowCall; this.shouldReject = shouldReject; @@ -131,6 +147,8 @@ public class CallFilteringResult { this.mCallBlockReason = callBlockReason; this.mCallScreeningAppName = callScreeningAppName; this.mCallScreeningComponentName = callScreeningComponentName; + this.mCallScreeningResponse = callScreeningResponse; + this.mIsResponseFromSystemDialer = isResponseFromSystemDialer; this.contactExists = contactExists; } @@ -148,25 +166,25 @@ public class CallFilteringResult { if (isBlockedByProvider(mCallBlockReason)) { return getCombinedCallFilteringResult(other, mCallBlockReason, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } else if (isBlockedByProvider(other.mCallBlockReason)) { return getCombinedCallFilteringResult(other, other.mCallBlockReason, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL - || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) { + || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } if (shouldReject && mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, - mCallScreeningAppName, mCallScreeningComponentName); + mCallScreeningAppName, mCallScreeningComponentName); } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls - .BLOCK_REASON_CALL_SCREENING_SERVICE) { + .BLOCK_REASON_CALL_SCREENING_SERVICE) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, - other.mCallScreeningAppName, other.mCallScreeningComponentName); + other.mCallScreeningAppName, other.mCallScreeningComponentName); } if (shouldScreenViaAudio) { @@ -177,15 +195,16 @@ public class CallFilteringResult { other.mCallScreeningAppName, other.mCallScreeningComponentName); } - return new Builder() + Builder b = new Builder() .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall) .setShouldReject(shouldReject || other.shouldReject) .setShouldSilence(shouldSilence || other.shouldSilence) .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog) .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification) .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio) - .setContactExists(contactExists || other.contactExists) - .build(); + .setContactExists(contactExists || other.contactExists); + combineScreeningResponses(b, this, other); + return b.build(); } private boolean isBlockedByProvider(int blockReason) { @@ -201,8 +220,9 @@ public class CallFilteringResult { } private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other, - int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) { - return new Builder() + int callBlockReason, CharSequence callScreeningAppName, + String callScreeningComponentName) { + Builder b = new Builder() .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall) .setShouldReject(shouldReject || other.shouldReject) .setShouldSilence(shouldSilence || other.shouldSilence) @@ -212,10 +232,33 @@ public class CallFilteringResult { .setCallBlockReason(callBlockReason) .setCallScreeningAppName(callScreeningAppName) .setCallScreeningComponentName(callScreeningComponentName) - .setContactExists(contactExists || other.contactExists) - .build(); + .setContactExists(contactExists || other.contactExists); + combineScreeningResponses(b, this, other); + return b.build(); } + private static void combineScreeningResponses(Builder builder, CallFilteringResult r1, + CallFilteringResult r2) { + if (r1.mIsResponseFromSystemDialer) { + builder.setCallScreeningResponse(r1.mCallScreeningResponse, true); + builder.setCallScreeningComponentName(r1.mCallScreeningComponentName); + builder.setCallScreeningAppName(r1.mCallScreeningAppName); + } else if (r2.mIsResponseFromSystemDialer) { + builder.setCallScreeningResponse(r2.mCallScreeningResponse, true); + builder.setCallScreeningComponentName(r2.mCallScreeningComponentName); + builder.setCallScreeningAppName(r2.mCallScreeningAppName); + } else { + if (r1.mCallScreeningResponse != null) { + builder.setCallScreeningResponse(r1.mCallScreeningResponse, false); + builder.setCallScreeningComponentName(r1.mCallScreeningComponentName); + builder.setCallScreeningAppName(r1.mCallScreeningAppName); + } else { + builder.setCallScreeningResponse(r2.mCallScreeningResponse, false); + builder.setCallScreeningComponentName(r2.mCallScreeningComponentName); + builder.setCallScreeningAppName(r2.mCallScreeningAppName); + } + } + } @Override public boolean equals(Object o) { diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java index 486cd8d80..4a308e0c9 100644 --- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java +++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.provider.CallLog; +import android.telecom.CallScreeningService; import android.telecom.Log; import android.telecom.TelecomManager; @@ -64,15 +65,44 @@ public class CallScreeningServiceFilter extends CallFilter { } @Override - public void allowCall(String callId) { - Long token = Binder.clearCallingIdentity(); + public void onScreeningResponse(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse callResponse) { + if (callResponse == null) { + Log.w(this, "Null responses are only supposed to happen for outgoing calls"); + return; + } + if (callResponse.shouldDisallowCall()) { + disallowCall(callId, componentName, callResponse); + } else if (callResponse.shouldSilenceCall()) { + silenceCall(callId, componentName, callResponse); + } else if (callResponse.shouldScreenCallViaAudioProcessing()) { + screenCallFurther(callId, componentName, callResponse); + } else { + allowCall(callId, componentName, callResponse); + } + } + + public void allowCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { + long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.aC"); try { if (mCall == null || (!mCall.getId().equals(callId))) { Log.w(this, "allowCall, unknown call id: %s", callId); } - Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mPriorStageResult); - mResultFuture.complete(mPriorStageResult); + CallFilteringResult result = new CallFilteringResult.Builder() + .setShouldAllowCall(true) + .setShouldReject(false) + .setShouldSilence(false) + .setShouldAddToCallLog(mPriorStageResult.shouldAddToCallLog) + .setShouldShowNotification(mPriorStageResult.shouldShowNotification) + .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) + .setContactExists(mPriorStageResult.contactExists) + .build(); + Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); + mResultFuture.complete(result); } finally { unbindCallScreeningService(); Binder.restoreCallingIdentity(token); @@ -80,24 +110,23 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void disallowCall(String callId, boolean shouldReject, - boolean shouldAddToCallLog, boolean shouldShowNotification, - ComponentName componentName) { + public void disallowCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.dC"); try { if (mCall != null && mCall.getId().equals(callId)) { CallFilteringResult result = new CallFilteringResult.Builder() .setShouldAllowCall(false) - .setShouldReject(shouldReject) + .setShouldReject(response.shouldRejectCall()) .setShouldSilence(false) - .setShouldAddToCallLog(shouldAddToCallLog + .setShouldAddToCallLog(!response.shouldSkipCallLog() || packageTypeShouldAdd(mPackagetype)) - .setShouldShowNotification(shouldShowNotification) + .setShouldShowNotification(!response.shouldSkipNotification()) .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) .setCallScreeningAppName(mAppName) .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); @@ -113,8 +142,8 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void silenceCall(String callId) { + public void silenceCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.sC"); try { @@ -125,6 +154,9 @@ public class CallScreeningServiceFilter extends CallFilter { .setShouldSilence(true) .setShouldAddToCallLog(true) .setShouldShowNotification(true) + .setCallScreeningResponse(response, isSystemDialer()) + .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); @@ -140,8 +172,8 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void screenCallFurther(String callId) { + public void screenCallFurther(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) { throw new SecurityException("Only the default/system dialer may request screen via" + "background call audio"); @@ -158,6 +190,8 @@ public class CallScreeningServiceFilter extends CallFilter { .setShouldSilence(false) .setShouldScreenViaAudio(true) .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java index e3c68c9b7..9fa864e60 100644 --- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java +++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java @@ -49,7 +49,7 @@ public class IncomingCallFilterGraph { private final HandlerThread mHandlerThread; private final TelecomSystem.SyncRoot mLock; private List<CallFilter> mFiltersList; - private CallFilter mDummyComplete; + private CallFilter mCompletionSentinel; private boolean mFinished; private CallFilteringResult mCurrentResult; private Context mContext; @@ -70,7 +70,7 @@ public class IncomingCallFilterGraph { scheduleFilter(filter); } } - if (mFilter.equals(mDummyComplete)) { + if (mFilter.equals(mCompletionSentinel)) { synchronized (mLock) { mFinished = true; mListener.onCallFilteringComplete(mCall, result, false); @@ -105,15 +105,15 @@ public class IncomingCallFilterGraph { public void performFiltering() { Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED); CallFilter dummyStart = new CallFilter(); - mDummyComplete = new CallFilter(); + mCompletionSentinel = new CallFilter(); for (CallFilter filter : mFiltersList) { addEdge(dummyStart, filter); } for (CallFilter filter : mFiltersList) { - addEdge(filter, mDummyComplete); + addEdge(filter, mCompletionSentinel); } - addEdge(dummyStart, mDummyComplete); + addEdge(dummyStart, mCompletionSentinel); scheduleFilter(dummyStart); mHandler.postDelayed(new Runnable("ICFG.pF", mLock) { @@ -159,7 +159,11 @@ public class IncomingCallFilterGraph { startFuture.thenComposeAsync(filter::startFilterLookup, new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) .thenApplyAsync(postFilterTask::whenDone, - new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)); + new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) + .exceptionally((t) -> { + Log.e(filter, t, "Encountered exception running filter"); + return null; + }); Log.i(TAG, "Filter %s scheduled.", filter); } diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java index 34975aab3..adeb3113d 100644 --- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java +++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java @@ -305,7 +305,7 @@ public class CallRedirectionProcessor implements CallRedirectionCallback { * The current rule to decide whether the implemented {@link CallRedirectionService} should * allow interactive responses with users is only based on whether it is in car mode. */ - mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarMode(); + mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarModeOrProjectionActive(); mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper( context, callsManager, phoneAccountRegistrar); mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection( diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java index 67634e445..4be75f8e8 100644 --- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java +++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java @@ -95,7 +95,8 @@ public final class BlockedNumbersUtil { if (showNotification) { Intent intent = new Intent(context, CallBlockDisabledActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_IMMUTABLE); String title = context.getString( R.string.phone_strings_call_blocking_turned_off_notification_title_txt); diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java index edea89bb0..0c1c5a3d9 100644 --- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java +++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java @@ -282,12 +282,12 @@ public class IncomingCallNotifier extends CallsManagerListenerBase { R.anim.on_going_call, getActionText(R.string.answer_incoming_call, R.color.notification_action_answer), PendingIntent.getBroadcast(mContext, 0, answerIntent, - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); builder.addAction( R.drawable.ic_close_dk, getActionText(R.string.decline_incoming_call, R.color.notification_action_decline), PendingIntent.getBroadcast(mContext, 0, rejectIntent, - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); return builder; } diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java index 4e32c9fc9..12d382090 100644 --- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java +++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java @@ -67,18 +67,15 @@ import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.telecom.CallerInfo; +import android.util.ArrayMap; import java.lang.Override; import java.lang.String; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; - -// TODO: Needed for move to system service: import com.android.internal.R; /** * Creates a notification for calls that the user missed (neither answered nor rejected). @@ -139,8 +136,10 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements private final DeviceIdleControllerAdapter mDeviceIdleControllerAdapter; private UserHandle mCurrentUserHandle; + // Used to guard access to mMissedCallCounts + private final Object mMissedCallCountsLock = new Object(); // Used to track the number of missed calls. - private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts; + private final Map<UserHandle, Integer> mMissedCallCounts; private List<UserHandle> mUsersToLoadAfterBootComplete = new ArrayList<>(); @@ -164,7 +163,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements mDefaultDialerCache = defaultDialerCache; mNotificationBuilderFactory = notificationBuilderFactory; - mMissedCallCounts = new ConcurrentHashMap<>(); + mMissedCallCounts = new ArrayMap<>(); } /** Clears missed call notification and marks the call log's missed calls as read. */ @@ -263,17 +262,16 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo, - UserHandle userHandle) { - int count = mMissedCallCounts.get(userHandle).get(); + UserHandle userHandle, int missedCallCount) { Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage) .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT, createClearMissedCallsPendingIntent(userHandle)) - .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count) + .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount) .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER, callInfo == null ? null : callInfo.getPhoneNumber()); - if (count == 1 && callInfo != null) { + if (missedCallCount == 1 && callInfo != null) { final Uri handleUri = callInfo.getHandle(); String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart(); @@ -284,8 +282,8 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } } - - Log.w(this, "Showing missed calls through default dialer."); + Log.i(this, "sendNotificationThroughDefaultDialer; count=%d, dialerPackage=%s", + missedCallCount, intent.getPackage()); Bundle options = exemptFromPowerSavingTemporarily(dialerPackage, userHandle); mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, options); } @@ -293,7 +291,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements /** * Create a system notification for the missed call. * - * @param call The missed call. + * @param callInfo The missed call. */ @Override public void showMissedCallNotification(@NonNull CallInfo callInfo) { @@ -311,13 +309,21 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) { - Log.i(this, "showMissedCallNotification: userHandle=%d", userHandle.getIdentifier()); - mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0)); - int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet(); + int missedCallCounts; + synchronized (mMissedCallCountsLock) { + Integer currentCount = mMissedCallCounts.get(userHandle); + missedCallCounts = currentCount == null ? 0 : currentCount; + missedCallCounts++; + mMissedCallCounts.put(userHandle, missedCallCounts); + } + + Log.i(this, "showMissedCallNotification: userHandle=%d, missedCallCount=%d", + userHandle.getIdentifier(), missedCallCounts); String dialerPackage = getDefaultDialerPackage(userHandle); if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { - sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle); + sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle, + missedCallCounts); return; } @@ -327,7 +333,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements // Display the first line of the notification: // 1 missed call: <caller name || handle> // More than 1 missed call: <number of calls> + "missed calls" - if (missCallCounts == 1) { + if (missedCallCounts == 1) { expandedText = getNameForMissedCallNotification(callInfo); CallerInfo ci = callInfo.getCallerInfo(); @@ -339,7 +345,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } else { titleResId = R.string.notification_missedCallsTitle; expandedText = - mContext.getString(R.string.notification_missedCallsMsg, missCallCounts); + mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts); } // Create a public viewable version of the notification, suitable for display when sensitive @@ -381,7 +387,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements String handle = callInfo.getHandleSchemeSpecificPart(); // Add additional actions when there is only 1 missed call, like call-back and SMS. - if (missCallCounts == 1) { + if (missedCallCounts == 1) { Log.d(this, "Add actions with number %s.", Log.piiHandle(handle)); if (!TextUtils.isEmpty(handle) @@ -410,7 +416,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } } else { Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle), - missCallCounts); + missedCallCounts); } Notification notification = builder.build(); @@ -430,12 +436,14 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements /** Cancels the "missed call" notification. */ private void cancelMissedCallNotification(UserHandle userHandle) { // Reset the number of missed calls to 0. - mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0)); - mMissedCallCounts.get(userHandle).set(0); + synchronized(mMissedCallCountsLock) { + mMissedCallCounts.put(userHandle, 0); + } String dialerPackage = getDefaultDialerPackage(userHandle); if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { - sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle); + sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle, + 0 /* missedCallCount */); return; } @@ -612,7 +620,9 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements Log.d(MissedCallNotifierImpl.this, "onQueryComplete()..."); if (cursor != null) { try { - mMissedCallCounts.remove(userHandle); + synchronized(mMissedCallCountsLock) { + mMissedCallCounts.remove(userHandle); + } while (cursor.moveToNext()) { // Get data about the missed call from the cursor final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER); diff --git a/testapps/callaudiotest/Android.bp b/testapps/callaudiotest/Android.bp new file mode 100644 index 000000000..81164e6f4 --- /dev/null +++ b/testapps/callaudiotest/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "TelecomCallAudioTestApp", + static_libs: [ + "androidx.legacy_legacy-support-v4", + "guava", + ], + srcs: ["src/**/*.java"], + platform_apis: true, + certificate: "platform", + privileged: true, +} diff --git a/testapps/callaudiotest/AndroidManifest.xml b/testapps/callaudiotest/AndroidManifest.xml new file mode 100644 index 000000000..014ab2909 --- /dev/null +++ b/testapps/callaudiotest/AndroidManifest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + coreApp="true" + package="com.android.server.telecom.callaudiotest"> + + <uses-sdk android:minSdkVersion="28" + android:targetSdkVersion="29"/> + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.CALL_PHONE"/> + <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/> + <uses-permission android:name="android.permission.READ_CALL_LOG"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/> + <uses-permission android:name="android.permission.WRITE_CALL_LOG"/> + + <application android:label="Telecom Call Audio Test"> + <uses-library android:name="android.test.runner"/> + + <service android:name="com.android.server.telecom.callaudiotest.CallAudioTestInCallService" + android:permission="android.permission.BIND_INCALL_SERVICE" + android:exported="true"> + <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" + android:value="false"/> + <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" + android:value="false"/> + <intent-filter> + <action android:name="android.telecom.InCallService"/> + </intent-filter> + </service> + + <activity android:name="com.android.server.telecom.callaudiotest.CallAudioTestActivity" + android:label="Call Audio Test" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/testapps/callaudiotest/res/layout/call_audio_test_activity.xml b/testapps/callaudiotest/res/layout/call_audio_test_activity.xml new file mode 100644 index 000000000..0d847f07a --- /dev/null +++ b/testapps/callaudiotest/res/layout/call_audio_test_activity.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + <TextView + android:id="@+id/appLabel" + android:layout_gravity="left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="25dp" + android:text="Call Audio Testing" /> + <TextView + android:id="@+id/appDescription" + android:layout_gravity="left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="12dp" + android:text="You can enabled auto answer and calls will be answered and some awesome audio will be looped to the caller." /> + <CheckBox + android:id="@+id/enableAutoAnswer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Answer calls and loop audio" /> +</LinearLayout> diff --git a/testapps/callaudiotest/res/raw/speech.m4a b/testapps/callaudiotest/res/raw/speech.m4a Binary files differnew file mode 100644 index 000000000..4161ad694 --- /dev/null +++ b/testapps/callaudiotest/res/raw/speech.m4a diff --git a/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java new file mode 100644 index 000000000..b75a4989a --- /dev/null +++ b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.callaudiotest; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; + +/** + * Activity to configure the auto answer behavior. + */ +public class CallAudioTestActivity extends Activity { + private static final int RESULT_PICK_FILE = 1; + public static final String AUDIO_TEST_PREFS = "audio_test_prefs"; + public static final String AUTO_ANSWER_ENABLED = "auto_answer_enabled"; + private CheckBox mEnable; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.call_audio_test_activity); + mEnable = findViewById(R.id.enableAutoAnswer); + mEnable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SharedPreferences prefs = getSharedPreferences(AUDIO_TEST_PREFS, MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean(AUTO_ANSWER_ENABLED, isChecked); + edit.apply(); + } + }); + loadPreferences(); + } + + private void loadPreferences() { + SharedPreferences prefs = getSharedPreferences(AUDIO_TEST_PREFS, MODE_PRIVATE); + mEnable.setChecked(prefs.getBoolean(AUTO_ANSWER_ENABLED, false)); + } +} diff --git a/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java new file mode 100644 index 000000000..bd98a7f02 --- /dev/null +++ b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 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.callaudiotest; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.media.AudioAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.IBinder; +import android.telecom.Call; +import android.telecom.InCallService; +import android.telecom.Log; +import android.telecom.VideoProfile; + +import java.io.IOException; +import java.lang.String; + +/** + * Simple test InCallService which answers a call automatically and plays back some looping audio. + * Intended for testing call audio between devices. + */ +public class CallAudioTestInCallService extends InCallService { + private Call mIncomingCall; + private boolean mIsAutoAnswerEnabled = false; + private MediaPlayer mMediaPlayer; + private Call.Callback mCallback = new Call.Callback() { + @Override + public void onStateChanged(Call call, int state) { + if (state == Call.STATE_ACTIVE) { + startPlaying(); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + SharedPreferences prefs = getSharedPreferences(CallAudioTestActivity.AUDIO_TEST_PREFS, + MODE_PRIVATE); + mIsAutoAnswerEnabled = prefs.getBoolean(CallAudioTestActivity.AUTO_ANSWER_ENABLED, false); + return super.onBind(intent); + } + + @Override + public void onCallAdded(Call call) { + if (!mIsAutoAnswerEnabled) { + Log.i(this, "onCallAdded - autoanswer disabled, skip"); + return; + } + if (call.getDetails().getState() == Call.STATE_RINGING) { + mIncomingCall = call; + mIncomingCall.registerCallback(mCallback); + mIncomingCall.answer(VideoProfile.STATE_AUDIO_ONLY); + + Log.i(this, "onCallAdded - ringing call"); + } else { + Log.i(this, "onCallAdded - nonringing call"); + } + } + + @Override + public void onCallRemoved(Call call) { + if (mIncomingCall == call) { + mIncomingCall = null; + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + } + } + } + + private void startPlaying() { + AudioDeviceInfo telephonyDevice = getTelephonyDevice(getSystemService(AudioManager.class)); + if (telephonyDevice != null) { + Log.i(this, "startPlaying: create player for speech"); + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnCompletionListener(mediaPlayer -> Log.w(this, "startPlaying: done")); + mMediaPlayer.setOnErrorListener( + (mediaPlayer, what, extra) -> { + Log.w(this, "startPlaying: playback failed!"); + return true; // Error handled + }); + mMediaPlayer.setLooping(true); + mMediaPlayer.setVolume(1.0f); + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build(); + mMediaPlayer.setAudioAttributes(audioAttributes); + try { + mMediaPlayer.setDataSource(getResources().openRawResourceFd(R.raw.speech)); + mMediaPlayer.prepare(); + } catch (IOException e) { + mMediaPlayer.release(); + throw new IllegalStateException(e); + } + if (!mMediaPlayer.setPreferredDevice(telephonyDevice)) { + Log.w(this, "startPlaying: setPreferredDevice failed"); + } + mMediaPlayer.start(); + + } + } + + private AudioDeviceInfo getTelephonyDevice(AudioManager audioManager) { + AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device: deviceList) { + if (device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) { + return device; + } + } + return null; + } +} diff --git a/testapps/companionapp/Android.bp b/testapps/companionapp/Android.bp new file mode 100644 index 000000000..8718b373e --- /dev/null +++ b/testapps/companionapp/Android.bp @@ -0,0 +1,28 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "TelecomCompanionApp", + static_libs: [ + "androidx.legacy_legacy-support-v4", + "guava", + ], + srcs: ["src/**/*.java"], +} diff --git a/testapps/companionapp/AndroidManifest.xml b/testapps/companionapp/AndroidManifest.xml new file mode 100644 index 000000000..7569d8feb --- /dev/null +++ b/testapps/companionapp/AndroidManifest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + coreApp="true" + package="com.android.server.telecom.companionapp"> + + <uses-sdk android:minSdkVersion="28" + android:targetSdkVersion="29"/> + + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS" /> + <uses-feature android:name="android.software.companion_device_setup"/> + + <application android:label="Telecom Companion"> + <uses-library android:name="android.test.runner"/> + + <activity android:name="com.android.server.telecom.companionapp.CompanionTestApp" + android:label="CompanionTestApp"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <service android:name="com.android.server.telecom.companionapp.CompanionInCallServiceImpl" + android:permission="android.permission.BIND_INCALL_SERVICE" + android:exported="true"> + <intent-filter> + <action android:name="android.telecom.InCallService"/> + </intent-filter> + </service> + </application> +</manifest> diff --git a/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png Binary files differnew file mode 100644 index 000000000..ed3ee454d --- /dev/null +++ b/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png diff --git a/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png Binary files differnew file mode 100644 index 000000000..a4add5149 --- /dev/null +++ b/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png diff --git a/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png Binary files differnew file mode 100644 index 000000000..41558f251 --- /dev/null +++ b/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png diff --git a/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png Binary files differnew file mode 100644 index 000000000..6006b1207 --- /dev/null +++ b/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png diff --git a/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png Binary files differnew file mode 100644 index 000000000..4f935bf59 --- /dev/null +++ b/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png diff --git a/testapps/companionapp/res/layout/main.xml b/testapps/companionapp/res/layout/main.xml new file mode 100644 index 000000000..e55375437 --- /dev/null +++ b/testapps/companionapp/res/layout/main.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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="fill_parent" + android:layout_height="fill_parent"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="This is a test app for companion in call service." + android:padding="10dp" + android:textColor="#FFFFFF"/> + <Button + android:id="@+id/buttonStart" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Start Association"/> + <Button + android:id="@+id/buttonStop" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Stop Association"/> +</LinearLayout> diff --git a/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java new file mode 100644 index 000000000..890cd7728 --- /dev/null +++ b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 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.companionapp; + +import android.content.Intent; +import android.telecom.Call; +import android.telecom.InCallService; +import android.telecom.Phone; +import android.util.Log; +import android.widget.Toast; + +import java.lang.Override; +import java.lang.String; + +/** + * Test Companion In-Call service implementation. + */ +public class CompanionInCallServiceImpl extends InCallService { + private static final String TAG = "CompanionInCallServiceImpl"; + + private Phone mPhone; + + private Phone.Listener mPhoneListener = new Phone.Listener() { + @Override + public void onCallAdded(Phone phone, Call call) { + Log.i(TAG, "onCallAdded: " + call.toString()); + Toast.makeText( + CompanionInCallServiceImpl.this, "onCallAdded", Toast.LENGTH_LONG).show(); + } + + @Override + public void onCallRemoved(Phone phone, Call call) { + Log.i(TAG, "onCallRemoved: " + call.toString()); + Toast.makeText( + CompanionInCallServiceImpl.this, "onCallRemoved", Toast.LENGTH_LONG).show(); + } + }; + + @Override + public void onPhoneCreated(Phone phone) { + Log.i(TAG, "onPhoneCreated"); + mPhone = phone; + mPhone.addListener(mPhoneListener); + } + + @Override + public boolean onUnbind(Intent intent) { + Log.i(TAG, "Companion TestApp InCallService unbind"); + mPhone.removeListener(mPhoneListener); + mPhone = null; + return super.onUnbind(intent); + } + + /** + * Used to bind a call + * @param intent + * @return + */ + @Override + public android.os.IBinder onBind(Intent intent) { + Log.d(TAG, "Companion TestApp InCallService bind"); + return super.onBind(intent); + } +} diff --git a/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java new file mode 100644 index 000000000..7ed1e1e75 --- /dev/null +++ b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 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.companionapp; + +import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.companion.AssociationRequest; +import android.companion.BluetoothDeviceFilter; +import android.companion.CompanionDeviceManager; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.ParcelUuid; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.Toast; + +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * Companion Test App in Telecom + */ +public class CompanionTestApp extends Activity { + + private static String logtag = "CompanionTestApp"; + private CompanionDeviceManager mDeviceManager; + private AssociationRequest mPairingRequest; + private BluetoothDeviceFilter mDeviceFilter; + + private static final int SELECT_DEVICE_REQUEST_CODE = 42; + + @Override + public void onCreate(Bundle savedInstanceState) { + setupDeviceAssociation(); + + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + Button buttonStart = (Button)findViewById(R.id.buttonStart); + buttonStart.setOnClickListener(startListener); + + Button buttonStop = (Button)findViewById(R.id.buttonStop); + buttonStop.setOnClickListener(stopListener); + } + + private OnClickListener startListener = new OnClickListener() { + public void onClick(View v) { + Log.d(logtag,"onClick() called - start button"); + Toast.makeText(CompanionTestApp.this, "Start Association", Toast.LENGTH_LONG).show(); + assosicate(); + Log.d(logtag,"onClick() ended - start button"); + } + }; + + private OnClickListener stopListener = new OnClickListener() { + public void onClick(View v) { + Log.d(logtag,"onClick() called - stop button"); + Toast.makeText(CompanionTestApp.this, "Stop Association", Toast.LENGTH_LONG).show(); + disassosicate(); + Log.d(logtag,"onClick() ended - stop button"); + } + }; + + // Setup Device Association Preparation, per + // https://developer.android.com/guide/topics/connectivity/companion-device-pairing + private void setupDeviceAssociation() { + mDeviceManager = getSystemService(CompanionDeviceManager.class); + + mDeviceFilter = new BluetoothDeviceFilter.Builder() + .build(); + + mPairingRequest = new AssociationRequest.Builder() + .addDeviceFilter(mDeviceFilter) + .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH) + .build(); + + } + + // Associate bluetooth device, per + // https://developer.android.com/guide/topics/connectivity/companion-device-pairing + private void assosicate() { + // When the app tries to pair with the Bluetooth device, show the + // appropriate pairing request dialog to the user. + mDeviceManager.associate(mPairingRequest, + new CompanionDeviceManager.Callback() { + @Override + public void onDeviceFound(IntentSender chooserLauncher) { + try { + startIntentSenderForResult(chooserLauncher, + SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0); + } catch (Exception ex) { + Log.d(logtag, "Callback onDeviceFound() Exception: " + ex); + } + } + + @Override + public void onFailure(CharSequence error) { + Log.d(logtag, "Callback onFailure() called - error: " + error); + } + }, + null); + } + + // Disassociate the associated bluetooth device + private void disassosicate() { + for (String macAddress : mDeviceManager.getAssociations()) { + mDeviceManager.disassociate(macAddress); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SELECT_DEVICE_REQUEST_CODE && + resultCode == Activity.RESULT_OK) { + // User has chosen to pair with the Bluetooth device. + BluetoothDevice deviceToPair = + data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE); + deviceToPair.createBond(); + } + } + + @Override + protected void onStart() { + Log.d(logtag,"onStart() called"); + super.onStart(); + } + + @Override + protected void onResume() { + Log.d(logtag,"onResume() called"); + super.onResume(); + } + + @Override + protected void onPause() { + Log.d(logtag,"onPause() called"); + super.onPause(); + } + + @Override + protected void onStop() { + Log.d(logtag,"onStop() called"); + super.onStop(); + } + + @Override + protected void onDestroy() { + Log.d(logtag,"onDestroy() called"); + super.onDestroy(); + } +} diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml index f8f919b27..22d357408 100644 --- a/testapps/res/layout/incall_screen.xml +++ b/testapps/res/layout/incall_screen.xml @@ -131,6 +131,11 @@ android:layout_height="wrap_content" android:layout_marginLeft="10dp"/> </LinearLayout> + <TextView + android:id="@+id/incoming_composer_attachments" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp"/> <Button android:id="@+id/disable_incallservice" android:layout_width="wrap_content" diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml index 3f397b89b..749d236da 100644 --- a/testapps/res/layout/testdialer_main.xml +++ b/testapps/res/layout/testdialer_main.xml @@ -59,6 +59,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/startCallWithRtt"/> + <CheckBox + android:id="@+id/add_composer_attachments_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/addComposerAttachments"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml index 7331d5a89..b1a1f8047 100644 --- a/testapps/res/values/donottranslate_strings.xml +++ b/testapps/res/values/donottranslate_strings.xml @@ -44,6 +44,8 @@ <string name="startCallWithRtt">Start call with RTT</string> + <string name="addComposerAttachments">Add call composer attachments</string> + <string name="rttIfaceButton">RTT</string> <string name="endRttButton">End RTT</string> diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java b/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java index 73bf43825..e1511e762 100644 --- a/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java +++ b/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java @@ -20,7 +20,7 @@ import android.telecom.BluetoothCallQualityReport; import android.telecom.Call; import android.telecom.CallAudioState; import android.telecom.CallDiagnosticService; -import android.telecom.DiagnosticCall; +import android.telecom.CallDiagnostics; import android.telecom.Log; import android.telephony.CallQuality; import android.telephony.ims.ImsReasonInfo; @@ -30,10 +30,10 @@ import androidx.annotation.Nullable; public class TestCallDiagnosticService extends CallDiagnosticService { - public static final class TestDiagnosticCall extends DiagnosticCall { + public static final class TestCallDiagnostics extends CallDiagnostics { public Call.Details details; - TestDiagnosticCall(Call.Details details) { + TestCallDiagnostics(Call.Details details) { this.details = details; } @@ -51,14 +51,14 @@ public class TestCallDiagnosticService extends CallDiagnosticService { @Override public CharSequence onCallDisconnected(int disconnectCause, int preciseDisconnectCause) { Log.i(this, "onCallDisconnected"); - return null; + return "GSM/CDMA call dropped because " + disconnectCause; } @Nullable @Override public CharSequence onCallDisconnected(@NonNull ImsReasonInfo disconnectReason) { Log.i(this, "onCallDisconnected"); - return null; + return "ImsCall dropped because something happened " + disconnectReason.mExtraMessage; } @Override @@ -69,13 +69,13 @@ public class TestCallDiagnosticService extends CallDiagnosticService { @NonNull @Override - public DiagnosticCall onInitializeDiagnosticCall(@NonNull Call.Details call) { + public CallDiagnostics onInitializeCallDiagnostics(@NonNull Call.Details call) { Log.i(this, "onInitiatlizeDiagnosticCall %s", call); - return new TestDiagnosticCall(call); + return new TestCallDiagnostics(call); } @Override - public void onRemoveDiagnosticCall(@NonNull DiagnosticCall call) { + public void onRemoveCallDiagnostics(@NonNull CallDiagnostics call) { Log.i(this, "onRemoveDiagnosticCall %s", call); } diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java index 1e0638733..010d6eeb1 100644 --- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java +++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java @@ -9,6 +9,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.PersistableBundle; @@ -32,8 +33,18 @@ public class TestDialerActivity extends Activity { private EditText mNumberView; private CheckBox mRttCheckbox; + private CheckBox mComposerCheckbox; private EditText mPriorityView; + private static final String COMPOSER_SUBJECT = "Sample call composer subject"; + private static final Location COMPOSER_LOCATION; + static { + // Area 51 + COMPOSER_LOCATION = new Location(""); + COMPOSER_LOCATION.setLongitude(-115.806407); + COMPOSER_LOCATION.setLatitude(37.236214); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -68,6 +79,7 @@ public class TestDialerActivity extends Activity { mNumberView = (EditText) findViewById(R.id.number); mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox); + mComposerCheckbox = (CheckBox) findViewById(R.id.add_composer_attachments_checkbox); findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -169,6 +181,11 @@ public class TestDialerActivity extends Activity { if (mRttCheckbox.isChecked()) { extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); } + if (mComposerCheckbox.isChecked()) { + extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT); + extras.putParcelable(TelecomManager.EXTRA_LOCATION, COMPOSER_LOCATION); + extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, COMPOSER_SUBJECT); + } Bundle intentExtras = new Bundle(); intentExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java index 3f3a2c8f3..bdd4c1a7b 100644 --- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java +++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; +import android.location.Location; import android.os.Bundle; import android.telecom.Call; import android.telecom.CallAudioState; @@ -28,6 +29,7 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.ims.ImsCallProfile; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; @@ -248,6 +250,47 @@ public class TestInCallUI extends Activity { enableInCallService(); } }); + + // Find the ringing call and populate the composer extras + for (int i = 0; i < TestCallList.getInstance().size(); i++) { + Call call = TestCallList.getInstance().getCall(i); + if (call.getState() == Call.STATE_RINGING) { + int priority = call.getDetails() + .getIntentExtras().getInt(TelecomManager.EXTRA_PRIORITY, -1); + Location location = call.getDetails() + .getIntentExtras().getParcelable(TelecomManager.EXTRA_LOCATION); + String subject = call.getDetails() + .getIntentExtras().getString(TelecomManager.EXTRA_CALL_SUBJECT); + boolean isBusiness = call.getDetails() + .getExtras().getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL); + + StringBuilder display = new StringBuilder(); + display.append("priority="); + switch (priority) { + case TelecomManager.PRIORITY_NORMAL: + display.append("normal"); + break; + case TelecomManager.PRIORITY_URGENT: + display.append("urgent"); + break; + default: + display.append("unset"); + } + display.append(";"); + if (location != null) { + display.append("lat=" + location.getLatitude()); + display.append("lon=" + location.getLongitude()); + } else { + display.append("loc=null"); + } + + display.append(" subject=" + subject); + display.append(" isBusiness=" + isBusiness); + TextView attachmentsTextView = findViewById(R.id.incoming_composer_attachments); + attachmentsTextView.setText(display.toString()); + break; + } + } } public void updateCallAudioState(CallAudioState cas) { diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 22f5348da..e8c69d419 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -26,6 +26,7 @@ <!-- TODO: Needed because we call BluetoothAdapter.getDefaultAdapter() statically, and BluetoothAdapter is a final class. --> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <!-- TODO: Needed because we call ActivityManager.getCurrentUser() statically. --> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> @@ -36,6 +37,13 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <!-- Used to access Projection State APIs --> + <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/> + + <!-- Used to access PlatformCompat APIs --> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <application android:label="@string/app_name" android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java index a4cc521d4..2a304a43d 100644 --- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java +++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.ContentResolver; import android.content.Context; import android.os.Build; import android.telecom.CallAudioState; @@ -185,6 +186,8 @@ public class AnalyticsTests extends TelecomSystemTest { @MediumTest @Test public void testAnalyticsTwoCalls() throws Exception { + when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class))) + .thenReturn((long) TEST_TIMEOUT); IdPair testCall1 = startAndMakeActiveIncomingCall( "650-555-1212", mPhoneAccountA0.getAccountHandle(), diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java index a8e1c00b4..a3b8654f5 100644 --- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java +++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java @@ -63,6 +63,7 @@ import com.google.common.base.Predicate; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -473,6 +474,7 @@ public class BasicCallTests extends TelecomSystemTest { @LargeTest @Test @FlakyTest + @Ignore("b/189904580") public void testIncomingCallFromBlockedNumberIsRejected() throws Exception { String phoneNumber = "650-555-1212"; blockNumber(phoneNumber); @@ -492,6 +494,7 @@ public class BasicCallTests extends TelecomSystemTest { waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(), TEST_TIMEOUT); + assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size()); for (CallerInfoAsyncQueryFactoryFixture.Request request : mCallerInfoAsyncQueryFactoryFixture.mRequests) { @@ -827,8 +830,7 @@ public class BasicCallTests extends TelecomSystemTest { private void blockNumberWithAnswer(String phoneNumber, Answer answer) throws Exception { when(getBlockedNumberProvider().call( - anyString(), - nullable(String.class), + any(), anyString(), eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER), eq(phoneNumber), diff --git a/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java new file mode 100644 index 000000000..56cb73570 --- /dev/null +++ b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 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.tests; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.os.UserHandle; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.telecom.settings.BlockedNumbersUtil; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BlockedNumbersUtilTests extends TelecomTestCase { + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + } + + @SmallTest + @Test + public void testPostNotification() { + BlockedNumbersUtil.updateEmergencyCallNotification(mContext, true); + NotificationManager mgr = mComponentContextFixture.getNotificationManager(); + verify(mgr).notifyAsUser(isNull(), anyInt(), any(Notification.class), + any(UserHandle.class)); + } + + @SmallTest + @Test + public void testDismissNotification() { + BlockedNumbersUtil.updateEmergencyCallNotification(mContext, false); + NotificationManager mgr = mComponentContextFixture.getNotificationManager(); + verify(mgr).cancelAsUser(isNull(), anyInt(), any(UserHandle.class)); + } +} diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java index bfa7a75bb..02fdecc3c 100644 --- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java +++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java @@ -26,8 +26,6 @@ import android.content.Intent; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; -import com.android.server.telecom.BluetoothAdapterProxy; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.bluetooth.BluetoothDeviceManager; import com.android.server.telecom.bluetooth.BluetoothRouteManager; import com.android.server.telecom.bluetooth.BluetoothStateReceiver; @@ -54,8 +52,8 @@ import java.util.Arrays; @RunWith(JUnit4.class) public class BluetoothDeviceManagerTest extends TelecomTestCase { @Mock BluetoothRouteManager mRouteManager; - @Mock BluetoothHeadsetProxy mHeadsetProxy; - @Mock BluetoothAdapterProxy mAdapterProxy; + @Mock BluetoothHeadset mBluetoothHeadset; + @Mock BluetoothAdapter mAdapter; @Mock BluetoothHearingAid mBluetoothHearingAid; BluetoothDeviceManager mBluetoothDeviceManager; @@ -82,18 +80,18 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L); mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); - mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy); + mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter); mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager); ArgumentCaptor<BluetoothProfile.ServiceListener> serviceCaptor = ArgumentCaptor.forClass(BluetoothProfile.ServiceListener.class); - verify(mAdapterProxy).getProfileProxy(eq(mContext), + verify(mAdapter).getProfileProxy(eq(mContext), serviceCaptor.capture(), eq(BluetoothProfile.HEADSET)); serviceListenerUnderTest = serviceCaptor.getValue(); receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager, mRouteManager); - mBluetoothDeviceManager.setHeadsetServiceForTesting(mHeadsetProxy); + mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset); mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid); } @@ -127,7 +125,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices()); } - + @SmallTest @Test public void testMultiDeviceConnectAndDisconnect() { @@ -173,7 +171,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mRouteManager).onDeviceLost(device1.getAddress()); verify(mRouteManager).onDeviceLost(device3.getAddress()); verify(mRouteManager, never()).onDeviceLost(device2.getAddress()); - assertNull(mBluetoothDeviceManager.getHeadsetService()); + assertNull(mBluetoothDeviceManager.getBluetoothHeadset()); assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices()); } @@ -192,7 +190,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mRouteManager).onDeviceLost(device2.getAddress()); verify(mRouteManager, never()).onDeviceLost(device1.getAddress()); verify(mRouteManager, never()).onDeviceLost(device3.getAddress()); - assertNull(mBluetoothDeviceManager.getHearingAidService()); + assertNull(mBluetoothDeviceManager.getBluetoothHearingAid()); assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices()); } @@ -216,12 +214,14 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { public void testConnectDisconnectAudioHeadset() { receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false)); - when(mAdapterProxy.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true); + when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class), + eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true); mBluetoothDeviceManager.connectAudio(device1.getAddress()); - verify(mAdapterProxy).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); - verify(mAdapterProxy, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL)); + verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); + verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), + eq(BluetoothAdapter.ACTIVE_DEVICE_ALL)); mBluetoothDeviceManager.disconnectAudio(); - verify(mHeadsetProxy).disconnectAudio(); + verify(mBluetoothHeadset).disconnectAudio(); } @SmallTest @@ -230,15 +230,17 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true)); mBluetoothDeviceManager.connectAudio(device2.getAddress()); - verify(mAdapterProxy).setActiveDevice(device2, BluetoothAdapter.ACTIVE_DEVICE_ALL); - verify(mHeadsetProxy, never()).connectAudio(); - verify(mAdapterProxy, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); + verify(mAdapter).setActiveDevice(device2, BluetoothAdapter.ACTIVE_DEVICE_ALL); + verify(mBluetoothHeadset, never()).connectAudio(); + verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), + eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); - when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(device2, null)); + when(mAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID))) + .thenReturn(Arrays.asList(device2, null)); mBluetoothDeviceManager.disconnectAudio(); - verify(mAdapterProxy).setActiveDevice(null, BluetoothAdapter.ACTIVE_DEVICE_ALL); - verify(mHeadsetProxy).disconnectAudio(); + verify(mAdapter).setActiveDevice(null, BluetoothAdapter.ACTIVE_DEVICE_ALL); + verify(mBluetoothHeadset).disconnectAudio(); } private Intent buildConnectionActionIntent(int state, BluetoothDevice device, diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java index b20ecfb22..28f69668e 100644 --- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java +++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java @@ -16,16 +16,17 @@ package com.android.server.telecom.tests; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; import android.content.ContentResolver; import android.os.Parcel; import android.telecom.Log; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.os.SomeArgs; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.bluetooth.BluetoothDeviceManager; @@ -60,8 +61,9 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03"); static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04"); + @Mock private BluetoothAdapter mBluetoothAdapter; @Mock private BluetoothDeviceManager mDeviceManager; - @Mock private BluetoothHeadsetProxy mHeadsetProxy; + @Mock private BluetoothHeadset mBluetoothHeadset; @Mock private BluetoothHearingAid mBluetoothHearingAid; @Mock private Timeouts.Adapter mTimeoutsAdapter; @Mock private BluetoothRouteManager.BluetoothStateListener mListener; @@ -86,7 +88,7 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null); when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( nullable(ContentResolver.class))).thenReturn(0L); - when(mHeadsetProxy.connectAudio()).thenReturn(false); + when(mBluetoothHeadset.connectAudio()).thenReturn(false); executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress()); // Wait 3 times: for the first connection attempt, the retry attempt, // the second retry, and once more to make sure there are only three attempts. @@ -125,7 +127,7 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { BluetoothRouteManager sm = setupStateMachine( BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, DEVICE1, null); - when(mHeadsetProxy.getAudioState(DEVICE1)) + when(mBluetoothHeadset.getAudioState(DEVICE1)) .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress()); @@ -143,7 +145,7 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null); when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( nullable(ContentResolver.class))).thenReturn(0L); - when(mHeadsetProxy.connectAudio()).thenReturn(false); + when(mBluetoothHeadset.connectAudio()).thenReturn(false); executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress()); // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction, // so wait twice for the retry attempt, again to make sure there are only three attempts, @@ -185,14 +187,15 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { .collect(Collectors.toList()); when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices); - when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices)); - when(mHeadsetProxy.getActiveDevice()).thenReturn(hfpActiveDevice); - when(mHeadsetProxy.getAudioState(hfpActiveDevice)) + when(mBluetoothHeadset.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices)); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) + .thenReturn(Arrays.asList(hfpActiveDevice)); + when(mBluetoothHeadset.getAudioState(hfpActiveDevice)) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); when(mBluetoothHearingAid.getConnectedDevices()) .thenReturn(Arrays.asList(hearingAidDevices)); - when(mBluetoothHearingAid.getActiveDevices()) + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID))) .thenReturn(Arrays.asList(hearingAidActiveDevice, null)); } @@ -215,11 +218,12 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { } private void resetMocks() { - reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter); - when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy); - when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid); - when(mHeadsetProxy.connectAudio()).thenReturn(true); - when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true); + reset(mDeviceManager, mListener, mBluetoothHeadset, mTimeoutsAdapter); + when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset); + when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid); + when(mDeviceManager.getBluetoothAdapter()).thenReturn(mBluetoothAdapter); + when(mBluetoothHeadset.connectAudio()).thenReturn(true); + when(mBluetoothHeadset.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true); when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( nullable(ContentResolver.class))).thenReturn(100000L); when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis( diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java index b36d74bf1..d96b68746 100644 --- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java +++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java @@ -16,15 +16,16 @@ package com.android.server.telecom.tests; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; import android.content.ContentResolver; import android.telecom.Log; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.os.SomeArgs; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.bluetooth.BluetoothDeviceManager; @@ -76,7 +77,7 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { private BluetoothDevice expectedConnectionDevice; private String expectedFinalStateName; private BluetoothDevice[] connectedDevices; - // the active device as returned by BluetoothHeadset#getActiveDevice + // the active device as returned by BluetoothAdapter#getActiveDevices private BluetoothDevice activeDevice = null; private List<BluetoothDevice> hearingAidBtDevices = Collections.emptyList(); @@ -183,7 +184,7 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { public BluetoothDevice expectedConnectionDevice; // Expected device to connect to. public String expectedFinalStateName; // Expected name of the final state. public BluetoothDevice[] connectedDevices; // array of connected devices - // the active device as returned by BluetoothHeadset#getActiveDevice + // the active device as returned by BluetoothAdapter#getActiveDevices private BluetoothDevice activeDevice = null; private List<BluetoothDevice> hearingAidBtDevices; @@ -236,7 +237,8 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { private final BluetoothRouteTestParameters mParams; @Mock private BluetoothDeviceManager mDeviceManager; - @Mock private BluetoothHeadsetProxy mHeadsetProxy; + @Mock private BluetoothAdapter mBluetoothAdapter; + @Mock private BluetoothHeadset mBluetoothHeadset; @Mock private BluetoothHearingAid mBluetoothHearingAid; @Mock private Timeouts.Adapter mTimeoutsAdapter; @Mock private BluetoothRouteManager.BluetoothStateListener mListener; @@ -245,6 +247,7 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { @Before public void setUp() throws Exception { super.setUp(); + when(mDeviceManager.getBluetoothAdapter()).thenReturn(mBluetoothAdapter); } @Override @@ -272,7 +275,8 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { SomeArgs args = SomeArgs.obtain(); args.arg1 = Log.createSubsession(); args.arg2 = mParams.initialDevice.getAddress(); - when(mHeadsetProxy.getActiveDevice()).thenReturn(null); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) + .thenReturn(Arrays.asList((BluetoothDevice) null)); sm.sendMessage(BluetoothRouteManager.BT_AUDIO_LOST, args); return true; }).when(mDeviceManager).disconnectAudio(); @@ -287,9 +291,11 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { sm.onActiveDeviceChanged(null, mParams.hearingAidBtDevices.contains(mParams.messageDevice)); if (mParams.hearingAidBtDevices.contains(mParams.messageDevice)) { - when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(null, null)); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID))) + .thenReturn(Arrays.asList(null, null)); } else { - when(mHeadsetProxy.getActiveDevice()).thenReturn(null); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) + .thenReturn(Arrays.asList((BluetoothDevice) null)); } sm.onDeviceLost(mParams.messageDevice.getAddress()); } else { @@ -344,13 +350,15 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { BluetoothDevice audioOnDevice, BluetoothDevice activeDevice) { when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length); when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices)); - when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices)); - when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice); - when(mHeadsetProxy.getAudioState(nullable(BluetoothDevice.class))) + when(mBluetoothHeadset.getConnectedDevices()).thenReturn(Arrays.asList(devices)); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) + .thenReturn(Arrays.asList(activeDevice)); + when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class))) .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); if (audioOnDevice != null) { - when(mHeadsetProxy.getActiveDevice()).thenReturn(audioOnDevice); - when(mHeadsetProxy.getAudioState(audioOnDevice)) + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) + .thenReturn(Arrays.asList(audioOnDevice)); + when(mBluetoothHeadset.getAudioState(audioOnDevice)) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); } } @@ -358,8 +366,8 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { private BluetoothRouteManager setupStateMachine(String initialState, BluetoothDevice initialDevice) { resetMocks(); - when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy); - when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid); + when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset); + when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid); when(mDeviceManager.connectAudio(nullable(String.class))).thenReturn(true); when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( nullable(ContentResolver.class))).thenReturn(100000L); @@ -375,7 +383,7 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { } private void resetMocks() { - clearInvocations(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter); + clearInvocations(mDeviceManager, mListener, mBluetoothHeadset, mTimeoutsAdapter); } @Parameterized.Parameters(name = "{0}") diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java index 976a4dae8..91ec7f35f 100644 --- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java +++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java @@ -17,7 +17,10 @@ package com.android.server.telecom.tests; import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.media.AudioManager; import android.media.IAudioService; import android.os.HandlerThread; @@ -151,6 +154,49 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase { @MediumTest @Test + public void testStreamRingMuteChange() { + CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( + mContext, + mockCallsManager, + mockBluetoothRouteManager, + mockWiredHeadsetManager, + mockStatusBarNotifier, + mAudioServiceFactory, + CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, + mThreadHandler.getLooper()); + stateMachine.setCallAudioManager(mockCallAudioManager); + CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, + CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); + stateMachine.initialize(initState); + + // Make sure we register a receiver for the STREAM_MUTE_CHANGED_ACTION so we can see if the + // ring stream unmutes. + ArgumentCaptor<BroadcastReceiver> brCaptor = ArgumentCaptor.forClass( + BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class); + verify(mContext, times(3)).registerReceiver(brCaptor.capture(), filterCaptor.capture()); + boolean foundValid = false; + for (int ix = 0; ix < brCaptor.getAllValues().size(); ix++) { + BroadcastReceiver receiver = brCaptor.getAllValues().get(ix); + IntentFilter filter = filterCaptor.getAllValues().get(ix); + if (!filter.hasAction(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { + continue; + } + + // Fake out a call to the broadcast receiver and make sure we call into audio manager + // to trigger re-evaluation of ringing. + Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_RING); + receiver.onReceive(mContext, intent); + verify(mockCallAudioManager).onRingerModeChange(); + foundValid = true; + } + assertTrue(foundValid); + } + + @MediumTest + @Test public void testSpeakerPersistence() { CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( mContext, diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java index dfc32588b..b9f5667ab 100644 --- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java +++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java @@ -18,6 +18,7 @@ package com.android.server.telecom.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -40,7 +41,9 @@ import android.content.res.Resources; import android.location.Country; import android.location.CountryDetector; import android.location.CountryListener; +import android.location.Location; import android.net.Uri; +import android.os.Bundle; import android.os.Looper; import android.os.PersistableBundle; import android.os.SystemClock; @@ -52,6 +55,7 @@ import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; @@ -172,7 +176,7 @@ public class CallLogManagerTest extends TelecomTestCase { when(userManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true); when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class))) .thenReturn(false); - when(userManager.getUsers(any(Boolean.class))) + when(userManager.getAliveUsers()) .thenReturn(Arrays.asList(userInfo, otherUserInfo, managedProfileUserInfo)); when(userManager.getUserInfo(eq(CURRENT_USER_ID))).thenReturn(userInfo); when(userManager.getUserInfo(eq(OTHER_USER_ID))).thenReturn(otherUserInfo); @@ -819,6 +823,54 @@ public class CallLogManagerTest extends TelecomTestCase { @SmallTest @Test + public void testCallComposerElements() { + Call fakeCall = makeFakeCall( + DisconnectCause.LOCAL, // disconnectCauseCode + false, // isConference + true, // isIncoming + 1L, // creationTimeMillis + 1000L, // ageMillis + TEL_PHONEHANDLE, // callHandle + mDefaultAccountHandle, // phoneAccountHandle + NO_VIDEO_STATE, // callVideoState + POST_DIAL_STRING, // postDialDigits + VIA_NUMBER_STRING, // viaNumber + UserHandle.of(CURRENT_USER_ID) + ); + String subject = "segmentation fault"; + // =) + double lat = 40.649723; + double lon = -80.082090; + Location location = new Location(""); + location.setLatitude(lat); + location.setLongitude(lon); + + Uri fakeProviderUri = Uri.parse("content://nothing_to_see_here/12345"); + + Bundle extras = new Bundle(); + extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT); + extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, subject); + extras.putParcelable(TelecomManager.EXTRA_LOCATION, location); + extras.putParcelable(TelecomManager.EXTRA_PICTURE_URI, fakeProviderUri); + when(fakeCall.getIntentExtras()).thenReturn(extras); + + mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, + CallState.DISCONNECTED); + ContentValues locationValues = verifyLocationInsertionWithCapture(CURRENT_USER_ID); + assertEquals(lat, locationValues.getAsDouble(CallLog.Locations.LATITUDE), 0); + assertEquals(lon, locationValues.getAsDouble(CallLog.Locations.LONGITUDE), 0); + + ContentValues callLogValues = verifyInsertionWithCapture(CURRENT_USER_ID); + assertEquals(subject, callLogValues.getAsString(Calls.SUBJECT)); + assertEquals(fakeProviderUri.toString(), + callLogValues.getAsString(Calls.COMPOSER_PHOTO_URI)); + assertEquals(TelecomManager.PRIORITY_URGENT, + (int) callLogValues.getAsInteger(Calls.PRIORITY)); + assertNotNull(callLogValues.getAsString(Calls.LOCATION)); + } + + @SmallTest + @Test public void testDoNotLogConferenceWithNoChildren() { Call fakeCall = makeFakeCall( DisconnectCause.LOCAL, // disconnectCauseCode @@ -976,6 +1028,14 @@ public class CallLogManagerTest extends TelecomTestCase { return captor.getValue(); } + private ContentValues verifyLocationInsertionWithCapture(int userId) { + Uri uri = ContentProvider.maybeAddUserId(CallLog.Locations.CONTENT_URI, userId); + ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class); + verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).times(1)).insert( + eq(uri), captor.capture()); + return captor.getValue(); + } + private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming, long creationTimeMillis, long ageMillis, Uri callHandle, PhoneAccountHandle phoneAccountHandle, int callVideoState, diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java index ff16880cc..0a896a8ce 100644 --- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java +++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java @@ -65,8 +65,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.ArrayList; - @RunWith(JUnit4.class) public class CallRedirectionProcessorTest extends TelecomTestCase { @Mock private Context mContext; @@ -151,7 +149,7 @@ public class CallRedirectionProcessorTest extends TelecomTestCase { } private void setIsInCarMode(boolean isInCarMode) { - when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode); + when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(isInCarMode); } private void enableUserDefinedCallRedirectionService() { diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java index c7b3a7eb5..68caf6776 100644 --- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java +++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java @@ -93,6 +93,16 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { .setShouldShowNotification(true) .build(); + private static final CallFilteringResult PASS_RESULT_WITH_NAME = + new CallFilteringResult.Builder() + .setShouldAllowCall(true) + .setShouldReject(false) + .setShouldAddToCallLog(true) + .setShouldShowNotification(true) + .setCallScreeningAppName(APP_NAME) + .setCallScreeningComponentName(COMPONENT_NAME.flattenToString()) + .build(); + @Override @Before public void setUp() throws Exception { @@ -235,8 +245,14 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder); ICallScreeningAdapter csAdapter = getCallScreeningAdapter(); - csAdapter.allowCall(CALL_ID); - assertEquals(PASS_RESULT, + CallScreeningService.CallResponse allowCallResponse = + new CallScreeningService.CallResponse.Builder() + .setDisallowCall(false) + .setRejectCall(false) + .setSilenceCall(false) + .build(); + csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, allowCallResponse.toParcelable()); + assertEquals(PASS_RESULT_WITH_NAME, resultFuture.toCompletableFuture().get( CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT, TimeUnit.MILLISECONDS)); @@ -264,12 +280,16 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { ServiceConnection serviceConnection = verifyBindingIntent(); serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder); + CallScreeningService.CallResponse disallowCallResponse = + new CallScreeningService.CallResponse.Builder() + .setDisallowCall(true) + .setRejectCall(true) + .setSkipCallLog(false) + .setSkipNotification(false) + .build(); ICallScreeningAdapter csAdapter = getCallScreeningAdapter(); - csAdapter.disallowCall(CALL_ID, - true, // shouldReject - true, //shouldAddToCallLog - true, // shouldShowNotification - COMPONENT_NAME); + csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, disallowCallResponse.toParcelable()); + assertEquals(expectedResult, resultFuture.toCompletableFuture().get( CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT, @@ -286,6 +306,8 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { .setShouldSilence(true) .setShouldAddToCallLog(true) .setShouldShowNotification(true) + .setCallScreeningAppName(APP_NAME) + .setCallScreeningComponentName(COMPONENT_NAME.flattenToString()) .build(); CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME, CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager, @@ -296,7 +318,13 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder); ICallScreeningAdapter csAdapter = getCallScreeningAdapter(); - csAdapter.silenceCall(CALL_ID); + CallScreeningService.CallResponse silenceCallResponse = + new CallScreeningService.CallResponse.Builder() + .setDisallowCall(false) + .setRejectCall(false) + .setSilenceCall(true) + .build(); + csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, silenceCallResponse.toParcelable()); assertEquals(expectedResult, resultFuture.toCompletableFuture().get( CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT, @@ -314,6 +342,7 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { .setShouldSilence(false) .setShouldScreenViaAudio(true) .setCallScreeningAppName(APP_NAME) + .setCallScreeningComponentName(COMPONENT_NAME.flattenToString()) .build(); CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME, CallScreeningServiceFilter.PACKAGE_TYPE_DEFAULT_DIALER, mContext, mCallsManager, @@ -323,8 +352,17 @@ public class CallScreeningServiceFilterTest extends TelecomTestCase { ServiceConnection serviceConnection = verifyBindingIntent(); serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder); + + CallScreeningService.CallResponse additionalScreeningResponse = + new CallScreeningService.CallResponse.Builder() + .setDisallowCall(false) + .setRejectCall(false) + .setSilenceCall(false) + .setShouldScreenCallViaAudioProcessing(true) + .build(); ICallScreeningAdapter csAdapter = getCallScreeningAdapter(); - csAdapter.screenCallFurther(CALL_ID); + csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, + additionalScreeningResponse.toParcelable()); assertEquals(expectedResult, resultFuture.toCompletableFuture().get( CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT, diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java index 08f353668..da7293330 100644 --- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java +++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java @@ -21,6 +21,7 @@ import static junit.framework.TestCase.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -36,11 +37,14 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -52,6 +56,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.telecom.CallerInfo; import android.telecom.Connection; +import android.telecom.DisconnectCause; +import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; @@ -75,6 +81,7 @@ import com.android.server.telecom.CallsManagerListenerBase; import com.android.server.telecom.ClockProxy; import com.android.server.telecom.ConnectionServiceFocusManager; import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory; +import com.android.server.telecom.ConnectionServiceWrapper; import com.android.server.telecom.DefaultDialerCache; import com.android.server.telecom.EmergencyCallHelper; import com.android.server.telecom.HeadsetMediaButton; @@ -228,6 +235,8 @@ public class CallsManagerTest extends TelecomTestCase { doNothing().when(mRoleManagerAdapter).setCurrentUserHandle(any()); when(mDisconnectedCallNotifierFactory.create(any(Context.class),any(CallsManager.class))) .thenReturn(mDisconnectedCallNotifier); + when(mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(any(ContentResolver.class))) + .thenReturn(2000L); mCallsManager = new CallsManager( mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, @@ -1106,6 +1115,46 @@ public class CallsManagerTest extends TelecomTestCase { assertFalse(mCallsManager.isInEmergencyCall()); } + + @SmallTest + @Test + public void testBlockNonEmergencyCallDuringEmergencyCall() throws Exception { + // Setup a call which the network identified as an emergency call. + Call ongoingCall = addSpyCall(); + ongoingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL); + assertTrue(mCallsManager.isInEmergencyCall()); + + Call newCall = addSpyCall(CallState.NEW); + ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class); + doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName(); + + // Ensure contact info lookup succeeds + doAnswer(invocation -> { + Uri handle = invocation.getArgument(0); + CallerInfo info = new CallerInfo(); + CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>(); + callerInfoFuture.complete(new Pair<>(handle, info)); + return callerInfoFuture; + }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class)); + + // Ensure we have candidate phone account handle info. + when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn( + SIM_1_HANDLE); + when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(), + any(), anyInt(), anyInt())).thenReturn( + new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE))); + mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(), + SIM_2_HANDLE.getUserHandle(), service); + + CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall( + newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(), + UserHandle.CURRENT, new Intent(), "com.test.stuff"); + + verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any()); + Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); + assertNull(result); + } + @SmallTest @Test public void testHasEmergencyCallIncomingCallPermitted() { @@ -1204,6 +1253,21 @@ public class CallsManagerTest extends TelecomTestCase { verify(ringingCall).reject(anyBoolean(), any(), any()); } + @SmallTest + @Test + public void testMakeRoomForOutgoingCallConnecting() { + Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING); + + Call newCall = createCall(SIM_1_HANDLE, CallState.NEW); + when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any())) + .thenReturn(false); + newCall.setHandle(Uri.fromParts("tel", "5551213", null), + TelecomManager.PRESENTATION_ALLOWED); + + assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall)); + verify(ongoingCall).disconnect(anyLong(), anyString()); + } + /** * Verifies that changes to a {@link PhoneAccount}'s * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. @@ -1508,6 +1572,59 @@ public class CallsManagerTest extends TelecomTestCase { eq(CallState.ACTIVE)); } + /** + * Verifies where a call diagnostic service is NOT in use that we don't try to relay to the + * CallDiagnosticService and that we get a synchronous disconnect. + * @throws Exception + */ + @MediumTest + @Test + public void testDisconnectCallSynchronous() throws Exception { + Call callSpy = addSpyCall(); + callSpy.setIsSimCall(true); + when(mCallDiagnosticServiceController.isConnected()).thenReturn(false); + mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR)); + + verify(mCallDiagnosticServiceController, never()).onCallDisconnected(any(Call.class), + any(DisconnectCause.class)); + verify(callSpy).setDisconnectCause(any(DisconnectCause.class)); + } + + @MediumTest + @Test + public void testDisconnectCallAsynchronous() throws Exception { + Call callSpy = addSpyCall(); + callSpy.setIsSimCall(true); + when(mCallDiagnosticServiceController.isConnected()).thenReturn(true); + when(mCallDiagnosticServiceController.onCallDisconnected(any(Call.class), + any(DisconnectCause.class))).thenReturn(true); + mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR)); + + verify(mCallDiagnosticServiceController).onCallDisconnected(any(Call.class), + any(DisconnectCause.class)); + verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class)); + } + + /** + * Verifies that if call state goes from DIALING to DISCONNECTED, and a call diagnostic service + * IS in use, it would call onCallDisconnected of the CallDiagnosticService + * @throws Exception + */ + @MediumTest + @Test + public void testDisconnectDialingCall() throws Exception { + Call callSpy = addSpyCall(CallState.DIALING); + callSpy.setIsSimCall(true); + when(mCallDiagnosticServiceController.isConnected()).thenReturn(true); + when(mCallDiagnosticServiceController.onCallDisconnected(any(Call.class), + any(DisconnectCause.class))).thenReturn(true); + mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR)); + + verify(mCallDiagnosticServiceController).onCallDisconnected(any(Call.class), + any(DisconnectCause.class)); + verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class)); + } + private Call addSpyCall() { return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE); } diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java index dbfcdb12a..4ad46ae52 100644 --- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java +++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java @@ -21,9 +21,6 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertNull; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - import android.app.UiModeManager; import com.android.server.telecom.CarModeTracker; @@ -33,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mock; @RunWith(JUnit4.class) public class CarModeTrackerTest extends TelecomTestCase { @@ -110,7 +106,7 @@ public class CarModeTrackerTest extends TelecomTestCase { @Test public void testForceExitCarMode() { testEnterCarModeBasic(); - mCarModeTracker.forceExitCarMode(CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.forceRemove(CAR_MODE_APP1_PACKAGE_NAME); assertFalse(mCarModeTracker.isInCarMode()); assertNull(mCarModeTracker.getCurrentCarModePackage()); } @@ -226,4 +222,109 @@ public class CarModeTrackerTest extends TelecomTestCase { CAR_MODE_APP3_PACKAGE_NAME); assertNull(mCarModeTracker.getCurrentCarModePackage()); } + + /** + * Verifies that setting automotive projection by itself works. + */ + @Test + public void testSetAutomotiveProjectionBasic() { + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME); + assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + // We should be tracking our car mode app. + assertEquals(1, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + } + + /** + * Verifies that if we set automotive projection more than once with the same package, nothing + * changes. + */ + @Test + public void testSetAutomotiveProjectionMultipleTimes() { + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME); + // Should still only have one app. + assertEquals(1, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + // It should be the same one. + assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + } + + /** + * Verifies that if we set automotive projection more than once, the new package overrides. + */ + @Test + public void testSetAutomotiveProjectionMultipleTimesDifferentPackages() { + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME); + // Should still only have one app. + assertEquals(1, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + // It should be the newer one. + assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + } + + /** + * Verifies that releasing automotive projection works as expected. + */ + @Test + public void testReleaseAutomotiveProjectionBasic() { + // Releasing before something's set shouldn't break anything. + mCarModeTracker.handleReleaseAutomotiveProjection(); + assertEquals(0, mCarModeTracker.getCarModeApps().size()); + assertFalse(mCarModeTracker.isInCarMode()); + + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.handleReleaseAutomotiveProjection(); + // Should be gone now. + assertEquals(0, mCarModeTracker.getCarModeApps().size()); + assertFalse(mCarModeTracker.isInCarMode()); + } + + /** + * Verifies that setting automotive projection overrides but doesn't overwrite car mode apps. + */ + @Test + public void testAutomotiveProjectionOverridesCarMode() { + mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP4_PACKAGE_NAME); + + // Should have two apps now, the car mode and the automotive projection one. + assertEquals(2, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + + // Automotive projection takes priority. + assertEquals(CAR_MODE_APP4_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + + // If we add another car mode app, automotive projection still has priority. + mCarModeTracker.handleEnterCarMode(Integer.MAX_VALUE, CAR_MODE_APP2_PACKAGE_NAME); + assertEquals(3, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + assertEquals(CAR_MODE_APP4_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + + // If we release automotive projection, we go back to the prioritized list of plain car + // mode apps. + mCarModeTracker.handleReleaseAutomotiveProjection(); + assertEquals(2, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + + // Make sure we didn't mess with the first app that was added. + mCarModeTracker.handleExitCarMode(Integer.MAX_VALUE, CAR_MODE_APP2_PACKAGE_NAME); + assertEquals(1, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + } + + /** + * Verifies that releasing automotive projection doesn't interfere with plain car mode apps. + */ + @Test + public void testReleaseAutomotiveProjectionNoopForCarModeApps() { + mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP1_PACKAGE_NAME); + mCarModeTracker.handleReleaseAutomotiveProjection(); + assertEquals(1, mCarModeTracker.getCarModeApps().size()); + assertTrue(mCarModeTracker.isInCarMode()); + assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage()); + } } diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java index ae8d943e2..ebb336e12 100644 --- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java @@ -30,7 +30,10 @@ import android.Manifest; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.StatusBarManager; +import android.app.UiModeManager; import android.app.role.RoleManager; +import android.content.AttributionSource; +import android.content.AttributionSourceState; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -47,6 +50,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.location.Country; import android.location.CountryDetector; import android.media.AudioManager; @@ -55,8 +59,11 @@ import android.os.Handler; import android.os.IInterface; import android.os.PersistableBundle; import android.os.PowerWhitelistManager; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.os.VibratorManager; +import android.permission.PermissionCheckerManager; import android.telecom.CallAudioState; import android.telecom.ConnectionService; import android.telecom.Log; @@ -68,6 +75,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.TelephonyRegistryManager; import android.test.mock.MockContext; +import android.util.DisplayMetrics; import java.io.File; import java.io.IOException; @@ -79,7 +87,9 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.Executor; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -206,6 +216,14 @@ public class ComponentContextFixture implements TestFixture<Context> { return mRoleManager; case Context.TELEPHONY_REGISTRY_SERVICE: return mTelephonyRegistryManager; + case Context.UI_MODE_SERVICE: + return mUiModeManager; + case Context.VIBRATOR_MANAGER_SERVICE: + return mVibratorManager; + case Context.PERMISSION_CHECKER_SERVICE: + return mPermissionCheckerManager; + case Context.SENSOR_PRIVACY_SERVICE: + return mSensorPrivacyManager; default: return null; } @@ -227,6 +245,14 @@ public class ComponentContextFixture implements TestFixture<Context> { return Context.TELEPHONY_SUBSCRIPTION_SERVICE; } else if (svcClass == TelephonyRegistryManager.class) { return Context.TELEPHONY_REGISTRY_SERVICE; + } else if (svcClass == UiModeManager.class) { + return Context.UI_MODE_SERVICE; + } else if (svcClass == VibratorManager.class) { + return Context.VIBRATOR_MANAGER_SERVICE; + } else if (svcClass == PermissionCheckerManager.class) { + return Context.PERMISSION_CHECKER_SERVICE; + } else if (svcClass == SensorPrivacyManager.class) { + return Context.SENSOR_PRIVACY_SERVICE; } throw new UnsupportedOperationException(); } @@ -252,6 +278,11 @@ public class ComponentContextFixture implements TestFixture<Context> { } @Override + public AttributionSource getAttributionSource() { + return mAttributionSource; + } + + @Override public ContentResolver getContentResolver() { return new ContentResolver(mApplicationContextSpy) { @Override @@ -444,6 +475,10 @@ public class ComponentContextFixture implements TestFixture<Context> { } } + private static final String PACKAGE_NAME = "com.android.server.telecom.tests"; + private final AttributionSource mAttributionSource = + new AttributionSource.Builder(Process.myUid()).setPackageName(PACKAGE_NAME).build(); + private final Multimap<String, ComponentName> mComponentNamesByAction = ArrayListMultimap.create(); private final Map<ComponentName, IInterface> mServiceByComponentName = new HashMap<>(); @@ -474,6 +509,7 @@ public class ComponentContextFixture implements TestFixture<Context> { private final Resources.Theme mResourcesTheme = mock(Resources.Theme.class); private final Resources mResources = mock(Resources.class); private final Context mApplicationContextSpy = spy(mApplicationContext); + private final DisplayMetrics mDisplayMetrics = mock(DisplayMetrics.class); private final PackageManager mPackageManager = mock(PackageManager.class); private final Executor mMainExecutor = mock(Executor.class); private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext)); @@ -491,7 +527,12 @@ public class ComponentContextFixture implements TestFixture<Context> { private final RoleManager mRoleManager = mock(RoleManager.class); private final TelephonyRegistryManager mTelephonyRegistryManager = mock(TelephonyRegistryManager.class); + private final VibratorManager mVibratorManager = mock(VibratorManager.class); + private final UiModeManager mUiModeManager = mock(UiModeManager.class); + private final PermissionCheckerManager mPermissionCheckerManager = + mock(PermissionCheckerManager.class); private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class); + private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class); private TelecomManager mTelecomManager = mock(TelecomManager.class); @@ -500,6 +541,9 @@ public class ComponentContextFixture implements TestFixture<Context> { when(mResources.getConfiguration()).thenReturn(mResourceConfiguration); when(mResources.getString(anyInt())).thenReturn(""); when(mResources.getStringArray(anyInt())).thenReturn(new String[0]); + when(mResources.newTheme()).thenReturn(mResourcesTheme); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + mDisplayMetrics.density = 3.125f; mResourceConfiguration.setLocale(Locale.TAIWAN); // TODO: Move into actual tests @@ -542,9 +586,10 @@ public class ComponentContextFixture implements TestFixture<Context> { }).when(mPackageManager).queryBroadcastReceiversAsUser((Intent) any(), anyInt(), anyInt()); // By default, tests use non-ui apps instead of 3rd party companion apps. - when(mPackageManager.checkPermission( - matches(Manifest.permission.CALL_COMPANION_APP), anyString())) - .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPermissionCheckerManager.checkPermission( + matches(Manifest.permission.CALL_COMPANION_APP), any(AttributionSourceState.class), + nullable(String.class), anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PermissionCheckerManager.PERMISSION_HARD_DENIED); try { when(mPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn( @@ -553,6 +598,7 @@ public class ComponentContextFixture implements TestFixture<Context> { } when(mPermissionInfo.isAppOp()).thenReturn(true); + when(mVibratorManager.getVibratorIds()).thenReturn(new int[0]); // Used in CreateConnectionProcessor to rank emergency numbers by viability. // For the test, make them all equal to INVALID so that the preferred PhoneAccount will be @@ -620,11 +666,15 @@ public class ComponentContextFixture implements TestFixture<Context> { serviceInfo.name = componentName.getClassName(); mServiceInfoByComponentName.put(componentName, serviceInfo); - // Used in InCallController to check permissions for CONTROL_INCALL_EXPERIENCE + // Used in InCallController to check permissions for CONTROL_INCALL_fvEXPERIENCE when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[] { componentName.getPackageName() }); when(mPackageManager.checkPermission(eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE), eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPermissionCheckerManager.checkPermission( + eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE), + any(AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(), + anyBoolean(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED); } public void addIntentReceiver(String action, ComponentName name) { @@ -667,6 +717,10 @@ public class ComponentContextFixture implements TestFixture<Context> { return mTelephonyManager; } + public NotificationManager getNotificationManager() { + return mNotificationManager; + } + private void addService(String action, ComponentName name, IInterface service) { mComponentNamesByAction.put(action, name); mServiceByComponentName.put(name, service); diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java index 5b4e80031..6e6646f7a 100755 --- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java +++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java @@ -34,6 +34,7 @@ import android.os.IInterface; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.Conference; import android.telecom.Connection; import android.telecom.ConnectionRequest; @@ -437,6 +438,10 @@ public class ConnectionServiceFixture implements TestFixture<IConnectionService> @Override public void handoverComplete(String callId, Session.Info sessionInfo) {} + + @Override + public void onCallFilteringCompleted(String callId, + Connection.CallFilteringCompletionInfo completionInfo, Session.Info sessionInfo) { } } FakeConnectionServiceDelegate mConnectionServiceDelegate; diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java index 0b926fe92..f66187835 100644 --- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java +++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java @@ -18,13 +18,16 @@ package com.android.server.telecom.tests; import static com.android.server.telecom.InCallController.IN_CALL_SERVICE_NOTIFICATION_ID; import static com.android.server.telecom.InCallController.NOTIFICATION_TAG; +import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; @@ -41,14 +44,19 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.UiModeManager; +import android.content.AttributionSource; +import android.content.AttributionSourceState; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -61,7 +69,9 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Process; import android.os.UserHandle; +import android.permission.PermissionCheckerManager; import android.telecom.CallAudioState; import android.telecom.InCallService; import android.telecom.ParcelableCall; @@ -72,10 +82,10 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.text.TextUtils; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.telecom.IInCallAdapter; import com.android.internal.telecom.IInCallService; import com.android.server.telecom.Analytics; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.Call; import com.android.server.telecom.CallsManager; import com.android.server.telecom.CarModeTracker; @@ -96,23 +106,27 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; import org.mockito.invocation.InvocationOnMock; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; @RunWith(JUnit4.class) public class InCallControllerTests extends TelecomTestCase { @Mock CallsManager mMockCallsManager; @Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar; - @Mock BluetoothHeadsetProxy mMockBluetoothHeadset; @Mock SystemStateHelper mMockSystemStateHelper; @Mock PackageManager mMockPackageManager; + @Mock PermissionCheckerManager mMockPermissionCheckerManager; @Mock Call mMockCall; @Mock Resources mMockResources; @Mock AppOpsManager mMockAppOpsManager; @@ -178,8 +192,12 @@ public class InCallControllerTests extends TelecomTestCase { when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter); when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); + when(mMockContext.getSystemService(eq(PermissionCheckerManager.class))) + .thenReturn(mMockPermissionCheckerManager); when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn( mMockPermissionInfo); + when(mMockContext.getAttributionSource()).thenReturn(new AttributionSource(Process.myUid(), + "com.android.server.telecom.tests", null)); mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager, mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter, mEmergencyCallHelper, mCarModeTracker, mClockProxy); @@ -210,21 +228,37 @@ public class InCallControllerTests extends TelecomTestCase { } return null; }).when(mMockPackageManager).getPackagesForUid(anyInt()); - when(mMockPackageManager.checkPermission( + + when(mMockPermissionCheckerManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), - matches(COMPANION_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); - when(mMockPackageManager.checkPermission( + matchesAttributionSourcePackage(COMPANION_PKG), nullable(String.class), + anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PackageManager.PERMISSION_DENIED); + + when(mMockPermissionCheckerManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), - matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED); - when(mMockPackageManager.checkPermission( + matchesAttributionSourcePackage(CAR_PKG), nullable(String.class), + anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + when(mMockPermissionCheckerManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), - matches(CAR2_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED); - when(mMockPackageManager.checkPermission( + matchesAttributionSourcePackage(CAR2_PKG), nullable(String.class), + anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + when(mMockPermissionCheckerManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), - matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED); - when(mMockPackageManager.checkPermission( + matchesAttributionSourcePackage(NONUI_PKG), nullable(String.class), + anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + when(mMockPermissionCheckerManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), - matches(APPOP_NONUI_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); + matchesAttributionSourcePackage(APPOP_NONUI_PKG), nullable(String.class), + anyBoolean(), anyBoolean(), anyBoolean(), anyInt())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0)); } @@ -243,14 +277,53 @@ public class InCallControllerTests extends TelecomTestCase { when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); + + mSystemStateListener.onCarModeChanged(666, CAR_PKG, true); + verify(mCarModeTracker).handleEnterCarMode(666, CAR_PKG); + assertTrue(mCarModeTracker.isInCarMode()); + + mSystemStateListener.onPackageUninstalled(CAR_PKG); + verify(mCarModeTracker).forceRemove(CAR_PKG); + assertFalse(mCarModeTracker.isInCarMode()); + } + + /** + * Ensure that if we remove a random unrelated app we don't exit car mode. + */ + @SmallTest + @Test + public void testRandomAppRemovalInCarMode() { + setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); + when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); mSystemStateListener.onCarModeChanged(666, CAR_PKG, true); verify(mCarModeTracker).handleEnterCarMode(666, CAR_PKG); assertTrue(mCarModeTracker.isInCarMode()); + mSystemStateListener.onPackageUninstalled("com.foo.test"); + verify(mCarModeTracker, never()).forceRemove(CAR_PKG); + assertTrue(mCarModeTracker.isInCarMode()); + } + + @SmallTest + @Test + public void testAutomotiveProjectionAppRemoval() { + setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); + when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); + + mSystemStateListener.onAutomotiveProjectionStateSet(CAR_PKG); + verify(mCarModeTracker).handleSetAutomotiveProjection(CAR_PKG); + assertTrue(mCarModeTracker.isInCarMode()); + mSystemStateListener.onPackageUninstalled(CAR_PKG); - verify(mCarModeTracker).forceExitCarMode(CAR_PKG); + verify(mCarModeTracker).forceRemove(CAR_PKG); assertFalse(mCarModeTracker.isInCarMode()); } @@ -439,7 +512,7 @@ public class InCallControllerTests extends TelecomTestCase { // Pretend that the call has gone away. when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList()); mInCallController.onCallRemoved(mMockCall); - waitForHandlerAction(new Handler(Looper.getMainLooper()), TelecomSystemTest.TEST_TIMEOUT); + waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT); verify(mMockPackageManager).revokeRuntimePermission(eq(SYS_PKG), eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle)); @@ -786,7 +859,7 @@ public class InCallControllerTests extends TelecomTestCase { setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); // Enable car mode - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true); // Now bind; we should only bind to one app. @@ -816,7 +889,7 @@ public class InCallControllerTests extends TelecomTestCase { matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); // Enable car mode - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); // Register the fact that the invalid app entered car mode. mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true); @@ -841,39 +914,45 @@ public class InCallControllerTests extends TelecomTestCase { @MediumTest @Test public void testBindToService_ThirdPartyApp() throws Exception { - setupMocks(false /* isExternalCall */); - setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */, - true /* system */, false /* external calls */, false /* self mgd in default */, - false /* self mgd in car*/); - - // Enable Third Party Companion App - when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn( - mMockPermissionInfo); - when(mMockPermissionInfo.isAppOp()).thenReturn(true); - when(mMockAppOpsManager.unsafeCheckOpRawNoThrow(matches( - AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS), eq(APPOP_NONUI_UID), - matches(APPOP_NONUI_PKG))).thenReturn(AppOpsManager.MODE_ALLOWED); - - // Now bind; we should bind to the system dialer and app op non ui app. - mInCallController.bindToServices(mMockCall); - - // Bind InCallServices - ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mMockContext, times(2)).bindServiceAsUser( - bindIntentCaptor.capture(), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS), - eq(UserHandle.CURRENT)); - - // Verify bind - assertEquals(2, bindIntentCaptor.getAllValues().size()); - - // Should have first bound to the system dialer. - verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS); - - // Should have next bound to the third party app op non ui app. - verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS); + final MockitoSession mockitoSession = ExtendedMockito.mockitoSession() + .strictness(Strictness.WARN) + .spyStatic(PermissionChecker.class) + .startMocking(); + try { + setupMocks(false /* isExternalCall */); + setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */, + true /* system */, false /* external calls */, false /* self mgd in default */, + false /* self mgd in car*/); + + // Enable Third Party Companion App + ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).when(() -> + PermissionChecker.checkPermissionForDataDeliveryFromDataSource( + any(Context.class), eq(Manifest.permission.MANAGE_ONGOING_CALLS), + anyInt(), any(AttributionSource.class), nullable(String.class))); + + // Now bind; we should bind to the system dialer and app op non ui app. + mInCallController.bindToServices(mMockCall); + + // Bind InCallServices + ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mMockContext, times(2)).bindServiceAsUser( + bindIntentCaptor.capture(), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS), + eq(UserHandle.CURRENT)); + + // Verify bind + assertEquals(2, bindIntentCaptor.getAllValues().size()); + + // Should have first bound to the system dialer. + verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS); + + // Should have next bound to the third party app op non ui app. + verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS); + } finally { + mockitoSession.finishMocking(); + } } /** @@ -951,6 +1030,31 @@ public class InCallControllerTests extends TelecomTestCase { */ @MediumTest @Test + public void testRandomAppRemovalWhenNotInCarMode() throws Exception { + setupMocks(true /* isExternalCall */); + setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); + // Bind to default dialer. + mInCallController.bindToServices(mMockCall); + + // Uninstall an unrelated app. + mSystemStateListener.onPackageUninstalled("com.joe.stuff"); + + // Bind InCallServices, just once; we should not re-bind to the same app. + ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mMockContext).bindServiceAsUser( + bindIntentCaptor.capture(), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS), + eq(UserHandle.CURRENT)); + } + + /** + * Ensures that the {@link InCallController} will bind to a higher priority car mode service + * when one becomes available. + */ + @MediumTest + @Test public void testCarmodeRebindHigherPriority() throws Exception { setupMocks(true /* isExternalCall */); setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); @@ -958,7 +1062,7 @@ public class InCallControllerTests extends TelecomTestCase { mInCallController.bindToServices(mMockCall); // Enable car mode and enter car mode at default priority. - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true); // And change to the second car mode app. @@ -1105,7 +1209,7 @@ public class InCallControllerTests extends TelecomTestCase { // Now switch to car mode. // Enable car mode and enter car mode at default priority. - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true); ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -1145,7 +1249,7 @@ public class InCallControllerTests extends TelecomTestCase { // Now switch to car mode. // Enable car mode and enter car mode at default priority. - when(mMockSystemStateHelper.isCarMode()).thenReturn(true); + when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true); mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true); // We currently will bind to the car-mode InCallService even if there are no calls available @@ -1379,6 +1483,28 @@ public class InCallControllerTests extends TelecomTestCase { } }).when(mMockPackageManager).queryIntentServicesAsUser( any(Intent.class), anyInt(), eq(CURRENT_USER_ID)); + + if (useDefaultDialer) { + when(mMockPackageManager + .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + } + + when(mMockPackageManager + .getComponentEnabledSetting(new ComponentName(SYS_PKG, SYS_CLASS))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + + when(mMockPackageManager + .getComponentEnabledSetting(new ComponentName(CAR_PKG, CAR_CLASS))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + + when(mMockPackageManager + .getComponentEnabledSetting(new ComponentName(COMPANION_PKG, COMPANION_CLASS))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + + when(mMockPackageManager + .getComponentEnabledSetting(new ComponentName(CAR2_PKG, CAR2_CLASS))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); } private void setupMockPackageManagerLocationPermission(final String pkg, @@ -1388,4 +1514,24 @@ public class InCallControllerTests extends TelecomTestCase { ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); } + + private static AttributionSourceState matchesAttributionSourcePackage( + @Nullable String packageName) { + return argThat(new PackageNameArgumentMatcher(packageName)); + } + + private static class PackageNameArgumentMatcher implements + ArgumentMatcher<AttributionSourceState> { + @Nullable + private final String mPackgeName; + + PackageNameArgumentMatcher(@Nullable String packageName) { + mPackgeName = packageName; + } + + @Override + public boolean matches(@NonNull AttributionSourceState attributionSource) { + return Objects.equals(mPackgeName, attributionSource.packageName); + } + } } diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java index db44dcd4b..2b05430c5 100644 --- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java +++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java @@ -29,6 +29,8 @@ import android.content.Context; import android.content.IContentProvider; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -65,11 +67,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; @@ -79,6 +83,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -132,6 +137,7 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { } } + private static final long TIMEOUT_DELAY = 5000; private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620"); private static final Uri SIP_CALL_HANDLE = Uri.parse("sip:testaddress@testdomain.com"); private static final String CALLER_NAME = "Fake Name"; @@ -139,6 +145,7 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { private static final String MISSED_CALLS_TITLE = "Missed Calls"; private static final String MISSED_CALLS_MSG = "%s missed calls"; private static final String USER_CALL_ACTIVITY_LABEL = "Phone"; + private static final String DEFAULT_DIALER_PACKAGE = "com.android.server.telecom.test"; private static final int REQUEST_ID = 0; private static final long CALL_TIMESTAMP; @@ -213,6 +220,49 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { cancelNotificationTestInternal(SECONARY_USER); } + @SmallTest + @Test + public void testDefaultDialerClear() { + MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer(); + missedCallNotifier.clearMissedCalls(PRIMARY_USER); + + ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(), + anyString(), any()); + Intent sentIntent = intentArgumentCaptor.getValue(); + assertEquals(0, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1)); + } + + @SmallTest + @Test + public void testDefaultDialerIncrement() { + MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer(); + PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); + MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, + CALL_TIMESTAMP, phoneAccount.getAccountHandle()); + + missedCallNotifier.showMissedCallNotification(fakeCall); + ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(), + anyString(), any()); + + Intent sentIntent = intentArgumentCaptor.getValue(); + assertEquals(1, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1)); + } + + private MissedCallNotifier setupMissedCallNotificationThroughDefaultDialer() { + mComponentContextFixture.addIntentReceiver( + TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME); + when(mDefaultDialerCache.getDefaultDialerApplication(anyInt())).thenReturn( + DEFAULT_DIALER_PACKAGE); + + Notification.Builder builder1 = makeNotificationBuilder("builder1"); + Notification.Builder builder2 = makeNotificationBuilder("builder2"); + MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = + makeNotificationBuilderFactory(builder1, builder1, builder2, builder2); + return makeMissedCallNotifier(fakeBuilderFactory, PRIMARY_USER); + } + private void cancelNotificationTestInternal(UserHandle userHandle) { Notification.Builder builder1 = makeNotificationBuilder("builder1"); Notification.Builder builder2 = makeNotificationBuilder("builder2"); @@ -458,7 +508,7 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) .build(); - when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class), + when(cp.query(any(), eq(queryUri), nullable(String[].class), nullable(Bundle.class), nullable(ICancellationSignal.class))) .thenReturn(mockMissedCallsCursor); @@ -528,7 +578,7 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { PRIMARY_USER.getIdentifier()); IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier()); - when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class), + when(cp.query(any(), eq(queryUri), nullable(String[].class), nullable(Bundle.class), nullable(ICancellationSignal.class))) .thenReturn(mockMissedCallsCursor); @@ -611,7 +661,7 @@ public class MissedCallNotifierImplTest extends TelecomTestCase { assertNotNull("Not expecting null options bundle", bundleCaptor.getValue()); BroadcastOptions options = new BroadcastOptions(bundleCaptor.getValue()); assertTrue("App must have a temporary exemption set.", - options.getTemporaryAppWhitelistDuration() > 0); + options.getTemporaryAppAllowlistDuration() > 0); // A notification should never be posted by Telecom verify(mNotificationManager, never()).notifyAsUser(nullable(String.class), anyInt(), diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java index a5b78b7c3..e6c6bacf4 100644 --- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java +++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java @@ -110,7 +110,7 @@ public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase { when(mPhoneAccountRegistrar.getPhoneAccountUnchecked( any(PhoneAccountHandle.class))).thenReturn(mPhoneAccount); when(mPhoneAccount.isSelfManaged()).thenReturn(true); - when(mSystemStateHelper.isCarMode()).thenReturn(false); + when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(false); } @Override diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java index 6c941fe4d..a50328386 100644 --- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java +++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java @@ -1,13 +1,12 @@ package com.android.server.telecom.tests; -import static com.android.server.telecom.TelecomSystem.*; +import static com.android.server.telecom.TelecomSystem.SyncRoot; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.content.ComponentName; @@ -17,6 +16,7 @@ import android.os.SystemClock; import android.telecom.Connection; import android.telecom.ParcelableCall; import android.telecom.PhoneAccountHandle; +import android.telephony.ims.ImsCallProfile; import android.test.suitebuilder.annotation.SmallTest; import com.android.server.telecom.Call; @@ -26,7 +26,6 @@ import com.android.server.telecom.ClockProxy; import com.android.server.telecom.ParcelableCallUtils; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.PhoneNumberUtilsAdapter; -import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.ui.ToastFactory; import org.junit.After; @@ -98,6 +97,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase { Bundle parceledExtras = call.getExtras(); assertFalse(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE)); + assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)); assertFalse(parceledExtras.containsKey("SomeExtra")); assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)); } @@ -115,6 +115,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase { Bundle parceledExtras = call.getExtras(); assertTrue(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE)); + assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)); assertTrue(parceledExtras.containsKey("SomeExtra")); assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)); } @@ -128,6 +129,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase { Bundle parceledExtras = call.getExtras(); assertTrue(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE)); + assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)); assertFalse(parceledExtras.containsKey("SomeExtra")); assertFalse(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)); } @@ -141,6 +143,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase { Bundle parceledExtras = call.getExtras(); assertFalse(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE)); + assertFalse(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)); assertFalse(parceledExtras.containsKey("SomeExtra")); assertFalse(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)); } @@ -192,6 +195,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase { extras.putString(Connection.EXTRA_SIP_INVITE, "scary data"); extras.putString("SomeExtra", "Extra Extra"); extras.putString(Connection.EXTRA_CALL_SUBJECT, "Blah"); + extras.putBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL, true); return extras; } } diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java index 38f63d2d2..0e93481cb 100644 --- a/tests/src/com/android/server/telecom/tests/RingerTest.java +++ b/tests/src/com/android/server/telecom/tests/RingerTest.java @@ -79,6 +79,16 @@ public class RingerTest extends TelecomTestCase { } @Override + public VibrationEffect resolve(int defaultAmplitude) { + return this; + } + + @Override + public VibrationEffect scale(float scaleFactor) { + return this; + } + + @Override public void validate() { // not needed } diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java index 7113148da..dc7d1fd8a 100644 --- a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java +++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java @@ -61,6 +61,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.internal.util.reflection.FieldSetter; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -93,6 +94,8 @@ public class SystemStateHelperTest extends TelecomTestCase { when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(mGravitySensor); when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProxSensor); + doReturn(mUiModeManager).when(mContext).getSystemService(UiModeManager.class); + mComponentContextFixture.putFloatResource( R.dimen.device_on_ear_xy_gravity_threshold, 5.5f); mComponentContextFixture.putFloatResource( @@ -119,17 +122,53 @@ public class SystemStateHelperTest extends TelecomTestCase { @SmallTest @Test public void testQuerySystemForCarMode_True() { - when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager); when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); - assertTrue(new SystemStateHelper(mContext, mLock).isCarMode()); + assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); } @SmallTest @Test public void testQuerySystemForCarMode_False() { - when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager); when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL); - assertFalse(new SystemStateHelper(mContext, mLock).isCarMode()); + assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); + } + + @SmallTest + @Test + public void testQuerySystemForAutomotiveProjection_True() { + when(mUiModeManager.getActiveProjectionTypes()) + .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); + assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); + + when(mUiModeManager.getActiveProjectionTypes()) + .thenReturn(UiModeManager.PROJECTION_TYPE_ALL); + assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); + } + + @SmallTest + @Test + public void testQuerySystemForAutomotiveProjection_False() { + when(mUiModeManager.getActiveProjectionTypes()) + .thenReturn(UiModeManager.PROJECTION_TYPE_NONE); + assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); + } + + @SmallTest + @Test + public void testQuerySystemForAutomotiveProjectionAndCarMode_True() { + when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + when(mUiModeManager.getActiveProjectionTypes()) + .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); + assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); + } + + @SmallTest + @Test + public void testQuerySystemForAutomotiveProjectionOrCarMode_nullService() { + when(mContext.getSystemService(UiModeManager.class)) + .thenReturn(mUiModeManager) // Without this, class construction will throw NPE. + .thenReturn(null); + assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive()); } @SmallTest @@ -206,6 +245,40 @@ public class SystemStateHelperTest extends TelecomTestCase { @SmallTest @Test + public void testOnSetReleaseAutomotiveProjection() { + SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock); + // We don't care what listener is registered, that's an implementation detail, but we need + // to call methods on whatever it is. + ArgumentCaptor<UiModeManager.OnProjectionStateChangedListener> listenerCaptor = + ArgumentCaptor.forClass(UiModeManager.OnProjectionStateChangedListener.class); + verify(mUiModeManager).addOnProjectionStateChangedListener( + eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE), any(), listenerCaptor.capture()); + systemStateHelper.addListener(mSystemStateListener); + + String packageName1 = "Sufjan Stevens"; + String packageName2 = "The Ascension"; + + // Should pay attention to automotive projection, though. + listenerCaptor.getValue().onProjectionStateChanged( + UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName2)); + verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName2); + + // Without any automotive projection, it should see it as released. + listenerCaptor.getValue().onProjectionStateChanged( + UiModeManager.PROJECTION_TYPE_NONE, Set.of()); + verify(mSystemStateListener).onAutomotiveProjectionStateReleased(); + + // Try the whole thing again, with different values. + listenerCaptor.getValue().onProjectionStateChanged( + UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName1)); + verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName1); + listenerCaptor.getValue().onProjectionStateChanged( + UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of()); + verify(mSystemStateListener, times(2)).onAutomotiveProjectionStateReleased(); + } + + @SmallTest + @Test public void testDeviceOnEarCorrectlyDetected() { doAnswer(invocation -> { SensorEventListener listener = invocation.getArgument(0); diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java index 0dfe29a08..3cec50b78 100644 --- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java +++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java @@ -460,10 +460,10 @@ public class TelecomServiceImplTest extends TelecomTestCase { @Test public void testGetPhoneAccount() throws RemoteException { makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); - assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16) - .getAccountHandle()); - assertEquals(SIP_PA_HANDLE_17, mTSIBinder.getPhoneAccount(SIP_PA_HANDLE_17) - .getAccountHandle()); + assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16, + mContext.getPackageName()).getAccountHandle()); + assertEquals(SIP_PA_HANDLE_17, mTSIBinder.getPhoneAccount(SIP_PA_HANDLE_17, + mContext.getPackageName()).getAccountHandle()); } @SmallTest @@ -1202,6 +1202,39 @@ public class TelecomServiceImplTest extends TelecomTestCase { } /** + * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()}. + * @throws Exception + */ + @SmallTest + @Test + public void testCannotEndSelfManagedCall() throws Exception { + Call call = mock(Call.class); + when(call.isSelfManaged()).thenReturn(true); + when(call.getState()).thenReturn(CallState.ACTIVE); + when(mFakeCallsManager.getFirstCallWithState(any())) + .thenReturn(call); + assertFalse(mTSIBinder.endCall(TEST_PACKAGE)); + verify(mFakeCallsManager, never()).disconnectCall(eq(call)); + } + + /** + * Ensure self-managed calls cannot be answered using {@link TelecomManager#acceptRingingCall()} + * or {@link TelecomManager#acceptRingingCall(int)}. + * @throws Exception + */ + @SmallTest + @Test + public void testCannotAnswerSelfManagedCall() throws Exception { + Call call = mock(Call.class); + when(call.isSelfManaged()).thenReturn(true); + when(call.getState()).thenReturn(CallState.ACTIVE); + when(mFakeCallsManager.getFirstCallWithState(any())) + .thenReturn(call); + mTSIBinder.acceptRingingCall(TEST_PACKAGE); + verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt()); + } + + /** * Register phone accounts for the supplied PhoneAccountHandles to make them * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl. * @param handles the handles for which phone accounts should be created for. |
