Handle prioritized car mode in Telecom.
Add basic support for receiving the broadcast that the prioritized car
mode has changed in Telecom; rest of implementation TBD.
Also added a fancy new car mode dialer app which can be used in car mode.
Bug: 136109592
Test: Use new test app to verify enter/exit car mode with priority.
Test: Run new CTS tests.
Change-Id: I93daadf341e7c158260a1cc76efc6085a29f7a38
Merged-In: I93daadf341e7c158260a1cc76efc6085a29f7a38
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 07ee6fa..3003aad 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION" />
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.HANDLE_CALL_INTENT" />
+ <uses-permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index c4df1d3..1c96ffd 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -707,6 +707,10 @@
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
}
}
+
+ @Override
+ public void onCarModeChanged(int priority, String packageName, boolean isCarMode) {
+ }
};
private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
index 69a46c6..2a7e233 100644
--- a/src/com/android/server/telecom/SystemStateHelper.java
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -39,7 +39,17 @@
*/
public class SystemStateHelper {
public static interface SystemStateListener {
- public void onCarModeChanged(boolean isCarMode);
+ void onCarModeChanged(boolean isCarMode);
+
+ /**
+ * Listener method to inform interested parties when a package name requests to enter or
+ * exit car mode.
+ * @param priority the priority of the enter/exit request.
+ * @param packageName the package name of the requester.
+ * @param isCarMode {@code true} if the package is entering car mode, {@code false}
+ * otherwise.
+ */
+ void onCarModeChanged(int priority, String packageName, boolean isCarMode);
}
private final Context mContext;
@@ -53,6 +63,22 @@
onEnterCarMode();
} else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
onExitCarMode();
+ } else if (UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED.equals(action)) {
+ int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+ UiModeManager.DEFAULT_PRIORITY);
+ String callingPackage = intent.getStringExtra(
+ UiModeManager.EXTRA_CALLING_PACKAGE);
+ Log.i(SystemStateHelper.this, "ENTER_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
+ priority, callingPackage);
+ onEnterCarMode(priority, callingPackage);
+ } else if (UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED.equals(action)) {
+ int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+ UiModeManager.DEFAULT_PRIORITY);
+ String callingPackage = intent.getStringExtra(
+ UiModeManager.EXTRA_CALLING_PACKAGE);
+ Log.i(SystemStateHelper.this, "EXIT_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
+ priority, callingPackage);
+ onExitCarMode(priority, callingPackage);
} else {
Log.w(this, "Unexpected intent received: %s", intent.getAction());
}
@@ -70,6 +96,8 @@
IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ intentFilter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
+ intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Log.i(this, "Registering car mode receiver: %s", intentFilter);
@@ -186,6 +214,22 @@
}
}
+ private void onEnterCarMode(int priority, String packageName) {
+ Log.i(this, "Entering carmode");
+ mIsCarMode = getSystemCarMode();
+ for (SystemStateListener listener : mListeners) {
+ listener.onCarModeChanged(priority, packageName, true /* isCarMode */);
+ }
+ }
+
+ private void onExitCarMode(int priority, String packageName) {
+ Log.i(this, "Exiting carmode");
+ mIsCarMode = getSystemCarMode();
+ for (SystemStateListener listener : mListeners) {
+ listener.onCarModeChanged(priority, packageName, false /* isCarMode */);
+ }
+ }
+
private void notifyCarMode() {
for (SystemStateListener listener : mListeners) {
listener.onCarModeChanged(mIsCarMode);
diff --git a/testapps/carmodedialer/Android.bp b/testapps/carmodedialer/Android.bp
new file mode 100644
index 0000000..7179b1f
--- /dev/null
+++ b/testapps/carmodedialer/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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.
+//
+
+android_test {
+ name: "TelecomCarModeApp",
+ static_libs: [
+ "androidx.legacy_legacy-support-v4",
+ "guava",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/testapps/carmodedialer/AndroidManifest.xml b/testapps/carmodedialer/AndroidManifest.xml
new file mode 100644
index 0000000..7f55f7e
--- /dev/null
+++ b/testapps/carmodedialer/AndroidManifest.xml
@@ -0,0 +1,90 @@
+<?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.carmodedialer">
+
+ <uses-sdk
+ android:minSdkVersion="28"
+ android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
+ <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.ENTER_CAR_MODE_PRIORITIZED" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+
+ <application android:label="Telecom CarMode">
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl"
+ android:permission="android.permission.BIND_INCALL_SERVICE" >
+ <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+ android:value="true"/>
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name="com.android.server.telecom.carmodedialer.CarModeInCallUI"
+ android:label="CarMode Dialer"
+ android:launchMode="singleInstance">
+ <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>
+
+ <activity android:name="com.android.server.telecom.carmodedialer.CarModeDialerActivity"
+ android:label="CarMode Dialer">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:mimeType="vnd.android.cursor.item/phone" />
+ <data android:mimeType="vnd.android.cursor.item/person" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="voicemail" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ <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/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..ed3ee45
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..a4add51
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..41558f2
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png b/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png
new file mode 100644
index 0000000..1bb4340
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..6006b12
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..4f935bf
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/layout/call_list_item.xml b/testapps/carmodedialer/res/layout/call_list_item.xml
new file mode 100644
index 0000000..9a77ceb
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/call_list_item.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView
+ android:id="@+id/phoneNumber"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/callState"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/duration"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/layout/incall_screen.xml b/testapps/carmodedialer/res/layout/incall_screen.xml
new file mode 100644
index 0000000..bef6915
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/incall_screen.xml
@@ -0,0 +1,119 @@
+<?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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <ListView
+ android:id="@+id/callListView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="#FFCC00"
+ android:dividerHeight="4px">
+ </ListView>
+ <GridLayout
+ android:columnCount="3"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/end_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/endCallButton" />
+ <Button
+ android:id="@+id/mute_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/muteButton" />
+ <Button
+ android:id="@+id/hold_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/holdButton"/>
+ <Button
+ android:id="@+id/rtt_iface_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rttIfaceButton"/>
+ <Button
+ android:id="@+id/answer_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/answerCallButton"/>
+ <Button
+ android:id="@+id/start_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/startRttButton"/>
+ <Button
+ android:id="@+id/accept_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/acceptRttButton"/>
+ <Button
+ android:id="@+id/request_handover_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/handoverButton"/>
+ </GridLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Spinner
+ android:id="@+id/available_bt_devices"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/set_bt_device_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/setBtDeviceButton"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/earpiece_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/earpieceButton"/>
+ <Button
+ android:id="@+id/speaker_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/speakerButton"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/current_route_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/currentRouteLabel"/>
+ <TextView
+ android:id="@+id/current_audio_route"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/layout/testdialer_main.xml b/testapps/carmodedialer/res/layout/testdialer_main.xml
new file mode 100644
index 0000000..c332d19
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/testdialer_main.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+
+<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="Telecom CarMode Dialer" />
+ <EditText
+ android:id="@+id/number"
+ android:inputType="number"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/place_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/placeCallButton" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:text="Priority:" />
+ <EditText
+ android:id="@+id/priority"
+ android:inputType="number"
+ android:text="100"
+ android:layout_width="50dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/enable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable Car mode" />
+ <Button
+ android:id="@+id/disable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disable Car mode" />
+ </LinearLayout>
+ <Button
+ android:id="@+id/toggle_incallservice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Toggle InCallService" />
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/values/donottranslate_strings.xml b/testapps/carmodedialer/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..3b5fa65
--- /dev/null
+++ b/testapps/carmodedialer/res/values/donottranslate_strings.xml
@@ -0,0 +1,108 @@
+<?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.
+-->
+
+<resources>
+ <!-- Application label -->
+ <string name="app_name">TelecommTests</string>
+
+ <!-- String for the TestCallActivity -->
+ <string name="testCallActivityLabel">Test Connection Service App</string>
+
+ <!-- String for the CarModeDialerActivity -->
+ <string name="testDialerActivityLabel">Test Dialer</string>
+
+ <!-- String for button in CarModeDialerActivity that reassigns the default Dialer -->
+ <string name="defaultDialerButton">Default dialer request</string>
+
+ <!-- String for button in CarModeDialerActivity that places a test call -->
+ <string name="placeCallButton">Place call</string>
+
+ <!-- String for button in CarModeDialerActivity that performs voicemail requests to verify
+ voicemail permissions -->
+ <string name="testVoicemailButton">Exercise voicemail permissions</string>
+
+ <!-- String for button in CarModeDialerActivity that tries to exercise the
+ TelecomManager.cancelMissedCallNotifications() functionality -->
+ <string name="cancelMissedButton">Cancel missed calls</string>
+
+ <string name="endCallButton">End Call</string>
+
+ <string name="answerCallButton">Answer</string>
+
+ <string name="startCallWithRtt">Start call with RTT</string>
+
+ <string name="rttIfaceButton">RTT</string>
+
+ <string name="endRttButton">End RTT</string>
+
+ <string name="startRttButton">Start RTT</string>
+
+ <string name="acceptRttButton">Accept RTT request</string>
+
+ <string name="muteButton">Mute</string>
+
+ <string name="holdButton">Hold</string>
+
+ <string name="handoverButton">Handover</string>
+
+ <string name="inCallUiAppLabel">Test InCall UI</string>
+
+ <string name="UssdUiAppLabel">Test Ussd UI</string>
+
+ <string name="placeUssdButton">Send USSD</string>
+
+ <string name="KeyUiAppLabel">Get Key UI</string>
+
+ <string name="getKeyButton">Get Key Json</string>
+
+ <string name="earpieceButton">Earpiece/Wired</string>
+
+ <string name="speakerButton">Speakerphone</string>
+
+ <string name="setBtDeviceButton">Set BT device</string>
+
+ <string name="currentRouteLabel">Current audio route</string>
+ <!-- String for button in SelfManagedCallingActivity. -->
+ <string name="checkIfPermittedBeforeCallingButton">Check if calls permitted before calling</string>
+
+ <string name="selfManagedCallingActivityLabel">Self-Managed Sample</string>
+
+ <string name="outgoingCallNotPermitted">Outgoing call not permitted.</string>
+
+ <string name="outgoingCallNotPermittedCS">Outgoing call not permitted (CS Reported).</string>
+
+ <string name="incomingCallNotPermitted">Incoming call not permitted.</string>
+
+ <string name="incomingCallNotPermittedCS">Incoming call not permitted (CS Reported).</string>
+
+ <string name="rttUiLabel">Test RTT UI</string>
+
+ <string-array name="rtt_mode_array">
+ <item>Full</item>
+ <item>HCO</item>
+ <item>VCO</item>
+ </string-array>
+
+ <string-array name="rtt_reply_one_liners">
+ <item>To RTT or not to RTT, that is the question...</item>
+ <item>Making TTY great again!</item>
+ <item>I would be more comfortable with real "Thyme" chatting. I don\'t know how to end
+ this pun</item>
+ <item>お疲れ様でした</item>
+ <item>The FCC has mandated that I respond... I will do so begrudgingly</item>
+ <item>😂😂😂💯</item>
+ </string-array>
+</resources>
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java
new file mode 100644
index 0000000..8ba59a7
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class CallListAdapter extends BaseAdapter {
+ private static final String TAG = "CallListAdapter";
+
+ private final CarModeCallList.Listener mListener = new CarModeCallList.Listener() {
+ @Override
+ public void onCallAdded(Call call) {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ notifyDataSetChanged();
+ if (mCallList.size() == 0) {
+ mCallList.removeListener(this);
+ }
+ }
+ };
+
+ private final LayoutInflater mLayoutInflater;
+ private final CarModeCallList mCallList;
+ private final Handler mHandler = new Handler();
+ private final Runnable mSecondsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ if (mCallList.size() > 0) {
+ mHandler.postDelayed(this, 1000);
+ }
+ }
+ };
+
+ public CallListAdapter(Context context) {
+ mLayoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mCallList = CarModeCallList.getInstance();
+ mCallList.addListener(mListener);
+ mHandler.postDelayed(mSecondsRunnable, 1000);
+ }
+
+
+ @Override
+ public int getCount() {
+ Log.i(TAG, "size reporting: " + mCallList.size());
+ return mCallList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ Log.i(TAG, "getView: " + position);
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.call_list_item, parent, false);
+ }
+
+ TextView phoneNumber = convertView.findViewById(R.id.phoneNumber);
+ TextView duration = convertView.findViewById(R.id.duration);
+ TextView state = convertView.findViewById(R.id.callState);
+
+ Call call = mCallList.getCall(position);
+ Uri handle = call.getDetails().getHandle();
+ phoneNumber.setText(handle == null ? "No number" : handle.getSchemeSpecificPart());
+
+ long durationMs = System.currentTimeMillis() - call.getDetails().getConnectTimeMillis();
+ duration.setText((durationMs / 1000) + " secs");
+
+ state.setText(getStateString(call));
+
+ Log.i(TAG, "Call found: " + ((handle == null) ? "null" : handle.getSchemeSpecificPart())
+ + ", " + durationMs);
+ Log.i(TAG, "Call extras: " + extrasToString(call.getDetails().getExtras()));
+ Log.i(TAG, "Call intent extras: " + extrasToString(call.getDetails().getIntentExtras()));
+
+ return convertView;
+ }
+
+ private String extrasToString(Bundle bundle) {
+ StringBuilder sb = new StringBuilder("[");
+ for (String key : bundle.keySet()) {
+ sb.append(key);
+ sb.append(": ");
+ sb.append(bundle.get(key));
+ sb.append("\n");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private static String getStateString(Call call) {
+ switch (call.getState()) {
+ case Call.STATE_ACTIVE:
+ return "active";
+ case Call.STATE_CONNECTING:
+ return "connecting";
+ case Call.STATE_DIALING:
+ return "dialing";
+ case Call.STATE_DISCONNECTED:
+ return "disconnected";
+ case Call.STATE_DISCONNECTING:
+ return "disconnecting";
+ case Call.STATE_HOLDING:
+ return "on hold";
+ case Call.STATE_NEW:
+ return "new";
+ case Call.STATE_RINGING:
+ return "ringing";
+ case Call.STATE_SELECT_PHONE_ACCOUNT:
+ return "select phone account";
+ default:
+ return "unknown";
+ }
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java
new file mode 100644
index 0000000..5fba5bd
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java
@@ -0,0 +1,260 @@
+/*
+ * 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.carmodedialer;
+
+import android.content.Context;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Maintains a list of calls received via the {@link TestInCallServiceImpl}.
+ */
+public class CarModeCallList extends Call.Callback {
+
+ public static abstract class Listener {
+ public void onCallAdded(Call call) {}
+ public void onCallRemoved(Call call) {}
+ public void onRttStarted(Call call) {}
+ public void onRttStopped(Call call) {}
+ public void onRttInitiationFailed(Call call, int reason) {}
+ public void onRttRequest(Call call, int id) {}
+ }
+
+ private static final com.android.server.telecom.carmodedialer.CarModeCallList
+ INSTANCE = new com.android.server.telecom.carmodedialer.CarModeCallList();
+ private static final String TAG = "TestCallList";
+
+ private class TestVideoCallListener extends InCallService.VideoCall.Callback {
+ private Call mCall;
+
+ public TestVideoCallListener(Call call) {
+ mCall = call;
+ }
+
+ @Override
+ public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
+ Log.v(TAG,
+ "onSessionModifyRequestReceived: videoState = " + videoProfile.getVideoState()
+ + " call = " + mCall);
+ }
+
+ @Override
+ public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile,
+ VideoProfile responseProfile) {
+ Log.v(TAG,
+ "onSessionModifyResponseReceived: status = " + status + " videoState = "
+ + responseProfile.getVideoState()
+ + " call = " + mCall);
+ }
+
+ @Override
+ public void onCallSessionEvent(int event) {
+
+ }
+
+ @Override
+ public void onPeerDimensionsChanged(int width, int height) {
+
+ }
+
+ @Override
+ public void onVideoQualityChanged(int videoQuality) {
+ Log.v(TAG,
+ "onVideoQualityChanged: videoQuality = " + videoQuality + " call = " + mCall);
+ }
+
+ @Override
+ public void onCallDataUsageChanged(long dataUsage) {
+
+ }
+
+ @Override
+ public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
+
+ }
+ }
+
+ // The calls the call list knows about.
+ private List<Call> mCalls = new LinkedList<Call>();
+ private Map<Call, TestVideoCallListener> mVideoCallListeners =
+ new ArrayMap<Call, TestVideoCallListener>();
+ private Set<Listener> mListeners = new ArraySet<Listener>();
+ private Context mContext;
+ private int mLastRttRequestId = -1;
+
+ /**
+ * Singleton accessor.
+ */
+ public static com.android.server.telecom.carmodedialer.CarModeCallList getInstance() {
+ return INSTANCE;
+ }
+
+ public void addListener(Listener listener) {
+ if (listener != null) {
+ mListeners.add(listener);
+ }
+ }
+
+ public boolean removeListener(Listener listener) {
+ return mListeners.remove(listener);
+ }
+
+ public Call getCall(int position) {
+ return mCalls.get(position);
+ }
+
+ public void addCall(Call call) {
+ if (mCalls.contains(call)) {
+ Log.e(TAG, "addCall: Call already added.");
+ return;
+ }
+ Log.i(TAG, "addCall: " + call + " " + System.identityHashCode(this));
+ mCalls.add(call);
+ call.registerCallback(this);
+
+ for (Listener l : mListeners) {
+ l.onCallAdded(call);
+ }
+ }
+
+ public void removeCall(Call call) {
+ if (!mCalls.contains(call)) {
+ Log.e(TAG, "removeCall: Call cannot be removed -- doesn't exist.");
+ return;
+ }
+ Log.i(TAG, "removeCall: " + call);
+ mCalls.remove(call);
+ call.unregisterCallback(this);
+
+ for (Listener l : mListeners) {
+ if (l != null) {
+ l.onCallRemoved(call);
+ }
+ }
+ }
+
+ public void clearCalls() {
+ for (Call call : new LinkedList<Call>(mCalls)) {
+ removeCall(call);
+ }
+
+ for (Call call : mVideoCallListeners.keySet()) {
+ if (call.getVideoCall() != null) {
+ call.getVideoCall().destroy();
+ }
+ }
+ mVideoCallListeners.clear();
+ }
+
+ public int size() {
+ return mCalls.size();
+ }
+
+ public int getLastRttRequestId() {
+ return mLastRttRequestId;
+ }
+
+ /**
+ * For any video calls tracked, sends an upgrade to video request.
+ */
+ public void sendUpgradeToVideoRequest(int videoState) {
+ Log.v(TAG, "sendUpgradeToVideoRequest : videoState = " + videoState);
+
+ for (Call call : mCalls) {
+ InCallService.VideoCall videoCall = call.getVideoCall();
+ Log.v(TAG, "sendUpgradeToVideoRequest: checkCall "+call);
+ if (videoCall == null) {
+ continue;
+ }
+
+ Log.v(TAG, "send upgrade to video request for call: " + call);
+ videoCall.sendSessionModifyRequest(new VideoProfile(videoState));
+ }
+ }
+
+ /**
+ * For any video calls which are active, sends an upgrade to video response with the specified
+ * video state.
+ *
+ * @param videoState The video state to respond with.
+ */
+ public void sendUpgradeToVideoResponse(int videoState) {
+ Log.v(TAG, "sendUpgradeToVideoResponse : videoState = " + videoState);
+
+ for (Call call : mCalls) {
+ InCallService.VideoCall videoCall = call.getVideoCall();
+ if (videoCall == null) {
+ continue;
+ }
+
+ Log.v(TAG, "send upgrade to video response for call: " + call);
+ videoCall.sendSessionModifyResponse(new VideoProfile(videoState));
+ }
+ }
+
+ @Override
+ public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {
+ Log.v(TAG, "onVideoCallChanged: call = " + call + " " + System.identityHashCode(this));
+ if (videoCall != null) {
+ if (!mVideoCallListeners.containsKey(call)) {
+ TestVideoCallListener listener = new TestVideoCallListener(call);
+ videoCall.registerCallback(listener);
+ mVideoCallListeners.put(call, listener);
+ Log.v(TAG, "onVideoCallChanged: added new listener");
+ }
+ }
+ }
+
+ @Override
+ public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
+ Log.v(TAG, "onRttStatusChanged: call = " + call + " " + System.identityHashCode(this));
+ if (enabled) {
+ for (Listener l : mListeners) {
+ l.onRttStarted(call);
+ }
+ } else {
+ for (Listener l : mListeners) {
+ l.onRttStopped(call);
+ }
+ }
+ }
+
+ @Override
+ public void onRttInitiationFailure(Call call, int reason) {
+ for (Listener l : mListeners) {
+ l.onRttInitiationFailed(call, reason);
+ }
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ mLastRttRequestId = id;
+ for (Listener l : mListeners) {
+ l.onRttRequest(call, id);
+ }
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java
new file mode 100644
index 0000000..e46d64f
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java
@@ -0,0 +1,156 @@
+/*
+ * 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.carmodedialer;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.EditText;
+import android.widget.Toast;
+
+public class CarModeDialerActivity extends Activity {
+ private static final int REQUEST_CODE_SET_DEFAULT_DIALER = 1;
+
+ private EditText mNumberView;
+ private EditText mPriorityView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.testdialer_main);
+
+ findViewById(R.id.place_call_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ placeCall();
+ }
+ });
+
+ mNumberView = (EditText) findViewById(R.id.number);
+ findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ enableCarMode();
+ }
+ });
+ findViewById(R.id.disable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableCarMode();
+ }
+ });
+ findViewById(R.id.toggle_incallservice).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ toggleInCallService();
+ }
+ });
+
+ mPriorityView = findViewById(R.id.priority);
+
+ updateMutableUi();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER) {
+ if (resultCode == RESULT_OK) {
+ showToast("User accepted request to become default dialer");
+ } else if (resultCode == RESULT_CANCELED) {
+ showToast("User declined request to become default dialer");
+ }
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ updateMutableUi();
+ }
+
+ private void updateMutableUi() {
+ Intent intent = getIntent();
+ if (intent != null) {
+ mNumberView.setText(intent.getDataString());
+ }
+ }
+
+ private void placeCall() {
+ final TelecomManager telecomManager =
+ (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL,
+ mNumberView.getText().toString(), null), createCallIntentExtras());
+ }
+
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ private Bundle createCallIntentExtras() {
+ Bundle extras = new Bundle();
+ extras.putString("com.android.server.telecom.carmodedialer.CALL_EXTRAS", "Tyler was here");
+
+ Bundle intentExtras = new Bundle();
+ intentExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
+ return intentExtras;
+ }
+
+ private void enableCarMode() {
+ int priority;
+ try {
+ priority = Integer.parseInt(mPriorityView.getText().toString());
+ } catch (NumberFormatException nfe) {
+ Toast.makeText(this, "Invalid priority; not enabling car mode.",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.enableCarMode(priority, 0);
+ Toast.makeText(this, "Enabling car mode with priority " + priority,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private void disableCarMode() {
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.disableCarMode(0);
+ Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
+ }
+
+ private void toggleInCallService() {
+ ComponentName uiComponent = new ComponentName(
+ com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl.class.getPackage().getName(),
+ com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl.class.getName());
+ boolean isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ getPackageManager().setComponentEnabledSetting(uiComponent,
+ isEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ Toast.makeText(this, "Is UI enabled? " + isEnabled, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java
new file mode 100644
index 0000000..2879bde
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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.carmodedialer;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.InCallService;
+import android.telecom.Phone;
+import android.util.Log;
+
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Test In-Call service implementation. Logs incoming events. Mainly used to test binding to
+ * multiple {@link InCallService} implementations.
+ */
+public class CarModeInCallServiceImpl extends InCallService {
+ private static final String TAG = "TestInCallServiceImpl";
+ public static com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl sInstance;
+
+ private Phone mPhone;
+
+ private Phone.Listener mPhoneListener = new Phone.Listener() {
+ @Override
+ public void onCallAdded(Phone phone, Call call) {
+ Log.i(TAG, "onCallAdded: " + call.toString());
+ CarModeCallList callList = CarModeCallList.getInstance();
+ callList.addCall(call);
+
+ if (callList.size() == 1) {
+ startInCallUI();
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Phone phone, Call call) {
+ Log.i(TAG, "onCallRemoved: "+call.toString());
+ CarModeCallList.getInstance().removeCall(call);
+ }
+ };
+
+ @Override
+ public void onPhoneCreated(Phone phone) {
+ Log.i(TAG, "onPhoneCreated");
+ mPhone = phone;
+ mPhone.addListener(mPhoneListener);
+ CarModeCallList.getInstance().clearCalls();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.i(TAG, "onPhoneDestroyed");
+ mPhone.removeListener(mPhoneListener);
+ mPhone = null;
+ CarModeCallList.getInstance().clearCalls();
+ sInstance = null;
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onCallAudioStateChanged(CallAudioState cas) {
+ if (CarModeInCallUI.sInstance != null) {
+ CarModeInCallUI.sInstance.updateCallAudioState(cas);
+ }
+ }
+
+ private void startInCallUI() {
+ sInstance = this;
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(this, CarModeInCallUI.class);
+ startActivity(intent);
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java
new file mode 100644
index 0000000..6d06862
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.Collection;
+
+public class CarModeInCallUI extends Activity {
+ private class BluetoothDeviceAdapter extends ArrayAdapter<BluetoothDevice> {
+ public BluetoothDeviceAdapter() {
+ super(com.android.server.telecom.carmodedialer.CarModeInCallUI.this, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ BluetoothDevice info = getItem(position);
+ TextView result = new TextView(com.android.server.telecom.carmodedialer.CarModeInCallUI.this);
+ result.setText(info.getName());
+ return result;
+ }
+
+ public void update(Collection<BluetoothDevice> devices) {
+ clear();
+ addAll(devices);
+ }
+ }
+
+ public static com.android.server.telecom.carmodedialer.CarModeInCallUI sInstance;
+ private ListView mListView;
+ private CarModeCallList mCallList;
+ private Spinner mBtDeviceList;
+ private BluetoothDeviceAdapter mBluetoothDeviceAdapter;
+ private TextView mCurrentRouteDisplay;
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sInstance = this;
+
+ setContentView(R.layout.incall_screen);
+
+ mListView = (ListView) findViewById(R.id.callListView);
+ mListView.setAdapter(new CallListAdapter(this));
+ mListView.setVisibility(View.VISIBLE);
+
+ mCallList = CarModeCallList.getInstance();
+ mCallList.addListener(new CarModeCallList.Listener() {
+ @Override
+ public void onCallRemoved(Call call) {
+ if (mCallList.size() == 0) {
+ Log.i(CarModeInCallUI.class.getSimpleName(), "Ending the incall UI");
+ finish();
+ }
+ }
+
+ @Override
+ public void onRttStarted(Call call) {
+ Toast.makeText(CarModeInCallUI.this, "RTT now enabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttStopped(Call call) {
+ Toast.makeText(CarModeInCallUI.this, "RTT now disabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttInitiationFailed(Call call, int reason) {
+ Toast.makeText(CarModeInCallUI.this, String.format("RTT failed to init: %d", reason),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ Toast.makeText(CarModeInCallUI.this, String.format("RTT request: %d", id),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ View endCallButton = findViewById(R.id.end_call_button);
+ View holdButton = findViewById(R.id.hold_button);
+ View muteButton = findViewById(R.id.mute_button);
+ View answerButton = findViewById(R.id.answer_button);
+ View setBtDeviceButton = findViewById(R.id.set_bt_device_button);
+ View earpieceButton = findViewById(R.id.earpiece_button);
+ View speakerButton = findViewById(R.id.speaker_button);
+ mBtDeviceList = findViewById(R.id.available_bt_devices);
+ mBluetoothDeviceAdapter = new BluetoothDeviceAdapter();
+ mBtDeviceList.setAdapter(mBluetoothDeviceAdapter);
+ mCurrentRouteDisplay = findViewById(R.id.current_audio_route);
+
+ endCallButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+ });
+ holdButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ if (call.getState() == Call.STATE_HOLDING) {
+ call.unhold();
+ } else {
+ call.hold();
+ }
+ }
+ }
+ });
+ muteButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+
+ }
+ }
+ });
+
+ answerButton.setOnClickListener(view -> {
+ Call call = mCallList.getCall(0);
+ if (call.getState() == Call.STATE_RINGING) {
+ call.answer(VideoProfile.STATE_AUDIO_ONLY);
+ }
+ });
+
+ earpieceButton.setOnClickListener(view -> {
+ CarModeInCallServiceImpl.sInstance.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
+ });
+
+ speakerButton.setOnClickListener(view -> {
+ CarModeInCallServiceImpl.sInstance.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+ });
+
+ setBtDeviceButton.setOnClickListener(view -> {
+ if (mBtDeviceList.getSelectedItem() != null
+ && CarModeInCallServiceImpl.sInstance != null) {
+ CarModeInCallServiceImpl.sInstance.requestBluetoothAudio(
+ (BluetoothDevice) mBtDeviceList.getSelectedItem());
+ }
+ });
+
+ }
+
+ public void updateCallAudioState(CallAudioState cas) {
+ mBluetoothDeviceAdapter.update(cas.getSupportedBluetoothDevices());
+ String routeText;
+ switch (cas.getRoute()) {
+ case CallAudioState.ROUTE_EARPIECE:
+ routeText = "Earpiece";
+ break;
+ case CallAudioState.ROUTE_SPEAKER:
+ routeText = "Speaker";
+ break;
+ case CallAudioState.ROUTE_WIRED_HEADSET:
+ routeText = "Wired";
+ break;
+ case CallAudioState.ROUTE_BLUETOOTH:
+ BluetoothDevice activeDevice = cas.getActiveBluetoothDevice();
+ routeText = activeDevice == null ? "null bt" : activeDevice.getName();
+ break;
+ default:
+ routeText = "unknown: " + cas.getRoute();
+ }
+ mCurrentRouteDisplay.setText(routeText);
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onDestroy() {
+ sInstance = null;
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java
new file mode 100644
index 0000000..6b2b009
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * 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.carmodedialer;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+/**
+ * Test in call service broadcast receiver.
+ */
+public class TestInCallServiceBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "TestInCallServiceBR";
+
+ /**
+ * Sends an upgrade to video request for any live calls.
+ */
+ public static final String ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE =
+ "android.server.telecom.testapps.ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE";
+
+ /**
+ * Sends an a response to an upgrade to video request.
+ */
+ public static final String ACTION_SEND_UPGRADE_RESPONSE =
+ "android.server.telecom.testapps.ACTION_SEND_UPGRADE_RESPONSE";
+
+ /**
+ * Handles broadcasts directed at the {@link CarModeInCallServiceImpl}.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.v(TAG, "onReceive: " + action);
+
+ if (ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE.equals(action)) {
+ final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+ CarModeCallList.getInstance().sendUpgradeToVideoRequest(videoState);
+ } else if (ACTION_SEND_UPGRADE_RESPONSE.equals(action)) {
+ final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+ CarModeCallList.getInstance().sendUpgradeToVideoResponse(videoState);
+ } else if (TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED.equals(action)) {
+ Log.i(TAG, "onReceive: registered " + intent.getExtras().get(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+ } else if (TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED.equals(action)) {
+ Log.i(TAG, "onReceive: unregistered " + intent.getExtras().get(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+ }
+ }
+}
diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml
index a9507c3..e2ea30c 100644
--- a/testapps/res/layout/testdialer_main.xml
+++ b/testapps/res/layout/testdialer_main.xml
@@ -59,11 +59,32 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/startCallWithRtt"/>
- <Button
- android:id="@+id/toggle_car_mode"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Toggle Car mode" />
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:text="Priority:" />
+ <EditText
+ android:id="@+id/priority"
+ android:inputType="number"
+ android:text="100"
+ android:layout_width="50dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/enable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable Car mode" />
+ <Button
+ android:id="@+id/disable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disable Car mode" />
+ </LinearLayout>
<Button
android:id="@+id/toggle_incallservice"
android:layout_width="wrap_content"
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 6a085ee..aa34070 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -21,20 +21,20 @@
<!-- String for the TestCallActivity -->
<string name="testCallActivityLabel">Test Connection Service App</string>
- <!-- String for the TestDialerActivity -->
+ <!-- String for the CarModeDialerActivity -->
<string name="testDialerActivityLabel">Test Dialer</string>
- <!-- String for button in TestDialerActivity that reassigns the default Dialer -->
+ <!-- String for button in CarModeDialerActivity that reassigns the default Dialer -->
<string name="defaultDialerButton">Default dialer request</string>
- <!-- String for button in TestDialerActivity that places a test call -->
+ <!-- String for button in CarModeDialerActivity that places a test call -->
<string name="placeCallButton">Place call</string>
- <!-- String for button in TestDialerActivity that performs voicemail requests to verify
+ <!-- String for button in CarModeDialerActivity that performs voicemail requests to verify
voicemail permissions -->
<string name="testVoicemailButton">Exercise voicemail permissions</string>
- <!-- String for button in TestDialerActivity that tries to exercise the
+ <!-- String for button in CarModeDialerActivity that tries to exercise the
TelecomManager.cancelMissedCallNotifications() functionality -->
<string name="cancelMissedButton">Cancel missed calls</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index 9aa076f..19f09e6 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -26,6 +26,7 @@
private EditText mNumberView;
private CheckBox mRttCheckbox;
+ private EditText mPriorityView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -61,10 +62,16 @@
mNumberView = (EditText) findViewById(R.id.number);
mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox);
- findViewById(R.id.toggle_car_mode).setOnClickListener(new OnClickListener() {
+ findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- toggleCarMode();
+ enableCarMode();
+ }
+ });
+ findViewById(R.id.disable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableCarMode();
}
});
findViewById(R.id.toggle_incallservice).setOnClickListener(new OnClickListener() {
@@ -73,6 +80,7 @@
toggleInCallService();
}
});
+ mPriorityView = findViewById(R.id.priority);
updateMutableUi();
}
@@ -158,14 +166,25 @@
return intentExtras;
}
- private void toggleCarMode() {
- UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
- boolean isCarMode = uiModeManager.getCurrentModeType() == UI_MODE_TYPE_CAR;
- if (isCarMode) {
- uiModeManager.disableCarMode(0);
- } else {
- uiModeManager.enableCarMode(0);
+ private void enableCarMode() {
+ int priority;
+ try {
+ priority = Integer.parseInt(mPriorityView.getText().toString());
+ } catch (NumberFormatException nfe) {
+ Toast.makeText(this, "Invalid priority; not enabling car mode.",
+ Toast.LENGTH_LONG).show();
+ return;
}
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.enableCarMode(priority, 0);
+ Toast.makeText(this, "Enabling car mode with priority " + priority,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private void disableCarMode() {
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.disableCarMode(0);
+ Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
}
private void toggleInCallService() {
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
index efe8796..1cbb5b8 100644
--- a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -131,7 +131,7 @@
new SystemStateHelper(mContext);
verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
- assertEquals(2, intentFilter.getValue().countActions());
+ assertEquals(4, intentFilter.getValue().countActions());
assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE, intentFilter.getValue().getAction(0));
assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE, intentFilter.getValue().getAction(1));
}